From bad7e2cfd85b6a16f92760ecdeab915005687cf6 Mon Sep 17 00:00:00 2001 From: RPRX <63339210+rprx@users.noreply.github.com> Date: Tue, 28 Jul 2020 15:00:23 +0000 Subject: [PATCH] VLESS PREVIEW 1.1 --- infra/conf/v2ray.go | 4 +- infra/conf/vless.go | 140 ++++++++ infra/conf/vless_test.go | 110 +++++++ main/distro/all/all.go | 2 + proxy/vless/account.go | 40 +++ proxy/vless/account.pb.go | 175 ++++++++++ proxy/vless/account.proto | 16 + proxy/vless/encoding/addons.go | 83 +++++ proxy/vless/encoding/addons.pb.go | 386 +++++++++++++++++++++++ proxy/vless/encoding/addons.proto | 12 + proxy/vless/encoding/encoding.go | 173 ++++++++++ proxy/vless/encoding/encoding_test.go | 126 ++++++++ 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 | 275 ++++++++++++++++ proxy/vless/inbound/config.proto | 23 ++ proxy/vless/inbound/errors.generated.go | 9 + proxy/vless/inbound/inbound.go | 308 ++++++++++++++++++ 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 | 177 +++++++++++ proxy/vless/validator.go | 50 +++ proxy/vless/vless.go | 8 + 26 files changed, 2326 insertions(+), 1 deletion(-) create mode 100644 infra/conf/vless.go create mode 100644 infra/conf/vless_test.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/encoding.go create mode 100644 proxy/vless/encoding/encoding_test.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 8f0d7d1a..f8499923 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 00000000..503fd781 --- /dev/null +++ b/infra/conf/vless.go @@ -0,0 +1,140 @@ +package conf + +import ( + "encoding/json" + + "github.com/golang/protobuf/proto" + + "v2ray.com/core/common/net" + "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 VLessInboundFallback struct { + Addr *Address `json:"addr"` + Port uint16 `json:"port"` + Unix string `json:"unix"` +} + +type VLessInboundConfig struct { + Users []json.RawMessage `json:"clients"` + Decryption string `json:"decryption"` + Fallback *VLessInboundFallback `json:"fallback"` +} + +// Build implements Buildable +func (c *VLessInboundConfig) Build() (proto.Message, error) { + + config := new(inbound.Config) + + if c.Decryption != "none" { + return nil, newError(`please add/set "decryption":"none" directly to every VLESS "settings"`) + } + config.Decryption = c.Decryption + + if c.Fallback != nil { + if c.Fallback.Unix != "" { + if c.Fallback.Unix[0] == '@' { + c.Fallback.Unix = "\x00" + c.Fallback.Unix[1:] + } + } else { + if c.Fallback.Port == 0 { + return nil, newError(`please fill in a valid value for "port" in VLESS "fallback"`) + } + } + if c.Fallback.Addr == nil { + c.Fallback.Addr = &Address{ + Address: net.ParseAddress("127.0.0.1"), + } + } + config.Fallback = &inbound.Fallback{ + Addr: c.Fallback.Addr.Build(), + Port: uint32(c.Fallback.Port), + Unix: c.Fallback.Unix, + } + } + + 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.Schedulers != "" { + return nil, newError(`VLESS attr "schedulers" 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.Schedulers != "" { + return nil, newError(`VLESS attr "schedulers" 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/infra/conf/vless_test.go b/infra/conf/vless_test.go new file mode 100644 index 00000000..aacd5302 --- /dev/null +++ b/infra/conf/vless_test.go @@ -0,0 +1,110 @@ +package conf_test + +import ( + "testing" + + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/serial" + . "v2ray.com/core/infra/conf" + "v2ray.com/core/proxy/vless" + "v2ray.com/core/proxy/vless/inbound" + "v2ray.com/core/proxy/vless/outbound" +) + +func TestVLessOutbound(t *testing.T) { + creator := func() Buildable { + return new(VLessOutboundConfig) + } + + runMultiTestCase(t, []TestCase{ + { + Input: `{ + "vnext": [{ + "address": "example.com", + "port": 443, + "users": [ + { + "id": "27848739-7e62-4138-9fd3-098a63964b6b", + "schedulers": "", + "encryption": "none", + "level": 0 + } + ] + }] + }`, + Parser: loadJSON(creator), + Output: &outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: &net.IPOrDomain{ + Address: &net.IPOrDomain_Domain{ + Domain: "example.com", + }, + }, + Port: 443, + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vless.Account{ + Id: "27848739-7e62-4138-9fd3-098a63964b6b", + Schedulers: "", + Encryption: "none", + }), + Level: 0, + }, + }, + }, + }, + }, + }, + }) +} + +func TestVLessInbound(t *testing.T) { + creator := func() Buildable { + return new(VLessInboundConfig) + } + + runMultiTestCase(t, []TestCase{ + { + Input: `{ + "clients": [ + { + "id": "27848739-7e62-4138-9fd3-098a63964b6b", + "schedulers": "", + "level": 0, + "email": "love@v2fly.org" + } + ], + "decryption": "none", + "fallback": { + "port": 80, + "unix": "@/dev/shm/domain.socket" + } + }`, + Parser: loadJSON(creator), + Output: &inbound.Config{ + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vless.Account{ + Id: "27848739-7e62-4138-9fd3-098a63964b6b", + Schedulers: "", + }), + Level: 0, + Email: "love@v2fly.org", + }, + }, + Decryption: "none", + Fallback: &inbound.Fallback{ + Addr: &net.IPOrDomain{ + Address: &net.IPOrDomain_Ip{ + Ip: []byte{127, 0, 0, 1}, + }, + }, + Port: 80, + Unix: "\x00/dev/shm/domain.socket", + }, + }, + }, + }) +} diff --git a/main/distro/all/all.go b/main/distro/all/all.go index 71144c08..9df5b6ac 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 00000000..ed76d5de --- /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), + Schedulers: a.Schedulers, // needs parser here? + Encryption: a.Encryption, // needs parser here? + }, nil +} + +// MemoryAccount is an in-memory form of VLess account. +type MemoryAccount struct { + // ID of the account. + ID *protocol.ID + // Schedulers of the account. + Schedulers 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 00000000..da5cc167 --- /dev/null +++ b/proxy/vless/account.pb.go @@ -0,0 +1,175 @@ +// 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"` + // Schedulers settings. + Schedulers string `protobuf:"bytes,2,opt,name=schedulers,proto3" json:"schedulers,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) GetSchedulers() string { + if x != nil { + return x.Schedulers + } + 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, 0x59, 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, 0x1e, 0x0a, + 0x0a, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 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 00000000..e259c78d --- /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; + // Schedulers settings. + string schedulers = 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 00000000..f4d4ac4d --- /dev/null +++ b/proxy/vless/encoding/addons.go @@ -0,0 +1,83 @@ +// +build !confonly + +package encoding + +import ( + "io" + + "github.com/golang/protobuf/proto" + + "v2ray.com/core/common/buf" + "v2ray.com/core/common/protocol" +) + +func EncodeHeaderAddons(buffer *buf.Buffer, addons *Addons) error { + + switch addons.Scheduler { + default: + + if err := buffer.WriteByte(0); err != nil { + return newError("failed to write addons protobuf length").Base(err).AtWarning() + } + + } + + return nil + +} + +func DecodeHeaderAddons(buffer *buf.Buffer, reader io.Reader) (*Addons, error) { + + addons := new(Addons) + + buffer.Clear() + if _, err := buffer.ReadFullFrom(reader, 1); err != nil { + return nil, newError("failed to read addons protobuf length").Base(err).AtWarning() + } + + if length := int32(buffer.Byte(0)); length != 0 { + + buffer.Clear() + if _, err := buffer.ReadFullFrom(reader, length); err != nil { + return nil, newError("failed to read addons protobuf value").Base(err).AtWarning() + } + + if err := proto.Unmarshal(buffer.Bytes(), addons); err != nil { + return nil, newError("failed to unmarshal addons protobuf value").Base(err).AtWarning() + } + + // Verification. + switch addons.Scheduler { + default: + + } + + } + + return addons, nil + +} + +// EncodeBodyAddons returns a Writer that auto-encrypt content written by caller. +func EncodeBodyAddons(writer io.Writer, request *protocol.RequestHeader, addons *Addons) buf.Writer { + + switch addons.Scheduler { + default: + + return buf.NewWriter(writer) + + } + +} + +// DecodeBodyAddons returns a Reader from which caller can fetch decrypted body. +func DecodeBodyAddons(reader io.Reader, request *protocol.RequestHeader, addons *Addons) buf.Reader { + + switch addons.Scheduler { + 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 00000000..271356b6 --- /dev/null +++ b/proxy/vless/encoding/addons.pb.go @@ -0,0 +1,386 @@ +// 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 { + Scheduler string `protobuf:"bytes,1,opt,name=Scheduler,proto3" json:"Scheduler,omitempty"` + SchedulerV []byte `protobuf:"bytes,2,opt,name=SchedulerV,proto3" json:"SchedulerV,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) GetScheduler() string { + if m != nil { + return m.Scheduler + } + return "" +} + +func (m *Addons) GetSchedulerV() []byte { + if m != nil { + return m.SchedulerV + } + 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{ + // 193 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, 0xe3, 0x62, 0x73, + 0x04, 0x6b, 0x10, 0x92, 0xe1, 0xe2, 0x0c, 0x4e, 0xce, 0x48, 0x4d, 0x29, 0xcd, 0x49, 0x2d, 0x92, + 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x42, 0x08, 0x08, 0xc9, 0x71, 0x71, 0xc1, 0x39, 0x61, 0x12, + 0x4c, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x48, 0x22, 0x4e, 0xc9, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, + 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x8c, 0xc7, 0x72, 0x0c, 0x5c, 0xca, 0xc9, 0xf9, 0xb9, + 0x7a, 0x04, 0xac, 0x0f, 0x60, 0x8c, 0xe2, 0x80, 0xb1, 0x57, 0x31, 0xc9, 0x87, 0x19, 0x05, 0x25, + 0x56, 0xea, 0x39, 0x83, 0x54, 0x07, 0x80, 0x55, 0x87, 0x81, 0x55, 0xbb, 0x42, 0x55, 0x24, 0xb1, + 0x81, 0x3d, 0x65, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xb6, 0x78, 0xd4, 0xe2, 0x08, 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.SchedulerV) > 0 { + i -= len(m.SchedulerV) + copy(dAtA[i:], m.SchedulerV) + i = encodeVarintAddons(dAtA, i, uint64(len(m.SchedulerV))) + i-- + dAtA[i] = 0x12 + } + if len(m.Scheduler) > 0 { + i -= len(m.Scheduler) + copy(dAtA[i:], m.Scheduler) + i = encodeVarintAddons(dAtA, i, uint64(len(m.Scheduler))) + 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.Scheduler) + if l > 0 { + n += 1 + l + sovAddons(uint64(l)) + } + l = len(m.SchedulerV) + 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 Scheduler", 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.Scheduler = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SchedulerV", 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.SchedulerV = append(m.SchedulerV[:0], dAtA[iNdEx:postIndex]...) + if m.SchedulerV == nil { + m.SchedulerV = []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 00000000..39ae2503 --- /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 Scheduler = 1; + bytes SchedulerV = 2; +} diff --git a/proxy/vless/encoding/encoding.go b/proxy/vless/encoding/encoding.go new file mode 100644 index 00000000..dc121a24 --- /dev/null +++ b/proxy/vless/encoding/encoding.go @@ -0,0 +1,173 @@ +package encoding + +import ( + "io" + + "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(writer io.Writer, request *protocol.RequestHeader, requestAddons *Addons) error { + + buffer := buf.StackNew() + defer buffer.Release() + + if err := buffer.WriteByte(request.Version); err != nil { + return newError("failed to write request version").Base(err).AtWarning() + } + + if _, err := buffer.Write(request.User.Account.(*vless.MemoryAccount).ID.Bytes()); err != nil { + return newError("failed to write request user id").Base(err).AtWarning() + } + + if err := EncodeHeaderAddons(&buffer, requestAddons); err != nil { + return newError("failed to encode request header addons").Base(err).AtWarning() + } + + if err := buffer.WriteByte(byte(request.Command)); err != nil { + return newError("failed to write request command").Base(err).AtWarning() + } + + if request.Command != protocol.RequestCommandMux { + if err := addrParser.WriteAddressPort(&buffer, request.Address, request.Port); err != nil { + return newError("failed to write request address and port").Base(err).AtWarning() + } + } + + if _, err := writer.Write(buffer.Bytes()); err != nil { + return newError("failed to write request header").Base(err).AtWarning() + } + + return nil +} + +// DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream. +func DecodeRequestHeader(reader io.Reader, validator *vless.Validator) (*protocol.RequestHeader, *Addons, error, *buf.Buffer) { + + buffer := buf.StackNew() + defer buffer.Release() + + pre := buf.New() + + if _, err := buffer.ReadFullFrom(reader, 1); err != nil { + pre.Write(buffer.Bytes()) + return nil, nil, newError("failed to read request version").Base(err).AtWarning(), pre + } + + request := &protocol.RequestHeader{ + Version: buffer.Byte(0), + } + + pre.Write(buffer.Bytes()) + + switch request.Version { + case 0: + + buffer.Clear() + if _, err := buffer.ReadFullFrom(reader, protocol.IDBytesLen); err != nil { + pre.Write(buffer.Bytes()) + return nil, nil, newError("failed to read request user id").Base(err).AtWarning(), pre + } + + var id [16]byte + copy(id[:], buffer.Bytes()) + + if request.User = validator.Get(id); request.User == nil { + pre.Write(buffer.Bytes()) + return nil, nil, newError("invalid request user id").AtWarning(), pre + } + + requestAddons, err := DecodeHeaderAddons(&buffer, reader) + if err != nil { + return nil, nil, newError("failed to decode request header addons").Base(err).AtWarning(), nil + } + + buffer.Clear() + if _, err := buffer.ReadFullFrom(reader, 1); err != nil { + return nil, nil, newError("failed to read request command").Base(err).AtWarning(), nil + } + + 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").AtWarning(), nil + } + + return request, requestAddons, nil, nil + + default: + + return nil, nil, newError("unexpected request version").AtWarning(), pre + + } + +} + +// EncodeResponseHeader writes encoded response header into the given writer. +func EncodeResponseHeader(writer io.Writer, request *protocol.RequestHeader, responseAddons *Addons) error { + + buffer := buf.StackNew() + defer buffer.Release() + + if err := buffer.WriteByte(request.Version); err != nil { + return newError("failed to write response version").Base(err).AtWarning() + } + + if err := EncodeHeaderAddons(&buffer, responseAddons); err != nil { + return newError("failed to encode response header addons").Base(err).AtWarning() + } + + if _, err := writer.Write(buffer.Bytes()); err != nil { + return newError("failed to write response header").Base(err).AtWarning() + } + + return nil +} + +// DecodeResponseHeader decodes and returns (if successful) a ResponseHeader from an input stream. +func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader, responseAddons *Addons) error { + + buffer := buf.StackNew() + defer buffer.Release() + + if _, err := buffer.ReadFullFrom(reader, 1); err != nil { + return newError("failed to read response version").Base(err).AtWarning() + } + + if buffer.Byte(0) != request.Version { + return newError("unexpected response version. Expecting ", int(request.Version), " but actually ", int(buffer.Byte(0))).AtWarning() + } + + responseAddons, err := DecodeHeaderAddons(&buffer, reader) + if err != nil { + return newError("failed to decode response header addons").Base(err).AtWarning() + } + + return nil +} diff --git a/proxy/vless/encoding/encoding_test.go b/proxy/vless/encoding/encoding_test.go new file mode 100644 index 00000000..1ca2cd08 --- /dev/null +++ b/proxy/vless/encoding/encoding_test.go @@ -0,0 +1,126 @@ +package encoding_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + + "v2ray.com/core/common" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/uuid" + "v2ray.com/core/proxy/vless" + . "v2ray.com/core/proxy/vless/encoding" +) + +func toAccount(a *vless.Account) protocol.Account { + account, err := a.AsAccount() + common.Must(err) + return account +} + +func TestRequestSerialization(t *testing.T) { + user := &protocol.MemoryUser{ + Level: 0, + Email: "test@v2fly.org", + } + id := uuid.New() + account := &vless.Account{ + Id: id.String(), + } + user.Account = toAccount(account) + + expectedRequest := &protocol.RequestHeader{ + Version: Version, + User: user, + Command: protocol.RequestCommandTCP, + Address: net.DomainAddress("www.v2fly.org"), + Port: net.Port(443), + } + expectedAddons := &Addons{} + + buffer := buf.StackNew() + common.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons)) + + Validator := new(vless.Validator) + Validator.Add(user) + + actualRequest, actualAddons, err, _ := DecodeRequestHeader(&buffer, Validator) + common.Must(err) + + if r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != "" { + t.Error(r) + } + if r := cmp.Diff(actualAddons, expectedAddons); r != "" { + t.Error(r) + } +} + +func TestInvalidRequest(t *testing.T) { + user := &protocol.MemoryUser{ + Level: 0, + Email: "test@v2fly.org", + } + id := uuid.New() + account := &vless.Account{ + Id: id.String(), + } + user.Account = toAccount(account) + + expectedRequest := &protocol.RequestHeader{ + Version: Version, + User: user, + Command: protocol.RequestCommand(100), + Address: net.DomainAddress("www.v2fly.org"), + Port: net.Port(443), + } + expectedAddons := &Addons{} + + buffer := buf.StackNew() + common.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons)) + + Validator := new(vless.Validator) + Validator.Add(user) + + _, _, err, _ := DecodeRequestHeader(&buffer, Validator) + if err == nil { + t.Error("nil error") + } +} + +func TestMuxRequest(t *testing.T) { + user := &protocol.MemoryUser{ + Level: 0, + Email: "test@v2fly.org", + } + id := uuid.New() + account := &vless.Account{ + Id: id.String(), + } + user.Account = toAccount(account) + + expectedRequest := &protocol.RequestHeader{ + Version: Version, + User: user, + Command: protocol.RequestCommandMux, + Address: net.DomainAddress("v1.mux.cool"), + } + expectedAddons := &Addons{} + + buffer := buf.StackNew() + common.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons)) + + Validator := new(vless.Validator) + Validator.Add(user) + + actualRequest, actualAddons, err, _ := DecodeRequestHeader(&buffer, Validator) + common.Must(err) + + if r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != "" { + t.Error(r) + } + if r := cmp.Diff(actualAddons, expectedAddons); r != "" { + t.Error(r) + } +} diff --git a/proxy/vless/encoding/errors.generated.go b/proxy/vless/encoding/errors.generated.go new file mode 100644 index 00000000..6fb68210 --- /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 00000000..79734c10 --- /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 00000000..039be433 --- /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 00000000..65777262 --- /dev/null +++ b/proxy/vless/inbound/config.pb.go @@ -0,0 +1,275 @@ +// 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" + net "v2ray.com/core/common/net" + 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 Fallback struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Addr *net.IPOrDomain `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"` + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + Unix string `protobuf:"bytes,3,opt,name=unix,proto3" json:"unix,omitempty"` +} + +func (x *Fallback) Reset() { + *x = Fallback{} + 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 *Fallback) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Fallback) ProtoMessage() {} + +func (x *Fallback) 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 Fallback.ProtoReflect.Descriptor instead. +func (*Fallback) Descriptor() ([]byte, []int) { + return file_v2ray_com_core_proxy_vless_inbound_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Fallback) GetAddr() *net.IPOrDomain { + if x != nil { + return x.Addr + } + return nil +} + +func (x *Fallback) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *Fallback) GetUnix() string { + if x != nil { + return x.Unix + } + return "" +} + +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"` + Fallback *Fallback `protobuf:"bytes,3,opt,name=fallback,proto3" json:"fallback,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_v2ray_com_core_proxy_vless_inbound_config_proto_msgTypes[1] + 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[1] + 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{1} +} + +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 "" +} + +func (x *Config) GetFallback() *Fallback { + if x != nil { + return x.Fallback + } + return nil +} + +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, 0x27, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, + 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 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, 0x69, 0x0a, 0x08, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, + 0x6b, 0x12, 0x35, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x75, 0x6e, 0x69, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x78, + 0x22, 0xa4, 0x01, 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, 0x12, 0x44, 0x0a, 0x08, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x28, 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, 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x08, 0x66, + 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 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, 2) +var file_v2ray_com_core_proxy_vless_inbound_config_proto_goTypes = []interface{}{ + (*Fallback)(nil), // 0: v2ray.core.proxy.vless.inbound.Fallback + (*Config)(nil), // 1: v2ray.core.proxy.vless.inbound.Config + (*net.IPOrDomain)(nil), // 2: v2ray.core.common.net.IPOrDomain + (*protocol.User)(nil), // 3: v2ray.core.common.protocol.User +} +var file_v2ray_com_core_proxy_vless_inbound_config_proto_depIdxs = []int32{ + 2, // 0: v2ray.core.proxy.vless.inbound.Fallback.addr:type_name -> v2ray.core.common.net.IPOrDomain + 3, // 1: v2ray.core.proxy.vless.inbound.Config.user:type_name -> v2ray.core.common.protocol.User + 0, // 2: v2ray.core.proxy.vless.inbound.Config.fallback:type_name -> v2ray.core.proxy.vless.inbound.Fallback + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] 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.(*Fallback); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v2ray_com_core_proxy_vless_inbound_config_proto_msgTypes[1].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: 2, + 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 00000000..f4884504 --- /dev/null +++ b/proxy/vless/inbound/config.proto @@ -0,0 +1,23 @@ +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/net/address.proto"; +import "v2ray.com/core/common/protocol/user.proto"; + +message Fallback { + v2ray.core.common.net.IPOrDomain addr = 1; + uint32 port = 2; + string unix = 3; +} + +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; + Fallback fallback = 3; +} diff --git a/proxy/vless/inbound/errors.generated.go b/proxy/vless/inbound/errors.generated.go new file mode 100644 index 00000000..90d805b1 --- /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 00000000..9a14481f --- /dev/null +++ b/proxy/vless/inbound/inbound.go @@ -0,0 +1,308 @@ +// +build !confonly + +package inbound + +//go:generate errorgen + +import ( + "context" + "io" + "strconv" + "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/retry" + "v2ray.com/core/common/session" + "v2ray.com/core/common/signal" + "v2ray.com/core/common/task" + "v2ray.com/core/features/dns" + 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) { + var dc dns.Client + if err := core.RequireFeatures(ctx, func(d dns.Client) error { + dc = d + return nil + }); err != nil { + return nil, err + } + return New(ctx, config.(*Config), dc) + })) +} + +// 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 + fallback *Fallback // or nil + addrport string +} + +// New creates a new VLess inbound handler. +func New(ctx context.Context, config *Config, dc dns.Client) (*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), + dns: dc, + } + + for _, user := range config.User { + u, err := user.ToMemoryUser() + if err != nil { + return nil, newError("failed to get VLESS user").Base(err).AtError() + } + if err := handler.AddUser(ctx, u); err != nil { + return nil, newError("failed to initiate user").Base(err).AtError() + } + } + + if config.Fallback != nil { + handler.fallback = config.Fallback + handler.addrport = handler.fallback.Addr.AsAddress().String() + ":" + strconv.Itoa(int(handler.fallback.Port)) + } + + 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() + } + + first := buf.New() + first.ReadFrom(connection) + + sid := session.ExportIDToError(ctx) + newError("firstLen = ", first.Len()).AtInfo().WriteToLog(sid) + + reader := &buf.BufferedReader{ + Reader: buf.NewReader(connection), + Buffer: buf.MultiBuffer{first}, + } + + var request *protocol.RequestHeader + var requestAddons *encoding.Addons + var err error + var pre *buf.Buffer + + if h.fallback != nil && first.Len() < 18 { + err = newError("fallback directly") + pre = buf.New() + } else { + request, requestAddons, err, pre = encoding.DecodeRequestHeader(reader, h.validator) + } + + if err != nil { + + if h.fallback != nil && pre != nil { + newError("fallback starts").AtInfo().WriteToLog(sid) + + var conn net.Conn + if err := retry.ExponentialBackoff(5, 100).On(func() error { + var dialer net.Dialer + var err error + if h.fallback.Unix != "" { + conn, err = dialer.DialContext(ctx, "unix", h.fallback.Unix) + } else { + conn, err = dialer.DialContext(ctx, "tcp", h.addrport) + } + if err != nil { + return err + } + return nil + }); err != nil { + return newError("failed to fallback connection").Base(err).AtWarning() + } + defer conn.Close() // nolint: errcheck + + ctx, cancel := context.WithCancel(ctx) + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + + writer := buf.NewWriter(connection) + + serverReader := buf.NewReader(conn) + serverWriter := buf.NewWriter(conn) + + postRequest := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + if pre.Len() > 0 { + if err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pre}); err != nil { + return newError("failed to fallback request pre").Base(err).AtWarning() + } + } + if err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil { + return err // ... + } + return nil + } + + getResponse := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) + if err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil { + return err // ... + } + return nil + } + + if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil { + common.Interrupt(serverReader) + common.Interrupt(serverWriter) + return newError("fallback ends").Base(err).AtInfo() + } + return 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).AtWarning() + } + 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()).AtInfo().WriteToLog(sid) + + if err := connection.SetReadDeadline(time.Time{}); err != nil { + newError("unable to set back read deadline").Base(err).AtWarning().WriteToLog(sid) + } + + 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).AtWarning() + } + + serverReader := link.Reader + serverWriter := link.Writer + + postRequest := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + + // default: clientReader := reader + clientReader := encoding.DecodeBodyAddons(reader, request, requestAddons) + + // from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer + if err := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)); err != nil { + return newError("failed to transfer request payload").Base(err).AtInfo() + } + + return nil + } + + getResponse := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) + + responseAddons := &encoding.Addons{ + Scheduler: requestAddons.Scheduler, + } + + bufferWriter := buf.NewBufferedWriter(buf.NewWriter(connection)) + if err := encoding.EncodeResponseHeader(bufferWriter, request, responseAddons); err != nil { + return newError("failed to encode response header").Base(err).AtWarning() + } + + // default: clientWriter := bufferWriter + clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, responseAddons) + { + multiBuffer, err := serverReader.ReadMultiBuffer() + if err != nil { + return err // ... + } + if err := clientWriter.WriteMultiBuffer(multiBuffer); err != nil { + return err // ... + } + } + + // Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer + if err := bufferWriter.SetBuffered(false); err != nil { + return newError("failed to write A response payload").Base(err).AtWarning() + } + + // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer + if err := buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)); err != nil { + return newError("failed to transfer response payload").Base(err).AtInfo() + } + + // Indicates the end of response payload. + switch responseAddons.Scheduler { + default: + + } + + return nil + } + + if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), getResponse); err != nil { + common.Interrupt(serverReader) + common.Interrupt(serverWriter) + return newError("connection ends").Base(err).AtInfo() + } + + return nil +} diff --git a/proxy/vless/outbound/config.go b/proxy/vless/outbound/config.go new file mode 100644 index 00000000..35bf561b --- /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 00000000..cdcc3f82 --- /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 00000000..732addf0 --- /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 00000000..37e984d8 --- /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 00000000..039e4fc6 --- /dev/null +++ b/proxy/vless/outbound/outbound.go @@ -0,0 +1,177 @@ +// +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).AtError() + } + 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 + + if err := retry.ExponentialBackoff(5, 200).On(func() error { + rec = v.serverPicker.PickServer() + var err error + conn, err = dialer.Dial(ctx, rec.Destination()) + if err != nil { + return err + } + return nil + }); 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()).AtInfo().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, + } + + account := request.User.Account.(*vless.MemoryAccount) + + requestAddons := &encoding.Addons{ + Scheduler: account.Schedulers, + } + + sessionPolicy := v.policyManager.ForLevel(request.User.Level) + + ctx, cancel := context.WithCancel(ctx) + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + + clientReader := link.Reader + clientWriter := link.Writer + + postRequest := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + + bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn)) + if err := encoding.EncodeRequestHeader(bufferWriter, request, requestAddons); err != nil { + return newError("failed to encode request header").Base(err).AtWarning() + } + + // default: serverWriter := bufferWriter + serverWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons) + if err := buf.CopyOnceTimeout(clientReader, serverWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { + return err // ... + } + + // Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer + if err := bufferWriter.SetBuffered(false); err != nil { + return newError("failed to write A request payload").Base(err).AtWarning() + } + + // from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer + if err := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)); err != nil { + return newError("failed to transfer request payload").Base(err).AtInfo() + } + + // Indicates the end of request payload. + switch requestAddons.Scheduler { + default: + + } + + return nil + } + + getResponse := func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) + + responseAddons := new(encoding.Addons) + + if err := encoding.DecodeResponseHeader(conn, request, responseAddons); err != nil { + return newError("failed to decode response header").Base(err).AtWarning() + } + + // default: serverReader := buf.NewReader(conn) + serverReader := encoding.DecodeBodyAddons(conn, request, responseAddons) + + // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer + if err := buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)); err != nil { + return newError("failed to transfer response payload").Base(err).AtInfo() + } + + return nil + } + + if err := task.Run(ctx, postRequest, task.OnSuccess(getResponse, task.Close(clientWriter))); err != nil { + return newError("connection ends").Base(err).AtInfo() + } + + return nil +} diff --git a/proxy/vless/validator.go b/proxy/vless/validator.go new file mode 100644 index 00000000..a19cc632 --- /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 00000000..9e6dc7ab --- /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