From 2797dbba4e2ac6d03eaad489ee8d59a77df8408a Mon Sep 17 00:00:00 2001 From: rprx <63339210+rprx@users.noreply.github.com> Date: Wed, 1 Jul 2020 00:15:30 +0800 Subject: [PATCH] VLESS CLEAN 2 --- infra/conf/v2ray.go | 4 +- infra/conf/vless.go | 110 +++++++ main/distro/all/all.go | 2 + proxy/vless/account.go | 40 +++ proxy/vless/account.pb.go | 174 ++++++++++ proxy/vless/account.proto | 16 + proxy/vless/encoding/addons.go | 127 ++++++++ proxy/vless/encoding/addons.pb.go | 385 +++++++++++++++++++++++ proxy/vless/encoding/addons.proto | 12 + proxy/vless/encoding/auth.go | 163 ++++++++++ proxy/vless/encoding/encoding.go | 148 +++++++++ proxy/vless/encoding/errors.generated.go | 9 + proxy/vless/errors.generated.go | 9 + proxy/vless/inbound/config.go | 3 + proxy/vless/inbound/config.pb.go | 173 ++++++++++ proxy/vless/inbound/config.proto | 15 + proxy/vless/inbound/errors.generated.go | 9 + proxy/vless/inbound/inbound.go | 206 ++++++++++++ proxy/vless/outbound/config.go | 3 + proxy/vless/outbound/config.pb.go | 164 ++++++++++ proxy/vless/outbound/config.proto | 13 + proxy/vless/outbound/errors.generated.go | 9 + proxy/vless/outbound/outbound.go | 173 ++++++++++ proxy/vless/validator.go | 50 +++ proxy/vless/vless.go | 8 + 25 files changed, 2024 insertions(+), 1 deletion(-) create mode 100644 infra/conf/vless.go create mode 100644 proxy/vless/account.go create mode 100644 proxy/vless/account.pb.go create mode 100644 proxy/vless/account.proto create mode 100644 proxy/vless/encoding/addons.go create mode 100644 proxy/vless/encoding/addons.pb.go create mode 100644 proxy/vless/encoding/addons.proto create mode 100644 proxy/vless/encoding/auth.go create mode 100644 proxy/vless/encoding/encoding.go create mode 100644 proxy/vless/encoding/errors.generated.go create mode 100644 proxy/vless/errors.generated.go create mode 100644 proxy/vless/inbound/config.go create mode 100644 proxy/vless/inbound/config.pb.go create mode 100644 proxy/vless/inbound/config.proto create mode 100644 proxy/vless/inbound/errors.generated.go create mode 100644 proxy/vless/inbound/inbound.go create mode 100644 proxy/vless/outbound/config.go create mode 100644 proxy/vless/outbound/config.pb.go create mode 100644 proxy/vless/outbound/config.proto create mode 100644 proxy/vless/outbound/errors.generated.go create mode 100644 proxy/vless/outbound/outbound.go create mode 100644 proxy/vless/validator.go create mode 100644 proxy/vless/vless.go diff --git a/infra/conf/v2ray.go b/infra/conf/v2ray.go index 8f0d7d1a72a..f8499923b6e 100644 --- a/infra/conf/v2ray.go +++ b/infra/conf/v2ray.go @@ -19,6 +19,7 @@ var ( "http": func() interface{} { return new(HttpServerConfig) }, "shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) }, "socks": func() interface{} { return new(SocksServerConfig) }, + "vless": func() interface{} { return new(VLessInboundConfig) }, "vmess": func() interface{} { return new(VMessInboundConfig) }, "mtproto": func() interface{} { return new(MTProtoServerConfig) }, }, "protocol", "settings") @@ -28,8 +29,9 @@ var ( "freedom": func() interface{} { return new(FreedomConfig) }, "http": func() interface{} { return new(HttpClientConfig) }, "shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) }, - "vmess": func() interface{} { return new(VMessOutboundConfig) }, "socks": func() interface{} { return new(SocksClientConfig) }, + "vless": func() interface{} { return new(VLessOutboundConfig) }, + "vmess": func() interface{} { return new(VMessOutboundConfig) }, "mtproto": func() interface{} { return new(MTProtoClientConfig) }, "dns": func() interface{} { return new(DnsOutboundConfig) }, }, "protocol", "settings") diff --git a/infra/conf/vless.go b/infra/conf/vless.go new file mode 100644 index 00000000000..0cf7dae6457 --- /dev/null +++ b/infra/conf/vless.go @@ -0,0 +1,110 @@ +package conf + +import ( + "encoding/json" + + "github.com/golang/protobuf/proto" + + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/serial" + "v2ray.com/core/proxy/vless" + "v2ray.com/core/proxy/vless/inbound" + "v2ray.com/core/proxy/vless/outbound" +) + +type VLessInboundConfig struct { + Users []json.RawMessage `json:"clients"` + Decryption string `json:"decryption"` +} + +// Build implements Buildable +func (c *VLessInboundConfig) Build() (proto.Message, error) { + + if c.Decryption != "none" { + return nil, newError(`please add/set "decryption":"none" directly to every VLess "settings"`) + } + config := &inbound.Config{ + Decryption: c.Decryption, + } + + config.User = make([]*protocol.User, len(c.Users)) + for idx, rawData := range c.Users { + user := new(protocol.User) + if err := json.Unmarshal(rawData, user); err != nil { + return nil, newError("invalid VLess user").Base(err) + } + account := new(vless.Account) + if err := json.Unmarshal(rawData, account); err != nil { + return nil, newError("invalid VLess user").Base(err) + } + + if account.Mess != "" { + return nil, newError(`VLess attr "mess" is not available in this version`) + } + if account.Encryption != "" { + return nil, newError(`VLess attr "encryption" should not in inbound settings`) + } + + user.Account = serial.ToTypedMessage(account) + config.User[idx] = user + } + + return config, nil +} + +type VLessOutboundTarget struct { + Address *Address `json:"address"` + Port uint16 `json:"port"` + Users []json.RawMessage `json:"users"` +} + +type VLessOutboundConfig struct { + Receivers []*VLessOutboundTarget `json:"vnext"` +} + +// Build implements Buildable +func (c *VLessOutboundConfig) Build() (proto.Message, error) { + + config := new(outbound.Config) + + if len(c.Receivers) == 0 { + return nil, newError("0 VLess receiver configured") + } + serverSpecs := make([]*protocol.ServerEndpoint, len(c.Receivers)) + for idx, rec := range c.Receivers { + if len(rec.Users) == 0 { + return nil, newError("0 user configured for VLess outbound") + } + if rec.Address == nil { + return nil, newError("address is not set in VLess outbound config") + } + spec := &protocol.ServerEndpoint{ + Address: rec.Address.Build(), + Port: uint32(rec.Port), + } + for _, rawUser := range rec.Users { + user := new(protocol.User) + if err := json.Unmarshal(rawUser, user); err != nil { + return nil, newError("invalid VLess user").Base(err) + } + account := new(vless.Account) + if err := json.Unmarshal(rawUser, account); err != nil { + return nil, newError("invalid VLess user").Base(err) + } + + if account.Mess != "" { + return nil, newError(`VLess attr "mess" is not available in this version`) + } + if account.Encryption != "none" { + return nil, newError(`please add/set "encryption":"none" for every VLess user in "users"`) + } + + user.Account = serial.ToTypedMessage(account) + spec.User = append(spec.User, user) + } + serverSpecs[idx] = spec + } + config.Receiver = serverSpecs + + return config, nil +} diff --git a/main/distro/all/all.go b/main/distro/all/all.go index 71144c0875f..9df5b6ac26f 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -31,6 +31,8 @@ import ( _ "v2ray.com/core/proxy/mtproto" _ "v2ray.com/core/proxy/shadowsocks" _ "v2ray.com/core/proxy/socks" + _ "v2ray.com/core/proxy/vless/inbound" + _ "v2ray.com/core/proxy/vless/outbound" _ "v2ray.com/core/proxy/vmess/inbound" _ "v2ray.com/core/proxy/vmess/outbound" diff --git a/proxy/vless/account.go b/proxy/vless/account.go new file mode 100644 index 00000000000..cb7bd2db66e --- /dev/null +++ b/proxy/vless/account.go @@ -0,0 +1,40 @@ +// +build !confonly + +package vless + +import ( + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/uuid" +) + +// AsAccount implements protocol.Account.AsAccount(). +func (a *Account) AsAccount() (protocol.Account, error) { + id, err := uuid.ParseString(a.Id) + if err != nil { + return nil, newError("failed to parse ID").Base(err).AtError() + } + return &MemoryAccount{ + ID: protocol.NewID(id), + Mess: a.Mess, // need parser? + Encryption: a.Encryption, // need parser? + }, nil +} + +// MemoryAccount is an in-memory form of VLess account. +type MemoryAccount struct { + // ID of the account. + ID *protocol.ID + // Mess of the account. Used for client connections for now. + Mess string + // Encryption of the account. Used for client connections, and only accepts "none" for now. + Encryption string +} + +// Equals implements protocol.Account.Equals(). +func (a *MemoryAccount) Equals(account protocol.Account) bool { + vlessAccount, ok := account.(*MemoryAccount) + if !ok { + return false + } + return a.ID.Equals(vlessAccount.ID) +} diff --git a/proxy/vless/account.pb.go b/proxy/vless/account.pb.go new file mode 100644 index 00000000000..6c4ed781f1d --- /dev/null +++ b/proxy/vless/account.pb.go @@ -0,0 +1,174 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.12.3 +// source: v2ray.com/core/proxy/vless/account.proto + +package vless + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +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) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Account struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 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"` + // Mess settings. Only applies to client side for now. + Mess string `protobuf:"bytes,2,opt,name=mess,proto3" json:"mess,omitempty"` + // Encryption settings. Only applies to client side, and only accepts "none" for now. + Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"` +} + +func (x *Account) Reset() { + *x = Account{} + if protoimpl.UnsafeEnabled { + mi := &file_v2ray_com_core_proxy_vless_account_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Account) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Account) ProtoMessage() {} + +func (x *Account) ProtoReflect() protoreflect.Message { + mi := &file_v2ray_com_core_proxy_vless_account_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Account.ProtoReflect.Descriptor instead. +func (*Account) Descriptor() ([]byte, []int) { + return file_v2ray_com_core_proxy_vless_account_proto_rawDescGZIP(), []int{0} +} + +func (x *Account) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Account) GetMess() string { + if x != nil { + return x.Mess + } + return "" +} + +func (x *Account) GetEncryption() string { + if x != nil { + return x.Encryption + } + return "" +} + +var File_v2ray_com_core_proxy_vless_account_proto protoreflect.FileDescriptor + +var file_v2ray_com_core_proxy_vless_account_proto_rawDesc = []byte{ + 0x0a, 0x28, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 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, 0x16, 0x76, 0x32, 0x72, 0x61, + 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, + 0x73, 0x73, 0x22, 0x4d, 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, 0x6d, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x73, + 0x73, 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, 0x42, 0x3e, 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, + 0x01, 0x5a, 0x05, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x16, 0x56, 0x32, 0x52, 0x61, 0x79, + 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, + 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v2ray_com_core_proxy_vless_account_proto_rawDescOnce sync.Once + file_v2ray_com_core_proxy_vless_account_proto_rawDescData = file_v2ray_com_core_proxy_vless_account_proto_rawDesc +) + +func file_v2ray_com_core_proxy_vless_account_proto_rawDescGZIP() []byte { + file_v2ray_com_core_proxy_vless_account_proto_rawDescOnce.Do(func() { + file_v2ray_com_core_proxy_vless_account_proto_rawDescData = protoimpl.X.CompressGZIP(file_v2ray_com_core_proxy_vless_account_proto_rawDescData) + }) + return file_v2ray_com_core_proxy_vless_account_proto_rawDescData +} + +var file_v2ray_com_core_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_v2ray_com_core_proxy_vless_account_proto_goTypes = []interface{}{ + (*Account)(nil), // 0: v2ray.core.proxy.vless.Account +} +var file_v2ray_com_core_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 +} + +func init() { file_v2ray_com_core_proxy_vless_account_proto_init() } +func file_v2ray_com_core_proxy_vless_account_proto_init() { + if File_v2ray_com_core_proxy_vless_account_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_v2ray_com_core_proxy_vless_account_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Account); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v2ray_com_core_proxy_vless_account_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_v2ray_com_core_proxy_vless_account_proto_goTypes, + DependencyIndexes: file_v2ray_com_core_proxy_vless_account_proto_depIdxs, + MessageInfos: file_v2ray_com_core_proxy_vless_account_proto_msgTypes, + }.Build() + File_v2ray_com_core_proxy_vless_account_proto = out.File + file_v2ray_com_core_proxy_vless_account_proto_rawDesc = nil + file_v2ray_com_core_proxy_vless_account_proto_goTypes = nil + file_v2ray_com_core_proxy_vless_account_proto_depIdxs = nil +} diff --git a/proxy/vless/account.proto b/proxy/vless/account.proto new file mode 100644 index 00000000000..a96bc82e97d --- /dev/null +++ b/proxy/vless/account.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package v2ray.core.proxy.vless; +option csharp_namespace = "V2Ray.Core.Proxy.Vless"; +option go_package = "vless"; +option java_package = "com.v2ray.core.proxy.vless"; +option java_multiple_files = true; + +message Account { + // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". + string id = 1; + // Mess settings. Only applies to client side for now. + string mess = 2; + // Encryption settings. Only applies to client side, and only accepts "none" for now. + string encryption = 3; +} diff --git a/proxy/vless/encoding/addons.go b/proxy/vless/encoding/addons.go new file mode 100644 index 00000000000..72ca6b9f25b --- /dev/null +++ b/proxy/vless/encoding/addons.go @@ -0,0 +1,127 @@ +// +build !confonly + +package encoding + +import ( + "crypto/rand" + "io" + + "github.com/golang/protobuf/proto" + + "v2ray.com/core/common" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/crypto" + "v2ray.com/core/common/protocol" +) + +func EncodeAddonsHeader(addons *Addons, buffer *buf.Buffer) { + + switch addons.MessName { + case "shake": + + addons.MessSeed = make([]byte, 16) + rand.Read(addons.MessSeed) + + bytes := common.Must2(proto.Marshal(addons)).([]byte) + + common.Must(buffer.WriteByte(byte(len(bytes)))) + common.Must2(buffer.Write(bytes)) + + default: + + common.Must(buffer.WriteByte(0)) + + } + +} + +func DecodeAddonsHeader(reader io.Reader, buffer *buf.Buffer) (*Addons, error) { + + addons := &Addons{} + + buffer.Clear() + if _, err := buffer.ReadFullFrom(reader, 1); err != nil { + return nil, newError("failed to read addons length").Base(err) + } + + length := int32(buffer.Byte(0)) + if length != 0 { + + buffer.Clear() + if _, err := buffer.ReadFullFrom(reader, length); err != nil { + return nil, newError("failed to read addons bytes").Base(err) + } + + common.Must(proto.Unmarshal(buffer.Bytes(), addons)) + + // Verification + switch addons.MessName { + case "shake": + if len(addons.MessSeed) != 16 { + return nil, newError("mess: shake's seed length is not 16") + } + } + + } + + return addons, nil + +} + +// EncodeAddonsBody returns a Writer that auto-encrypt content written by caller. +func EncodeAddonsBody(request *protocol.RequestHeader, addons *Addons, writer io.Writer) buf.Writer { + + switch addons.MessName { + case "shake": + + var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{} + sizeParser = NewShakeSizeParser(addons.MessSeed) + var padding crypto.PaddingLengthGenerator + //padding = sizeParser.(crypto.PaddingLengthGenerator) + + if request.Command.TransferType() == protocol.TransferTypeStream { + return crypto.NewChunkStreamWriter(sizeParser, writer) + } + auth := &crypto.AEADAuthenticator{ + AEAD: new(NoOpAuthenticator), + NonceGenerator: crypto.GenerateEmptyBytes(), + AdditionalDataGenerator: crypto.GenerateEmptyBytes(), + } + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding) + + default: + + return buf.NewWriter(writer) + + } + +} + +// DecodeAddonsBody returns a Reader from which caller can fetch decrypted body. +func DecodeAddonsBody(request *protocol.RequestHeader, addons *Addons, reader io.Reader) buf.Reader { + + switch addons.MessName { + case "shake": + + var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{} + sizeParser = NewShakeSizeParser(addons.MessSeed) + var padding crypto.PaddingLengthGenerator + //padding = sizeParser.(crypto.PaddingLengthGenerator) + + if request.Command.TransferType() == protocol.TransferTypeStream { + return crypto.NewChunkStreamReader(sizeParser, reader) + } + auth := &crypto.AEADAuthenticator{ + AEAD: new(NoOpAuthenticator), + NonceGenerator: crypto.GenerateEmptyBytes(), + AdditionalDataGenerator: crypto.GenerateEmptyBytes(), + } + return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding) + + default: + + return buf.NewReader(reader) + + } + +} diff --git a/proxy/vless/encoding/addons.pb.go b/proxy/vless/encoding/addons.pb.go new file mode 100644 index 00000000000..bd78b4c9c64 --- /dev/null +++ b/proxy/vless/encoding/addons.pb.go @@ -0,0 +1,385 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: v2ray.com/core/proxy/vless/encoding/addons.proto + +package encoding + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type Addons struct { + MessName string `protobuf:"bytes,1,opt,name=MessName,proto3" json:"MessName,omitempty"` + MessSeed []byte `protobuf:"bytes,2,opt,name=MessSeed,proto3" json:"MessSeed,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Addons) Reset() { *m = Addons{} } +func (m *Addons) String() string { return proto.CompactTextString(m) } +func (*Addons) ProtoMessage() {} +func (*Addons) Descriptor() ([]byte, []int) { + return fileDescriptor_d597c8244066ecf1, []int{0} +} +func (m *Addons) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Addons) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Addons.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Addons) XXX_Merge(src proto.Message) { + xxx_messageInfo_Addons.Merge(m, src) +} +func (m *Addons) XXX_Size() int { + return m.Size() +} +func (m *Addons) XXX_DiscardUnknown() { + xxx_messageInfo_Addons.DiscardUnknown(m) +} + +var xxx_messageInfo_Addons proto.InternalMessageInfo + +func (m *Addons) GetMessName() string { + if m != nil { + return m.MessName + } + return "" +} + +func (m *Addons) GetMessSeed() []byte { + if m != nil { + return m.MessSeed + } + return nil +} + +func init() { + proto.RegisterType((*Addons)(nil), "v2ray.core.proxy.vless.encoding.Addons") +} + +func init() { + proto.RegisterFile("v2ray.com/core/proxy/vless/encoding/addons.proto", fileDescriptor_d597c8244066ecf1) +} + +var fileDescriptor_d597c8244066ecf1 = []byte{ + // 191 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x28, 0x33, 0x2a, 0x4a, + 0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x2f, 0x28, 0xca, 0xaf, 0xa8, + 0xd4, 0x2f, 0xcb, 0x49, 0x2d, 0x2e, 0xd6, 0x4f, 0xcd, 0x4b, 0xce, 0x4f, 0xc9, 0xcc, 0x4b, 0xd7, + 0x4f, 0x4c, 0x49, 0xc9, 0xcf, 0x2b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x87, 0xe9, + 0x28, 0x4a, 0xd5, 0x03, 0xab, 0xd6, 0x03, 0xab, 0xd6, 0x83, 0xa9, 0x56, 0x72, 0xe0, 0x62, 0x73, + 0x04, 0x6b, 0x10, 0x92, 0xe2, 0xe2, 0xf0, 0x4d, 0x2d, 0x2e, 0xf6, 0x4b, 0xcc, 0x4d, 0x95, 0x60, + 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0xf3, 0x61, 0x72, 0xc1, 0xa9, 0xa9, 0x29, 0x12, 0x4c, 0x0a, + 0x8c, 0x1a, 0x3c, 0x41, 0x70, 0xbe, 0x53, 0xf2, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, + 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe3, 0xb1, 0x1c, 0x03, 0x97, 0x72, 0x72, 0x7e, 0xae, 0x1e, 0x01, + 0x8b, 0x03, 0x18, 0xa3, 0x38, 0x60, 0xec, 0x55, 0x4c, 0xf2, 0x61, 0x46, 0x41, 0x89, 0x95, 0x7a, + 0xce, 0x20, 0xd5, 0x01, 0x60, 0xd5, 0x61, 0x60, 0xd5, 0xae, 0x50, 0x15, 0x49, 0x6c, 0x60, 0xef, + 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x1b, 0xf5, 0xa1, 0x34, 0x02, 0x01, 0x00, 0x00, +} + +func (m *Addons) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Addons) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Addons) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.MessSeed) > 0 { + i -= len(m.MessSeed) + copy(dAtA[i:], m.MessSeed) + i = encodeVarintAddons(dAtA, i, uint64(len(m.MessSeed))) + i-- + dAtA[i] = 0x12 + } + if len(m.MessName) > 0 { + i -= len(m.MessName) + copy(dAtA[i:], m.MessName) + i = encodeVarintAddons(dAtA, i, uint64(len(m.MessName))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintAddons(dAtA []byte, offset int, v uint64) int { + offset -= sovAddons(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Addons) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.MessName) + if l > 0 { + n += 1 + l + sovAddons(uint64(l)) + } + l = len(m.MessSeed) + if l > 0 { + n += 1 + l + sovAddons(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func sovAddons(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozAddons(x uint64) (n int) { + return sovAddons(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Addons) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAddons + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Addons: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Addons: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MessName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAddons + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAddons + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAddons + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MessName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MessSeed", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAddons + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthAddons + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthAddons + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MessSeed = append(m.MessSeed[:0], dAtA[iNdEx:postIndex]...) + if m.MessSeed == nil { + m.MessSeed = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAddons(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthAddons + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthAddons + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipAddons(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAddons + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAddons + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAddons + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthAddons + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupAddons + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthAddons + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthAddons = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowAddons = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupAddons = fmt.Errorf("proto: unexpected end of group") +) diff --git a/proxy/vless/encoding/addons.proto b/proxy/vless/encoding/addons.proto new file mode 100644 index 00000000000..7530e2a73e1 --- /dev/null +++ b/proxy/vless/encoding/addons.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package v2ray.core.proxy.vless.encoding; +option csharp_namespace = "V2Ray.Core.Proxy.Vless.Encoding"; +option go_package = "encoding"; +option java_package = "com.v2ray.core.proxy.vless.encoding"; +option java_multiple_files = true; + +message Addons { + string MessName = 1; + bytes MessSeed = 2; +} diff --git a/proxy/vless/encoding/auth.go b/proxy/vless/encoding/auth.go new file mode 100644 index 00000000000..252eef2bf43 --- /dev/null +++ b/proxy/vless/encoding/auth.go @@ -0,0 +1,163 @@ +package encoding + +import ( + "crypto/md5" + "encoding/binary" + "hash/fnv" + + "golang.org/x/crypto/sha3" + + "v2ray.com/core/common" +) + +// Authenticate authenticates a byte array using Fnv hash. +func Authenticate(b []byte) uint32 { + fnv1hash := fnv.New32a() + common.Must2(fnv1hash.Write(b)) + return fnv1hash.Sum32() +} + +type NoOpAuthenticator struct{} + +func (NoOpAuthenticator) NonceSize() int { + return 0 +} + +func (NoOpAuthenticator) Overhead() int { + return 0 +} + +// Seal implements AEAD.Seal(). +func (NoOpAuthenticator) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + return append(dst[:0], plaintext...) +} + +// Open implements AEAD.Open(). +func (NoOpAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + return append(dst[:0], ciphertext...), nil +} + +// FnvAuthenticator is an AEAD based on Fnv hash. +type FnvAuthenticator struct { +} + +// NonceSize implements AEAD.NonceSize(). +func (*FnvAuthenticator) NonceSize() int { + return 0 +} + +// Overhead impelements AEAD.Overhead(). +func (*FnvAuthenticator) Overhead() int { + return 4 +} + +// Seal implements AEAD.Seal(). +func (*FnvAuthenticator) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + dst = append(dst, 0, 0, 0, 0) + binary.BigEndian.PutUint32(dst, Authenticate(plaintext)) + return append(dst, plaintext...) +} + +// Open implements AEAD.Open(). +func (*FnvAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + if binary.BigEndian.Uint32(ciphertext[:4]) != Authenticate(ciphertext[4:]) { + return dst, newError("invalid authentication") + } + return append(dst, ciphertext[4:]...), nil +} + +// GenerateChacha20Poly1305Key generates a 32-byte key from a given 16-byte array. +func GenerateChacha20Poly1305Key(b []byte) []byte { + key := make([]byte, 32) + t := md5.Sum(b) + copy(key, t[:]) + t = md5.Sum(key[:16]) + copy(key[16:], t[:]) + return key +} + +type ShakeSizeParser struct { + shake sha3.ShakeHash + buffer [2]byte +} + +func NewShakeSizeParser(nonce []byte) *ShakeSizeParser { + shake := sha3.NewShake128() + common.Must2(shake.Write(nonce)) + return &ShakeSizeParser{ + shake: shake, + } +} + +func (*ShakeSizeParser) SizeBytes() int32 { + return 2 +} + +func (s *ShakeSizeParser) next() uint16 { + common.Must2(s.shake.Read(s.buffer[:])) + return binary.BigEndian.Uint16(s.buffer[:]) +} + +func (s *ShakeSizeParser) Decode(b []byte) (uint16, error) { + mask := s.next() + size := binary.BigEndian.Uint16(b) + return mask ^ size, nil +} + +func (s *ShakeSizeParser) Encode(size uint16, b []byte) []byte { + mask := s.next() + binary.BigEndian.PutUint16(b, mask^size) + return b[:2] +} + +func (s *ShakeSizeParser) NextPaddingLen() uint16 { + return s.next() % 64 +} + +func (s *ShakeSizeParser) MaxPaddingLen() uint16 { + return 64 +} + +/* +type BlakeSizeParser struct { + blake *blake3.Hasher + buffer [2]byte +} + +func NewBlakeSizeParser(nonce []byte) *BlakeSizeParser { + blake := blake3.New() + common.Must2(blake.Write(nonce)) + return &BlakeSizeParser{ + blake: blake, + } +} + +func (*BlakeSizeParser) SizeBytes() int32 { + return 2 +} + +func (s *BlakeSizeParser) next() uint16 { + common.Must2(s.blake.Digest().Read((s.buffer[:]))) + return binary.BigEndian.Uint16(s.buffer[:]) +} + +func (s *BlakeSizeParser) Decode(b []byte) (uint16, error) { + mask := s.next() + size := binary.BigEndian.Uint16(b) + return mask ^ size, nil +} + +func (s *BlakeSizeParser) Encode(size uint16, b []byte) []byte { + mask := s.next() + binary.BigEndian.PutUint16(b, mask^size) + return b[:2] +} + +func (s *BlakeSizeParser) NextPaddingLen() uint16 { + return s.next() % 64 +} + +func (s *BlakeSizeParser) MaxPaddingLen() uint16 { + return 64 +} +*/ diff --git a/proxy/vless/encoding/encoding.go b/proxy/vless/encoding/encoding.go new file mode 100644 index 00000000000..1b78106d267 --- /dev/null +++ b/proxy/vless/encoding/encoding.go @@ -0,0 +1,148 @@ +package encoding + +import ( + "io" + + "v2ray.com/core/common" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/proxy/vless" +) + +//go:generate errorgen + +const ( + Version = byte(0) +) + +var addrParser = protocol.NewAddressParser( + protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4), + protocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain), + protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6), + protocol.PortThenAddress(), +) + +// EncodeRequestHeader writes encoded request header into the given writer. +func EncodeRequestHeader(request *protocol.RequestHeader, addons *Addons, writer io.Writer) error { + + buffer := buf.StackNew() + defer buffer.Release() + + common.Must(buffer.WriteByte(request.Version)) + common.Must2(buffer.Write(request.User.Account.(*vless.MemoryAccount).ID.Bytes())) + + EncodeAddonsHeader(addons, &buffer) + + common.Must(buffer.WriteByte(byte(request.Command))) + if request.Command != protocol.RequestCommandMux { + if err := addrParser.WriteAddressPort(&buffer, request.Address, request.Port); err != nil { + return newError("failed to write address and port").Base(err) + } + } + + common.Must2(writer.Write(buffer.Bytes())) + return nil +} + +// DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream. +func DecodeRequestHeader(validator *vless.Validator, reader io.Reader) (*protocol.RequestHeader, *Addons, error) { + + buffer := buf.StackNew() + defer buffer.Release() + + if _, err := buffer.ReadFullFrom(reader, 1); err != nil { + return nil, nil, newError("failed to read request version").Base(err).AtWarning() + } + + request := &protocol.RequestHeader{ + Version: buffer.Byte(0), + } + + switch request.Version { + case 0: + + buffer.Clear() + if _, err := buffer.ReadFullFrom(reader, protocol.IDBytesLen); err != nil { + return nil, nil, newError("failed to read request user").Base(err) + } + + var id [16]byte + copy(id[:], buffer.Bytes()) + + request.User = validator.Get(id) + if request.User == nil { + return nil, nil, newError("invalid request user") + } + + addons, err := DecodeAddonsHeader(reader, &buffer) + if err != nil { + return nil, nil, err + } + + buffer.Clear() + if _, err := buffer.ReadFullFrom(reader, 1); err != nil { + return nil, nil, newError("failed to read request command").Base(err) + } + + request.Command = protocol.RequestCommand(buffer.Byte(0)) + switch request.Command { + case protocol.RequestCommandMux: + request.Address = net.DomainAddress("v1.mux.cool") + request.Port = 0 + case protocol.RequestCommandTCP, protocol.RequestCommandUDP: + if addr, port, err := addrParser.ReadAddressPort(&buffer, reader); err == nil { + request.Address = addr + request.Port = port + } + } + + if request.Address == nil { + return nil, nil, newError("invalid request address") + } + + return request, addons, nil + + default: + + return nil, nil, newError("unexpected request version") + + } + +} + +// EncodeResponseHeader writes encoded response header into the given writer. +func EncodeResponseHeader(request *protocol.RequestHeader, response *Addons, writer io.Writer) error { + + buffer := buf.StackNew() + defer buffer.Release() + + common.Must(buffer.WriteByte(request.Version)) + + EncodeAddonsHeader(response, &buffer) + + common.Must2(writer.Write(buffer.Bytes())) + return nil +} + +// DecodeResponseHeader decodes and returns (if successful) a ResponseHeader from an input stream. +func DecodeResponseHeader(request *protocol.RequestHeader, reader io.Reader) (*Addons, error) { + + buffer := buf.StackNew() + defer buffer.Release() + + if _, err := buffer.ReadFullFrom(reader, 1); err != nil { + return nil, newError("failed to read response version").Base(err).AtWarning() + } + + if buffer.Byte(0) != request.Version { + return nil, newError("unexpected response version. Expecting ", int(request.Version), " but actually ", int(buffer.Byte(0))) + } + + response, err := DecodeAddonsHeader(reader, &buffer) + if err != nil { + return nil, err + } + + return response, nil +} diff --git a/proxy/vless/encoding/errors.generated.go b/proxy/vless/encoding/errors.generated.go new file mode 100644 index 00000000000..6fb682106f0 --- /dev/null +++ b/proxy/vless/encoding/errors.generated.go @@ -0,0 +1,9 @@ +package encoding + +import "v2ray.com/core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/proxy/vless/errors.generated.go b/proxy/vless/errors.generated.go new file mode 100644 index 00000000000..79734c109ad --- /dev/null +++ b/proxy/vless/errors.generated.go @@ -0,0 +1,9 @@ +package vless + +import "v2ray.com/core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/proxy/vless/inbound/config.go b/proxy/vless/inbound/config.go new file mode 100644 index 00000000000..039be43378b --- /dev/null +++ b/proxy/vless/inbound/config.go @@ -0,0 +1,3 @@ +// +build !confonly + +package inbound diff --git a/proxy/vless/inbound/config.pb.go b/proxy/vless/inbound/config.pb.go new file mode 100644 index 00000000000..3cdd142b7f9 --- /dev/null +++ b/proxy/vless/inbound/config.pb.go @@ -0,0 +1,173 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.12.3 +// source: v2ray.com/core/proxy/vless/inbound/config.proto + +package inbound + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + protocol "v2ray.com/core/common/protocol" +) + +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) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + User []*protocol.User `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"` + // Decryption settings. Only applies to server side, and only accepts "none" for now. + Decryption string `protobuf:"bytes,2,opt,name=decryption,proto3" json:"decryption,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_v2ray_com_core_proxy_vless_inbound_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_v2ray_com_core_proxy_vless_inbound_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && 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_v2ray_com_core_proxy_vless_inbound_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetUser() []*protocol.User { + if x != nil { + return x.User + } + return nil +} + +func (x *Config) GetDecryption() string { + if x != nil { + return x.Decryption + } + return "" +} + +var File_v2ray_com_core_proxy_vless_inbound_config_proto protoreflect.FileDescriptor + +var file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDesc = []byte{ + 0x0a, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x1e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x1a, 0x29, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, + 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5e, 0x0a, 0x06, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, + 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x50, 0x0a, 0x22, + 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x1e, + 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, + 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDescOnce sync.Once + file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDescData = file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDesc +) + +func file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDescGZIP() []byte { + file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDescOnce.Do(func() { + file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDescData) + }) + return file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDescData +} + +var file_v2ray_com_core_proxy_vless_inbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_v2ray_com_core_proxy_vless_inbound_config_proto_goTypes = []interface{}{ + (*Config)(nil), // 0: v2ray.core.proxy.vless.inbound.Config + (*protocol.User)(nil), // 1: v2ray.core.common.protocol.User +} +var file_v2ray_com_core_proxy_vless_inbound_config_proto_depIdxs = []int32{ + 1, // 0: v2ray.core.proxy.vless.inbound.Config.user:type_name -> v2ray.core.common.protocol.User + 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_v2ray_com_core_proxy_vless_inbound_config_proto_init() } +func file_v2ray_com_core_proxy_vless_inbound_config_proto_init() { + if File_v2ray_com_core_proxy_vless_inbound_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_v2ray_com_core_proxy_vless_inbound_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_v2ray_com_core_proxy_vless_inbound_config_proto_goTypes, + DependencyIndexes: file_v2ray_com_core_proxy_vless_inbound_config_proto_depIdxs, + MessageInfos: file_v2ray_com_core_proxy_vless_inbound_config_proto_msgTypes, + }.Build() + File_v2ray_com_core_proxy_vless_inbound_config_proto = out.File + file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDesc = nil + file_v2ray_com_core_proxy_vless_inbound_config_proto_goTypes = nil + file_v2ray_com_core_proxy_vless_inbound_config_proto_depIdxs = nil +} diff --git a/proxy/vless/inbound/config.proto b/proxy/vless/inbound/config.proto new file mode 100644 index 00000000000..50e40181945 --- /dev/null +++ b/proxy/vless/inbound/config.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package v2ray.core.proxy.vless.inbound; +option csharp_namespace = "V2Ray.Core.Proxy.Vless.Inbound"; +option go_package = "inbound"; +option java_package = "com.v2ray.core.proxy.vless.inbound"; +option java_multiple_files = true; + +import "v2ray.com/core/common/protocol/user.proto"; + +message Config { + repeated v2ray.core.common.protocol.User user = 1; + // Decryption settings. Only applies to server side, and only accepts "none" for now. + string decryption = 2; +} diff --git a/proxy/vless/inbound/errors.generated.go b/proxy/vless/inbound/errors.generated.go new file mode 100644 index 00000000000..90d805b1e7b --- /dev/null +++ b/proxy/vless/inbound/errors.generated.go @@ -0,0 +1,9 @@ +package inbound + +import "v2ray.com/core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/proxy/vless/inbound/inbound.go b/proxy/vless/inbound/inbound.go new file mode 100644 index 00000000000..5aeb19b79eb --- /dev/null +++ b/proxy/vless/inbound/inbound.go @@ -0,0 +1,206 @@ +// +build !confonly + +package inbound + +//go:generate errorgen + +import ( + "context" + "io" + "time" + + "v2ray.com/core" + "v2ray.com/core/common" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/errors" + "v2ray.com/core/common/log" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/session" + "v2ray.com/core/common/signal" + "v2ray.com/core/common/task" + feature_inbound "v2ray.com/core/features/inbound" + "v2ray.com/core/features/policy" + "v2ray.com/core/features/routing" + "v2ray.com/core/proxy/vless" + "v2ray.com/core/proxy/vless/encoding" + "v2ray.com/core/transport/internet" +) + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return New(ctx, config.(*Config)) + })) +} + +// 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 +} + +// New creates a new VLess inbound handler. +func New(ctx context.Context, config *Config) (*Handler, error) { + + v := core.MustFromContext(ctx) + handler := &Handler{ + inboundHandlerManager: v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager), + policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), + validator: new(vless.Validator), + } + + for _, user := range config.User { + u, err := user.ToMemoryUser() + if err != nil { + return nil, newError("failed to get VLess user").Base(err) + } + if err := handler.AddUser(ctx, u); err != nil { + return nil, newError("failed to initiate user").Base(err) + } + } + + return handler, nil +} + +// Close implements common.Closable.Close(). +func (h *Handler) Close() error { + return errors.Combine(common.Close(h.validator)) +} + +// AddUser implements proxy.UserManager.AddUser(). +func (h *Handler) AddUser(ctx context.Context, u *protocol.MemoryUser) error { + return h.validator.Add(u) +} + +// RemoveUser implements proxy.UserManager.RemoveUser(). +func (h *Handler) RemoveUser(ctx context.Context, e string) error { + return h.validator.Del(e) +} + +// Network implements proxy.Inbound.Network(). +func (*Handler) Network() []net.Network { + return []net.Network{net.Network_TCP} +} + +// Process implements proxy.Inbound.Process(). +func (h *Handler) Process(ctx context.Context, network net.Network, connection internet.Connection, dispatcher routing.Dispatcher) error { + + sessionPolicy := h.policyManager.ForLevel(0) + if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { + return newError("unable to set read deadline").Base(err).AtWarning() + } + + reader := &buf.BufferedReader{Reader: buf.NewReader(connection)} + request, addons, err := encoding.DecodeRequestHeader(h.validator, reader) + if err != nil { + if errors.Cause(err) != io.EOF { + log.Record(&log.AccessMessage{ + From: connection.RemoteAddr(), + To: "", + Status: log.AccessRejected, + Reason: err, + }) + err = newError("invalid request from ", connection.RemoteAddr()).Base(err).AtInfo() + } + return err + } + + if request.Command != protocol.RequestCommandMux { + ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ + From: connection.RemoteAddr(), + To: request.Destination(), + Status: log.AccessAccepted, + Reason: "", + Email: request.User.Email, + }) + } + + newError("received request for ", request.Destination()).WriteToLog(session.ExportIDToError(ctx)) + + if err := connection.SetReadDeadline(time.Time{}); err != nil { + newError("unable to set back read deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) + } + + inbound := session.InboundFromContext(ctx) + if inbound == nil { + panic("no inbound metadata") + } + inbound.User = request.User + + sessionPolicy = h.policyManager.ForLevel(request.User.Level) + + ctx, cancel := context.WithCancel(ctx) + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + + ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer) + link, err := dispatcher.Dispatch(ctx, request.Destination()) + if err != nil { + return newError("failed to dispatch request to ", request.Destination()).Base(err) + } + + requestDone := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + + bodyReader := encoding.DecodeAddonsBody(request, addons, reader) + if err := buf.Copy(bodyReader, link.Writer, buf.UpdateActivity(timer)); err != nil { + return newError("failed to transfer request").Base(err) + } + + return nil + } + + responseDone := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) + + writer := buf.NewBufferedWriter(buf.NewWriter(connection)) + defer writer.Flush() + + response := &encoding.Addons{ + MessName: addons.MessName, + } + + if err := encoding.EncodeResponseHeader(request, response, writer); err != nil { + return newError("failed to encode response").Base(err).AtWarning() + } + bodyWriter := encoding.EncodeAddonsBody(request, response, writer) + + { + // Optimize for small response packet. + data, err := link.Reader.ReadMultiBuffer() + if err != nil { + return err + } + + if err := bodyWriter.WriteMultiBuffer(data); err != nil { + return err + } + } + + if err := writer.SetBuffered(false); err != nil { + return err + } + + if err := buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer)); err != nil { + return err + } + + // Indicates the end of transmission. + if response.MessName == "shake" { + if err := bodyWriter.WriteMultiBuffer(buf.MultiBuffer{}); err != nil { + return err + } + } + + return nil + } + + var requestDonePost = task.OnSuccess(requestDone, task.Close(link.Writer)) + if err := task.Run(ctx, requestDonePost, responseDone); err != nil { + common.Interrupt(link.Reader) + common.Interrupt(link.Writer) + return newError("connection ends").Base(err) + } + + return nil +} diff --git a/proxy/vless/outbound/config.go b/proxy/vless/outbound/config.go new file mode 100644 index 00000000000..35bf561b663 --- /dev/null +++ b/proxy/vless/outbound/config.go @@ -0,0 +1,3 @@ +// +build !confonly + +package outbound diff --git a/proxy/vless/outbound/config.pb.go b/proxy/vless/outbound/config.pb.go new file mode 100644 index 00000000000..cdcc3f82b50 --- /dev/null +++ b/proxy/vless/outbound/config.pb.go @@ -0,0 +1,164 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.12.3 +// source: v2ray.com/core/proxy/vless/outbound/config.proto + +package outbound + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + protocol "v2ray.com/core/common/protocol" +) + +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) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Receiver []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=receiver,proto3" json:"receiver,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_v2ray_com_core_proxy_vless_outbound_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_v2ray_com_core_proxy_vless_outbound_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && 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_v2ray_com_core_proxy_vless_outbound_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetReceiver() []*protocol.ServerEndpoint { + if x != nil { + return x.Receiver + } + return nil +} + +var File_v2ray_com_core_proxy_vless_outbound_config_proto protoreflect.FileDescriptor + +var file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDesc = []byte{ + 0x0a, 0x30, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x6f, 0x75, 0x74, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x1f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x6f, 0x75, 0x74, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x1a, 0x30, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, + 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x50, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x46, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x72, + 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x42, 0x53, 0x0a, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x76, + 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, + 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, + 0x5a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x1f, 0x56, 0x32, 0x52, + 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, + 0x65, 0x73, 0x73, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDescOnce sync.Once + file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDescData = file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDesc +) + +func file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDescGZIP() []byte { + file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDescOnce.Do(func() { + file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDescData) + }) + return file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDescData +} + +var file_v2ray_com_core_proxy_vless_outbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_v2ray_com_core_proxy_vless_outbound_config_proto_goTypes = []interface{}{ + (*Config)(nil), // 0: v2ray.core.proxy.vless.outbound.Config + (*protocol.ServerEndpoint)(nil), // 1: v2ray.core.common.protocol.ServerEndpoint +} +var file_v2ray_com_core_proxy_vless_outbound_config_proto_depIdxs = []int32{ + 1, // 0: v2ray.core.proxy.vless.outbound.Config.receiver:type_name -> v2ray.core.common.protocol.ServerEndpoint + 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_v2ray_com_core_proxy_vless_outbound_config_proto_init() } +func file_v2ray_com_core_proxy_vless_outbound_config_proto_init() { + if File_v2ray_com_core_proxy_vless_outbound_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_v2ray_com_core_proxy_vless_outbound_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_v2ray_com_core_proxy_vless_outbound_config_proto_goTypes, + DependencyIndexes: file_v2ray_com_core_proxy_vless_outbound_config_proto_depIdxs, + MessageInfos: file_v2ray_com_core_proxy_vless_outbound_config_proto_msgTypes, + }.Build() + File_v2ray_com_core_proxy_vless_outbound_config_proto = out.File + file_v2ray_com_core_proxy_vless_outbound_config_proto_rawDesc = nil + file_v2ray_com_core_proxy_vless_outbound_config_proto_goTypes = nil + file_v2ray_com_core_proxy_vless_outbound_config_proto_depIdxs = nil +} diff --git a/proxy/vless/outbound/config.proto b/proxy/vless/outbound/config.proto new file mode 100644 index 00000000000..732addf0f9f --- /dev/null +++ b/proxy/vless/outbound/config.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package v2ray.core.proxy.vless.outbound; +option csharp_namespace = "V2Ray.Core.Proxy.Vless.Outbound"; +option go_package = "outbound"; +option java_package = "com.v2ray.core.proxy.vless.outbound"; +option java_multiple_files = true; + +import "v2ray.com/core/common/protocol/server_spec.proto"; + +message Config { + repeated v2ray.core.common.protocol.ServerEndpoint receiver = 1; +} diff --git a/proxy/vless/outbound/errors.generated.go b/proxy/vless/outbound/errors.generated.go new file mode 100644 index 00000000000..37e984d8758 --- /dev/null +++ b/proxy/vless/outbound/errors.generated.go @@ -0,0 +1,9 @@ +package outbound + +import "v2ray.com/core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go new file mode 100644 index 00000000000..aa890dbfd30 --- /dev/null +++ b/proxy/vless/outbound/outbound.go @@ -0,0 +1,173 @@ +// +build !confonly + +package outbound + +//go:generate errorgen + +import ( + "context" + "time" + + "v2ray.com/core" + "v2ray.com/core/common" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/retry" + "v2ray.com/core/common/session" + "v2ray.com/core/common/signal" + "v2ray.com/core/common/task" + "v2ray.com/core/features/policy" + "v2ray.com/core/proxy/vless" + "v2ray.com/core/proxy/vless/encoding" + "v2ray.com/core/transport" + "v2ray.com/core/transport/internet" +) + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return New(ctx, config.(*Config)) + })) +} + +// Handler is an outbound connection handler for VLess protocol. +type Handler struct { + serverList *protocol.ServerList + serverPicker protocol.ServerPicker + policyManager policy.Manager +} + +// New creates a new VLess outbound handler. +func New(ctx context.Context, config *Config) (*Handler, error) { + + serverList := protocol.NewServerList() + for _, rec := range config.Receiver { + s, err := protocol.NewServerSpecFromPB(*rec) + if err != nil { + return nil, newError("failed to parse server spec").Base(err) + } + serverList.AddServer(s) + } + + v := core.MustFromContext(ctx) + handler := &Handler{ + serverList: serverList, + serverPicker: protocol.NewRoundRobinServerPicker(serverList), + policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), + } + + return handler, nil +} + +// Process implements proxy.Outbound.Process(). +func (v *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { + + var rec *protocol.ServerSpec + var conn internet.Connection + + err := retry.ExponentialBackoff(5, 200).On(func() error { + rec = v.serverPicker.PickServer() + rawConn, err := dialer.Dial(ctx, rec.Destination()) + if err != nil { + return err + } + conn = rawConn + return nil + }) + if err != nil { + return newError("failed to find an available destination").Base(err).AtWarning() + } + defer conn.Close() // nolint: errcheck + + outbound := session.OutboundFromContext(ctx) + if outbound == nil || !outbound.Target.IsValid() { + return newError("target not specified").AtError() + } + + target := outbound.Target + newError("tunneling request to ", target, " via ", rec.Destination()).WriteToLog(session.ExportIDToError(ctx)) + + command := protocol.RequestCommandTCP + if target.Network == net.Network_UDP { + command = protocol.RequestCommandUDP + } + if target.Address.Family().IsDomain() && target.Address.Domain() == "v1.mux.cool" { + command = protocol.RequestCommandMux + } + + request := &protocol.RequestHeader{ + Version: encoding.Version, + User: rec.PickUser(), + Command: command, + Address: target.Address, + Port: target.Port, + //Option: protocol.RequestOptionChunkStream, + } + + account := request.User.Account.(*vless.MemoryAccount) + //request.Security = account.Security + + addons := &encoding.Addons{ + MessName: account.Mess, + } + + input := link.Reader + output := link.Writer + + sessionPolicy := v.policyManager.ForLevel(request.User.Level) + + ctx, cancel := context.WithCancel(ctx) + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + + requestDone := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + + writer := buf.NewBufferedWriter(buf.NewWriter(conn)) + if err := encoding.EncodeRequestHeader(request, addons, writer); err != nil { + return newError("failed to encode request").Base(err).AtWarning() + } + + bodyWriter := encoding.EncodeAddonsBody(request, addons, writer) + if err := buf.CopyOnceTimeout(input, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { + return newError("failed to write first payload").Base(err) + } + + if err := writer.SetBuffered(false); err != nil { + return err + } + + if err := buf.Copy(input, bodyWriter, buf.UpdateActivity(timer)); err != nil { + return err + } + + // Indicates the end of transmission. + if addons.MessName == "shake" { + if err := bodyWriter.WriteMultiBuffer(buf.MultiBuffer{}); err != nil { + return err + } + } + + return nil + } + + responseDone := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) + + reader := &buf.BufferedReader{Reader: buf.NewReader(conn)} + response, err := encoding.DecodeResponseHeader(request, reader) + if err != nil { + return newError("failed to read header").Base(err) + } + + bodyReader := encoding.DecodeAddonsBody(request, response, reader) + + return buf.Copy(bodyReader, output, buf.UpdateActivity(timer)) + } + + var responseDonePost = task.OnSuccess(responseDone, task.Close(output)) + if err := task.Run(ctx, requestDone, responseDonePost); err != nil { + return newError("connection ends").Base(err) + } + + return nil +} diff --git a/proxy/vless/validator.go b/proxy/vless/validator.go new file mode 100644 index 00000000000..a19cc6323cf --- /dev/null +++ b/proxy/vless/validator.go @@ -0,0 +1,50 @@ +// +build !confonly + +package vless + +import ( + "strings" + "sync" + + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/uuid" +) + +type Validator struct { + // Considering email's usage here, map + sync.Mutex/RWMutex may have better performance. + email sync.Map + users sync.Map +} + +func (v *Validator) Add(u *protocol.MemoryUser) error { + if u.Email != "" { + _, loaded := v.email.LoadOrStore(strings.ToLower(u.Email), u) + if loaded { + return newError("User ", u.Email, " already exists.") + } + } + v.users.Store(u.Account.(*MemoryAccount).ID.UUID(), u) + return nil +} + +func (v *Validator) Del(e string) error { + if e == "" { + return newError("Email must not be empty.") + } + le := strings.ToLower(e) + u, _ := v.email.Load(le) + if u == nil { + return newError("User ", e, " not found.") + } + v.email.Delete(le) + v.users.Delete(u.(*protocol.MemoryUser).Account.(*MemoryAccount).ID.UUID()) + return nil +} + +func (v *Validator) Get(id uuid.UUID) *protocol.MemoryUser { + u, _ := v.users.Load(id) + if u != nil { + return u.(*protocol.MemoryUser) + } + return nil +} diff --git a/proxy/vless/vless.go b/proxy/vless/vless.go new file mode 100644 index 00000000000..9e6dc7ab07a --- /dev/null +++ b/proxy/vless/vless.go @@ -0,0 +1,8 @@ +// Package vless contains the implementation of VLess protocol and transportation. +// +// VLess contains both inbound and outbound connections. VLess inbound is usually used on servers +// together with 'freedom' to talk to final destination, while VLess outbound is usually used on +// clients with 'socks' for proxying. +package vless + +//go:generate errorgen