From 711f98707733dbb992f5e8af6b1b718957b82043 Mon Sep 17 00:00:00 2001 From: null Date: Wed, 3 Jun 2026 16:41:31 +0800 Subject: [PATCH 1/7] brutal --- infra/conf/transport_internet.go | 31 +++++ transport/internet/finalmask/brutal/config.go | 13 ++ .../internet/finalmask/brutal/config.pb.go | 123 ++++++++++++++++++ .../internet/finalmask/brutal/config.proto | 11 ++ transport/internet/finalmask/brutal/conn.go | 15 +++ .../internet/finalmask/brutal/conn_linux.go | 40 ++++++ transport/internet/finalmask/finalmask.go | 5 +- 7 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 transport/internet/finalmask/brutal/config.go create mode 100644 transport/internet/finalmask/brutal/config.pb.go create mode 100644 transport/internet/finalmask/brutal/config.proto create mode 100644 transport/internet/finalmask/brutal/conn.go create mode 100644 transport/internet/finalmask/brutal/conn_linux.go diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 7a86a08bab30..d284209e0085 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -3,6 +3,7 @@ package conf import ( "context" "encoding/base64" + "encoding/binary" "encoding/hex" "encoding/json" "math" @@ -22,6 +23,7 @@ import ( "github.com/xtls/xray-core/common/platform/filesystem" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/finalmask/brutal" "github.com/xtls/xray-core/transport/internet/finalmask/fragment" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm" @@ -1230,6 +1232,7 @@ var ( customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{ + "brutal": func() interface{} { return new(Brutal) }, "header-custom": func() interface{} { return new(HeaderCustomTCP) }, "fragment": func() interface{} { return new(FragmentMask) }, "sudoku": func() interface{} { return new(Sudoku) }, @@ -1247,6 +1250,34 @@ var ( }, "type", "settings") ) +type Brutal struct { + Rate uint64 `json:"rate"` + Cwnd uint64 `json:"cwnd"` +} + +func (c *Brutal) Build() (proto.Message, error) { + rate := c.Rate + cwnd := c.Cwnd + if rate == 0 { + rate = 125000 + } + if cwnd == 0 { + cwnd = 20 + } + if rate < 62500 { + return nil, errors.New("invalid rate ", rate) + } + if cwnd < 5 || cwnd > 80 { + return nil, errors.New("invalid cwnd ", cwnd) + } + params := make([]byte, 16) + binary.NativeEndian.PutUint64(params, rate) + binary.NativeEndian.PutUint64(params[8:], cwnd) + return &brutal.Config{ + Params: params, + }, nil +} + type TCPItem struct { Delay Int32Range `json:"delay"` Rand int32 `json:"rand"` diff --git a/transport/internet/finalmask/brutal/config.go b/transport/internet/finalmask/brutal/config.go new file mode 100644 index 000000000000..a0778419be82 --- /dev/null +++ b/transport/internet/finalmask/brutal/config.go @@ -0,0 +1,13 @@ +package brutal + +import "net" + +func (c *Config) TCP() {} + +func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) { + return NewConn(c, raw) +} + +func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) { + return NewConn(c, raw) +} diff --git a/transport/internet/finalmask/brutal/config.pb.go b/transport/internet/finalmask/brutal/config.pb.go new file mode 100644 index 000000000000..70ca46b0f837 --- /dev/null +++ b/transport/internet/finalmask/brutal/config.pb.go @@ -0,0 +1,123 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v6.33.5 +// source: transport/internet/finalmask/brutal/config.proto + +package brutal + +import ( + 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"` + Params []byte `protobuf:"bytes,1,opt,name=params,proto3" json:"params,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Config) Reset() { + *x = Config{} + mi := &file_transport_internet_finalmask_brutal_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_brutal_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_brutal_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetParams() []byte { + if x != nil { + return x.Params + } + return nil +} + +var File_transport_internet_finalmask_brutal_config_proto protoreflect.FileDescriptor + +const file_transport_internet_finalmask_brutal_config_proto_rawDesc = "" + + "\n" + + "0transport/internet/finalmask/brutal/config.proto\x12(xray.transport.internet.finalmask.brutal\" \n" + + "\x06Config\x12\x16\n" + + "\x06params\x18\x01 \x01(\fR\x06paramsB\x9a\x01\n" + + ",com.xray.transport.internet.finalmask.brutalP\x01Z=github.com/xtls/xray-core/transport/internet/finalmask/brutal\xaa\x02(Xray.Transport.Internet.Finalmask.Brutalb\x06proto3" + +var ( + file_transport_internet_finalmask_brutal_config_proto_rawDescOnce sync.Once + file_transport_internet_finalmask_brutal_config_proto_rawDescData []byte +) + +func file_transport_internet_finalmask_brutal_config_proto_rawDescGZIP() []byte { + file_transport_internet_finalmask_brutal_config_proto_rawDescOnce.Do(func() { + file_transport_internet_finalmask_brutal_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_brutal_config_proto_rawDesc), len(file_transport_internet_finalmask_brutal_config_proto_rawDesc))) + }) + return file_transport_internet_finalmask_brutal_config_proto_rawDescData +} + +var file_transport_internet_finalmask_brutal_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_transport_internet_finalmask_brutal_config_proto_goTypes = []any{ + (*Config)(nil), // 0: xray.transport.internet.finalmask.brutal.Config +} +var file_transport_internet_finalmask_brutal_config_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_transport_internet_finalmask_brutal_config_proto_init() } +func file_transport_internet_finalmask_brutal_config_proto_init() { + if File_transport_internet_finalmask_brutal_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_brutal_config_proto_rawDesc), len(file_transport_internet_finalmask_brutal_config_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transport_internet_finalmask_brutal_config_proto_goTypes, + DependencyIndexes: file_transport_internet_finalmask_brutal_config_proto_depIdxs, + MessageInfos: file_transport_internet_finalmask_brutal_config_proto_msgTypes, + }.Build() + File_transport_internet_finalmask_brutal_config_proto = out.File + file_transport_internet_finalmask_brutal_config_proto_goTypes = nil + file_transport_internet_finalmask_brutal_config_proto_depIdxs = nil +} diff --git a/transport/internet/finalmask/brutal/config.proto b/transport/internet/finalmask/brutal/config.proto new file mode 100644 index 000000000000..b50411341d76 --- /dev/null +++ b/transport/internet/finalmask/brutal/config.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package xray.transport.internet.finalmask.brutal; +option csharp_namespace = "Xray.Transport.Internet.Finalmask.Brutal"; +option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/brutal"; +option java_package = "com.xray.transport.internet.finalmask.brutal"; +option java_multiple_files = true; + +message Config { + bytes params = 1; +} \ No newline at end of file diff --git a/transport/internet/finalmask/brutal/conn.go b/transport/internet/finalmask/brutal/conn.go new file mode 100644 index 000000000000..fc3c25838798 --- /dev/null +++ b/transport/internet/finalmask/brutal/conn.go @@ -0,0 +1,15 @@ +//go:build !linux + +package brutal + +import ( + "context" + "net" + + "github.com/xtls/xray-core/common/errors" +) + +func NewConn(c *Config, raw net.Conn) (net.Conn, error) { + errors.LogError(context.Background(), "unsupported system") + return raw, nil +} diff --git a/transport/internet/finalmask/brutal/conn_linux.go b/transport/internet/finalmask/brutal/conn_linux.go new file mode 100644 index 000000000000..d0a3af00a4f3 --- /dev/null +++ b/transport/internet/finalmask/brutal/conn_linux.go @@ -0,0 +1,40 @@ +//go:build linux + +package brutal + +import ( + "context" + "net" + "reflect" + "syscall" + + "github.com/pires/go-proxyproto" + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/errors" + "golang.org/x/sys/unix" +) + +func NewConn(c *Config, raw net.Conn) (net.Conn, error) { + conn := raw + if pc, ok := conn.(*proxyproto.Conn); ok { + conn = pc.Raw() + } + if _, ok := conn.(*net.TCPConn); !ok { + errors.LogError(context.Background(), "unsupported conn ", reflect.TypeOf(conn)) + } + sysConn := common.Must2(conn.(*net.TCPConn).SyscallConn()) + err := sysConn.Control(func(fd uintptr) { + if err := unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "brutal"); err != nil { + errors.LogErrorInner(context.Background(), err, "failed to set congestion") + return + } + if err := syscall.SetsockoptString(int(fd), unix.IPPROTO_TCP, 23301, string(c.Params)); err != nil { + errors.LogErrorInner(context.Background(), err, "failed to set params") + return + } + }) + if err != nil { + errors.LogErrorInner(context.Background(), err, "failed to control connection") + } + return raw, nil +} diff --git a/transport/internet/finalmask/finalmask.go b/transport/internet/finalmask/finalmask.go index d7697cfe161d..5a16a2ac3fbf 100644 --- a/transport/internet/finalmask/finalmask.go +++ b/transport/internet/finalmask/finalmask.go @@ -256,9 +256,8 @@ func (l *tcpListener) Accept() (net.Conn, error) { newConn, err := l.m.WrapConnServer(conn) if err != nil { - errors.LogDebugInner(context.Background(), err, "mask err") - _ = conn.Close() - return nil, err + errors.LogErrorInner(context.Background(), err, "[mask] failed to wrap connection, falling back to the original connection") + return conn, nil } return newConn, nil From b8f55b1eab25122647025e4f1182e02f445aa825 Mon Sep 17 00:00:00 2001 From: null Date: Wed, 3 Jun 2026 17:01:35 +0800 Subject: [PATCH 2/7] ? --- transport/internet/finalmask/tcp_test.go | 68 ------------------------ 1 file changed, 68 deletions(-) diff --git a/transport/internet/finalmask/tcp_test.go b/transport/internet/finalmask/tcp_test.go index 2ae888d0b345..c6125aa9152b 100644 --- a/transport/internet/finalmask/tcp_test.go +++ b/transport/internet/finalmask/tcp_test.go @@ -47,14 +47,6 @@ type layerMaskTcp struct { mask finalmask.Tcpmask } -type failingWrapMask struct{} - -func (failingWrapMask) TCP() {} -func (f failingWrapMask) WrapConnClient(raw net.Conn) (net.Conn, error) { return raw, nil } -func (f failingWrapMask) WrapConnServer(raw net.Conn) (net.Conn, error) { - return nil, io.ErrClosedPipe -} - func TestConnReadWrite(t *testing.T) { cases := []layerMaskTcp{ { @@ -255,63 +247,3 @@ func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) { t.Fatalf("expected server timeout after client auth failure, got %v", readErr) } } - -func TestTCPWrapListenerRejectsImmediateWrapErrors(t *testing.T) { - clientManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}}) - serverManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}}) - - rawLn, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - defer rawLn.Close() - - ln, err := serverManager.WrapListener(rawLn) - if err != nil { - t.Fatal(err) - } - - accepted := make(chan struct { - conn net.Conn - err error - }, 1) - go func() { - conn, err := ln.Accept() - accepted <- struct { - conn net.Conn - err error - }{conn: conn, err: err} - }() - - clientRaw, err := net.Dial("tcp", rawLn.Addr().String()) - if err != nil { - t.Fatal(err) - } - defer clientRaw.Close() - - client, err := clientManager.WrapConnClient(clientRaw) - if err != nil { - t.Fatal(err) - } - - _ = client.SetDeadline(time.Now().Add(time.Second)) - - writeErr := make(chan error, 1) - go func() { - _, err := client.Write([]byte("payload")) - writeErr <- err - }() - - result := <-accepted - if result.err == nil { - if result.conn != nil { - result.conn.Close() - } - t.Fatal("expected wrapped listener accept to fail") - } - if result.conn != nil { - result.conn.Close() - t.Fatalf("expected no raw conn on wrapped listener failure, got %T", result.conn) - } - <-writeErr -} From 4c7c386cdd7886bc48167d1310f7aadacff34201 Mon Sep 17 00:00:00 2001 From: null Date: Wed, 3 Jun 2026 17:32:08 +0800 Subject: [PATCH 3/7] chore & force-brutal --- infra/conf/transport_internet.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index d284209e0085..9d3328777afd 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -1232,7 +1232,7 @@ var ( customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{ - "brutal": func() interface{} { return new(Brutal) }, + "force-brutal": func() interface{} { return new(Brutal) }, "header-custom": func() interface{} { return new(HeaderCustomTCP) }, "fragment": func() interface{} { return new(FragmentMask) }, "sudoku": func() interface{} { return new(Sudoku) }, @@ -1252,7 +1252,7 @@ var ( type Brutal struct { Rate uint64 `json:"rate"` - Cwnd uint64 `json:"cwnd"` + Cwnd uint32 `json:"cwnd"` } func (c *Brutal) Build() (proto.Message, error) { @@ -1272,7 +1272,7 @@ func (c *Brutal) Build() (proto.Message, error) { } params := make([]byte, 16) binary.NativeEndian.PutUint64(params, rate) - binary.NativeEndian.PutUint64(params[8:], cwnd) + binary.NativeEndian.PutUint32(params[8:], cwnd) return &brutal.Config{ Params: params, }, nil From 4a90edd4f9ea6e66a0670afac15200d3de6c1f9f Mon Sep 17 00:00:00 2001 From: null Date: Wed, 3 Jun 2026 18:06:20 +0800 Subject: [PATCH 4/7] __packed --- infra/conf/transport_internet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 9d3328777afd..15bc048c08ca 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -1270,7 +1270,7 @@ func (c *Brutal) Build() (proto.Message, error) { if cwnd < 5 || cwnd > 80 { return nil, errors.New("invalid cwnd ", cwnd) } - params := make([]byte, 16) + params := make([]byte, 12) // __packed https://github.com/apernet/tcp-brutal/blob/204aeea3437a83599c1c1fa1b97e4425cfdfc49d/brutal.c#L75 binary.NativeEndian.PutUint64(params, rate) binary.NativeEndian.PutUint32(params[8:], cwnd) return &brutal.Config{ From b17af7d30743abe306457c01a34dc7cc35fc8ccf Mon Sep 17 00:00:00 2001 From: null Date: Wed, 3 Jun 2026 19:42:08 +0800 Subject: [PATCH 5/7] restore --- transport/internet/finalmask/finalmask.go | 5 +- transport/internet/finalmask/tcp_test.go | 68 +++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/transport/internet/finalmask/finalmask.go b/transport/internet/finalmask/finalmask.go index 5a16a2ac3fbf..e3dce9da6cc4 100644 --- a/transport/internet/finalmask/finalmask.go +++ b/transport/internet/finalmask/finalmask.go @@ -256,8 +256,9 @@ func (l *tcpListener) Accept() (net.Conn, error) { newConn, err := l.m.WrapConnServer(conn) if err != nil { - errors.LogErrorInner(context.Background(), err, "[mask] failed to wrap connection, falling back to the original connection") - return conn, nil + errors.LogErrorInner(context.Background(), err, "[mask] failed to wrap connection") + _ = conn.Close() + return nil, err } return newConn, nil diff --git a/transport/internet/finalmask/tcp_test.go b/transport/internet/finalmask/tcp_test.go index c6125aa9152b..2ae888d0b345 100644 --- a/transport/internet/finalmask/tcp_test.go +++ b/transport/internet/finalmask/tcp_test.go @@ -47,6 +47,14 @@ type layerMaskTcp struct { mask finalmask.Tcpmask } +type failingWrapMask struct{} + +func (failingWrapMask) TCP() {} +func (f failingWrapMask) WrapConnClient(raw net.Conn) (net.Conn, error) { return raw, nil } +func (f failingWrapMask) WrapConnServer(raw net.Conn) (net.Conn, error) { + return nil, io.ErrClosedPipe +} + func TestConnReadWrite(t *testing.T) { cases := []layerMaskTcp{ { @@ -247,3 +255,63 @@ func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) { t.Fatalf("expected server timeout after client auth failure, got %v", readErr) } } + +func TestTCPWrapListenerRejectsImmediateWrapErrors(t *testing.T) { + clientManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}}) + serverManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{failingWrapMask{}}) + + rawLn, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer rawLn.Close() + + ln, err := serverManager.WrapListener(rawLn) + if err != nil { + t.Fatal(err) + } + + accepted := make(chan struct { + conn net.Conn + err error + }, 1) + go func() { + conn, err := ln.Accept() + accepted <- struct { + conn net.Conn + err error + }{conn: conn, err: err} + }() + + clientRaw, err := net.Dial("tcp", rawLn.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer clientRaw.Close() + + client, err := clientManager.WrapConnClient(clientRaw) + if err != nil { + t.Fatal(err) + } + + _ = client.SetDeadline(time.Now().Add(time.Second)) + + writeErr := make(chan error, 1) + go func() { + _, err := client.Write([]byte("payload")) + writeErr <- err + }() + + result := <-accepted + if result.err == nil { + if result.conn != nil { + result.conn.Close() + } + t.Fatal("expected wrapped listener accept to fail") + } + if result.conn != nil { + result.conn.Close() + t.Fatalf("expected no raw conn on wrapped listener failure, got %T", result.conn) + } + <-writeErr +} From a31ed4c56a36a9a9fca2c42e35ac7e9164e39269 Mon Sep 17 00:00:00 2001 From: null Date: Wed, 3 Jun 2026 23:11:31 +0800 Subject: [PATCH 6/7] unix --- transport/internet/finalmask/brutal/conn_linux.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/transport/internet/finalmask/brutal/conn_linux.go b/transport/internet/finalmask/brutal/conn_linux.go index d0a3af00a4f3..c753b0f751e6 100644 --- a/transport/internet/finalmask/brutal/conn_linux.go +++ b/transport/internet/finalmask/brutal/conn_linux.go @@ -6,7 +6,6 @@ import ( "context" "net" "reflect" - "syscall" "github.com/pires/go-proxyproto" "github.com/xtls/xray-core/common" @@ -28,7 +27,7 @@ func NewConn(c *Config, raw net.Conn) (net.Conn, error) { errors.LogErrorInner(context.Background(), err, "failed to set congestion") return } - if err := syscall.SetsockoptString(int(fd), unix.IPPROTO_TCP, 23301, string(c.Params)); err != nil { + if err := unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, 23301, string(c.Params)); err != nil { errors.LogErrorInner(context.Background(), err, "failed to set params") return } From 9dba2f1f7ae0918026f73d3c3c93a8681aa70ab7 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 4 Jun 2026 00:47:06 +0800 Subject: [PATCH 7/7] 16 --- infra/conf/transport_internet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 15bc048c08ca..9d3328777afd 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -1270,7 +1270,7 @@ func (c *Brutal) Build() (proto.Message, error) { if cwnd < 5 || cwnd > 80 { return nil, errors.New("invalid cwnd ", cwnd) } - params := make([]byte, 12) // __packed https://github.com/apernet/tcp-brutal/blob/204aeea3437a83599c1c1fa1b97e4425cfdfc49d/brutal.c#L75 + params := make([]byte, 16) binary.NativeEndian.PutUint64(params, rate) binary.NativeEndian.PutUint32(params[8:], cwnd) return &brutal.Config{