Browse Source

VLESS PREVIEW 1.1

RPRX 5 years ago
parent
commit
bad7e2cfd8

+ 3 - 1
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")

+ 140 - 0
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
+}

+ 110 - 0
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",
+				},
+			},
+		},
+	})
+}

+ 2 - 0
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"
 

+ 40 - 0
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)
+}

+ 175 - 0
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
+}

+ 16 - 0
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;
+}

+ 83 - 0
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)
+
+	}
+
+}

+ 386 - 0
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")
+)

+ 12 - 0
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;
+}

+ 173 - 0
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
+}

+ 126 - 0
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)
+	}
+}

+ 9 - 0
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{})
+}

+ 9 - 0
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{})
+}

+ 3 - 0
proxy/vless/inbound/config.go

@@ -0,0 +1,3 @@
+// +build !confonly
+
+package inbound

+ 275 - 0
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
+}

+ 23 - 0
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;
+}

+ 9 - 0
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{})
+}

+ 308 - 0
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
+}

+ 3 - 0
proxy/vless/outbound/config.go

@@ -0,0 +1,3 @@
+// +build !confonly
+
+package outbound

+ 164 - 0
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
+}

+ 13 - 0
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;
+}

+ 9 - 0
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{})
+}

+ 177 - 0
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
+}

+ 50 - 0
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
+}

+ 8 - 0
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