diff --git a/go.sum b/go.sum index 6ace7d64..1385e723 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= -golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= diff --git a/infra/conf/vless.go b/infra/conf/vless.go index e6d231e0..1e14882c 100644 --- a/infra/conf/vless.go +++ b/infra/conf/vless.go @@ -78,8 +78,13 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`) } - if account.Reverse != nil && account.Reverse.Tag == "" { - return nil, errors.New(`VLESS clients: "tag" can't be empty for "reverse"`) + if account.Reverse != nil { + if account.Reverse.Tag == "" { + return nil, errors.New(`VLESS clients: "tag" can't be empty for "reverse"`) + } + if account.Reverse.Sniffing != nil { // may not be reached: error json unmarshal + return nil, errors.New(`VLESS clients: inbound's "reverse" can't have "sniffing"`) + } } user.Account = serial.ToTypedMessage(account) @@ -197,6 +202,28 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { return config, nil } +type VLessReverseConfig struct { + Tag string `json:"tag"` + Sniffing *SniffingConfig `json:"sniffing"` +} + +func (c *VLessReverseConfig) Build() (*vless.Reverse, error) { + if c.Tag == "" { + return nil, errors.New(`VLESS reverse: "tag" can't be empty`) + } + r := &vless.Reverse{ + Tag: c.Tag, + } + if c.Sniffing != nil { + sc, err := c.Sniffing.Build() + if err != nil { + return nil, errors.New(`VLESS reverse: invalid "sniffing" config`).Base(err) + } + r.Sniffing = sc + } + return r, nil +} + type VLessOutboundVnext struct { Address *Address `json:"address"` Port uint16 `json:"port"` @@ -212,7 +239,7 @@ type VLessOutboundConfig struct { Flow string `json:"flow"` Seed string `json:"seed"` Encryption string `json:"encryption"` - Reverse *vless.Reverse `json:"reverse"` + Reverse *VLessReverseConfig `json:"reverse"` Testpre uint32 `json:"testpre"` Testseed []uint32 `json:"testseed"` Vnext []*VLessOutboundVnext `json:"vnext"` @@ -260,13 +287,22 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) { account.Flow = c.Flow //account.Seed = c.Seed account.Encryption = c.Encryption - account.Reverse = c.Reverse + if c.Reverse != nil { + rvs, err := c.Reverse.Build() + if err != nil { + return nil, err + } + account.Reverse = rvs + } account.Testpre = c.Testpre account.Testseed = c.Testseed } else { if err := json.Unmarshal(rawUser, account); err != nil { return nil, errors.New(`VLESS users: invalid user`).Base(err) } + if account.Reverse != nil { // may not be reached: error json unmarshal + return nil, errors.New(`VLESS users: please use simplified outbound's config style to use "reverse"`) + } } u, err := uuid.ParseString(account.Id) @@ -326,10 +362,6 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) { return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption) } - if account.Reverse != nil && account.Reverse.Tag == "" { - return nil, errors.New(`VLESS clients: "tag" can't be empty for "reverse"`) - } - user.Account = serial.ToTypedMessage(account) spec.User = user break diff --git a/proxy/vless/account.pb.go b/proxy/vless/account.pb.go index ef132c91..a5c35be4 100644 --- a/proxy/vless/account.pb.go +++ b/proxy/vless/account.pb.go @@ -7,6 +7,7 @@ package vless 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 Reverse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Sniffing *proxyman.SniffingConfig `protobuf:"bytes,2,opt,name=sniffing,proto3" json:"sniffing,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -65,6 +67,13 @@ func (x *Reverse) GetTag() string { return "" } +func (x *Reverse) GetSniffing() *proxyman.SniffingConfig { + if x != nil { + return x.Sniffing + } + return nil +} + type Account struct { state protoimpl.MessageState `protogen:"open.v1"` // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". @@ -179,9 +188,10 @@ var File_proxy_vless_account_proto protoreflect.FileDescriptor const file_proxy_vless_account_proto_rawDesc = "" + "\n" + - "\x19proxy/vless/account.proto\x12\x10xray.proxy.vless\"\x1b\n" + + "\x19proxy/vless/account.proto\x12\x10xray.proxy.vless\x1a\x19app/proxyman/config.proto\"Z\n" + "\aReverse\x12\x10\n" + - "\x03tag\x18\x01 \x01(\tR\x03tag\"\x86\x02\n" + + "\x03tag\x18\x01 \x01(\tR\x03tag\x12=\n" + + "\bsniffing\x18\x02 \x01(\v2!.xray.app.proxyman.SniffingConfigR\bsniffing\"\x86\x02\n" + "\aAccount\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04flow\x18\x02 \x01(\tR\x04flow\x12\x1e\n" + @@ -210,16 +220,18 @@ func file_proxy_vless_account_proto_rawDescGZIP() []byte { var file_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_proxy_vless_account_proto_goTypes = []any{ - (*Reverse)(nil), // 0: xray.proxy.vless.Reverse - (*Account)(nil), // 1: xray.proxy.vless.Account + (*Reverse)(nil), // 0: xray.proxy.vless.Reverse + (*Account)(nil), // 1: xray.proxy.vless.Account + (*proxyman.SniffingConfig)(nil), // 2: xray.app.proxyman.SniffingConfig } var file_proxy_vless_account_proto_depIdxs = []int32{ - 0, // 0: xray.proxy.vless.Account.reverse:type_name -> xray.proxy.vless.Reverse - 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 + 2, // 0: xray.proxy.vless.Reverse.sniffing:type_name -> xray.app.proxyman.SniffingConfig + 0, // 1: xray.proxy.vless.Account.reverse:type_name -> xray.proxy.vless.Reverse + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_proxy_vless_account_proto_init() } diff --git a/proxy/vless/account.proto b/proxy/vless/account.proto index d0f1ee22..296ce9b7 100644 --- a/proxy/vless/account.proto +++ b/proxy/vless/account.proto @@ -6,8 +6,11 @@ option go_package = "github.com/xtls/xray-core/proxy/vless"; option java_package = "com.xray.proxy.vless"; option java_multiple_files = true; +import "app/proxyman/config.proto"; + message Reverse { string tag = 1; + xray.app.proxyman.SniffingConfig sniffing = 2; } message Account { diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go index a4fe1e93..75e74fe0 100644 --- a/proxy/vless/outbound/outbound.go +++ b/proxy/vless/outbound/outbound.go @@ -97,14 +97,26 @@ func New(ctx context.Context, config *Config) (*Handler, error) { } if a.Reverse != nil { + rvsCtx := session.ContextWithInbound(ctx, &session.Inbound{ + Tag: a.Reverse.Tag, + User: handler.server.User, // TODO: email + }) + if sc := a.Reverse.Sniffing; sc != nil && sc.Enabled { + rvsCtx = session.ContextWithContent(rvsCtx, &session.Content{ + SniffingRequest: session.SniffingRequest{ + Enabled: sc.Enabled, + OverrideDestinationForProtocol: sc.DestinationOverride, + ExcludeForDomain: sc.DomainsExcluded, + MetadataOnly: sc.MetadataOnly, + RouteOnly: sc.RouteOnly, + }, + }) + } handler.reverse = &Reverse{ tag: a.Reverse.Tag, dispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher), - ctx: session.ContextWithInbound(ctx, &session.Inbound{ - Tag: a.Reverse.Tag, - User: handler.server.User, // TODO: email - }), - handler: handler, + ctx: rvsCtx, + handler: handler, } handler.reverse.monitorTask = &task.Periodic{ Execute: handler.reverse.monitor,