diff --git a/app/dispatcher/default.go b/app/dispatcher/default.go index e3524350..c17bfcd1 100644 --- a/app/dispatcher/default.go +++ b/app/dispatcher/default.go @@ -483,6 +483,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport. handler = h } else { errors.LogWarning(ctx, "non existing outTag: ", outTag) + return } } else { errors.LogInfo(ctx, "default route for ", destination) diff --git a/app/proxyman/outbound/handler.go b/app/proxyman/outbound/handler.go index ef5eed0e..361c88ad 100644 --- a/app/proxyman/outbound/handler.go +++ b/app/proxyman/outbound/handler.go @@ -108,6 +108,8 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou } h.proxyConfig = proxyConfig + ctx = session.ContextWithHandler(ctx, h) + rawProxyHandler, err := common.CreateObject(ctx, proxyConfig) if err != nil { return nil, err diff --git a/app/reverse/bridge.go b/app/reverse/bridge.go index 20719ef1..b86d153b 100644 --- a/app/reverse/bridge.go +++ b/app/reverse/bridge.go @@ -94,10 +94,10 @@ func (b *Bridge) Close() error { } type BridgeWorker struct { - tag string - worker *mux.ServerWorker - dispatcher routing.Dispatcher - state Control_State + Tag string + Worker *mux.ServerWorker + Dispatcher routing.Dispatcher + State Control_State } func NewBridgeWorker(domain string, tag string, d routing.Dispatcher) (*BridgeWorker, error) { @@ -115,15 +115,15 @@ func NewBridgeWorker(domain string, tag string, d routing.Dispatcher) (*BridgeWo } w := &BridgeWorker{ - dispatcher: d, - tag: tag, + Dispatcher: d, + Tag: tag, } worker, err := mux.NewServerWorker(context.Background(), w, link) if err != nil { return nil, err } - w.worker = worker + w.Worker = worker return w, nil } @@ -141,11 +141,11 @@ func (w *BridgeWorker) Close() error { } func (w *BridgeWorker) IsActive() bool { - return w.state == Control_ACTIVE && !w.worker.Closed() + return w.State == Control_ACTIVE && !w.Worker.Closed() } func (w *BridgeWorker) Connections() uint32 { - return w.worker.ActiveConnections() + return w.Worker.ActiveConnections() } func (w *BridgeWorker) handleInternalConn(link *transport.Link) { @@ -161,8 +161,8 @@ func (w *BridgeWorker) handleInternalConn(link *transport.Link) { errors.LogInfoInner(context.Background(), err, "failed to parse proto message") break } - if ctl.State != w.state { - w.state = ctl.State + if ctl.State != w.State { + w.State = ctl.State } } } @@ -171,9 +171,9 @@ func (w *BridgeWorker) handleInternalConn(link *transport.Link) { func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) { if !isInternalDomain(dest) { ctx = session.ContextWithInbound(ctx, &session.Inbound{ - Tag: w.tag, + Tag: w.Tag, }) - return w.dispatcher.Dispatch(ctx, dest) + return w.Dispatcher.Dispatch(ctx, dest) } opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)} @@ -194,12 +194,12 @@ func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*tra func (w *BridgeWorker) DispatchLink(ctx context.Context, dest net.Destination, link *transport.Link) error { if !isInternalDomain(dest) { ctx = session.ContextWithInbound(ctx, &session.Inbound{ - Tag: w.tag, + Tag: w.Tag, }) - return w.dispatcher.DispatchLink(ctx, dest, link) + return w.Dispatcher.DispatchLink(ctx, dest, link) } - link = w.dispatcher.(*dispatcher.DefaultDispatcher).WrapLink(ctx, link) + link = w.Dispatcher.(*dispatcher.DefaultDispatcher).WrapLink(ctx, link) w.handleInternalConn(link) return nil diff --git a/app/reverse/portal.go b/app/reverse/portal.go index 5104238a..11bfc514 100644 --- a/app/reverse/portal.go +++ b/app/reverse/portal.go @@ -97,7 +97,6 @@ func (p *Portal) HandleConnection(ctx context.Context, link *transport.Link) err link.Writer = &buf.EndpointOverrideWriter{Writer: link.Writer, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address} } - return p.client.Dispatch(ctx, link) } diff --git a/app/reverse/reverse.go b/app/reverse/reverse.go index f550a23f..dcd24c57 100644 --- a/app/reverse/reverse.go +++ b/app/reverse/reverse.go @@ -12,7 +12,7 @@ import ( ) const ( - internalDomain = "reverse.internal.v2fly.org" // make reverse proxy compatible with v2fly + internalDomain = "reverse" ) func isDomain(dest net.Destination, domain string) bool { diff --git a/common/protocol/headers.go b/common/protocol/headers.go index fb785d73..f6614ec7 100644 --- a/common/protocol/headers.go +++ b/common/protocol/headers.go @@ -16,11 +16,12 @@ const ( RequestCommandTCP = RequestCommand(0x01) RequestCommandUDP = RequestCommand(0x02) RequestCommandMux = RequestCommand(0x03) + RequestCommandRvs = RequestCommand(0x04) ) func (c RequestCommand) TransferType() TransferType { switch c { - case RequestCommandTCP, RequestCommandMux: + case RequestCommandTCP, RequestCommandMux, RequestCommandRvs: return TransferTypeStream case RequestCommandUDP: return TransferTypePacket diff --git a/common/session/context.go b/common/session/context.go index ba3530b5..6a812f99 100644 --- a/common/session/context.go +++ b/common/session/context.go @@ -6,6 +6,7 @@ import ( "github.com/xtls/xray-core/common/ctx" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/routing" ) @@ -16,13 +17,13 @@ const ( inboundSessionKey ctx.SessionKey = 1 outboundSessionKey ctx.SessionKey = 2 contentSessionKey ctx.SessionKey = 3 - muxPreferredSessionKey ctx.SessionKey = 4 // unused - sockoptSessionKey ctx.SessionKey = 5 // used by dokodemo to only receive sockopt.Mark - trackedConnectionErrorKey ctx.SessionKey = 6 // used by observer to get outbound error - dispatcherKey ctx.SessionKey = 7 // used by ss2022 inbounds to get dispatcher - timeoutOnlyKey ctx.SessionKey = 8 // mux context's child contexts to only cancel when its own traffic times out - allowedNetworkKey ctx.SessionKey = 9 // muxcool server control incoming request tcp/udp - handlerSessionKey ctx.SessionKey = 10 // unused + muxPreferredSessionKey ctx.SessionKey = 4 // unused + sockoptSessionKey ctx.SessionKey = 5 // used by dokodemo to only receive sockopt.Mark + trackedConnectionErrorKey ctx.SessionKey = 6 // used by observer to get outbound error + dispatcherKey ctx.SessionKey = 7 // used by ss2022 inbounds to get dispatcher + timeoutOnlyKey ctx.SessionKey = 8 // mux context's child contexts to only cancel when its own traffic times out + allowedNetworkKey ctx.SessionKey = 9 // muxcool server control incoming request tcp/udp + handlerSessionKey ctx.SessionKey = 10 // outbound gets full handler mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer ) @@ -163,6 +164,17 @@ func AllowedNetworkFromContext(ctx context.Context) net.Network { return net.Network_Unknown } +func ContextWithHandler(ctx context.Context, handler outbound.Handler) context.Context { + return context.WithValue(ctx, handlerSessionKey, handler) +} + +func HandlerFromContext(ctx context.Context) outbound.Handler { + if val, ok := ctx.Value(handlerSessionKey).(outbound.Handler); ok { + return val + } + return nil +} + func ContextWithMitmAlpn11(ctx context.Context, alpn11 bool) context.Context { return context.WithValue(ctx, mitmAlpn11Key, alpn11) } diff --git a/infra/conf/vless.go b/infra/conf/vless.go index 999e3b9b..aa410e5c 100644 --- a/infra/conf/vless.go +++ b/infra/conf/vless.go @@ -77,6 +77,10 @@ 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"`) + } + user.Account = serial.ToTypedMessage(account) config.Clients[idx] = user } @@ -199,13 +203,30 @@ type VLessOutboundVnext struct { } type VLessOutboundConfig struct { - Vnext []*VLessOutboundVnext `json:"vnext"` + Address *Address `json:"address"` + Port uint16 `json:"port"` + Level uint32 `json:"level"` + Email string `json:"email"` + Id string `json:"id"` + Flow string `json:"flow"` + Seed string `json:"seed"` + Encryption string `json:"encryption"` + Reverse *vless.Reverse `json:"reverse"` + Vnext []*VLessOutboundVnext `json:"vnext"` } // Build implements Buildable func (c *VLessOutboundConfig) Build() (proto.Message, error) { config := new(outbound.Config) - + if c.Address != nil { + c.Vnext = []*VLessOutboundVnext{ + { + Address: c.Address, + Port: c.Port, + Users: []json.RawMessage{{}}, + }, + } + } if len(c.Vnext) != 1 { return nil, errors.New(`VLESS settings: "vnext" should have one and only one member`) } @@ -224,12 +245,25 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) { } for idx, rawUser := range rec.Users { user := new(protocol.User) - if err := json.Unmarshal(rawUser, user); err != nil { - return nil, errors.New(`VLESS users: invalid user`).Base(err) + if c.Address != nil { + user.Level = c.Level + user.Email = c.Email + } else { + if err := json.Unmarshal(rawUser, user); err != nil { + return nil, errors.New(`VLESS users: invalid user`).Base(err) + } } account := new(vless.Account) - if err := json.Unmarshal(rawUser, account); err != nil { - return nil, errors.New(`VLESS users: invalid user`).Base(err) + if c.Address != nil { + account.Id = c.Id + account.Flow = c.Flow + //account.Seed = c.Seed + account.Encryption = c.Encryption + account.Reverse = c.Reverse + } else { + if err := json.Unmarshal(rawUser, account); err != nil { + return nil, errors.New(`VLESS users: invalid user`).Base(err) + } } u, err := uuid.ParseString(account.Id) @@ -288,6 +322,10 @@ 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[idx] = user } diff --git a/proxy/vless/account.go b/proxy/vless/account.go index ce3eca27..ac00ea53 100644 --- a/proxy/vless/account.go +++ b/proxy/vless/account.go @@ -21,6 +21,7 @@ func (a *Account) AsAccount() (protocol.Account, error) { XorMode: a.XorMode, Seconds: a.Seconds, Padding: a.Padding, + Reverse: a.Reverse, }, nil } @@ -35,6 +36,8 @@ type MemoryAccount struct { XorMode uint32 Seconds uint32 Padding string + + Reverse *Reverse } // Equals implements protocol.Account.Equals(). @@ -54,5 +57,6 @@ func (a *MemoryAccount) ToProto() proto.Message { XorMode: a.XorMode, Seconds: a.Seconds, Padding: a.Padding, + Reverse: a.Reverse, } } diff --git a/proxy/vless/account.pb.go b/proxy/vless/account.pb.go index b3027c47..5822f512 100644 --- a/proxy/vless/account.pb.go +++ b/proxy/vless/account.pb.go @@ -20,6 +20,51 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type Reverse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` +} + +func (x *Reverse) Reset() { + *x = Reverse{} + mi := &file_proxy_vless_account_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Reverse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Reverse) ProtoMessage() {} + +func (x *Reverse) ProtoReflect() protoreflect.Message { + mi := &file_proxy_vless_account_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 Reverse.ProtoReflect.Descriptor instead. +func (*Reverse) Descriptor() ([]byte, []int) { + return file_proxy_vless_account_proto_rawDescGZIP(), []int{0} +} + +func (x *Reverse) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + type Account struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -28,16 +73,17 @@ type Account struct { // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Flow settings. May be "xtls-rprx-vision". - Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"` - Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"` - XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"` - Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` - Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"` + Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"` + Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"` + XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"` + Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` + Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"` + Reverse *Reverse `protobuf:"bytes,7,opt,name=reverse,proto3" json:"reverse,omitempty"` } func (x *Account) Reset() { *x = Account{} - mi := &file_proxy_vless_account_proto_msgTypes[0] + mi := &file_proxy_vless_account_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -49,7 +95,7 @@ func (x *Account) String() string { func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { - mi := &file_proxy_vless_account_proto_msgTypes[0] + mi := &file_proxy_vless_account_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -62,7 +108,7 @@ func (x *Account) ProtoReflect() protoreflect.Message { // Deprecated: Use Account.ProtoReflect.Descriptor instead. func (*Account) Descriptor() ([]byte, []int) { - return file_proxy_vless_account_proto_rawDescGZIP(), []int{0} + return file_proxy_vless_account_proto_rawDescGZIP(), []int{1} } func (x *Account) GetId() string { @@ -107,28 +153,40 @@ func (x *Account) GetPadding() string { return "" } +func (x *Account) GetReverse() *Reverse { + if x != nil { + return x.Reverse + } + return nil +} + var File_proxy_vless_account_proto protoreflect.FileDescriptor var file_proxy_vless_account_proto_rawDesc = []byte{ 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, - 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x9b, 0x01, - 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, - 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, - 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, - 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, - 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x52, 0x0a, 0x14, 0x63, - 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, - 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, - 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, - 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x1b, 0x0a, + 0x07, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0xd0, 0x01, 0x0a, 0x07, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x6e, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, + 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, + 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x18, + 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x33, 0x0a, 0x07, 0x72, 0x65, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x52, 0x65, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x52, 0x07, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x42, 0x52, 0x0a, + 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, + 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, + 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, + 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, + 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -143,16 +201,18 @@ func file_proxy_vless_account_proto_rawDescGZIP() []byte { return file_proxy_vless_account_proto_rawDescData } -var file_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_proxy_vless_account_proto_goTypes = []any{ - (*Account)(nil), // 0: xray.proxy.vless.Account + (*Reverse)(nil), // 0: xray.proxy.vless.Reverse + (*Account)(nil), // 1: xray.proxy.vless.Account } var file_proxy_vless_account_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 + 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 } func init() { file_proxy_vless_account_proto_init() } @@ -166,7 +226,7 @@ func file_proxy_vless_account_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_vless_account_proto_rawDesc, NumEnums: 0, - NumMessages: 1, + NumMessages: 2, NumExtensions: 0, NumServices: 0, }, diff --git a/proxy/vless/account.proto b/proxy/vless/account.proto index dab861bc..047311dd 100644 --- a/proxy/vless/account.proto +++ b/proxy/vless/account.proto @@ -6,6 +6,10 @@ option go_package = "github.com/xtls/xray-core/proxy/vless"; option java_package = "com.xray.proxy.vless"; option java_multiple_files = true; +message Reverse { + string tag = 1; +} + message Account { // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". string id = 1; @@ -16,4 +20,6 @@ message Account { uint32 xorMode = 4; uint32 seconds = 5; string padding = 6; + + Reverse reverse = 7; } diff --git a/proxy/vless/encoding/encoding.go b/proxy/vless/encoding/encoding.go index c830ac62..fe2c6bc8 100644 --- a/proxy/vless/encoding/encoding.go +++ b/proxy/vless/encoding/encoding.go @@ -46,7 +46,7 @@ func EncodeRequestHeader(writer io.Writer, request *protocol.RequestHeader, requ return errors.New("failed to write request command").Base(err) } - if request.Command != protocol.RequestCommandMux { + if request.Command != protocol.RequestCommandMux && request.Command != protocol.RequestCommandRvs { if err := addrParser.WriteAddressPort(&buffer, request.Address, request.Port); err != nil { return errors.New("failed to write request address and port").Base(err) } @@ -112,7 +112,8 @@ func DecodeRequestHeader(isfb bool, first *buf.Buffer, reader io.Reader, validat switch request.Command { case protocol.RequestCommandMux: request.Address = net.DomainAddress("v1.mux.cool") - request.Port = 0 + case protocol.RequestCommandRvs: + request.Address = net.DomainAddress("v1.rvs.cool") case protocol.RequestCommandTCP, protocol.RequestCommandUDP: if addr, port, err := addrParser.ReadAddressPort(&buffer, reader); err == nil { request.Address = addr diff --git a/proxy/vless/inbound/inbound.go b/proxy/vless/inbound/inbound.go index 98db4f71..2f15b5d8 100644 --- a/proxy/vless/inbound/inbound.go +++ b/proxy/vless/inbound/inbound.go @@ -12,19 +12,24 @@ import ( "time" "unsafe" + "github.com/xtls/xray-core/app/dispatcher" + "github.com/xtls/xray-core/app/reverse" "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/log" + "github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/retry" + "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/dns" feature_inbound "github.com/xtls/xray-core/features/inbound" + "github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/proxy" @@ -66,12 +71,14 @@ func init() { // Handler is an inbound connection handler that handles messages in VLess protocol. type Handler struct { - inboundHandlerManager feature_inbound.Manager - policyManager policy.Manager - validator vless.Validator - dns dns.Client - decryption *encryption.ServerInstance - fallbacks map[string]map[string]map[string]*Fallback // or nil + inboundHandlerManager feature_inbound.Manager + policyManager policy.Manager + validator vless.Validator + decryption *encryption.ServerInstance + outboundHandlerManager outbound.Manager + defaultDispatcher *dispatcher.DefaultDispatcher + ctx context.Context + fallbacks map[string]map[string]map[string]*Fallback // or nil // regexps map[string]*regexp.Regexp // or nil } @@ -79,10 +86,12 @@ type Handler struct { func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Validator) (*Handler, error) { v := core.MustFromContext(ctx) handler := &Handler{ - inboundHandlerManager: v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager), - policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), - dns: dc, - validator: validator, + inboundHandlerManager: v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager), + policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), + validator: validator, + outboundHandlerManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager), + defaultDispatcher: v.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher), + ctx: ctx, } if config.Decryption != "" && config.Decryption != "none" { @@ -174,11 +183,49 @@ func isMuxAndNotXUDP(request *protocol.RequestHeader, first *buf.Buffer) bool { firstBytes[6] == 2) // Network type: UDP } +func (h *Handler) GetReverse(a *vless.MemoryAccount) (*Reverse, error) { + u := h.validator.Get(a.ID.UUID()) + if u == nil { + return nil, errors.New("reverse: user " + a.ID.String() + " doesn't exist anymore") + } + a = u.Account.(*vless.MemoryAccount) + if a.Reverse == nil || a.Reverse.Tag == "" { + return nil, errors.New("reverse: user " + a.ID.String() + " is not allowed to create reverse proxy") + } + r := h.outboundHandlerManager.GetHandler(a.Reverse.Tag) + if r == nil { + picker, _ := reverse.NewStaticMuxPicker() + r = &Reverse{tag: a.Reverse.Tag, picker: picker, client: &mux.ClientManager{Picker: picker}} + for len(h.outboundHandlerManager.ListHandlers(h.ctx)) == 0 { + time.Sleep(time.Second) // prevents this outbound from becoming the default outbound + } + if err := h.outboundHandlerManager.AddHandler(h.ctx, r); err != nil { + return nil, err + } + } + if r, ok := r.(*Reverse); ok { + return r, nil + } + return nil, errors.New("reverse: outbound " + a.Reverse.Tag + " is not type Reverse") +} + +func (h *Handler) RemoveReverse(u *protocol.MemoryUser) { + if u != nil { + a := u.Account.(*vless.MemoryAccount) + if a.Reverse != nil && a.Reverse.Tag != "" { + h.outboundHandlerManager.RemoveHandler(h.ctx, a.Reverse.Tag) + } + } +} + // Close implements common.Closable.Close(). func (h *Handler) Close() error { if h.decryption != nil { h.decryption.Close() } + for _, u := range h.validator.GetAll() { + h.RemoveReverse(u) + } return errors.Combine(common.Close(h.validator)) } @@ -189,6 +236,7 @@ func (h *Handler) AddUser(ctx context.Context, u *protocol.MemoryUser) error { // RemoveUser implements proxy.UserManager.RemoveUser(). func (h *Handler) RemoveUser(ctx context.Context, e string) error { + h.RemoveReverse(h.validator.GetByEmail(e)) return h.validator.Del(e) } @@ -500,7 +548,8 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s switch request.Command { case protocol.RequestCommandUDP: return errors.New(requestAddons.Flow + " doesn't support UDP").AtWarning() - case protocol.RequestCommandMux: + case protocol.RequestCommandMux, protocol.RequestCommandRvs: + inbound.CanSpliceCopy = 3 fallthrough // we will break Mux connections that contain TCP requests case protocol.RequestCommandTCP: var t reflect.Type @@ -565,6 +614,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, false, ctx, connection, nil) bufferWriter.SetFlushNext() + if request.Command == protocol.RequestCommandRvs { + r, err := h.GetReverse(account) + if err != nil { + return err + } + return r.NewMux(ctx, h.defaultDispatcher.WrapLink(ctx, &transport.Link{Reader: clientReader, Writer: clientWriter})) + } + if err := dispatcher.DispatchLink(ctx, request.Destination(), &transport.Link{ Reader: clientReader, Writer: clientWriter}, @@ -573,3 +630,58 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s } return nil } + +type Reverse struct { + tag string + picker *reverse.StaticMuxPicker + client *mux.ClientManager +} + +func (r *Reverse) Tag() string { + return r.tag +} + +func (r *Reverse) NewMux(ctx context.Context, link *transport.Link) error { + muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{}) + if err != nil { + return errors.New("failed to create mux client worker").Base(err).AtWarning() + } + worker, err := reverse.NewPortalWorker(muxClient) + if err != nil { + return errors.New("failed to create portal worker").Base(err).AtWarning() + } + r.picker.AddWorker(worker) + select { + case <-ctx.Done(): + case <-muxClient.WaitClosed(): + } + return nil +} + +func (r *Reverse) Dispatch(ctx context.Context, link *transport.Link) { + outbounds := session.OutboundsFromContext(ctx) + ob := outbounds[len(outbounds)-1] + if ob != nil { + if ob.Target.Network == net.Network_UDP && ob.OriginalTarget.Address != nil && ob.OriginalTarget.Address != ob.Target.Address { + link.Reader = &buf.EndpointOverrideReader{Reader: link.Reader, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address} + link.Writer = &buf.EndpointOverrideWriter{Writer: link.Writer, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address} + } + r.client.Dispatch(ctx, link) + } +} + +func (r *Reverse) Start() error { + return nil +} + +func (r *Reverse) Close() error { + return nil +} + +func (r *Reverse) SenderSettings() *serial.TypedMessage { + return nil +} + +func (r *Reverse) ProxySettings() *serial.TypedMessage { + return nil +} diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go index 58c1646d..30b9dcf9 100644 --- a/proxy/vless/outbound/outbound.go +++ b/proxy/vless/outbound/outbound.go @@ -11,9 +11,12 @@ import ( "unsafe" utls "github.com/refraction-networking/utls" + proxyman "github.com/xtls/xray-core/app/proxyman/outbound" + "github.com/xtls/xray-core/app/reverse" "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/mux" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/retry" @@ -23,6 +26,7 @@ import ( "github.com/xtls/xray-core/common/xudp" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/policy" + "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless/encoding" @@ -32,6 +36,7 @@ import ( "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" + "github.com/xtls/xray-core/transport/pipe" ) func init() { @@ -47,6 +52,7 @@ type Handler struct { policyManager policy.Manager cone bool encryption *encryption.ClientInstance + reverse *Reverse } // New creates a new VLess outbound handler. @@ -82,14 +88,39 @@ func New(ctx context.Context, config *Config) (*Handler, error) { } } + if a.Reverse != nil { + handler.reverse = &Reverse{ + tag: a.Reverse.Tag, + dispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher), + ctx: ctx, + handler: handler, + } + handler.reverse.monitorTask = &task.Periodic{ + Execute: handler.reverse.monitor, + Interval: time.Second * 2, + } + go func() { + time.Sleep(2 * time.Second) + handler.reverse.Start() + }() + } + return handler, nil } +// Close implements common.Closable.Close(). +func (h *Handler) Close() error { + if h.reverse != nil { + return h.reverse.Close() + } + return nil +} + // Process implements proxy.Outbound.Process(). func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { outbounds := session.OutboundsFromContext(ctx) ob := outbounds[len(outbounds)-1] - if !ob.Target.IsValid() { + if !ob.Target.IsValid() && ob.Target.Address.String() != "v1.rvs.cool" { return errors.New("target not specified").AtError() } ob.Name = "vless" @@ -127,8 +158,16 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte if target.Network == net.Network_UDP { command = protocol.RequestCommandUDP } - if target.Address.Family().IsDomain() && target.Address.Domain() == "v1.mux.cool" { - command = protocol.RequestCommandMux + if target.Address.Family().IsDomain() { + switch target.Address.Domain() { + case "v1.mux.cool": + command = protocol.RequestCommandMux + case "v1.rvs.cool": + if target.Network != net.Network_Unknown { + return errors.New("nice try baby").AtError() + } + command = protocol.RequestCommandRvs + } } request := &protocol.RequestHeader{ @@ -321,3 +360,67 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return nil } + +type Reverse struct { + tag string + dispatcher routing.Dispatcher + ctx context.Context + handler *Handler + workers []*reverse.BridgeWorker + monitorTask *task.Periodic +} + +func (r *Reverse) monitor() error { + var activeWorkers []*reverse.BridgeWorker + for _, w := range r.workers { + if w.IsActive() { + activeWorkers = append(activeWorkers, w) + } + } + if len(activeWorkers) != len(r.workers) { + r.workers = activeWorkers + } + + var numConnections uint32 + var numWorker uint32 + for _, w := range r.workers { + if w.IsActive() { + numConnections += w.Connections() + numWorker++ + } + } + if numWorker == 0 || numConnections/numWorker > 16 { + reader1, writer1 := pipe.New(pipe.WithSizeLimit(2 * buf.Size)) + reader2, writer2 := pipe.New(pipe.WithSizeLimit(2 * buf.Size)) + link1 := &transport.Link{Reader: reader1, Writer: writer2} + link2 := &transport.Link{Reader: reader2, Writer: writer1} + w := &reverse.BridgeWorker{ + Tag: r.tag, + Dispatcher: r.dispatcher, + } + worker, err := mux.NewServerWorker(r.ctx, w, link1) + if err != nil { + errors.LogWarningInner(r.ctx, err, "failed to create mux server worker") + return nil + } + w.Worker = worker + r.workers = append(r.workers, w) + go func() { + ctx := session.ContextWithOutbounds(r.ctx, []*session.Outbound{{ + Target: net.Destination{Address: net.DomainAddress("v1.rvs.cool")}, + }}) + r.handler.Process(ctx, link2, session.HandlerFromContext(ctx).(*proxyman.Handler)) + common.Interrupt(reader1) + common.Interrupt(reader2) + }() + } + return nil +} + +func (r *Reverse) Start() error { + return r.monitorTask.Start() +} + +func (r *Reverse) Close() error { + return r.monitorTask.Close() +}