From 2130e6ae7cf1926f7573853304015df5c589ad1b Mon Sep 17 00:00:00 2001 From: null Date: Mon, 15 Jun 2026 20:57:07 +0800 Subject: [PATCH 01/24] 1 --- transport/internet/finalmask/udphop/conn.go | 1 + 1 file changed, 1 insertion(+) create mode 100644 transport/internet/finalmask/udphop/conn.go diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go new file mode 100644 index 000000000000..bc5334ccde49 --- /dev/null +++ b/transport/internet/finalmask/udphop/conn.go @@ -0,0 +1 @@ +package udphop From 00a95aaf24291379e483d224a287ff84f76328e3 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 14:31:17 +0800 Subject: [PATCH 02/24] udphop --- transport/internet/finalmask/finalmask.go | 4 - .../internet/finalmask/fragment/config.go | 3 - .../finalmask/header/custom/config.go | 6 - .../finalmask/mkcp/aes128gcm/config.go | 2 - .../internet/finalmask/mkcp/header/config.go | 2 - .../finalmask/mkcp/original/config.go | 2 - transport/internet/finalmask/noise/config.go | 3 - transport/internet/finalmask/realm/config.go | 6 +- .../internet/finalmask/salamander/config.go | 4 - transport/internet/finalmask/sudoku/config.go | 6 - transport/internet/finalmask/udphop/config.go | 20 ++ .../internet/finalmask/udphop/config.pb.go | 162 ++++++++++ .../internet/finalmask/udphop/config.proto | 18 ++ transport/internet/finalmask/udphop/conn.go | 298 ++++++++++++++++++ transport/internet/finalmask/xdns/config.go | 3 - transport/internet/finalmask/xicmp/client.go | 10 +- transport/internet/finalmask/xicmp/config.go | 7 +- 17 files changed, 505 insertions(+), 51 deletions(-) create mode 100644 transport/internet/finalmask/udphop/config.go create mode 100644 transport/internet/finalmask/udphop/config.pb.go create mode 100644 transport/internet/finalmask/udphop/config.proto diff --git a/transport/internet/finalmask/finalmask.go b/transport/internet/finalmask/finalmask.go index 4683082edc30..6e98b009919b 100644 --- a/transport/internet/finalmask/finalmask.go +++ b/transport/internet/finalmask/finalmask.go @@ -10,8 +10,6 @@ import ( ) type Udpmask interface { - UDP() - WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) } @@ -195,8 +193,6 @@ func (c *headerManagerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) } type Tcpmask interface { - TCP() - WrapConnClient(net.Conn) (net.Conn, error) WrapConnServer(net.Conn) (net.Conn, error) } diff --git a/transport/internet/finalmask/fragment/config.go b/transport/internet/finalmask/fragment/config.go index 165f11ca9d25..9610c0596b85 100644 --- a/transport/internet/finalmask/fragment/config.go +++ b/transport/internet/finalmask/fragment/config.go @@ -2,9 +2,6 @@ package fragment import "net" -func (c *Config) TCP() { -} - func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) { return NewConnClient(c, raw, false) } diff --git a/transport/internet/finalmask/header/custom/config.go b/transport/internet/finalmask/header/custom/config.go index c054718bf0e5..7e2eaab74f97 100644 --- a/transport/internet/finalmask/header/custom/config.go +++ b/transport/internet/finalmask/header/custom/config.go @@ -4,8 +4,6 @@ import ( "net" ) -func (c *TCPConfig) TCP() {} - func (c *TCPConfig) WrapConnClient(raw net.Conn) (net.Conn, error) { return NewConnClientTCP(c, raw) } @@ -14,8 +12,6 @@ func (c *TCPConfig) WrapConnServer(raw net.Conn) (net.Conn, error) { return NewConnServerTCP(c, raw) } -func (c *UDPConfig) UDP() {} - func (c *UDPConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { return NewConnClientUDP(c, raw) } @@ -24,8 +20,6 @@ func (c *UDPConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCou return NewConnServerUDP(c, raw) } -func (c *UDPStandaloneConfig) UDP() {} - func (c *UDPStandaloneConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { return NewConnClientUDPStandalone(c, raw) } diff --git a/transport/internet/finalmask/mkcp/aes128gcm/config.go b/transport/internet/finalmask/mkcp/aes128gcm/config.go index 70d87ae006bf..d7cd0d41b422 100644 --- a/transport/internet/finalmask/mkcp/aes128gcm/config.go +++ b/transport/internet/finalmask/mkcp/aes128gcm/config.go @@ -4,8 +4,6 @@ import ( "net" ) -func (c *Config) UDP() {} - func (c *Config) HeaderConn() {} func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { diff --git a/transport/internet/finalmask/mkcp/header/config.go b/transport/internet/finalmask/mkcp/header/config.go index 17774871ebfa..0b5c67be6ecf 100644 --- a/transport/internet/finalmask/mkcp/header/config.go +++ b/transport/internet/finalmask/mkcp/header/config.go @@ -4,8 +4,6 @@ import ( "net" ) -func (c *Config) UDP() {} - func (c *Config) HeaderConn() {} func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { diff --git a/transport/internet/finalmask/mkcp/original/config.go b/transport/internet/finalmask/mkcp/original/config.go index b6b42c73da5b..98bf964a9377 100644 --- a/transport/internet/finalmask/mkcp/original/config.go +++ b/transport/internet/finalmask/mkcp/original/config.go @@ -4,8 +4,6 @@ import ( "net" ) -func (c *Config) UDP() {} - func (c *Config) HeaderConn() {} func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { diff --git a/transport/internet/finalmask/noise/config.go b/transport/internet/finalmask/noise/config.go index 1764c0b19b19..ecb5db292a8b 100644 --- a/transport/internet/finalmask/noise/config.go +++ b/transport/internet/finalmask/noise/config.go @@ -2,9 +2,6 @@ package noise import "net" -func (c *Config) UDP() { -} - func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { return NewConnClient(c, raw) } diff --git a/transport/internet/finalmask/realm/config.go b/transport/internet/finalmask/realm/config.go index 2604fa8abbc6..4b8232f78c20 100644 --- a/transport/internet/finalmask/realm/config.go +++ b/transport/internet/finalmask/realm/config.go @@ -5,15 +5,11 @@ import ( "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/transport/internet" - "github.com/xtls/xray-core/transport/internet/hysteria/udphop" ) -func (c *Config) UDP() {} - func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { _, ok1 := raw.(*internet.FakePacketConn) - _, ok2 := raw.(*udphop.UdpHopPacketConn) - if level != 0 || ok1 || ok2 { + if level != 0 || ok1 { return nil, errors.New("realm requires being at the outermost level") } return NewConnClient(c, raw) diff --git a/transport/internet/finalmask/salamander/config.go b/transport/internet/finalmask/salamander/config.go index 192c466b7b98..7d019141eee2 100644 --- a/transport/internet/finalmask/salamander/config.go +++ b/transport/internet/finalmask/salamander/config.go @@ -4,8 +4,6 @@ import ( "net" ) -func (c *Config) UDP() {} - func (c *Config) HeaderConn() {} func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { @@ -16,8 +14,6 @@ func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount return NewSalamanderConnServer(c, raw) } -func (c *GeckoConfig) UDP() {} - func (c *GeckoConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { return NewGeckoConnClient(c, raw) } diff --git a/transport/internet/finalmask/sudoku/config.go b/transport/internet/finalmask/sudoku/config.go index 58a4562f0e1f..f160a8b52c1b 100644 --- a/transport/internet/finalmask/sudoku/config.go +++ b/transport/internet/finalmask/sudoku/config.go @@ -6,12 +6,6 @@ import ( "github.com/xtls/xray-core/common/errors" ) -func (c *Config) TCP() { -} - -func (c *Config) UDP() { -} - // Sudoku in finalmask mode is a pure appearance transform with no standalone handshake. // TCP always keeps classic sudoku on uplink and uses packed downlink optimization on server writes. func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) { diff --git a/transport/internet/finalmask/udphop/config.go b/transport/internet/finalmask/udphop/config.go new file mode 100644 index 000000000000..5e8fda5fec7d --- /dev/null +++ b/transport/internet/finalmask/udphop/config.go @@ -0,0 +1,20 @@ +package udphop + +import ( + "net" + + "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/transport/internet" +) + +func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { + _, ok1 := raw.(*internet.FakePacketConn) + if level != 0 || ok1 { + return nil, errors.New("udphop requires being at the outermost level") + } + return NewUDPHopConn(c, raw) +} + +func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { + return nil, errors.New("udphop: client only") +} diff --git a/transport/internet/finalmask/udphop/config.pb.go b/transport/internet/finalmask/udphop/config.pb.go new file mode 100644 index 000000000000..6659660e2c10 --- /dev/null +++ b/transport/internet/finalmask/udphop/config.pb.go @@ -0,0 +1,162 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v6.33.5 +// source: transport/internet/finalmask/udphop/config.proto + +package udphop + +import ( + internet "github.com/xtls/xray-core/transport/internet" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Config struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sockopt *internet.SocketConfig `protobuf:"bytes,1,opt,name=sockopt,proto3" json:"sockopt,omitempty"` + IPs []string `protobuf:"bytes,2,rep,name=IPs,proto3" json:"IPs,omitempty"` + Ports []uint32 `protobuf:"varint,3,rep,packed,name=ports,proto3" json:"ports,omitempty"` + IntervalMin int64 `protobuf:"varint,4,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"` + IntervalMax int64 `protobuf:"varint,5,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Config) Reset() { + *x = Config{} + mi := &file_transport_internet_finalmask_udphop_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_finalmask_udphop_config_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_transport_internet_finalmask_udphop_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetSockopt() *internet.SocketConfig { + if x != nil { + return x.Sockopt + } + return nil +} + +func (x *Config) GetIPs() []string { + if x != nil { + return x.IPs + } + return nil +} + +func (x *Config) GetPorts() []uint32 { + if x != nil { + return x.Ports + } + return nil +} + +func (x *Config) GetIntervalMin() int64 { + if x != nil { + return x.IntervalMin + } + return 0 +} + +func (x *Config) GetIntervalMax() int64 { + if x != nil { + return x.IntervalMax + } + return 0 +} + +var File_transport_internet_finalmask_udphop_config_proto protoreflect.FileDescriptor + +const file_transport_internet_finalmask_udphop_config_proto_rawDesc = "" + + "\n" + + "0transport/internet/finalmask/udphop/config.proto\x12(xray.transport.internet.finalmask.udphop\x1a\x1ftransport/internet/config.proto\"\xb7\x01\n" + + "\x06Config\x12?\n" + + "\asockopt\x18\x01 \x01(\v2%.xray.transport.internet.SocketConfigR\asockopt\x12\x10\n" + + "\x03IPs\x18\x02 \x03(\tR\x03IPs\x12\x14\n" + + "\x05ports\x18\x03 \x03(\rR\x05ports\x12!\n" + + "\finterval_min\x18\x04 \x01(\x03R\vintervalMin\x12!\n" + + "\finterval_max\x18\x05 \x01(\x03R\vintervalMaxB\x9a\x01\n" + + ",com.xray.transport.internet.finalmask.udphopP\x01Z=github.com/xtls/xray-core/transport/internet/finalmask/udphop\xaa\x02(Xray.Transport.Internet.Finalmask.Udphopb\x06proto3" + +var ( + file_transport_internet_finalmask_udphop_config_proto_rawDescOnce sync.Once + file_transport_internet_finalmask_udphop_config_proto_rawDescData []byte +) + +func file_transport_internet_finalmask_udphop_config_proto_rawDescGZIP() []byte { + file_transport_internet_finalmask_udphop_config_proto_rawDescOnce.Do(func() { + file_transport_internet_finalmask_udphop_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_udphop_config_proto_rawDesc), len(file_transport_internet_finalmask_udphop_config_proto_rawDesc))) + }) + return file_transport_internet_finalmask_udphop_config_proto_rawDescData +} + +var file_transport_internet_finalmask_udphop_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_transport_internet_finalmask_udphop_config_proto_goTypes = []any{ + (*Config)(nil), // 0: xray.transport.internet.finalmask.udphop.Config + (*internet.SocketConfig)(nil), // 1: xray.transport.internet.SocketConfig +} +var file_transport_internet_finalmask_udphop_config_proto_depIdxs = []int32{ + 1, // 0: xray.transport.internet.finalmask.udphop.Config.sockopt:type_name -> xray.transport.internet.SocketConfig + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_transport_internet_finalmask_udphop_config_proto_init() } +func file_transport_internet_finalmask_udphop_config_proto_init() { + if File_transport_internet_finalmask_udphop_config_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_udphop_config_proto_rawDesc), len(file_transport_internet_finalmask_udphop_config_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transport_internet_finalmask_udphop_config_proto_goTypes, + DependencyIndexes: file_transport_internet_finalmask_udphop_config_proto_depIdxs, + MessageInfos: file_transport_internet_finalmask_udphop_config_proto_msgTypes, + }.Build() + File_transport_internet_finalmask_udphop_config_proto = out.File + file_transport_internet_finalmask_udphop_config_proto_goTypes = nil + file_transport_internet_finalmask_udphop_config_proto_depIdxs = nil +} diff --git a/transport/internet/finalmask/udphop/config.proto b/transport/internet/finalmask/udphop/config.proto new file mode 100644 index 000000000000..543a5f2fd9e6 --- /dev/null +++ b/transport/internet/finalmask/udphop/config.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package xray.transport.internet.finalmask.udphop; +option csharp_namespace = "Xray.Transport.Internet.Finalmask.Udphop"; +option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/udphop"; +option java_package = "com.xray.transport.internet.finalmask.udphop"; +option java_multiple_files = true; + +import "transport/internet/config.proto"; + +message Config { + xray.transport.internet.SocketConfig sockopt = 1; + repeated string IPs = 2; + repeated uint32 ports = 3; + int64 interval_min = 4; + int64 interval_max = 5; +} + diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index bc5334ccde49..33e657f103ff 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -1 +1,299 @@ package udphop + +import ( + "context" + "crypto/rand" + goerrors "errors" + "io" + mrand "math/rand" + gonet "net" + "net/netip" + "sync" + "time" + + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/finalmask" +) + +var pool = sync.Pool{ + New: func() any { + return make([]byte, finalmask.UDPSize) + }, +} + +type packet struct { + p []byte + addr net.Addr + err error +} + +type udpHopConn struct { + conn net.PacketConn + sockopt *internet.SocketConfig + + ips []netip.Prefix + ports []uint32 + intervalMin time.Duration + intervalMax time.Duration + + deadline time.Time + readDeadline time.Time + writeDeadline time.Time + + pre net.PacketConn + cur net.PacketConn + addr *net.UDPAddr + readCh chan packet + closeCh chan struct{} + mu sync.Mutex +} + +func NewUDPHopConn(c *Config, raw net.PacketConn) (net.PacketConn, error) { + if len(c.Ports) == 0 { + return nil, errors.New("empty ports") + } + if c.IntervalMin < 5 || c.IntervalMax < 5 { + return nil, errors.New("invalid interval") + } + ips := make([]netip.Prefix, 0, len(c.IPs)) + for _, ip := range c.IPs { + prefix, err := netip.ParsePrefix(ip) + if err == nil { + ips = append(ips, prefix) + continue + } + addr, err := netip.ParseAddr(ip) + if err == nil { + ips = append(ips, netip.PrefixFrom(addr, addr.BitLen())) + continue + } + return nil, errors.New("invalid ips") + } + conn := &udpHopConn{ + conn: raw, + sockopt: c.Sockopt, + + ips: ips, + ports: c.Ports, + intervalMin: time.Duration(c.IntervalMin), + intervalMax: time.Duration(c.IntervalMax), + + readCh: make(chan packet), + closeCh: make(chan struct{}), + } + go conn.hopLoop() + return conn, nil +} + +func (c *udpHopConn) closed() bool { + select { + case <-c.closeCh: + return true + default: + return false + } +} + +func (c *udpHopConn) nextInterval() time.Duration { + if c.intervalMin == c.intervalMax { + return c.intervalMin + } + return c.intervalMin + time.Duration(mrand.Int63n(int64(c.intervalMax-c.intervalMin)+1)) +} + +func (c *udpHopConn) hop() { + var addr *net.UDPAddr + switch { + case len(c.ips) > 0: + addr = &net.UDPAddr{ + IP: randPrefix(c.ips[mrand.Intn(len(c.ips))]), + Port: int(c.ports[mrand.Intn(len(c.ports))]), + } + case c.addr != nil: + addr = &net.UDPAddr{ + IP: c.addr.IP, + Port: int(c.ports[mrand.Intn(len(c.ports))]), + } + default: + return + } + raw, err := internet.DialSystem(context.Background(), net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.sockopt) + if err != nil { + errors.LogErrorInner(context.Background(), err, "hop err") + return + } + cur := raw.(*internet.PacketConnWrapper).PacketConn + cur.SetDeadline(c.deadline) + cur.SetReadDeadline(c.readDeadline) + cur.SetWriteDeadline(c.writeDeadline) + if c.pre != nil { + _ = c.pre.Close() + } + c.pre = c.cur + c.cur = cur + c.addr = addr + go c.recv(cur) +} + +func (c *udpHopConn) recv(conn net.PacketConn) { + for { + if c.closed() { + return + } + p := pool.Get().([]byte) + n, addr, err := conn.ReadFrom(p) + if err != nil { + pool.Put(p[:cap(p)]) + if goerrors.Is(err, io.EOF) || goerrors.Is(err, io.ErrClosedPipe) || goerrors.Is(err, gonet.ErrClosed) { + return + } + var netErr net.Error + if goerrors.As(err, &netErr) && netErr.Timeout() { + select { + case c.readCh <- packet{err: err}: + case <-c.closeCh: + return + } + } + errors.LogErrorInner(context.Background(), err, "recv err") + continue + } + select { + case c.readCh <- packet{p: p[:n], addr: addr}: + case <-c.closeCh: + pool.Put(p[:cap(p)]) + return + } + } +} + +func (c *udpHopConn) hopLoop() { + ticker := time.NewTicker(c.nextInterval()) + defer ticker.Stop() + for { + select { + case <-ticker.C: + ticker.Reset(c.nextInterval()) + c.mu.Lock() + c.hop() + c.mu.Unlock() + case <-c.closeCh: + return + } + } +} + +func (c *udpHopConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + select { + case packet := <-c.readCh: + if packet.p != nil { + n = copy(p, packet.p) + pool.Put(packet.p[:cap(packet.p)]) + } + return n, packet.addr, packet.err + case <-c.closeCh: + return 0, nil, io.EOF + } +} + +func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.addr == nil { + c.addr = &net.UDPAddr{ + IP: addr.(*net.UDPAddr).IP, + Port: addr.(*net.UDPAddr).Port, + } + } + + if c.cur == nil { + c.hop() + if c.cur == nil { + return 0, nil + } + } + + return c.cur.WriteTo(p, c.addr) +} + +func (c *udpHopConn) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + if c.closed() { + return nil + } + close(c.closeCh) + if c.pre != nil { + _ = c.pre.Close() + } + if c.cur != nil { + _ = c.cur.Close() + } + _ = c.conn.Close() + return nil +} + +func (c *udpHopConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +func (c *udpHopConn) SetDeadline(t time.Time) error { + c.mu.Lock() + defer c.mu.Unlock() + c.deadline = t + if c.pre != nil { + _ = c.pre.SetDeadline(t) + } + if c.cur != nil { + _ = c.cur.SetDeadline(t) + } + return nil +} + +func (c *udpHopConn) SetReadDeadline(t time.Time) error { + c.mu.Lock() + defer c.mu.Unlock() + c.readDeadline = t + if c.pre != nil { + _ = c.pre.SetReadDeadline(t) + } + if c.cur != nil { + _ = c.cur.SetReadDeadline(t) + } + return nil +} + +func (c *udpHopConn) SetWriteDeadline(t time.Time) error { + c.mu.Lock() + defer c.mu.Unlock() + c.writeDeadline = t + if c.pre != nil { + _ = c.pre.SetWriteDeadline(t) + } + if c.cur != nil { + _ = c.cur.SetWriteDeadline(t) + } + return nil +} + +func randPrefix(p netip.Prefix) []byte { + if p.IsSingleIP() { + return p.Addr().AsSlice() + } + b := p.Addr().AsSlice() + prefix := p.Bits() + var new [16]byte + common.Must2(rand.Read(new[:len(b)])) + i := prefix / 8 + j := prefix % 8 + if i+1 < len(b) { + copy(b[i+1:], new[i+1:]) + } + mask := byte(0xff << (8 - j)) + b[i] = (b[i] & mask) | (new[i] &^ mask) + return b +} diff --git a/transport/internet/finalmask/xdns/config.go b/transport/internet/finalmask/xdns/config.go index bac0456eb247..46241476e853 100644 --- a/transport/internet/finalmask/xdns/config.go +++ b/transport/internet/finalmask/xdns/config.go @@ -4,9 +4,6 @@ import ( "net" ) -func (c *Config) UDP() { -} - func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { // _, ok1 := raw.(*internet.FakePacketConn) // _, ok2 := raw.(*udphop.UdpHopPacketConn) diff --git a/transport/internet/finalmask/xicmp/client.go b/transport/internet/finalmask/xicmp/client.go index 3777bd879b94..ebe59b21f2bd 100644 --- a/transport/internet/finalmask/xicmp/client.go +++ b/transport/internet/finalmask/xicmp/client.go @@ -167,7 +167,7 @@ func (c *xicmpConnClient) recv4() { addr: addr, }: case <-c.closedCh: - pool.Put(p) + pool.Put(p[:cap(p)]) return } } @@ -237,7 +237,7 @@ func (c *xicmpConnClient) recv6() { addr: addr, }: case <-c.closedCh: - pool.Put(p) + pool.Put(p[:cap(p)]) return } } @@ -248,7 +248,7 @@ func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) { case packet := <-c.readCh: if packet.p != nil { n = copy(p, packet.p) - pool.Put(packet.p) + pool.Put(packet.p[:cap(packet.p)]) } return n, packet.addr, packet.err case <-c.closedCh: @@ -279,8 +279,8 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) { addr = &net.IPAddr{IP: ip} } - b := pool.Get().([]byte)[:finalmask.UDPSize] - defer pool.Put(b) + b := pool.Get().([]byte) + defer pool.Put(b[:cap(b)]) copy(b[8:], c.clientID[:]) copy(b[16:], p) diff --git a/transport/internet/finalmask/xicmp/config.go b/transport/internet/finalmask/xicmp/config.go index fdcb02ae701f..f99784c1a344 100644 --- a/transport/internet/finalmask/xicmp/config.go +++ b/transport/internet/finalmask/xicmp/config.go @@ -5,16 +5,11 @@ import ( "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/transport/internet" - "github.com/xtls/xray-core/transport/internet/hysteria/udphop" ) -func (c *Config) UDP() { -} - func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) { _, ok1 := raw.(*internet.FakePacketConn) - _, ok2 := raw.(*udphop.UdpHopPacketConn) - if level != 0 || ok1 || ok2 { + if level != 0 || ok1 { return nil, errors.New("xicmp requires being at the outermost level") } return NewConnClient(c, raw) From 86605e075177b1862c431db839b049e3dc974d87 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 14:41:05 +0800 Subject: [PATCH 03/24] quicParams & infra --- infra/conf/transport_internet.go | 64 +++------- transport/internet/config.pb.go | 163 +++++++------------------ transport/internet/config.proto | 8 +- transport/internet/hysteria/dialer.go | 34 ------ transport/internet/hysteria/hub.go | 1 - transport/internet/splithttp/dialer.go | 34 ------ transport/internet/splithttp/hub.go | 1 - 7 files changed, 62 insertions(+), 243 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 9b08f8c35a95..47f8dc324172 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -57,17 +57,10 @@ type KCPConfig struct { DownCap *uint32 `json:"downlinkCapacity"` CwndMultiplier *uint32 `json:"cwndMultiplier"` MaxSendingWindow *uint32 `json:"maxSendingWindow"` - - HeaderConfig json.RawMessage `json:"header"` - Seed *string `json:"seed"` } // Build implements Buildable. func (c *KCPConfig) Build() (proto.Message, error) { - if c.HeaderConfig != nil || c.Seed != nil { - return nil, errors.PrintRemovedFeatureError("mkcp header & seed", "finalmask/udp header-* & mkcp-original & mkcp-aes128gcm") - } - config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config) if c.Mtu != nil { @@ -525,11 +518,6 @@ func (b Bandwidth) Bps() (uint64, error) { return uint64(val*float64(mul)) / 8, nil } -type UdpHop struct { - PortList PortList `json:"ports"` - Interval Int32Range `json:"interval"` -} - type Masquerade struct { Type string `json:"type"` @@ -545,14 +533,8 @@ type Masquerade struct { } type HysteriaConfig struct { - Version int32 `json:"version"` - Auth string `json:"auth"` - - Congestion *string `json:"congestion"` - Up *Bandwidth `json:"up"` - Down *Bandwidth `json:"down"` - UdpHop *UdpHop `json:"udphop"` - + Version int32 `json:"version"` + Auth string `json:"auth"` UdpIdleTimeout int64 `json:"udpIdleTimeout"` Masquerade Masquerade `json:"masquerade"` } @@ -562,10 +544,6 @@ func (c *HysteriaConfig) Build() (proto.Message, error) { return nil, errors.New("version != 2") } - if c.Congestion != nil || c.Up != nil || c.Down != nil || c.UdpHop != nil { - errors.LogWarning(context.Background(), "congestion & up & down & udphop move to finalmask/quicParams") - } - if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) { return nil, errors.New("UdpIdleTimeout must be between 2 and 600") } @@ -653,20 +631,20 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) { } type QuicParamsConfig struct { - Congestion string `json:"congestion"` - Debug bool `json:"debug"` - BbrProfile string `json:"bbrProfile"` - BrutalUp Bandwidth `json:"brutalUp"` - BrutalDown Bandwidth `json:"brutalDown"` - UdpHop UdpHop `json:"udpHop"` - InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"` - MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"` - InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"` - MaxConnectionReceiveWindow uint64 `json:"maxConnectionReceiveWindow"` - MaxIdleTimeout int64 `json:"maxIdleTimeout"` - KeepAlivePeriod int64 `json:"keepAlivePeriod"` - DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"` - MaxIncomingStreams int64 `json:"maxIncomingStreams"` + Congestion string `json:"congestion"` + Debug bool `json:"debug"` + BbrProfile string `json:"bbrProfile"` + BrutalUp Bandwidth `json:"brutalUp"` + BrutalDown Bandwidth `json:"brutalDown"` + + InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"` + MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"` + InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"` + MaxConnectionReceiveWindow uint64 `json:"maxConnectionReceiveWindow"` + MaxIdleTimeout int64 `json:"maxIdleTimeout"` + KeepAlivePeriod int64 `json:"keepAlivePeriod"` + DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"` + MaxIncomingStreams int64 `json:"maxIncomingStreams"` } type TLSConfig struct { @@ -2226,10 +2204,6 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal") } - if (c.FinalMask.QuicParams.UdpHop.Interval.From != 0 && c.FinalMask.QuicParams.UdpHop.Interval.From < 5) || (c.FinalMask.QuicParams.UdpHop.Interval.To != 0 && c.FinalMask.QuicParams.UdpHop.Interval.To < 5) { - return nil, errors.New("Interval must be at least 5") - } - if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 { return nil, errors.New("InitStreamReceiveWindow must be at least 16384") } @@ -2262,11 +2236,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { BbrProfile: profile, BrutalUp: up, BrutalDown: down, - UdpHop: &internet.UdpHop{ - Ports: c.FinalMask.QuicParams.UdpHop.PortList.Build().Ports(), - IntervalMin: int64(c.FinalMask.QuicParams.UdpHop.Interval.From), - IntervalMax: int64(c.FinalMask.QuicParams.UdpHop.Interval.To), - }, + InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow, MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow, InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow, diff --git a/transport/internet/config.pb.go b/transport/internet/config.pb.go index ee696b43b980..723b9536be92 100644 --- a/transport/internet/config.pb.go +++ b/transport/internet/config.pb.go @@ -206,7 +206,7 @@ func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber { // Deprecated: Use SocketConfig_TProxyMode.Descriptor instead. func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) { - return file_transport_internet_config_proto_rawDescGZIP(), []int{6, 0} + return file_transport_internet_config_proto_rawDescGZIP(), []int{5, 0} } type TransportConfig struct { @@ -382,73 +382,12 @@ func (x *StreamConfig) GetSocketSettings() *SocketConfig { return nil } -type UdpHop struct { - state protoimpl.MessageState `protogen:"open.v1"` - Ports []uint32 `protobuf:"varint,1,rep,packed,name=ports,proto3" json:"ports,omitempty"` - IntervalMin int64 `protobuf:"varint,2,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"` - IntervalMax int64 `protobuf:"varint,3,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UdpHop) Reset() { - *x = UdpHop{} - mi := &file_transport_internet_config_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UdpHop) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UdpHop) ProtoMessage() {} - -func (x *UdpHop) ProtoReflect() protoreflect.Message { - mi := &file_transport_internet_config_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UdpHop.ProtoReflect.Descriptor instead. -func (*UdpHop) Descriptor() ([]byte, []int) { - return file_transport_internet_config_proto_rawDescGZIP(), []int{2} -} - -func (x *UdpHop) GetPorts() []uint32 { - if x != nil { - return x.Ports - } - return nil -} - -func (x *UdpHop) GetIntervalMin() int64 { - if x != nil { - return x.IntervalMin - } - return 0 -} - -func (x *UdpHop) GetIntervalMax() int64 { - if x != nil { - return x.IntervalMax - } - return 0 -} - type QuicParams struct { state protoimpl.MessageState `protogen:"open.v1"` Congestion string `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"` BbrProfile string `protobuf:"bytes,2,opt,name=bbr_profile,json=bbrProfile,proto3" json:"bbr_profile,omitempty"` BrutalUp uint64 `protobuf:"varint,3,opt,name=brutal_up,json=brutalUp,proto3" json:"brutal_up,omitempty"` BrutalDown uint64 `protobuf:"varint,4,opt,name=brutal_down,json=brutalDown,proto3" json:"brutal_down,omitempty"` - UdpHop *UdpHop `protobuf:"bytes,5,opt,name=udp_hop,json=udpHop,proto3" json:"udp_hop,omitempty"` InitStreamReceiveWindow uint64 `protobuf:"varint,6,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"` MaxStreamReceiveWindow uint64 `protobuf:"varint,7,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"` InitConnReceiveWindow uint64 `protobuf:"varint,8,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"` @@ -463,7 +402,7 @@ type QuicParams struct { func (x *QuicParams) Reset() { *x = QuicParams{} - mi := &file_transport_internet_config_proto_msgTypes[3] + mi := &file_transport_internet_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -475,7 +414,7 @@ func (x *QuicParams) String() string { func (*QuicParams) ProtoMessage() {} func (x *QuicParams) ProtoReflect() protoreflect.Message { - mi := &file_transport_internet_config_proto_msgTypes[3] + mi := &file_transport_internet_config_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -488,7 +427,7 @@ func (x *QuicParams) ProtoReflect() protoreflect.Message { // Deprecated: Use QuicParams.ProtoReflect.Descriptor instead. func (*QuicParams) Descriptor() ([]byte, []int) { - return file_transport_internet_config_proto_rawDescGZIP(), []int{3} + return file_transport_internet_config_proto_rawDescGZIP(), []int{2} } func (x *QuicParams) GetCongestion() string { @@ -519,13 +458,6 @@ func (x *QuicParams) GetBrutalDown() uint64 { return 0 } -func (x *QuicParams) GetUdpHop() *UdpHop { - if x != nil { - return x.UdpHop - } - return nil -} - func (x *QuicParams) GetInitStreamReceiveWindow() uint64 { if x != nil { return x.InitStreamReceiveWindow @@ -592,7 +524,7 @@ type ProxyConfig struct { func (x *ProxyConfig) Reset() { *x = ProxyConfig{} - mi := &file_transport_internet_config_proto_msgTypes[4] + mi := &file_transport_internet_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -604,7 +536,7 @@ func (x *ProxyConfig) String() string { func (*ProxyConfig) ProtoMessage() {} func (x *ProxyConfig) ProtoReflect() protoreflect.Message { - mi := &file_transport_internet_config_proto_msgTypes[4] + mi := &file_transport_internet_config_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -617,7 +549,7 @@ func (x *ProxyConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ProxyConfig.ProtoReflect.Descriptor instead. func (*ProxyConfig) Descriptor() ([]byte, []int) { - return file_transport_internet_config_proto_rawDescGZIP(), []int{4} + return file_transport_internet_config_proto_rawDescGZIP(), []int{3} } func (x *ProxyConfig) GetTag() string { @@ -648,7 +580,7 @@ type CustomSockopt struct { func (x *CustomSockopt) Reset() { *x = CustomSockopt{} - mi := &file_transport_internet_config_proto_msgTypes[5] + mi := &file_transport_internet_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -660,7 +592,7 @@ func (x *CustomSockopt) String() string { func (*CustomSockopt) ProtoMessage() {} func (x *CustomSockopt) ProtoReflect() protoreflect.Message { - mi := &file_transport_internet_config_proto_msgTypes[5] + mi := &file_transport_internet_config_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -673,7 +605,7 @@ func (x *CustomSockopt) ProtoReflect() protoreflect.Message { // Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead. func (*CustomSockopt) Descriptor() ([]byte, []int) { - return file_transport_internet_config_proto_rawDescGZIP(), []int{5} + return file_transport_internet_config_proto_rawDescGZIP(), []int{4} } func (x *CustomSockopt) GetSystem() string { @@ -753,7 +685,7 @@ type SocketConfig struct { func (x *SocketConfig) Reset() { *x = SocketConfig{} - mi := &file_transport_internet_config_proto_msgTypes[6] + mi := &file_transport_internet_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -765,7 +697,7 @@ func (x *SocketConfig) String() string { func (*SocketConfig) ProtoMessage() {} func (x *SocketConfig) ProtoReflect() protoreflect.Message { - mi := &file_transport_internet_config_proto_msgTypes[6] + mi := &file_transport_internet_config_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -778,7 +710,7 @@ func (x *SocketConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead. func (*SocketConfig) Descriptor() ([]byte, []int) { - return file_transport_internet_config_proto_rawDescGZIP(), []int{6} + return file_transport_internet_config_proto_rawDescGZIP(), []int{5} } func (x *SocketConfig) GetMark() int32 { @@ -940,7 +872,7 @@ type HappyEyeballsConfig struct { func (x *HappyEyeballsConfig) Reset() { *x = HappyEyeballsConfig{} - mi := &file_transport_internet_config_proto_msgTypes[7] + mi := &file_transport_internet_config_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -952,7 +884,7 @@ func (x *HappyEyeballsConfig) String() string { func (*HappyEyeballsConfig) ProtoMessage() {} func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message { - mi := &file_transport_internet_config_proto_msgTypes[7] + mi := &file_transport_internet_config_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -965,7 +897,7 @@ func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead. func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) { - return file_transport_internet_config_proto_rawDescGZIP(), []int{7} + return file_transport_internet_config_proto_rawDescGZIP(), []int{6} } func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool { @@ -1016,11 +948,7 @@ const file_transport_internet_config_proto_rawDesc = "" + "\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12D\n" + "\vquic_params\x18\f \x01(\v2#.xray.transport.internet.QuicParamsR\n" + "quicParams\x12N\n" + - "\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"d\n" + - "\x06UdpHop\x12\x14\n" + - "\x05ports\x18\x01 \x03(\rR\x05ports\x12!\n" + - "\finterval_min\x18\x02 \x01(\x03R\vintervalMin\x12!\n" + - "\finterval_max\x18\x03 \x01(\x03R\vintervalMax\"\xf2\x04\n" + + "\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"\xb8\x04\n" + "\n" + "QuicParams\x12\x1e\n" + "\n" + @@ -1030,8 +958,7 @@ const file_transport_internet_config_proto_rawDesc = "" + "bbrProfile\x12\x1b\n" + "\tbrutal_up\x18\x03 \x01(\x04R\bbrutalUp\x12\x1f\n" + "\vbrutal_down\x18\x04 \x01(\x04R\n" + - "brutalDown\x128\n" + - "\audp_hop\x18\x05 \x01(\v2\x1f.xray.transport.internet.UdpHopR\x06udpHop\x12;\n" + + "brutalDown\x12;\n" + "\x1ainit_stream_receive_window\x18\x06 \x01(\x04R\x17initStreamReceiveWindow\x129\n" + "\x19max_stream_receive_window\x18\a \x01(\x04R\x16maxStreamReceiveWindow\x127\n" + "\x18init_conn_receive_window\x18\b \x01(\x04R\x15initConnReceiveWindow\x125\n" + @@ -1127,42 +1054,40 @@ func file_transport_internet_config_proto_rawDescGZIP() []byte { } var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_transport_internet_config_proto_goTypes = []any{ (DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy (AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy (SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode (*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig (*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig - (*UdpHop)(nil), // 5: xray.transport.internet.UdpHop - (*QuicParams)(nil), // 6: xray.transport.internet.QuicParams - (*ProxyConfig)(nil), // 7: xray.transport.internet.ProxyConfig - (*CustomSockopt)(nil), // 8: xray.transport.internet.CustomSockopt - (*SocketConfig)(nil), // 9: xray.transport.internet.SocketConfig - (*HappyEyeballsConfig)(nil), // 10: xray.transport.internet.HappyEyeballsConfig - (*serial.TypedMessage)(nil), // 11: xray.common.serial.TypedMessage - (*net.IPOrDomain)(nil), // 12: xray.common.net.IPOrDomain + (*QuicParams)(nil), // 5: xray.transport.internet.QuicParams + (*ProxyConfig)(nil), // 6: xray.transport.internet.ProxyConfig + (*CustomSockopt)(nil), // 7: xray.transport.internet.CustomSockopt + (*SocketConfig)(nil), // 8: xray.transport.internet.SocketConfig + (*HappyEyeballsConfig)(nil), // 9: xray.transport.internet.HappyEyeballsConfig + (*serial.TypedMessage)(nil), // 10: xray.common.serial.TypedMessage + (*net.IPOrDomain)(nil), // 11: xray.common.net.IPOrDomain } var file_transport_internet_config_proto_depIdxs = []int32{ - 11, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage - 12, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain + 10, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage + 11, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain 3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig - 11, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage - 11, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage - 11, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage - 6, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams - 9, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig - 5, // 8: xray.transport.internet.QuicParams.udp_hop:type_name -> xray.transport.internet.UdpHop - 2, // 9: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode - 0, // 10: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy - 8, // 11: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt - 1, // 12: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy - 10, // 13: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig - 14, // [14:14] is the sub-list for method output_type - 14, // [14:14] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 10, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage + 10, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage + 10, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage + 5, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams + 8, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig + 2, // 8: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode + 0, // 9: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy + 7, // 10: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt + 1, // 11: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy + 9, // 12: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_transport_internet_config_proto_init() } @@ -1176,7 +1101,7 @@ func file_transport_internet_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)), NumEnums: 3, - NumMessages: 8, + NumMessages: 7, NumExtensions: 0, NumServices: 0, }, diff --git a/transport/internet/config.proto b/transport/internet/config.proto index c139111aa9de..ea12f17bd76b 100644 --- a/transport/internet/config.proto +++ b/transport/internet/config.proto @@ -64,18 +64,12 @@ message StreamConfig { SocketConfig socket_settings = 6; } -message UdpHop { - repeated uint32 ports = 1; - int64 interval_min = 2; - int64 interval_max = 3; -} - message QuicParams { string congestion = 1; string bbr_profile = 2; uint64 brutal_up = 3; uint64 brutal_down = 4; - UdpHop udp_hop = 5; + uint64 init_stream_receive_window = 6; uint64 max_stream_receive_window = 7; uint64 init_conn_receive_window = 8; diff --git a/transport/internet/hysteria/dialer.go b/transport/internet/hysteria/dialer.go index a928cac72903..132dd6817664 100644 --- a/transport/internet/hysteria/dialer.go +++ b/transport/internet/hysteria/dialer.go @@ -3,7 +3,6 @@ package hysteria import ( "context" go_tls "crypto/tls" - "math/rand" "net/http" "net/url" "reflect" @@ -22,7 +21,6 @@ import ( "github.com/xtls/xray-core/transport/internet/finalmask" "github.com/xtls/xray-core/transport/internet/hysteria/congestion" "github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr" - "github.com/xtls/xray-core/transport/internet/hysteria/udphop" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" ) @@ -78,7 +76,6 @@ func (c *client) dial(ctx context.Context) error { if quicParams == nil { quicParams = &internet.QuicParams{ BbrProfile: string(bbr.ProfileStandard), - UdpHop: &internet.UdpHop{}, } } @@ -114,35 +111,8 @@ func (c *client) dial(ctx context.Context) error { // quicConfig.KeepAlivePeriod = 10 * time.Second // } - udpHopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) { - conn, err := internet.DialSystem(ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.socketConfig) - if err != nil { - errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest") - return nil, errors.New("") - } - - var pktConn net.PacketConn - - switch c := conn.(type) { - case *internet.PacketConnWrapper: - pktConn = c.PacketConn - case *cnc.Connection: - pktConn = &internet.FakePacketConn{Conn: c} - default: - panic(reflect.TypeOf(c)) - } - - return pktConn, nil - } - var pktConn net.PacketConn var udpAddr *net.UDPAddr - var index int - - if len(quicParams.UdpHop.Ports) > 0 { - index = rand.Intn(len(quicParams.UdpHop.Ports)) - c.dest.Port = net.Port(quicParams.UdpHop.Ports[index]) - } raw, err := internet.DialSystem(ctx, c.dest, c.socketConfig) if err != nil { @@ -159,10 +129,6 @@ func (c *client) dial(ctx context.Context) error { panic(reflect.TypeOf(c)) } - if len(quicParams.UdpHop.Ports) > 0 { - pktConn = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer, pktConn, index) - } - if c.udpmaskManager != nil { newConn, err := c.udpmaskManager.WrapPacketConnClient(pktConn) if err != nil { diff --git a/transport/internet/hysteria/hub.go b/transport/internet/hysteria/hub.go index d20313b5d031..10f623ec07fa 100644 --- a/transport/internet/hysteria/hub.go +++ b/transport/internet/hysteria/hub.go @@ -257,7 +257,6 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti if quicParams == nil { quicParams = &internet.QuicParams{ BbrProfile: string(bbr.ProfileStandard), - UdpHop: &internet.UdpHop{}, } } diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index 817d935528c9..54734039a0b7 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -5,7 +5,6 @@ import ( gotls "crypto/tls" "fmt" "io" - "math/rand" "net/http" "net/http/httptrace" "net/url" @@ -28,7 +27,6 @@ import ( "github.com/xtls/xray-core/transport/internet/browser_dialer" "github.com/xtls/xray-core/transport/internet/hysteria/congestion" "github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr" - "github.com/xtls/xray-core/transport/internet/hysteria/udphop" "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" @@ -162,7 +160,6 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea if quicParams == nil { quicParams = &internet.QuicParams{ BbrProfile: string(bbr.ProfileStandard), - UdpHop: &internet.UdpHop{}, } } @@ -197,35 +194,8 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea QUICConfig: quicConfig, TLSClientConfig: gotlsConfig, Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) { - udpHopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) { - conn, err := internet.DialSystem(ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), streamSettings.SocketSettings) - if err != nil { - errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest") - return nil, errors.New("") - } - - var pktConn net.PacketConn - - switch c := conn.(type) { - case *internet.PacketConnWrapper: - pktConn = c.PacketConn - case *cnc.Connection: - pktConn = &internet.FakePacketConn{Conn: c} - default: - panic(reflect.TypeOf(c)) - } - - return pktConn, nil - } - var pktConn net.PacketConn var udpAddr *net.UDPAddr - var index int - - if len(quicParams.UdpHop.Ports) > 0 { - index = rand.Intn(len(quicParams.UdpHop.Ports)) - dest.Port = net.Port(quicParams.UdpHop.Ports[index]) - } raw, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings) if err != nil { @@ -242,10 +212,6 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea panic(reflect.TypeOf(c)) } - if len(quicParams.UdpHop.Ports) > 0 { - pktConn = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer, pktConn, index) - } - if streamSettings.UdpmaskManager != nil { newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn) if err != nil { diff --git a/transport/internet/splithttp/hub.go b/transport/internet/splithttp/hub.go index a28866cf922e..86c7c7862951 100644 --- a/transport/internet/splithttp/hub.go +++ b/transport/internet/splithttp/hub.go @@ -487,7 +487,6 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet if quicParams == nil { quicParams = &internet.QuicParams{ BbrProfile: string(bbr.ProfileStandard), - UdpHop: &internet.UdpHop{}, } } From 51af66d7e6f4f837b455492622580376236cb019 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 15:01:12 +0800 Subject: [PATCH 04/24] infra --- infra/conf/transport_internet.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 47f8dc324172..3c99f6a5db9f 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -31,6 +31,7 @@ import ( "github.com/xtls/xray-core/transport/internet/finalmask/realm" "github.com/xtls/xray-core/transport/internet/finalmask/salamander" finalsudoku "github.com/xtls/xray-core/transport/internet/finalmask/sudoku" + "github.com/xtls/xray-core/transport/internet/finalmask/udphop" "github.com/xtls/xray-core/transport/internet/finalmask/xdns" "github.com/xtls/xray-core/transport/internet/finalmask/xicmp" "github.com/xtls/xray-core/transport/internet/httpupgrade" @@ -1238,6 +1239,7 @@ var ( "xdns": func() interface{} { return new(Xdns) }, "xicmp": func() interface{} { return new(Xicmp) }, "realm": func() interface{} { return new(Realm) }, + "udphop": func() interface{} { return new(UDPHop) }, }, "type", "settings") ) @@ -1964,6 +1966,32 @@ func (c *Realm) Build() (proto.Message, error) { }, nil } +type UDPHop struct { + Sockopt *SocketConfig `json:"sockopt"` + IPs []string `json:"ips"` + Ports PortList `json:"ports"` + Interval Int32Range `json:"interval"` +} + +func (c *UDPHop) Build() (proto.Message, error) { + var sockopt *internet.SocketConfig + if c.Sockopt != nil { + var err error + sockopt, err = c.Sockopt.Build() + if err != nil { + return nil, err + } + } + + return &udphop.Config{ + Sockopt: sockopt, + IPs: c.IPs, + Ports: c.Ports.Build().Ports(), + IntervalMin: int64(c.Interval.From), + IntervalMax: int64(c.Interval.To), + }, nil +} + type Mask struct { Type string `json:"type"` Settings *json.RawMessage `json:"settings"` From 292f2595417e153dab4dab393c44468ff9019a80 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 15:05:43 +0800 Subject: [PATCH 05/24] chore --- transport/internet/finalmask/xicmp/client.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/transport/internet/finalmask/xicmp/client.go b/transport/internet/finalmask/xicmp/client.go index ebe59b21f2bd..3777bd879b94 100644 --- a/transport/internet/finalmask/xicmp/client.go +++ b/transport/internet/finalmask/xicmp/client.go @@ -167,7 +167,7 @@ func (c *xicmpConnClient) recv4() { addr: addr, }: case <-c.closedCh: - pool.Put(p[:cap(p)]) + pool.Put(p) return } } @@ -237,7 +237,7 @@ func (c *xicmpConnClient) recv6() { addr: addr, }: case <-c.closedCh: - pool.Put(p[:cap(p)]) + pool.Put(p) return } } @@ -248,7 +248,7 @@ func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) { case packet := <-c.readCh: if packet.p != nil { n = copy(p, packet.p) - pool.Put(packet.p[:cap(packet.p)]) + pool.Put(packet.p) } return n, packet.addr, packet.err case <-c.closedCh: @@ -279,8 +279,8 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) { addr = &net.IPAddr{IP: ip} } - b := pool.Get().([]byte) - defer pool.Put(b[:cap(b)]) + b := pool.Get().([]byte)[:finalmask.UDPSize] + defer pool.Put(b) copy(b[8:], c.clientID[:]) copy(b[16:], p) From 1b6f61bd88ec3ded69ddfc6333e0e51e4cca99e3 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 15:11:13 +0800 Subject: [PATCH 06/24] cnc --- transport/internet/finalmask/udphop/conn.go | 22 +++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index 33e657f103ff..a7fdb8d9db2d 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -8,12 +8,14 @@ import ( mrand "math/rand" gonet "net" "net/netip" + "reflect" "sync" "time" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/finalmask" ) @@ -120,22 +122,30 @@ func (c *udpHopConn) hop() { default: return } + var pkt net.PacketConn raw, err := internet.DialSystem(context.Background(), net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.sockopt) if err != nil { errors.LogErrorInner(context.Background(), err, "hop err") return } - cur := raw.(*internet.PacketConnWrapper).PacketConn - cur.SetDeadline(c.deadline) - cur.SetReadDeadline(c.readDeadline) - cur.SetWriteDeadline(c.writeDeadline) + switch c := raw.(type) { + case *internet.PacketConnWrapper: + pkt = c.PacketConn + case *cnc.Connection: + pkt = &internet.FakePacketConn{Conn: c} + default: + panic(reflect.TypeOf(c)) + } + pkt.SetDeadline(c.deadline) + pkt.SetReadDeadline(c.readDeadline) + pkt.SetWriteDeadline(c.writeDeadline) if c.pre != nil { _ = c.pre.Close() } c.pre = c.cur - c.cur = cur + c.cur = pkt c.addr = addr - go c.recv(cur) + go c.recv(pkt) } func (c *udpHopConn) recv(conn net.PacketConn) { From 8fec52a17941d805424b0453364fd3ff60b6e6a2 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 15:17:37 +0800 Subject: [PATCH 07/24] hop check --- transport/internet/finalmask/udphop/conn.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index a7fdb8d9db2d..017e1ff15f1a 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -107,6 +107,9 @@ func (c *udpHopConn) nextInterval() time.Duration { } func (c *udpHopConn) hop() { + if c.closed() { + return + } var addr *net.UDPAddr switch { case len(c.ips) > 0: From d16843cf9872857df77bd0115faaa31b21850f44 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 15:20:24 +0800 Subject: [PATCH 08/24] remove udphop --- transport/internet/hysteria/udphop/conn.go | 254 --------------------- 1 file changed, 254 deletions(-) delete mode 100644 transport/internet/hysteria/udphop/conn.go diff --git a/transport/internet/hysteria/udphop/conn.go b/transport/internet/hysteria/udphop/conn.go deleted file mode 100644 index 8ab8339d5e8b..000000000000 --- a/transport/internet/hysteria/udphop/conn.go +++ /dev/null @@ -1,254 +0,0 @@ -package udphop - -import ( - "errors" - "math/rand" - "net" - "sync" - "time" - - "github.com/xtls/xray-core/transport/internet/finalmask" -) - -const ( - packetQueueSize = 1024 - udpBufferSize = finalmask.UDPSize - - defaultHopInterval = 30 * time.Second -) - -type UdpHopPacketConn struct { - Addrs []net.Addr - HopIntervalMin time.Duration - HopIntervalMax time.Duration - ListenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error) - - connMutex sync.RWMutex - prevConn net.PacketConn - currentConn net.PacketConn - addrIndex int - - deadline time.Time - readDeadline time.Time - writeDeadline time.Time - - recvQueue chan *udpPacket - closeChan chan struct{} - closed bool - - bufPool sync.Pool -} - -type udpPacket struct { - Buf []byte - N int - Addr net.Addr - Err error -} - -func NewUDPHopPacketConn(addrs []net.Addr, hopIntervalMin time.Duration, hopIntervalMax time.Duration, listenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error), currentConn net.PacketConn, addrIndex int) net.PacketConn { - if len(addrs) == 0 { - panic("len(addrs) == 0") - } - if hopIntervalMin == 0 { - hopIntervalMin = defaultHopInterval - } - if hopIntervalMax == 0 { - hopIntervalMax = defaultHopInterval - } - if hopIntervalMin < 5*time.Second { - panic("hopIntervalMin < 5*time.Second") - } - if hopIntervalMax < 5*time.Second { - panic("hopIntervalMax < 5*time.Second") - } - if hopIntervalMax < hopIntervalMin { - panic("hopIntervalMax < hopIntervalMin") - } - if listenUDPFunc == nil { - panic("listenUDPFunc is nil") - } - hConn := &UdpHopPacketConn{ - Addrs: addrs, - HopIntervalMin: hopIntervalMin, - HopIntervalMax: hopIntervalMax, - ListenUDPFunc: listenUDPFunc, - prevConn: nil, - currentConn: currentConn, - addrIndex: addrIndex, - recvQueue: make(chan *udpPacket, packetQueueSize), - closeChan: make(chan struct{}), - bufPool: sync.Pool{ - New: func() interface{} { - return make([]byte, udpBufferSize) - }, - }, - } - go hConn.recvLoop(hConn.currentConn) - go hConn.hopLoop() - return hConn -} - -func (u *UdpHopPacketConn) recvLoop(conn net.PacketConn) { - for { - buf := u.bufPool.Get().([]byte) - n, addr, err := conn.ReadFrom(buf) - if err != nil { - u.bufPool.Put(buf) - var netErr net.Error - if errors.As(err, &netErr) && netErr.Timeout() { - u.recvQueue <- &udpPacket{nil, 0, nil, netErr} - continue - } - return - } - select { - case u.recvQueue <- &udpPacket{buf, n, addr, nil}: - default: - u.bufPool.Put(buf) - } - } -} - -func (u *UdpHopPacketConn) hopLoop() { - timer := time.NewTimer(u.nextHopInterval()) - defer timer.Stop() - for { - select { - case <-timer.C: - u.hop() - timer.Reset(u.nextHopInterval()) - case <-u.closeChan: - return - } - } -} - -func (u *UdpHopPacketConn) nextHopInterval() time.Duration { - if u.HopIntervalMin == u.HopIntervalMax { - return u.HopIntervalMin - } - return u.HopIntervalMin + time.Duration(rand.Int63n(int64(u.HopIntervalMax-u.HopIntervalMin)+1)) -} - -func (u *UdpHopPacketConn) hop() { - u.connMutex.Lock() - defer u.connMutex.Unlock() - if u.closed { - return - } - addrIndex := rand.Intn(len(u.Addrs)) - newConn, err := u.ListenUDPFunc(u.Addrs[addrIndex].(*net.UDPAddr)) - if err != nil { - return - } - if u.prevConn != nil { - _ = u.prevConn.Close() - } - u.prevConn = u.currentConn - u.addrIndex = addrIndex - u.currentConn = newConn - if !u.deadline.IsZero() { - _ = u.currentConn.SetDeadline(u.deadline) - } - if !u.readDeadline.IsZero() { - _ = u.currentConn.SetReadDeadline(u.readDeadline) - } - if !u.writeDeadline.IsZero() { - _ = u.currentConn.SetWriteDeadline(u.writeDeadline) - } - go u.recvLoop(newConn) -} - -func (u *UdpHopPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) { - for { - select { - case p := <-u.recvQueue: - if p.Err != nil { - return 0, nil, p.Err - } - n := copy(b, p.Buf[:p.N]) - u.bufPool.Put(p.Buf) - return n, p.Addr, nil - case <-u.closeChan: - return 0, nil, net.ErrClosed - } - } -} - -func (u *UdpHopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { - u.connMutex.RLock() - defer u.connMutex.RUnlock() - if u.closed { - return 0, net.ErrClosed - } - return u.currentConn.WriteTo(b, u.Addrs[u.addrIndex]) -} - -func (u *UdpHopPacketConn) Close() error { - u.connMutex.Lock() - defer u.connMutex.Unlock() - if u.closed { - return nil - } - if u.prevConn != nil { - _ = u.prevConn.Close() - } - err := u.currentConn.Close() - close(u.closeChan) - u.closed = true - u.Addrs = nil - return err -} - -func (u *UdpHopPacketConn) LocalAddr() net.Addr { - u.connMutex.RLock() - defer u.connMutex.RUnlock() - return u.currentConn.LocalAddr() -} - -func (u *UdpHopPacketConn) SetDeadline(t time.Time) error { - u.connMutex.Lock() - defer u.connMutex.Unlock() - u.deadline = t - u.readDeadline = t - u.writeDeadline = t - if u.prevConn != nil { - _ = u.prevConn.SetDeadline(t) - } - return u.currentConn.SetDeadline(t) -} - -func (u *UdpHopPacketConn) SetReadDeadline(t time.Time) error { - u.connMutex.Lock() - defer u.connMutex.Unlock() - u.deadline = time.Time{} - u.readDeadline = t - if u.prevConn != nil { - _ = u.prevConn.SetReadDeadline(t) - } - return u.currentConn.SetReadDeadline(t) -} - -func (u *UdpHopPacketConn) SetWriteDeadline(t time.Time) error { - u.connMutex.Lock() - defer u.connMutex.Unlock() - u.deadline = time.Time{} - u.writeDeadline = t - if u.prevConn != nil { - _ = u.prevConn.SetWriteDeadline(t) - } - return u.currentConn.SetWriteDeadline(t) -} - -func ToAddrs(ip net.IP, ports []uint32) []net.Addr { - var addrs []net.Addr - for _, port := range ports { - addr := &net.UDPAddr{ - IP: ip, - Port: int(port), - } - addrs = append(addrs, addr) - } - return addrs -} From 73e1ea7b10e43fd5644b4f86e953571ee602a0f1 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 15:34:09 +0800 Subject: [PATCH 09/24] send err --- common/crypto/crypto.go | 2 +- transport/internet/finalmask/udphop/conn.go | 27 ++++++++++----------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/common/crypto/crypto.go b/common/crypto/crypto.go index 49ce164ba011..7a809037d21f 100644 --- a/common/crypto/crypto.go +++ b/common/crypto/crypto.go @@ -10,7 +10,7 @@ import ( // [,) func RandBetween(from int64, to int64) int64 { - if from == to { + if d := from - to; d == 0 || d == -1 || d == 1 { return from } if from > to { diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index 017e1ff15f1a..5a4ee3cdab2e 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -13,6 +13,7 @@ import ( "time" "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net/cnc" @@ -38,8 +39,8 @@ type udpHopConn struct { ips []netip.Prefix ports []uint32 - intervalMin time.Duration - intervalMax time.Duration + intervalMin int64 + intervalMax int64 deadline time.Time readDeadline time.Time @@ -80,8 +81,8 @@ func NewUDPHopConn(c *Config, raw net.PacketConn) (net.PacketConn, error) { ips: ips, ports: c.Ports, - intervalMin: time.Duration(c.IntervalMin), - intervalMax: time.Duration(c.IntervalMax), + intervalMin: c.IntervalMin, + intervalMax: c.IntervalMax, readCh: make(chan packet), closeCh: make(chan struct{}), @@ -99,13 +100,6 @@ func (c *udpHopConn) closed() bool { } } -func (c *udpHopConn) nextInterval() time.Duration { - if c.intervalMin == c.intervalMax { - return c.intervalMin - } - return c.intervalMin + time.Duration(mrand.Int63n(int64(c.intervalMax-c.intervalMin)+1)) -} - func (c *udpHopConn) hop() { if c.closed() { return @@ -184,12 +178,12 @@ func (c *udpHopConn) recv(conn net.PacketConn) { } func (c *udpHopConn) hopLoop() { - ticker := time.NewTicker(c.nextInterval()) + ticker := time.NewTicker(time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1))) defer ticker.Stop() for { select { case <-ticker.C: - ticker.Reset(c.nextInterval()) + ticker.Reset(time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1))) c.mu.Lock() c.hop() c.mu.Unlock() @@ -230,7 +224,12 @@ func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { } } - return c.cur.WriteTo(p, c.addr) + _, err = c.cur.WriteTo(p, c.addr) + if err != nil { + errors.LogErrorInner(context.Background(), err, "send err") + return 0, nil + } + return len(p), nil } func (c *udpHopConn) Close() error { From e07eaa14cb9243ce13160de725531bde217d5826 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 15:55:03 +0800 Subject: [PATCH 10/24] crypto --- common/crypto/crypto.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/crypto/crypto.go b/common/crypto/crypto.go index 7a809037d21f..e9320a1c1079 100644 --- a/common/crypto/crypto.go +++ b/common/crypto/crypto.go @@ -10,12 +10,12 @@ import ( // [,) func RandBetween(from int64, to int64) int64 { - if d := from - to; d == 0 || d == -1 || d == 1 { - return from - } if from > to { from, to = to, from } + if d := to - from; d == 0 || d == 1 { + return from + } bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) return from + bigInt.Int64() } From e794c0a93efa2226428fa4c436548d0ac66b0a9f Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 16:54:13 +0800 Subject: [PATCH 11/24] fix memory leak --- transport/internet/finalmask/udphop/conn.go | 16 +++-- transport/internet/finalmask/xicmp/client.go | 44 ++++++++---- transport/internet/finalmask/xicmp/server.go | 74 ++++++++++++-------- 3 files changed, 87 insertions(+), 47 deletions(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index 5a4ee3cdab2e..771fde9e99f3 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -148,21 +148,21 @@ func (c *udpHopConn) hop() { func (c *udpHopConn) recv(conn net.PacketConn) { for { if c.closed() { - return + break } p := pool.Get().([]byte) n, addr, err := conn.ReadFrom(p) if err != nil { pool.Put(p[:cap(p)]) if goerrors.Is(err, io.EOF) || goerrors.Is(err, io.ErrClosedPipe) || goerrors.Is(err, gonet.ErrClosed) { - return + break } var netErr net.Error if goerrors.As(err, &netErr) && netErr.Timeout() { select { case c.readCh <- packet{err: err}: case <-c.closeCh: - return + goto exit } } errors.LogErrorInner(context.Background(), err, "recv err") @@ -172,9 +172,17 @@ func (c *udpHopConn) recv(conn net.PacketConn) { case c.readCh <- packet{p: p[:n], addr: addr}: case <-c.closeCh: pool.Put(p[:cap(p)]) - return + goto exit } } +exit: + select { + case packet := <-c.readCh: + if packet.p != nil { + pool.Put(packet.p[:cap(packet.p)]) + } + default: + } } func (c *udpHopConn) hopLoop() { diff --git a/transport/internet/finalmask/xicmp/client.go b/transport/internet/finalmask/xicmp/client.go index 3777bd879b94..230af0e950db 100644 --- a/transport/internet/finalmask/xicmp/client.go +++ b/transport/internet/finalmask/xicmp/client.go @@ -45,7 +45,7 @@ type xicmpConnClient struct { id int seq int readCh chan packet - closedCh chan struct{} + closeCh chan struct{} mu sync.Mutex } @@ -81,7 +81,7 @@ func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) { id: mathrand.Intn(65536), seq: 1, readCh: make(chan packet), - closedCh: make(chan struct{}), + closeCh: make(chan struct{}), } go conn.recv4() @@ -96,7 +96,7 @@ func (c *xicmpConnClient) ring(a, b uint16) uint16 { func (c *xicmpConnClient) closed() bool { select { - case <-c.closedCh: + case <-c.closeCh: return true default: return false @@ -108,7 +108,7 @@ func (c *xicmpConnClient) recv4() { for { if c.closed() { - return + break } n, addr, err := c.icmp4.ReadFrom(b[:]) @@ -119,8 +119,8 @@ func (c *xicmpConnClient) recv4() { case c.readCh <- packet{ err: err, }: - case <-c.closedCh: - return + case <-c.closeCh: + goto exit } } continue @@ -166,10 +166,18 @@ func (c *xicmpConnClient) recv4() { p: p, addr: addr, }: - case <-c.closedCh: + case <-c.closeCh: pool.Put(p) - return + goto exit + } + } +exit: + select { + case packet := <-c.readCh: + if packet.p != nil { + pool.Put(packet.p) } + default: } } @@ -189,8 +197,8 @@ func (c *xicmpConnClient) recv6() { case c.readCh <- packet{ err: err, }: - case <-c.closedCh: - return + case <-c.closeCh: + goto exit } } continue @@ -236,11 +244,19 @@ func (c *xicmpConnClient) recv6() { p: p, addr: addr, }: - case <-c.closedCh: + case <-c.closeCh: pool.Put(p) - return + goto exit } } +exit: + select { + case packet := <-c.readCh: + if packet.p != nil { + pool.Put(packet.p) + } + default: + } } func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) { @@ -251,7 +267,7 @@ func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) { pool.Put(packet.p) } return n, packet.addr, packet.err - case <-c.closedCh: + case <-c.closeCh: return 0, nil, io.EOF } } @@ -307,7 +323,7 @@ func (c *xicmpConnClient) Close() error { if c.closed() { return nil } - close(c.closedCh) + close(c.closeCh) _ = c.icmp4.Close() _ = c.icmp6.Close() _ = c.conn.Close() diff --git a/transport/internet/finalmask/xicmp/server.go b/transport/internet/finalmask/xicmp/server.go index 05ba48bbbbac..97f71e1aa086 100644 --- a/transport/internet/finalmask/xicmp/server.go +++ b/transport/internet/finalmask/xicmp/server.go @@ -37,14 +37,14 @@ type record struct { } type xicmpConnServer struct { - conn net.PacketConn - icmp4 *icmp.PacketConn - icmp6 *icmp.PacketConn - ips map[netip.Addr]struct{} - rec map[string]record - readCh chan packet - closedCh chan struct{} - mu sync.Mutex + conn net.PacketConn + icmp4 *icmp.PacketConn + icmp6 *icmp.PacketConn + ips map[netip.Addr]struct{} + rec map[string]record + readCh chan packet + closeCh chan struct{} + mu sync.Mutex } func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { @@ -63,13 +63,13 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { } conn := &xicmpConnServer{ - conn: raw, - icmp4: icmp4, - icmp6: icmp6, - ips: ips, - rec: make(map[string]record), - readCh: make(chan packet), - closedCh: make(chan struct{}), + conn: raw, + icmp4: icmp4, + icmp6: icmp6, + ips: ips, + rec: make(map[string]record), + readCh: make(chan packet), + closeCh: make(chan struct{}), } go conn.clean() @@ -81,7 +81,7 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { func (c *xicmpConnServer) closed() bool { select { - case <-c.closedCh: + case <-c.closeCh: return true default: return false @@ -102,7 +102,7 @@ func (c *xicmpConnServer) clean() { } } c.mu.Unlock() - case <-c.closedCh: + case <-c.closeCh: return } } @@ -113,7 +113,7 @@ func (c *xicmpConnServer) recv4() { for { if c.closed() { - return + break } n, addr, err := c.icmp4.ReadFrom(b[:]) @@ -124,8 +124,8 @@ func (c *xicmpConnServer) recv4() { case c.readCh <- packet{ err: err, }: - case <-c.closedCh: - return + case <-c.closeCh: + goto exit } } continue @@ -179,11 +179,19 @@ func (c *xicmpConnServer) recv4() { p: p, addr: cAddr, }: - case <-c.closedCh: + case <-c.closeCh: pool.Put(p) - return + goto exit } } +exit: + select { + case packet := <-c.readCh: + if packet.p != nil { + pool.Put(packet.p) + } + default: + } } func (c *xicmpConnServer) recv6() { @@ -191,7 +199,7 @@ func (c *xicmpConnServer) recv6() { for { if c.closed() { - return + break } n, addr, err := c.icmp6.ReadFrom(b[:]) @@ -202,8 +210,8 @@ func (c *xicmpConnServer) recv6() { case c.readCh <- packet{ err: err, }: - case <-c.closedCh: - return + case <-c.closeCh: + goto exit } } continue @@ -257,11 +265,19 @@ func (c *xicmpConnServer) recv6() { p: p, addr: cAddr, }: - case <-c.closedCh: + case <-c.closeCh: pool.Put(p) - return + goto exit } } +exit: + select { + case packet := <-c.readCh: + if packet.p != nil { + pool.Put(packet.p) + } + default: + } } func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) { @@ -272,7 +288,7 @@ func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) { pool.Put(packet.p) } return n, packet.addr, packet.err - case <-c.closedCh: + case <-c.closeCh: return 0, nil, io.EOF } } @@ -323,7 +339,7 @@ func (c *xicmpConnServer) Close() error { if c.closed() { return nil } - close(c.closedCh) + close(c.closeCh) _ = c.icmp4.Close() _ = c.icmp6.Close() _ = c.conn.Close() From 6268df235349d7b930fc4f773f848fb8d01c5128 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 17:16:34 +0800 Subject: [PATCH 12/24] udphop clean --- transport/internet/finalmask/udphop/conn.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index 771fde9e99f3..f8a49b97fc29 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -176,12 +176,14 @@ func (c *udpHopConn) recv(conn net.PacketConn) { } } exit: - select { - case packet := <-c.readCh: - if packet.p != nil { - pool.Put(packet.p[:cap(packet.p)]) + if c.closed() { + select { + case packet := <-c.readCh: + if packet.p != nil { + pool.Put(packet.p[:cap(packet.p)]) + } + default: } - default: } } From 5624e66719c49ad8303b224424d3d68d98a380e1 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 19:56:31 +0800 Subject: [PATCH 13/24] chore --- transport/internet/finalmask/udphop/conn.go | 4 +-- .../internet/finalmask/xicmp/server_oob.go | 28 +++++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index f8a49b97fc29..3a672179b91f 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -188,12 +188,12 @@ exit: } func (c *udpHopConn) hopLoop() { - ticker := time.NewTicker(time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1))) + ticker := time.NewTicker(time.Second * time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1))) defer ticker.Stop() for { select { case <-ticker.C: - ticker.Reset(time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1))) + ticker.Reset(time.Second * time.Duration(crypto.RandBetween(c.intervalMin, c.intervalMax+1))) c.mu.Lock() c.hop() c.mu.Unlock() diff --git a/transport/internet/finalmask/xicmp/server_oob.go b/transport/internet/finalmask/xicmp/server_oob.go index 0a04b0802da7..987dafd6d537 100644 --- a/transport/internet/finalmask/xicmp/server_oob.go +++ b/transport/internet/finalmask/xicmp/server_oob.go @@ -122,7 +122,7 @@ func (c *xicmpConnServer) recv4() { for { if c.closed() { - return + break } n, cm, addr, err := c.ipv4PC.ReadFrom(b[:]) @@ -134,7 +134,7 @@ func (c *xicmpConnServer) recv4() { err: err, }: case <-c.closedCh: - return + goto exit } } continue @@ -191,9 +191,17 @@ func (c *xicmpConnServer) recv4() { }: case <-c.closedCh: pool.Put(p) - return + goto exit } } +exit: + select { + case packet := <-c.readCh: + if packet.p != nil { + pool.Put(packet.p) + } + default: + } } func (c *xicmpConnServer) recv6() { @@ -201,7 +209,7 @@ func (c *xicmpConnServer) recv6() { for { if c.closed() { - return + break } n, cm, addr, err := c.ipv6PC.ReadFrom(b[:]) @@ -213,7 +221,7 @@ func (c *xicmpConnServer) recv6() { err: err, }: case <-c.closedCh: - return + goto exit } } continue @@ -270,9 +278,17 @@ func (c *xicmpConnServer) recv6() { }: case <-c.closedCh: pool.Put(p) - return + goto exit } } +exit: + select { + case packet := <-c.readCh: + if packet.p != nil { + pool.Put(packet.p) + } + default: + } } func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) { From 6e1478677d496671e7f50f2d10d633dbfc16f05f Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 19:58:38 +0800 Subject: [PATCH 14/24] infra --- infra/conf/transport_internet.go | 18 ++++++++++++++++++ transport/internet/finalmask/udphop/conn.go | 12 ++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 3c99f6a5db9f..0ee83ae94799 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -1983,6 +1983,24 @@ func (c *UDPHop) Build() (proto.Message, error) { } } + for _, ip := range c.IPs { + _, err := netip.ParsePrefix(ip) + if err == nil { + continue + } + _, err = netip.ParseAddr(ip) + if err == nil { + continue + } + return nil, errors.New("invalid ips") + } + if len(c.Ports.Build().Ports()) == 0 { + return nil, errors.New("empty ports") + } + if c.Interval.From < 5 || c.Interval.To < 5 { + return nil, errors.New("invalid interval") + } + return &udphop.Config{ Sockopt: sockopt, IPs: c.IPs, diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index 3a672179b91f..e39565ac4919 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -55,12 +55,6 @@ type udpHopConn struct { } func NewUDPHopConn(c *Config, raw net.PacketConn) (net.PacketConn, error) { - if len(c.Ports) == 0 { - return nil, errors.New("empty ports") - } - if c.IntervalMin < 5 || c.IntervalMax < 5 { - return nil, errors.New("invalid interval") - } ips := make([]netip.Prefix, 0, len(c.IPs)) for _, ip := range c.IPs { prefix, err := netip.ParsePrefix(ip) @@ -75,6 +69,12 @@ func NewUDPHopConn(c *Config, raw net.PacketConn) (net.PacketConn, error) { } return nil, errors.New("invalid ips") } + if len(c.Ports) == 0 { + return nil, errors.New("empty ports") + } + if c.IntervalMin < 5 || c.IntervalMax < 5 { + return nil, errors.New("invalid interval") + } conn := &udpHopConn{ conn: raw, sockopt: c.Sockopt, From b0bcb2ab06dc2d205cd27a3bba4df8c253dc38ba Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 20:02:45 +0800 Subject: [PATCH 15/24] hopLoop --- transport/internet/finalmask/udphop/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index e39565ac4919..890c402ba715 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -87,7 +87,6 @@ func NewUDPHopConn(c *Config, raw net.PacketConn) (net.PacketConn, error) { readCh: make(chan packet), closeCh: make(chan struct{}), } - go conn.hopLoop() return conn, nil } @@ -232,6 +231,7 @@ func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { if c.cur == nil { return 0, nil } + go c.hopLoop() } _, err = c.cur.WriteTo(p, c.addr) From 70e5d2af4d3b528687c52536bf5c524f50c35bd7 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 20:04:13 +0800 Subject: [PATCH 16/24] chore --- .../internet/finalmask/xicmp/server_oob.go | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/transport/internet/finalmask/xicmp/server_oob.go b/transport/internet/finalmask/xicmp/server_oob.go index 987dafd6d537..610c1d17af74 100644 --- a/transport/internet/finalmask/xicmp/server_oob.go +++ b/transport/internet/finalmask/xicmp/server_oob.go @@ -39,16 +39,16 @@ type record struct { } type xicmpConnServer struct { - conn net.PacketConn - icmp4 *icmp.PacketConn - icmp6 *icmp.PacketConn - ipv4PC *ipv4.PacketConn - ipv6PC *ipv6.PacketConn - ips map[netip.Addr]struct{} - rec map[string]record - readCh chan packet - closedCh chan struct{} - mu sync.Mutex + conn net.PacketConn + icmp4 *icmp.PacketConn + icmp6 *icmp.PacketConn + ipv4PC *ipv4.PacketConn + ipv6PC *ipv6.PacketConn + ips map[netip.Addr]struct{} + rec map[string]record + readCh chan packet + closeCh chan struct{} + mu sync.Mutex } func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { @@ -67,15 +67,15 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { } conn := &xicmpConnServer{ - conn: raw, - icmp4: icmp4, - icmp6: icmp6, - ipv4PC: icmp4.IPv4PacketConn(), - ipv6PC: icmp6.IPv6PacketConn(), - ips: ips, - rec: make(map[string]record), - readCh: make(chan packet), - closedCh: make(chan struct{}), + conn: raw, + icmp4: icmp4, + icmp6: icmp6, + ipv4PC: icmp4.IPv4PacketConn(), + ipv6PC: icmp6.IPv6PacketConn(), + ips: ips, + rec: make(map[string]record), + readCh: make(chan packet), + closeCh: make(chan struct{}), } common.Must(conn.ipv4PC.SetControlMessage(ipv4.FlagDst, true)) @@ -90,7 +90,7 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { func (c *xicmpConnServer) closed() bool { select { - case <-c.closedCh: + case <-c.closeCh: return true default: return false @@ -111,7 +111,7 @@ func (c *xicmpConnServer) clean() { } } c.mu.Unlock() - case <-c.closedCh: + case <-c.closeCh: return } } @@ -133,7 +133,7 @@ func (c *xicmpConnServer) recv4() { case c.readCh <- packet{ err: err, }: - case <-c.closedCh: + case <-c.closeCh: goto exit } } @@ -189,7 +189,7 @@ func (c *xicmpConnServer) recv4() { p: p, addr: cAddr, }: - case <-c.closedCh: + case <-c.closeCh: pool.Put(p) goto exit } @@ -220,7 +220,7 @@ func (c *xicmpConnServer) recv6() { case c.readCh <- packet{ err: err, }: - case <-c.closedCh: + case <-c.closeCh: goto exit } } @@ -276,7 +276,7 @@ func (c *xicmpConnServer) recv6() { p: p, addr: cAddr, }: - case <-c.closedCh: + case <-c.closeCh: pool.Put(p) goto exit } @@ -299,7 +299,7 @@ func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) { pool.Put(packet.p) } return n, packet.addr, packet.err - case <-c.closedCh: + case <-c.closeCh: return 0, nil, io.EOF } } @@ -350,7 +350,7 @@ func (c *xicmpConnServer) Close() error { if c.closed() { return nil } - close(c.closedCh) + close(c.closeCh) _ = c.icmp4.Close() _ = c.icmp6.Close() _ = c.conn.Close() From 27616db471813a32e5453f68009e12564774fc47 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 20 Jun 2026 20:20:58 +0800 Subject: [PATCH 17/24] sync.WaitGroup --- transport/internet/finalmask/udphop/conn.go | 13 +++++++++---- transport/internet/finalmask/xicmp/client.go | 17 +++++++++++------ transport/internet/finalmask/xicmp/server.go | 17 +++++++++++------ .../internet/finalmask/xicmp/server_oob.go | 17 +++++++++++------ transport/internet/hysteria/conn.go | 11 ++++------- 5 files changed, 46 insertions(+), 29 deletions(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index 890c402ba715..23f0e90971ff 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -51,6 +51,7 @@ type udpHopConn struct { addr *net.UDPAddr readCh chan packet closeCh chan struct{} + wg sync.WaitGroup mu sync.Mutex } @@ -141,10 +142,13 @@ func (c *udpHopConn) hop() { c.pre = c.cur c.cur = pkt c.addr = addr + c.wg.Add(1) go c.recv(pkt) } func (c *udpHopConn) recv(conn net.PacketConn) { + defer c.wg.Done() + for { if c.closed() { break @@ -203,16 +207,15 @@ func (c *udpHopConn) hopLoop() { } func (c *udpHopConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - select { - case packet := <-c.readCh: + packet, ok := <-c.readCh + if ok { if packet.p != nil { n = copy(p, packet.p) pool.Put(packet.p[:cap(packet.p)]) } return n, packet.addr, packet.err - case <-c.closeCh: - return 0, nil, io.EOF } + return 0, nil, io.EOF } func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { @@ -256,6 +259,8 @@ func (c *udpHopConn) Close() error { _ = c.cur.Close() } _ = c.conn.Close() + c.wg.Wait() + close(c.readCh) return nil } diff --git a/transport/internet/finalmask/xicmp/client.go b/transport/internet/finalmask/xicmp/client.go index 230af0e950db..9a44d5342993 100644 --- a/transport/internet/finalmask/xicmp/client.go +++ b/transport/internet/finalmask/xicmp/client.go @@ -46,6 +46,7 @@ type xicmpConnClient struct { seq int readCh chan packet closeCh chan struct{} + wg sync.WaitGroup mu sync.Mutex } @@ -84,6 +85,7 @@ func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) { closeCh: make(chan struct{}), } + conn.wg.Add(2) go conn.recv4() go conn.recv6() @@ -104,8 +106,9 @@ func (c *xicmpConnClient) closed() bool { } func (c *xicmpConnClient) recv4() { - var b [finalmask.UDPSize]byte + defer c.wg.Done() + var b [finalmask.UDPSize]byte for { if c.closed() { break @@ -182,8 +185,9 @@ exit: } func (c *xicmpConnClient) recv6() { - var b [finalmask.UDPSize]byte + defer c.wg.Done() + var b [finalmask.UDPSize]byte for { if c.closed() { break @@ -260,16 +264,15 @@ exit: } func (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - select { - case packet := <-c.readCh: + packet, ok := <-c.readCh + if ok { if packet.p != nil { n = copy(p, packet.p) pool.Put(packet.p) } return n, packet.addr, packet.err - case <-c.closeCh: - return 0, nil, io.EOF } + return 0, nil, io.EOF } func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) { @@ -327,6 +330,8 @@ func (c *xicmpConnClient) Close() error { _ = c.icmp4.Close() _ = c.icmp6.Close() _ = c.conn.Close() + c.wg.Wait() + close(c.readCh) return nil } diff --git a/transport/internet/finalmask/xicmp/server.go b/transport/internet/finalmask/xicmp/server.go index 97f71e1aa086..da2251601435 100644 --- a/transport/internet/finalmask/xicmp/server.go +++ b/transport/internet/finalmask/xicmp/server.go @@ -44,6 +44,7 @@ type xicmpConnServer struct { rec map[string]record readCh chan packet closeCh chan struct{} + wg sync.WaitGroup mu sync.Mutex } @@ -73,6 +74,7 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { } go conn.clean() + conn.wg.Add(2) go conn.recv4() go conn.recv6() @@ -109,8 +111,9 @@ func (c *xicmpConnServer) clean() { } func (c *xicmpConnServer) recv4() { - var b [finalmask.UDPSize]byte + defer c.wg.Done() + var b [finalmask.UDPSize]byte for { if c.closed() { break @@ -195,8 +198,9 @@ exit: } func (c *xicmpConnServer) recv6() { - var b [finalmask.UDPSize]byte + defer c.wg.Done() + var b [finalmask.UDPSize]byte for { if c.closed() { break @@ -281,16 +285,15 @@ exit: } func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - select { - case packet := <-c.readCh: + packet, ok := <-c.readCh + if ok { if packet.p != nil { n = copy(p, packet.p) pool.Put(packet.p) } return n, packet.addr, packet.err - case <-c.closeCh: - return 0, nil, io.EOF } + return 0, nil, io.EOF } func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) { @@ -343,6 +346,8 @@ func (c *xicmpConnServer) Close() error { _ = c.icmp4.Close() _ = c.icmp6.Close() _ = c.conn.Close() + c.wg.Wait() + close(c.readCh) return nil } diff --git a/transport/internet/finalmask/xicmp/server_oob.go b/transport/internet/finalmask/xicmp/server_oob.go index 610c1d17af74..a5f113e2b37c 100644 --- a/transport/internet/finalmask/xicmp/server_oob.go +++ b/transport/internet/finalmask/xicmp/server_oob.go @@ -48,6 +48,7 @@ type xicmpConnServer struct { rec map[string]record readCh chan packet closeCh chan struct{} + wg sync.WaitGroup mu sync.Mutex } @@ -82,6 +83,7 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { common.Must(conn.ipv6PC.SetControlMessage(ipv6.FlagDst, true)) go conn.clean() + conn.wg.Add(2) go conn.recv4() go conn.recv6() @@ -118,8 +120,9 @@ func (c *xicmpConnServer) clean() { } func (c *xicmpConnServer) recv4() { - var b [finalmask.UDPSize]byte + defer c.wg.Done() + var b [finalmask.UDPSize]byte for { if c.closed() { break @@ -205,8 +208,9 @@ exit: } func (c *xicmpConnServer) recv6() { - var b [finalmask.UDPSize]byte + defer c.wg.Done() + var b [finalmask.UDPSize]byte for { if c.closed() { break @@ -292,16 +296,15 @@ exit: } func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - select { - case packet := <-c.readCh: + packet, ok := <-c.readCh + if ok { if packet.p != nil { n = copy(p, packet.p) pool.Put(packet.p) } return n, packet.addr, packet.err - case <-c.closeCh: - return 0, nil, io.EOF } + return 0, nil, io.EOF } func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) { @@ -354,6 +357,8 @@ func (c *xicmpConnServer) Close() error { _ = c.icmp4.Close() _ = c.icmp6.Close() _ = c.conn.Close() + c.wg.Wait() + close(c.readCh) return nil } diff --git a/transport/internet/hysteria/conn.go b/transport/internet/hysteria/conn.go index ce2a4af31cf7..07b8f6d9fc6e 100644 --- a/transport/internet/hysteria/conn.go +++ b/transport/internet/hysteria/conn.go @@ -103,14 +103,11 @@ func (c *InterConn) Update() { func (c *InterConn) Read(p []byte) (int, error) { b, ok := <-c.ch - if !ok { - return 0, io.EOF - } - if len(p) < len(b) { - return 0, io.ErrShortBuffer + if ok { + c.Update() + return copy(p, b), nil } - c.Update() - return copy(p, b), nil + return 0, io.EOF } func (c *InterConn) Write(p []byte) (int, error) { From 79fa7cc0b4d37a814297516a65ac53d3485350b7 Mon Sep 17 00:00:00 2001 From: null Date: Sun, 21 Jun 2026 00:21:29 +0800 Subject: [PATCH 18/24] chore --- transport/internet/finalmask/udphop/conn.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index 23f0e90971ff..0292fad2bc7f 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -237,12 +237,7 @@ func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { go c.hopLoop() } - _, err = c.cur.WriteTo(p, c.addr) - if err != nil { - errors.LogErrorInner(context.Background(), err, "send err") - return 0, nil - } - return len(p), nil + return c.cur.WriteTo(p, c.addr) } func (c *udpHopConn) Close() error { From f9f235e77c1c37337c3499fce684977061459db4 Mon Sep 17 00:00:00 2001 From: null Date: Sun, 21 Jun 2026 00:46:03 +0800 Subject: [PATCH 19/24] slices.Reverse --- transport/internet/finalmask/finalmask.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/transport/internet/finalmask/finalmask.go b/transport/internet/finalmask/finalmask.go index 6e98b009919b..db723de56e46 100644 --- a/transport/internet/finalmask/finalmask.go +++ b/transport/internet/finalmask/finalmask.go @@ -19,15 +19,14 @@ type UdpmaskManager struct { } func NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager { - return &UdpmaskManager{ - udpmasks: udpmasks, - } + slices.Reverse(udpmasks) + return &UdpmaskManager{udpmasks: udpmasks} } func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) { var sizes []int var conns []net.PacketConn - for i, mask := range slices.Backward(m.udpmasks) { + for i, mask := range m.udpmasks { if _, ok := mask.(headerConn); ok { conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1) if err != nil { @@ -60,7 +59,7 @@ func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketCon func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) { var sizes []int var conns []net.PacketConn - for i, mask := range slices.Backward(m.udpmasks) { + for i, mask := range m.udpmasks { if _, ok := mask.(headerConn); ok { conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1) if err != nil { @@ -202,14 +201,13 @@ type TcpmaskManager struct { } func NewTcpmaskManager(tcpmasks []Tcpmask) *TcpmaskManager { - return &TcpmaskManager{ - tcpmasks: tcpmasks, - } + slices.Reverse(tcpmasks) + return &TcpmaskManager{tcpmasks: tcpmasks} } func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) { var err error - for _, mask := range slices.Backward(m.tcpmasks) { + for _, mask := range m.tcpmasks { raw, err = mask.WrapConnClient(raw) if err != nil { return nil, err @@ -220,7 +218,7 @@ func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) { func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) { var err error - for _, mask := range slices.Backward(m.tcpmasks) { + for _, mask := range m.tcpmasks { raw, err = mask.WrapConnServer(raw) if err != nil { return nil, err From 779fc75c1991730632b31ea9210407938a59b09e Mon Sep 17 00:00:00 2001 From: null Date: Sun, 21 Jun 2026 02:11:34 +0800 Subject: [PATCH 20/24] better cleanup --- transport/internet/finalmask/udphop/conn.go | 23 ++++++------ transport/internet/finalmask/xicmp/client.go | 35 +++++++------------ transport/internet/finalmask/xicmp/server.go | 35 +++++++------------ .../internet/finalmask/xicmp/server_oob.go | 35 +++++++------------ 4 files changed, 49 insertions(+), 79 deletions(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index 0292fad2bc7f..a636f5b2e645 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -151,7 +151,7 @@ func (c *udpHopConn) recv(conn net.PacketConn) { for { if c.closed() { - break + return } p := pool.Get().([]byte) n, addr, err := conn.ReadFrom(p) @@ -165,7 +165,7 @@ func (c *udpHopConn) recv(conn net.PacketConn) { select { case c.readCh <- packet{err: err}: case <-c.closeCh: - goto exit + return } } errors.LogErrorInner(context.Background(), err, "recv err") @@ -175,17 +175,7 @@ func (c *udpHopConn) recv(conn net.PacketConn) { case c.readCh <- packet{p: p[:n], addr: addr}: case <-c.closeCh: pool.Put(p[:cap(p)]) - goto exit - } - } -exit: - if c.closed() { - select { - case packet := <-c.readCh: - if packet.p != nil { - pool.Put(packet.p[:cap(packet.p)]) - } - default: + return } } } @@ -255,6 +245,13 @@ func (c *udpHopConn) Close() error { } _ = c.conn.Close() c.wg.Wait() + select { + case p := <-c.readCh: + if p.p != nil { + pool.Put(p.p[:cap(p.p)]) + } + default: + } close(c.readCh) return nil } diff --git a/transport/internet/finalmask/xicmp/client.go b/transport/internet/finalmask/xicmp/client.go index 9a44d5342993..99d11ea42bf8 100644 --- a/transport/internet/finalmask/xicmp/client.go +++ b/transport/internet/finalmask/xicmp/client.go @@ -111,7 +111,7 @@ func (c *xicmpConnClient) recv4() { var b [finalmask.UDPSize]byte for { if c.closed() { - break + return } n, addr, err := c.icmp4.ReadFrom(b[:]) @@ -123,7 +123,7 @@ func (c *xicmpConnClient) recv4() { err: err, }: case <-c.closeCh: - goto exit + return } } continue @@ -171,17 +171,9 @@ func (c *xicmpConnClient) recv4() { }: case <-c.closeCh: pool.Put(p) - goto exit + return } } -exit: - select { - case packet := <-c.readCh: - if packet.p != nil { - pool.Put(packet.p) - } - default: - } } func (c *xicmpConnClient) recv6() { @@ -190,7 +182,7 @@ func (c *xicmpConnClient) recv6() { var b [finalmask.UDPSize]byte for { if c.closed() { - break + return } n, addr, err := c.icmp6.ReadFrom(b[:]) @@ -202,7 +194,7 @@ func (c *xicmpConnClient) recv6() { err: err, }: case <-c.closeCh: - goto exit + return } } continue @@ -250,16 +242,8 @@ func (c *xicmpConnClient) recv6() { }: case <-c.closeCh: pool.Put(p) - goto exit - } - } -exit: - select { - case packet := <-c.readCh: - if packet.p != nil { - pool.Put(packet.p) + return } - default: } } @@ -331,6 +315,13 @@ func (c *xicmpConnClient) Close() error { _ = c.icmp6.Close() _ = c.conn.Close() c.wg.Wait() + select { + case p := <-c.readCh: + if p.p != nil { + pool.Put(p.p) + } + default: + } close(c.readCh) return nil } diff --git a/transport/internet/finalmask/xicmp/server.go b/transport/internet/finalmask/xicmp/server.go index da2251601435..085248dce8d7 100644 --- a/transport/internet/finalmask/xicmp/server.go +++ b/transport/internet/finalmask/xicmp/server.go @@ -116,7 +116,7 @@ func (c *xicmpConnServer) recv4() { var b [finalmask.UDPSize]byte for { if c.closed() { - break + return } n, addr, err := c.icmp4.ReadFrom(b[:]) @@ -128,7 +128,7 @@ func (c *xicmpConnServer) recv4() { err: err, }: case <-c.closeCh: - goto exit + return } } continue @@ -184,16 +184,8 @@ func (c *xicmpConnServer) recv4() { }: case <-c.closeCh: pool.Put(p) - goto exit - } - } -exit: - select { - case packet := <-c.readCh: - if packet.p != nil { - pool.Put(packet.p) + return } - default: } } @@ -203,7 +195,7 @@ func (c *xicmpConnServer) recv6() { var b [finalmask.UDPSize]byte for { if c.closed() { - break + return } n, addr, err := c.icmp6.ReadFrom(b[:]) @@ -215,7 +207,7 @@ func (c *xicmpConnServer) recv6() { err: err, }: case <-c.closeCh: - goto exit + return } } continue @@ -271,16 +263,8 @@ func (c *xicmpConnServer) recv6() { }: case <-c.closeCh: pool.Put(p) - goto exit - } - } -exit: - select { - case packet := <-c.readCh: - if packet.p != nil { - pool.Put(packet.p) + return } - default: } } @@ -347,6 +331,13 @@ func (c *xicmpConnServer) Close() error { _ = c.icmp6.Close() _ = c.conn.Close() c.wg.Wait() + select { + case p := <-c.readCh: + if p.p != nil { + pool.Put(p.p) + } + default: + } close(c.readCh) return nil } diff --git a/transport/internet/finalmask/xicmp/server_oob.go b/transport/internet/finalmask/xicmp/server_oob.go index a5f113e2b37c..fc5681b21b1f 100644 --- a/transport/internet/finalmask/xicmp/server_oob.go +++ b/transport/internet/finalmask/xicmp/server_oob.go @@ -125,7 +125,7 @@ func (c *xicmpConnServer) recv4() { var b [finalmask.UDPSize]byte for { if c.closed() { - break + return } n, cm, addr, err := c.ipv4PC.ReadFrom(b[:]) @@ -137,7 +137,7 @@ func (c *xicmpConnServer) recv4() { err: err, }: case <-c.closeCh: - goto exit + return } } continue @@ -194,16 +194,8 @@ func (c *xicmpConnServer) recv4() { }: case <-c.closeCh: pool.Put(p) - goto exit - } - } -exit: - select { - case packet := <-c.readCh: - if packet.p != nil { - pool.Put(packet.p) + return } - default: } } @@ -213,7 +205,7 @@ func (c *xicmpConnServer) recv6() { var b [finalmask.UDPSize]byte for { if c.closed() { - break + return } n, cm, addr, err := c.ipv6PC.ReadFrom(b[:]) @@ -225,7 +217,7 @@ func (c *xicmpConnServer) recv6() { err: err, }: case <-c.closeCh: - goto exit + return } } continue @@ -282,16 +274,8 @@ func (c *xicmpConnServer) recv6() { }: case <-c.closeCh: pool.Put(p) - goto exit - } - } -exit: - select { - case packet := <-c.readCh: - if packet.p != nil { - pool.Put(packet.p) + return } - default: } } @@ -358,6 +342,13 @@ func (c *xicmpConnServer) Close() error { _ = c.icmp6.Close() _ = c.conn.Close() c.wg.Wait() + select { + case p := <-c.readCh: + if p.p != nil { + pool.Put(p.p) + } + default: + } close(c.readCh) return nil } From ef15ea5db0a9eeb1b55f25ffc9fa4ec1fff91f7d Mon Sep 17 00:00:00 2001 From: null Date: Thu, 25 Jun 2026 16:21:00 +0800 Subject: [PATCH 21/24] recv err --- transport/internet/finalmask/udphop/conn.go | 10 ++++++++-- transport/internet/finalmask/xicmp/client.go | 7 +++++-- transport/internet/finalmask/xicmp/server.go | 7 +++++-- transport/internet/finalmask/xicmp/server_oob.go | 7 +++++-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index a636f5b2e645..191f805dc339 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -167,8 +167,9 @@ func (c *udpHopConn) recv(conn net.PacketConn) { case <-c.closeCh: return } + } else { + errors.LogErrorInner(context.Background(), err, "recv err") } - errors.LogErrorInner(context.Background(), err, "recv err") continue } select { @@ -227,7 +228,12 @@ func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { go c.hopLoop() } - return c.cur.WriteTo(p, c.addr) + _, err = c.cur.WriteTo(p, c.addr) + if err != nil { + errors.LogErrorInner(context.Background(), err, "send err") + return 0, err + } + return len(p), nil } func (c *udpHopConn) Close() error { diff --git a/transport/internet/finalmask/xicmp/client.go b/transport/internet/finalmask/xicmp/client.go index 99d11ea42bf8..faee4def16bc 100644 --- a/transport/internet/finalmask/xicmp/client.go +++ b/transport/internet/finalmask/xicmp/client.go @@ -125,6 +125,8 @@ func (c *xicmpConnClient) recv4() { case <-c.closeCh: return } + } else { + errors.LogErrorInner(context.Background(), err, "recv4 err") } continue } @@ -196,6 +198,8 @@ func (c *xicmpConnClient) recv6() { case <-c.closeCh: return } + } else { + errors.LogErrorInner(context.Background(), err, "recv6 err") } continue } @@ -297,10 +301,9 @@ func (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) { } if err != nil { - errors.LogErrorInner(context.Background(), err, "xicmp write") + errors.LogErrorInner(context.Background(), err, "send err") return 0, err } - return len(p), nil } diff --git a/transport/internet/finalmask/xicmp/server.go b/transport/internet/finalmask/xicmp/server.go index 085248dce8d7..ec119125a0e2 100644 --- a/transport/internet/finalmask/xicmp/server.go +++ b/transport/internet/finalmask/xicmp/server.go @@ -130,6 +130,8 @@ func (c *xicmpConnServer) recv4() { case <-c.closeCh: return } + } else { + errors.LogErrorInner(context.Background(), err, "recv4 err") } continue } @@ -209,6 +211,8 @@ func (c *xicmpConnServer) recv6() { case <-c.closeCh: return } + } else { + errors.LogErrorInner(context.Background(), err, "recv6 err") } continue } @@ -313,10 +317,9 @@ func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) { } if err != nil { - errors.LogErrorInner(context.Background(), err, "xicmp write") + errors.LogErrorInner(context.Background(), err, "send err") return 0, err } - return len(p), nil } diff --git a/transport/internet/finalmask/xicmp/server_oob.go b/transport/internet/finalmask/xicmp/server_oob.go index fc5681b21b1f..a0262e60ee93 100644 --- a/transport/internet/finalmask/xicmp/server_oob.go +++ b/transport/internet/finalmask/xicmp/server_oob.go @@ -139,6 +139,8 @@ func (c *xicmpConnServer) recv4() { case <-c.closeCh: return } + } else { + errors.LogErrorInner(context.Background(), err, "recv4 err") } continue } @@ -219,6 +221,8 @@ func (c *xicmpConnServer) recv6() { case <-c.closeCh: return } + } else { + errors.LogErrorInner(context.Background(), err, "recv6 err") } continue } @@ -324,10 +328,9 @@ func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) { } if err != nil { - errors.LogErrorInner(context.Background(), err, "xicmp write") + errors.LogErrorInner(context.Background(), err, "send err") return 0, err } - return len(p), nil } From 23053199de88a5f58f77727b9c78dc6be22cb921 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 25 Jun 2026 16:44:38 +0800 Subject: [PATCH 22/24] chore --- transport/internet/finalmask/udphop/conn.go | 3 +-- transport/internet/finalmask/xicmp/client.go | 6 ++---- transport/internet/finalmask/xicmp/server.go | 6 ++---- transport/internet/finalmask/xicmp/server_oob.go | 6 ++---- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index 191f805dc339..3c2c38d8de7c 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -167,9 +167,8 @@ func (c *udpHopConn) recv(conn net.PacketConn) { case <-c.closeCh: return } - } else { - errors.LogErrorInner(context.Background(), err, "recv err") } + errors.LogErrorInner(context.Background(), err, "recv err") continue } select { diff --git a/transport/internet/finalmask/xicmp/client.go b/transport/internet/finalmask/xicmp/client.go index faee4def16bc..93db4e307a0d 100644 --- a/transport/internet/finalmask/xicmp/client.go +++ b/transport/internet/finalmask/xicmp/client.go @@ -125,9 +125,8 @@ func (c *xicmpConnClient) recv4() { case <-c.closeCh: return } - } else { - errors.LogErrorInner(context.Background(), err, "recv4 err") } + errors.LogErrorInner(context.Background(), err, "recv4 err") continue } @@ -198,9 +197,8 @@ func (c *xicmpConnClient) recv6() { case <-c.closeCh: return } - } else { - errors.LogErrorInner(context.Background(), err, "recv6 err") } + errors.LogErrorInner(context.Background(), err, "recv6 err") continue } diff --git a/transport/internet/finalmask/xicmp/server.go b/transport/internet/finalmask/xicmp/server.go index ec119125a0e2..f3fb429ba696 100644 --- a/transport/internet/finalmask/xicmp/server.go +++ b/transport/internet/finalmask/xicmp/server.go @@ -130,9 +130,8 @@ func (c *xicmpConnServer) recv4() { case <-c.closeCh: return } - } else { - errors.LogErrorInner(context.Background(), err, "recv4 err") } + errors.LogErrorInner(context.Background(), err, "recv4 err") continue } @@ -211,9 +210,8 @@ func (c *xicmpConnServer) recv6() { case <-c.closeCh: return } - } else { - errors.LogErrorInner(context.Background(), err, "recv6 err") } + errors.LogErrorInner(context.Background(), err, "recv6 err") continue } diff --git a/transport/internet/finalmask/xicmp/server_oob.go b/transport/internet/finalmask/xicmp/server_oob.go index a0262e60ee93..d8bdb93c3d93 100644 --- a/transport/internet/finalmask/xicmp/server_oob.go +++ b/transport/internet/finalmask/xicmp/server_oob.go @@ -139,9 +139,8 @@ func (c *xicmpConnServer) recv4() { case <-c.closeCh: return } - } else { - errors.LogErrorInner(context.Background(), err, "recv4 err") } + errors.LogErrorInner(context.Background(), err, "recv4 err") continue } @@ -221,9 +220,8 @@ func (c *xicmpConnServer) recv6() { case <-c.closeCh: return } - } else { - errors.LogErrorInner(context.Background(), err, "recv6 err") } + errors.LogErrorInner(context.Background(), err, "recv6 err") continue } From 4ceb0876167b664194d69f0b27fd2b97a407ac63 Mon Sep 17 00:00:00 2001 From: null Date: Sun, 28 Jun 2026 16:28:07 +0800 Subject: [PATCH 23/24] overwrite only --- infra/conf/transport_internet.go | 20 ++++++----- .../internet/finalmask/udphop/config.pb.go | 29 ++++++++++------ .../internet/finalmask/udphop/config.proto | 9 ++--- transport/internet/finalmask/udphop/conn.go | 34 +++++++++++++------ 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 0ee83ae94799..c0fbb3fdfa60 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -1967,10 +1967,11 @@ func (c *Realm) Build() (proto.Message, error) { } type UDPHop struct { - Sockopt *SocketConfig `json:"sockopt"` - IPs []string `json:"ips"` - Ports PortList `json:"ports"` - Interval Int32Range `json:"interval"` + Sockopt *SocketConfig `json:"sockopt"` + OverwriteOnly bool `json:"overwriteOnly"` + IPs []string `json:"ips"` + Ports PortList `json:"ports"` + Interval Int32Range `json:"interval"` } func (c *UDPHop) Build() (proto.Message, error) { @@ -2002,11 +2003,12 @@ func (c *UDPHop) Build() (proto.Message, error) { } return &udphop.Config{ - Sockopt: sockopt, - IPs: c.IPs, - Ports: c.Ports.Build().Ports(), - IntervalMin: int64(c.Interval.From), - IntervalMax: int64(c.Interval.To), + Sockopt: sockopt, + OverwriteOnly: c.OverwriteOnly, + IPs: c.IPs, + Ports: c.Ports.Build().Ports(), + IntervalMin: int64(c.Interval.From), + IntervalMax: int64(c.Interval.To), }, nil } diff --git a/transport/internet/finalmask/udphop/config.pb.go b/transport/internet/finalmask/udphop/config.pb.go index 6659660e2c10..66b09c50bc57 100644 --- a/transport/internet/finalmask/udphop/config.pb.go +++ b/transport/internet/finalmask/udphop/config.pb.go @@ -25,10 +25,11 @@ const ( type Config struct { state protoimpl.MessageState `protogen:"open.v1"` Sockopt *internet.SocketConfig `protobuf:"bytes,1,opt,name=sockopt,proto3" json:"sockopt,omitempty"` - IPs []string `protobuf:"bytes,2,rep,name=IPs,proto3" json:"IPs,omitempty"` - Ports []uint32 `protobuf:"varint,3,rep,packed,name=ports,proto3" json:"ports,omitempty"` - IntervalMin int64 `protobuf:"varint,4,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"` - IntervalMax int64 `protobuf:"varint,5,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"` + OverwriteOnly bool `protobuf:"varint,2,opt,name=overwrite_only,json=overwriteOnly,proto3" json:"overwrite_only,omitempty"` + IPs []string `protobuf:"bytes,3,rep,name=IPs,proto3" json:"IPs,omitempty"` + Ports []uint32 `protobuf:"varint,4,rep,packed,name=ports,proto3" json:"ports,omitempty"` + IntervalMin int64 `protobuf:"varint,5,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"` + IntervalMax int64 `protobuf:"varint,6,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -70,6 +71,13 @@ func (x *Config) GetSockopt() *internet.SocketConfig { return nil } +func (x *Config) GetOverwriteOnly() bool { + if x != nil { + return x.OverwriteOnly + } + return false +} + func (x *Config) GetIPs() []string { if x != nil { return x.IPs @@ -102,13 +110,14 @@ var File_transport_internet_finalmask_udphop_config_proto protoreflect.FileDescr const file_transport_internet_finalmask_udphop_config_proto_rawDesc = "" + "\n" + - "0transport/internet/finalmask/udphop/config.proto\x12(xray.transport.internet.finalmask.udphop\x1a\x1ftransport/internet/config.proto\"\xb7\x01\n" + + "0transport/internet/finalmask/udphop/config.proto\x12(xray.transport.internet.finalmask.udphop\x1a\x1ftransport/internet/config.proto\"\xde\x01\n" + "\x06Config\x12?\n" + - "\asockopt\x18\x01 \x01(\v2%.xray.transport.internet.SocketConfigR\asockopt\x12\x10\n" + - "\x03IPs\x18\x02 \x03(\tR\x03IPs\x12\x14\n" + - "\x05ports\x18\x03 \x03(\rR\x05ports\x12!\n" + - "\finterval_min\x18\x04 \x01(\x03R\vintervalMin\x12!\n" + - "\finterval_max\x18\x05 \x01(\x03R\vintervalMaxB\x9a\x01\n" + + "\asockopt\x18\x01 \x01(\v2%.xray.transport.internet.SocketConfigR\asockopt\x12%\n" + + "\x0eoverwrite_only\x18\x02 \x01(\bR\roverwriteOnly\x12\x10\n" + + "\x03IPs\x18\x03 \x03(\tR\x03IPs\x12\x14\n" + + "\x05ports\x18\x04 \x03(\rR\x05ports\x12!\n" + + "\finterval_min\x18\x05 \x01(\x03R\vintervalMin\x12!\n" + + "\finterval_max\x18\x06 \x01(\x03R\vintervalMaxB\x9a\x01\n" + ",com.xray.transport.internet.finalmask.udphopP\x01Z=github.com/xtls/xray-core/transport/internet/finalmask/udphop\xaa\x02(Xray.Transport.Internet.Finalmask.Udphopb\x06proto3" var ( diff --git a/transport/internet/finalmask/udphop/config.proto b/transport/internet/finalmask/udphop/config.proto index 543a5f2fd9e6..53b18a550be4 100644 --- a/transport/internet/finalmask/udphop/config.proto +++ b/transport/internet/finalmask/udphop/config.proto @@ -10,9 +10,10 @@ import "transport/internet/config.proto"; message Config { xray.transport.internet.SocketConfig sockopt = 1; - repeated string IPs = 2; - repeated uint32 ports = 3; - int64 interval_min = 4; - int64 interval_max = 5; + bool overwrite_only = 2; + repeated string IPs = 3; + repeated uint32 ports = 4; + int64 interval_min = 5; + int64 interval_max = 6; } diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index 3c2c38d8de7c..f9cf6a79e058 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -34,8 +34,9 @@ type packet struct { } type udpHopConn struct { - conn net.PacketConn - sockopt *internet.SocketConfig + conn net.PacketConn + sockopt *internet.SocketConfig + overwriteOnly bool ips []netip.Prefix ports []uint32 @@ -77,8 +78,9 @@ func NewUDPHopConn(c *Config, raw net.PacketConn) (net.PacketConn, error) { return nil, errors.New("invalid interval") } conn := &udpHopConn{ - conn: raw, - sockopt: c.Sockopt, + conn: raw, + sockopt: c.Sockopt, + overwriteOnly: c.OverwriteOnly, ips: ips, ports: c.Ports, @@ -212,6 +214,23 @@ func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { c.mu.Lock() defer c.mu.Unlock() + if c.overwriteOnly { + if c.addr == nil { + if len(c.ips) > 0 { + addr = &net.UDPAddr{ + IP: randPrefix(c.ips[mrand.Intn(len(c.ips))]), + Port: int(c.ports[mrand.Intn(len(c.ports))]), + } + } else { + addr = &net.UDPAddr{ + IP: addr.(*net.UDPAddr).IP, + Port: int(c.ports[mrand.Intn(len(c.ports))]), + } + } + } + return c.conn.WriteTo(p, c.addr) + } + if c.addr == nil { c.addr = &net.UDPAddr{ IP: addr.(*net.UDPAddr).IP, @@ -227,12 +246,7 @@ func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { go c.hopLoop() } - _, err = c.cur.WriteTo(p, c.addr) - if err != nil { - errors.LogErrorInner(context.Background(), err, "send err") - return 0, err - } - return len(p), nil + return c.cur.WriteTo(p, c.addr) } func (c *udpHopConn) Close() error { From 8cfec083a98a30f1a7a9b8b76704be8e162af81a Mon Sep 17 00:00:00 2001 From: null Date: Sun, 28 Jun 2026 19:17:21 +0800 Subject: [PATCH 24/24] chore --- transport/internet/finalmask/udphop/conn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transport/internet/finalmask/udphop/conn.go b/transport/internet/finalmask/udphop/conn.go index f9cf6a79e058..2706a36c1e81 100644 --- a/transport/internet/finalmask/udphop/conn.go +++ b/transport/internet/finalmask/udphop/conn.go @@ -217,12 +217,12 @@ func (c *udpHopConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { if c.overwriteOnly { if c.addr == nil { if len(c.ips) > 0 { - addr = &net.UDPAddr{ + c.addr = &net.UDPAddr{ IP: randPrefix(c.ips[mrand.Intn(len(c.ips))]), Port: int(c.ports[mrand.Intn(len(c.ports))]), } } else { - addr = &net.UDPAddr{ + c.addr = &net.UDPAddr{ IP: addr.(*net.UDPAddr).IP, Port: int(c.ports[mrand.Intn(len(c.ports))]), }