From c815c2f2df61dd37fe8c5cec2037e3b93c27266c Mon Sep 17 00:00:00 2001 From: j2rong4cn <36783515+j2rong4cn@users.noreply.github.com> Date: Fri, 19 Jun 2026 07:17:01 +0800 Subject: [PATCH] Loopback outbound: Add `sniffing` (#6326) Example: https://github.com/XTLS/Xray-core/pull/6326#issue-4659701786 --- infra/conf/loopback.go | 14 ++++++++++++-- proxy/loopback/config.pb.go | 32 ++++++++++++++++++++++---------- proxy/loopback/config.proto | 3 +++ proxy/loopback/loopback.go | 19 ++++++++++++++----- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/infra/conf/loopback.go b/infra/conf/loopback.go index 87d349cee..ef30ddc3d 100644 --- a/infra/conf/loopback.go +++ b/infra/conf/loopback.go @@ -1,14 +1,24 @@ package conf import ( + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/proxy/loopback" "google.golang.org/protobuf/proto" ) type LoopbackConfig struct { - InboundTag string `json:"inboundTag"` + InboundTag string `json:"inboundTag"` + Sniffing *SniffingConfig `json:"sniffing"` } func (l LoopbackConfig) Build() (proto.Message, error) { - return &loopback.Config{InboundTag: l.InboundTag}, nil + c := &loopback.Config{InboundTag: l.InboundTag} + if l.Sniffing != nil { + sc, err := l.Sniffing.Build() + if err != nil { + return nil, errors.New("failed to build sniffing config").Base(err) + } + c.Sniffing = sc + } + return c, nil } diff --git a/proxy/loopback/config.pb.go b/proxy/loopback/config.pb.go index ee2728843..2f0619601 100644 --- a/proxy/loopback/config.pb.go +++ b/proxy/loopback/config.pb.go @@ -7,6 +7,7 @@ package loopback import ( + proxyman "github.com/xtls/xray-core/app/proxyman" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -22,8 +23,9 @@ const ( ) type Config struct { - state protoimpl.MessageState `protogen:"open.v1"` - InboundTag string `protobuf:"bytes,1,opt,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + InboundTag string `protobuf:"bytes,1,opt,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"` + Sniffing *proxyman.SniffingConfig `protobuf:"bytes,2,opt,name=sniffing,proto3" json:"sniffing,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -65,14 +67,22 @@ func (x *Config) GetInboundTag() string { return "" } +func (x *Config) GetSniffing() *proxyman.SniffingConfig { + if x != nil { + return x.Sniffing + } + return nil +} + var File_proxy_loopback_config_proto protoreflect.FileDescriptor const file_proxy_loopback_config_proto_rawDesc = "" + "\n" + - "\x1bproxy/loopback/config.proto\x12\x13xray.proxy.loopback\")\n" + + "\x1bproxy/loopback/config.proto\x12\x13xray.proxy.loopback\x1a\x19app/proxyman/config.proto\"h\n" + "\x06Config\x12\x1f\n" + "\vinbound_tag\x18\x01 \x01(\tR\n" + - "inboundTagB[\n" + + "inboundTag\x12=\n" + + "\bsniffing\x18\x02 \x01(\v2!.xray.app.proxyman.SniffingConfigR\bsniffingB[\n" + "\x17com.xray.proxy.loopbackP\x01Z(github.com/xtls/xray-core/proxy/loopback\xaa\x02\x13Xray.Proxy.Loopbackb\x06proto3" var ( @@ -89,14 +99,16 @@ func file_proxy_loopback_config_proto_rawDescGZIP() []byte { var file_proxy_loopback_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proxy_loopback_config_proto_goTypes = []any{ - (*Config)(nil), // 0: xray.proxy.loopback.Config + (*Config)(nil), // 0: xray.proxy.loopback.Config + (*proxyman.SniffingConfig)(nil), // 1: xray.app.proxyman.SniffingConfig } var file_proxy_loopback_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 + 1, // 0: xray.proxy.loopback.Config.sniffing:type_name -> xray.app.proxyman.SniffingConfig + 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_proxy_loopback_config_proto_init() } diff --git a/proxy/loopback/config.proto b/proxy/loopback/config.proto index 7b8fe9016..8543dda6a 100644 --- a/proxy/loopback/config.proto +++ b/proxy/loopback/config.proto @@ -6,6 +6,9 @@ option go_package = "github.com/xtls/xray-core/proxy/loopback"; option java_package = "com.xray.proxy.loopback"; option java_multiple_files = true; +import "app/proxyman/config.proto"; + message Config { string inbound_tag = 1; + xray.app.proxyman.SniffingConfig sniffing = 2; } diff --git a/proxy/loopback/loopback.go b/proxy/loopback/loopback.go index cb2b7e9ba..185496474 100644 --- a/proxy/loopback/loopback.go +++ b/proxy/loopback/loopback.go @@ -3,6 +3,7 @@ package loopback import ( "context" + proxyman "github.com/xtls/xray-core/app/proxyman" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/session" @@ -13,7 +14,8 @@ import ( ) type Loopback struct { - config *Config + inboundTag string + sniffingRequest session.SniffingRequest dispatcherInstance routing.Dispatcher } @@ -29,6 +31,7 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet errors.LogInfo(ctx, "opening connection to ", destination) content := new(session.Content) content.SkipDNSResolve = true + content.SniffingRequest = l.sniffingRequest ctx = session.ContextWithContent(ctx, content) inbound := &session.Inbound{} @@ -37,20 +40,26 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet // get a shallow copy to avoid modifying the inbound tag in upstream context *inbound = *originInbound } - inbound.Tag = l.config.InboundTag + inbound.Tag = l.inboundTag ctx = session.ContextWithInbound(ctx, inbound) err := l.dispatcherInstance.DispatchLink(ctx, destination, link) if err != nil { - errors.New(ctx, "failed to process loopback connection").Base(err) - return err + return errors.New(ctx, "failed to process loopback connection").Base(err) } return nil } func (l *Loopback) init(config *Config, dispatcherInstance routing.Dispatcher) error { l.dispatcherInstance = dispatcherInstance - l.config = config + l.inboundTag = config.InboundTag + if config.Sniffing.GetEnabled() { + request, err := proxyman.BuildSniffingRequest(config.Sniffing) + if err != nil { + return errors.New("failed to build loopback sniffing request").Base(err).AtError() + } + l.sniffingRequest = request + } return nil }