From 6b525a606e478b7477596dfe1965698f2eb06db4 Mon Sep 17 00:00:00 2001 From: hossinasaadi Date: Mon, 8 Jun 2026 21:33:06 +0100 Subject: [PATCH 1/5] feat: implement plugin outbound support with configurable client handlers and traffic statistics --- infra/conf/xray.go | 19 ++++++ proxy/plugin/client.go | 123 +++++++++++++++++++++++++++++++++++ proxy/plugin/config.pb.go | 131 ++++++++++++++++++++++++++++++++++++++ proxy/plugin/config.proto | 9 +++ proxy/plugin/plugin.go | 48 ++++++++++++++ 5 files changed, 330 insertions(+) create mode 100644 proxy/plugin/client.go create mode 100644 proxy/plugin/config.pb.go create mode 100644 proxy/plugin/config.proto create mode 100644 proxy/plugin/plugin.go diff --git a/infra/conf/xray.go b/infra/conf/xray.go index 3e6e4371036e..fceec7505645 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -14,7 +14,9 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/serial" core "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/proxy/plugin" "github.com/xtls/xray-core/transport/internet" + "google.golang.org/protobuf/proto" ) var ( @@ -48,6 +50,7 @@ var ( "hysteria": func() interface{} { return new(HysteriaClientConfig) }, "dns": func() interface{} { return new(DNSOutboundConfig) }, "wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} }, + "plugin": func() interface{} { return new(PluginOutboundConfig) }, }, "protocol", "settings") ) @@ -656,3 +659,19 @@ func ParseSendThough(Addr *string) *Address { addr.Address = net.ParseAddress(strings.Split(*Addr, "/")[0]) return &addr } + +type PluginOutboundConfig struct { + Name string `json:"name"` + Params *json.RawMessage `json:"params"` +} + +func (c *PluginOutboundConfig) Build() (proto.Message, error) { + var paramsStr string + if c.Params != nil { + paramsStr = string(*c.Params) + } + return &plugin.ClientConfig{ + Name: c.Name, + Params: paramsStr, + }, nil +} diff --git a/proxy/plugin/client.go b/proxy/plugin/client.go new file mode 100644 index 000000000000..bc300f208069 --- /dev/null +++ b/proxy/plugin/client.go @@ -0,0 +1,123 @@ +package plugin + +import ( + "context" + + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/features/policy" + "github.com/xtls/xray-core/features/stats" + "github.com/xtls/xray-core/transport" + "github.com/xtls/xray-core/transport/internet" +) + +type Client struct { + name string + params string +} + +func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { + var tag string + if handler := session.FullHandlerFromContext(ctx); handler != nil { + tag = handler.Tag() + } + TriggerOnPluginRegistered(tag, config.Name, config.Params) + + return &Client{ + name: config.Name, + params: config.Params, + }, nil +} + +type sizeStatReader struct { + buf.Reader + counter stats.Counter +} + +func (r *sizeStatReader) ReadMultiBuffer() (buf.MultiBuffer, error) { + mb, err := r.Reader.ReadMultiBuffer() + if r.counter != nil { + r.counter.Add(int64(mb.Len())) + } + return mb, err +} + +type sizeStatWriter struct { + buf.Writer + counter stats.Counter +} + +func (w *sizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { + if w.counter != nil { + w.counter.Add(int64(mb.Len())) + } + return w.Writer.WriteMultiBuffer(mb) +} + +func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { + outbounds := session.OutboundsFromContext(ctx) + ob := outbounds[len(outbounds)-1] + if !ob.Target.IsValid() { + return errors.New("target not specified.") + } + destination := ob.Target + + handlerFunc := GetHandler(c.name) + if handlerFunc == nil { + return errors.New("plugin outbound handler not registered: ", c.name) + } + + var tag string + if len(outbounds) > 0 { + tag = outbounds[len(outbounds)-1].Tag + } + if len(tag) > 0 { + if v := core.FromContext(ctx); v != nil { + if pmFeature := v.GetFeature(policy.ManagerType()); pmFeature != nil { + if pm, ok := pmFeature.(policy.Manager); ok { + if smFeature := v.GetFeature(stats.ManagerType()); smFeature != nil { + if sm, ok := smFeature.(stats.Manager); ok { + var uplinkCounter stats.Counter + var downlinkCounter stats.Counter + if pm.ForSystem().Stats.OutboundUplink { + name := "outbound>>>" + tag + ">>>traffic>>>uplink" + if c, err := stats.GetOrRegisterCounter(sm, name); err == nil && c != nil { + uplinkCounter = c + } + } + if pm.ForSystem().Stats.OutboundDownlink { + name := "outbound>>>" + tag + ">>>traffic>>>downlink" + if c, err := stats.GetOrRegisterCounter(sm, name); err == nil && c != nil { + downlinkCounter = c + } + } + if downlinkCounter != nil { + link.Reader = &sizeStatReader{ + Reader: link.Reader, + counter: downlinkCounter, + } + } + if uplinkCounter != nil { + link.Writer = &sizeStatWriter{ + Writer: link.Writer, + counter: uplinkCounter, + } + } + } + } + } + } + } + } + + return handlerFunc(ctx, destination, link) +} + +func init() { + common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return NewClient(ctx, config.(*ClientConfig)) + })) +} diff --git a/proxy/plugin/config.pb.go b/proxy/plugin/config.pb.go new file mode 100644 index 000000000000..33e90e926dd7 --- /dev/null +++ b/proxy/plugin/config.pb.go @@ -0,0 +1,131 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v6.32.1 +// source: config.proto + +package plugin + +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 ClientConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Params string `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientConfig) Reset() { + *x = ClientConfig{} + mi := &file_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientConfig) ProtoMessage() {} + +func (x *ClientConfig) ProtoReflect() protoreflect.Message { + mi := &file_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 ClientConfig.ProtoReflect.Descriptor instead. +func (*ClientConfig) Descriptor() ([]byte, []int) { + return file_config_proto_rawDescGZIP(), []int{0} +} + +func (x *ClientConfig) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ClientConfig) GetParams() string { + if x != nil { + return x.Params + } + return "" +} + +var File_config_proto protoreflect.FileDescriptor + +const file_config_proto_rawDesc = "" + + "\n" + + "\fconfig.proto\x12\x11xray.proxy.plugin\":\n" + + "\fClientConfig\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + + "\x06params\x18\x02 \x01(\tR\x06paramsB(Z&github.com/xtls/xray-core/proxy/pluginb\x06proto3" + +var ( + file_config_proto_rawDescOnce sync.Once + file_config_proto_rawDescData []byte +) + +func file_config_proto_rawDescGZIP() []byte { + file_config_proto_rawDescOnce.Do(func() { + file_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_config_proto_rawDesc), len(file_config_proto_rawDesc))) + }) + return file_config_proto_rawDescData +} + +var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_config_proto_goTypes = []any{ + (*ClientConfig)(nil), // 0: xray.proxy.plugin.ClientConfig +} +var file_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_config_proto_init() } +func file_config_proto_init() { + if File_config_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_config_proto_rawDesc), len(file_config_proto_rawDesc)), + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_config_proto_goTypes, + DependencyIndexes: file_config_proto_depIdxs, + MessageInfos: file_config_proto_msgTypes, + }.Build() + File_config_proto = out.File + file_config_proto_goTypes = nil + file_config_proto_depIdxs = nil +} diff --git a/proxy/plugin/config.proto b/proxy/plugin/config.proto new file mode 100644 index 000000000000..1b95b041d11e --- /dev/null +++ b/proxy/plugin/config.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package xray.proxy.plugin; +option go_package = "github.com/xtls/xray-core/proxy/plugin"; + +message ClientConfig { + string name = 1; + string params = 2; +} diff --git a/proxy/plugin/plugin.go b/proxy/plugin/plugin.go new file mode 100644 index 000000000000..e14c9b68e9c2 --- /dev/null +++ b/proxy/plugin/plugin.go @@ -0,0 +1,48 @@ +package plugin + +import ( + "context" + "sync" + + v2net "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport" +) + +type OutboundHandlerFunc func(ctx context.Context, dest v2net.Destination, link *transport.Link) error + +type OnPluginRegisteredFunc func(tag string, name string, params string) + +var ( + handlersMu sync.RWMutex + handlers = make(map[string]OutboundHandlerFunc) + + onPluginRegisteredMu sync.Mutex + onPluginRegistered OnPluginRegisteredFunc +) + +func RegisterHandler(name string, handler OutboundHandlerFunc) { + handlersMu.Lock() + defer handlersMu.Unlock() + handlers[name] = handler +} + +func GetHandler(name string) OutboundHandlerFunc { + handlersMu.RLock() + defer handlersMu.RUnlock() + return handlers[name] +} + +func SetOnPluginRegistered(cb OnPluginRegisteredFunc) { + onPluginRegisteredMu.Lock() + defer onPluginRegisteredMu.Unlock() + onPluginRegistered = cb +} + +func TriggerOnPluginRegistered(tag string, name string, params string) { + onPluginRegisteredMu.Lock() + cb := onPluginRegistered + onPluginRegisteredMu.Unlock() + if cb != nil { + cb(tag, name, params) + } +} From 91440c2a4893d24b1ab585ee012a81715ee8fab9 Mon Sep 17 00:00:00 2001 From: hossinasaadi Date: Mon, 8 Jun 2026 21:36:22 +0100 Subject: [PATCH 2/5] refactor: extract plugin configuration logic into a dedicated file --- infra/conf/plugin.go | 24 ++++++++++++++++++++++++ infra/conf/xray.go | 18 ------------------ 2 files changed, 24 insertions(+), 18 deletions(-) create mode 100644 infra/conf/plugin.go diff --git a/infra/conf/plugin.go b/infra/conf/plugin.go new file mode 100644 index 000000000000..09df5eba7946 --- /dev/null +++ b/infra/conf/plugin.go @@ -0,0 +1,24 @@ +package conf + +import ( + "encoding/json" + + "github.com/xtls/xray-core/proxy/plugin" + "google.golang.org/protobuf/proto" +) + +type PluginOutboundConfig struct { + Name string `json:"name"` + Params *json.RawMessage `json:"params"` +} + +func (c *PluginOutboundConfig) Build() (proto.Message, error) { + var paramsStr string + if c.Params != nil { + paramsStr = string(*c.Params) + } + return &plugin.ClientConfig{ + Name: c.Name, + Params: paramsStr, + }, nil +} diff --git a/infra/conf/xray.go b/infra/conf/xray.go index fceec7505645..63f1406607ff 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -14,9 +14,7 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/serial" core "github.com/xtls/xray-core/core" - "github.com/xtls/xray-core/proxy/plugin" "github.com/xtls/xray-core/transport/internet" - "google.golang.org/protobuf/proto" ) var ( @@ -659,19 +657,3 @@ func ParseSendThough(Addr *string) *Address { addr.Address = net.ParseAddress(strings.Split(*Addr, "/")[0]) return &addr } - -type PluginOutboundConfig struct { - Name string `json:"name"` - Params *json.RawMessage `json:"params"` -} - -func (c *PluginOutboundConfig) Build() (proto.Message, error) { - var paramsStr string - if c.Params != nil { - paramsStr = string(*c.Params) - } - return &plugin.ClientConfig{ - Name: c.Name, - Params: paramsStr, - }, nil -} From db697bec23cb178d2b1d19aaf6975ca78cdeab83 Mon Sep 17 00:00:00 2001 From: hossinasaadi Date: Mon, 8 Jun 2026 22:00:04 +0100 Subject: [PATCH 3/5] refactor: regenerate protobuf --- proxy/plugin/config.pb.go | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/proxy/plugin/config.pb.go b/proxy/plugin/config.pb.go index 33e90e926dd7..1b3c0f4fe3af 100644 --- a/proxy/plugin/config.pb.go +++ b/proxy/plugin/config.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.6 +// protoc-gen-go v1.36.11 // protoc v6.32.1 -// source: config.proto +// source: proxy/plugin/config.proto package plugin @@ -31,7 +31,7 @@ type ClientConfig struct { func (x *ClientConfig) Reset() { *x = ClientConfig{} - mi := &file_config_proto_msgTypes[0] + mi := &file_proxy_plugin_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -43,7 +43,7 @@ func (x *ClientConfig) String() string { func (*ClientConfig) ProtoMessage() {} func (x *ClientConfig) ProtoReflect() protoreflect.Message { - mi := &file_config_proto_msgTypes[0] + mi := &file_proxy_plugin_config_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -56,7 +56,7 @@ func (x *ClientConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. func (*ClientConfig) Descriptor() ([]byte, []int) { - return file_config_proto_rawDescGZIP(), []int{0} + return file_proxy_plugin_config_proto_rawDescGZIP(), []int{0} } func (x *ClientConfig) GetName() string { @@ -73,32 +73,32 @@ func (x *ClientConfig) GetParams() string { return "" } -var File_config_proto protoreflect.FileDescriptor +var File_proxy_plugin_config_proto protoreflect.FileDescriptor -const file_config_proto_rawDesc = "" + +const file_proxy_plugin_config_proto_rawDesc = "" + "\n" + - "\fconfig.proto\x12\x11xray.proxy.plugin\":\n" + + "\x19proxy/plugin/config.proto\x12\x11xray.proxy.plugin\":\n" + "\fClientConfig\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + "\x06params\x18\x02 \x01(\tR\x06paramsB(Z&github.com/xtls/xray-core/proxy/pluginb\x06proto3" var ( - file_config_proto_rawDescOnce sync.Once - file_config_proto_rawDescData []byte + file_proxy_plugin_config_proto_rawDescOnce sync.Once + file_proxy_plugin_config_proto_rawDescData []byte ) -func file_config_proto_rawDescGZIP() []byte { - file_config_proto_rawDescOnce.Do(func() { - file_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_config_proto_rawDesc), len(file_config_proto_rawDesc))) +func file_proxy_plugin_config_proto_rawDescGZIP() []byte { + file_proxy_plugin_config_proto_rawDescOnce.Do(func() { + file_proxy_plugin_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_plugin_config_proto_rawDesc), len(file_proxy_plugin_config_proto_rawDesc))) }) - return file_config_proto_rawDescData + return file_proxy_plugin_config_proto_rawDescData } -var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_config_proto_goTypes = []any{ +var file_proxy_plugin_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_proxy_plugin_config_proto_goTypes = []any{ (*ClientConfig)(nil), // 0: xray.proxy.plugin.ClientConfig } -var file_config_proto_depIdxs = []int32{ +var file_proxy_plugin_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 @@ -106,26 +106,26 @@ var file_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for field type_name } -func init() { file_config_proto_init() } -func file_config_proto_init() { - if File_config_proto != nil { +func init() { file_proxy_plugin_config_proto_init() } +func file_proxy_plugin_config_proto_init() { + if File_proxy_plugin_config_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_config_proto_rawDesc), len(file_config_proto_rawDesc)), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_plugin_config_proto_rawDesc), len(file_proxy_plugin_config_proto_rawDesc)), NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_config_proto_goTypes, - DependencyIndexes: file_config_proto_depIdxs, - MessageInfos: file_config_proto_msgTypes, + GoTypes: file_proxy_plugin_config_proto_goTypes, + DependencyIndexes: file_proxy_plugin_config_proto_depIdxs, + MessageInfos: file_proxy_plugin_config_proto_msgTypes, }.Build() - File_config_proto = out.File - file_config_proto_goTypes = nil - file_config_proto_depIdxs = nil + File_proxy_plugin_config_proto = out.File + file_proxy_plugin_config_proto_goTypes = nil + file_proxy_plugin_config_proto_depIdxs = nil } From 2c71202ca4d3790ab65bef3ab633d106e6ce33ce Mon Sep 17 00:00:00 2001 From: hossinasaadi Date: Mon, 8 Jun 2026 22:18:57 +0100 Subject: [PATCH 4/5] feat: change params field type from string to bytes --- infra/conf/plugin.go | 6 +++--- proxy/plugin/client.go | 2 +- proxy/plugin/config.pb.go | 10 +++++----- proxy/plugin/config.proto | 2 +- proxy/plugin/plugin.go | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/infra/conf/plugin.go b/infra/conf/plugin.go index 09df5eba7946..1c533892d7ec 100644 --- a/infra/conf/plugin.go +++ b/infra/conf/plugin.go @@ -13,12 +13,12 @@ type PluginOutboundConfig struct { } func (c *PluginOutboundConfig) Build() (proto.Message, error) { - var paramsStr string + var paramsBytes []byte if c.Params != nil { - paramsStr = string(*c.Params) + paramsBytes = []byte(*c.Params) } return &plugin.ClientConfig{ Name: c.Name, - Params: paramsStr, + Params: paramsBytes, }, nil } diff --git a/proxy/plugin/client.go b/proxy/plugin/client.go index bc300f208069..813b49bef459 100644 --- a/proxy/plugin/client.go +++ b/proxy/plugin/client.go @@ -16,7 +16,7 @@ import ( type Client struct { name string - params string + params []byte } func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { diff --git a/proxy/plugin/config.pb.go b/proxy/plugin/config.pb.go index 1b3c0f4fe3af..109e31f1eafa 100644 --- a/proxy/plugin/config.pb.go +++ b/proxy/plugin/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.32.1 +// protoc v6.33.5 // source: proxy/plugin/config.proto package plugin @@ -24,7 +24,7 @@ const ( type ClientConfig struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Params string `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"` + Params []byte `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -66,11 +66,11 @@ func (x *ClientConfig) GetName() string { return "" } -func (x *ClientConfig) GetParams() string { +func (x *ClientConfig) GetParams() []byte { if x != nil { return x.Params } - return "" + return nil } var File_proxy_plugin_config_proto protoreflect.FileDescriptor @@ -80,7 +80,7 @@ const file_proxy_plugin_config_proto_rawDesc = "" + "\x19proxy/plugin/config.proto\x12\x11xray.proxy.plugin\":\n" + "\fClientConfig\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + - "\x06params\x18\x02 \x01(\tR\x06paramsB(Z&github.com/xtls/xray-core/proxy/pluginb\x06proto3" + "\x06params\x18\x02 \x01(\fR\x06paramsB(Z&github.com/xtls/xray-core/proxy/pluginb\x06proto3" var ( file_proxy_plugin_config_proto_rawDescOnce sync.Once diff --git a/proxy/plugin/config.proto b/proxy/plugin/config.proto index 1b95b041d11e..ce9f3ef0d78f 100644 --- a/proxy/plugin/config.proto +++ b/proxy/plugin/config.proto @@ -5,5 +5,5 @@ option go_package = "github.com/xtls/xray-core/proxy/plugin"; message ClientConfig { string name = 1; - string params = 2; + bytes params = 2; } diff --git a/proxy/plugin/plugin.go b/proxy/plugin/plugin.go index e14c9b68e9c2..42f59021e8de 100644 --- a/proxy/plugin/plugin.go +++ b/proxy/plugin/plugin.go @@ -10,7 +10,7 @@ import ( type OutboundHandlerFunc func(ctx context.Context, dest v2net.Destination, link *transport.Link) error -type OnPluginRegisteredFunc func(tag string, name string, params string) +type OnPluginRegisteredFunc func(tag string, name string, params []byte) var ( handlersMu sync.RWMutex @@ -38,7 +38,7 @@ func SetOnPluginRegistered(cb OnPluginRegisteredFunc) { onPluginRegistered = cb } -func TriggerOnPluginRegistered(tag string, name string, params string) { +func TriggerOnPluginRegistered(tag string, name string, params []byte) { onPluginRegisteredMu.Lock() cb := onPluginRegistered onPluginRegisteredMu.Unlock() From ecd558ba03ad709d9a7d8ca5e6a5e22d06da0c1a Mon Sep 17 00:00:00 2001 From: hossinasaadi Date: Tue, 9 Jun 2026 01:02:10 +0100 Subject: [PATCH 5/5] fix: swap uplink and downlink counter --- proxy/plugin/client.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proxy/plugin/client.go b/proxy/plugin/client.go index 813b49bef459..e1dab6105a3b 100644 --- a/proxy/plugin/client.go +++ b/proxy/plugin/client.go @@ -94,16 +94,16 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter downlinkCounter = c } } - if downlinkCounter != nil { + if uplinkCounter != nil { link.Reader = &sizeStatReader{ Reader: link.Reader, - counter: downlinkCounter, + counter: uplinkCounter, } } - if uplinkCounter != nil { + if downlinkCounter != nil { link.Writer = &sizeStatWriter{ Writer: link.Writer, - counter: uplinkCounter, + counter: downlinkCounter, } } }