Browse Source

accounts in protobuf

Darien Raymond 9 năm trước cách đây
mục cha
commit
d08cba000f
40 tập tin đã thay đổi với 787 bổ sung221 xóa
  1. 8 1
      .vscode/settings.json
  2. 6 0
      common/protocol/account.go
  3. 1 1
      common/protocol/headers.go
  4. 5 5
      common/protocol/server_picker_test.go
  5. 20 9
      common/protocol/server_spec.go
  6. 81 0
      common/protocol/server_spec.pb.go
  7. 13 0
      common/protocol/server_spec.proto
  8. 0 31
      common/protocol/server_spec_test.go
  9. 28 19
      common/protocol/user.go
  10. 55 0
      common/protocol/user.pb.go
  11. 12 0
      common/protocol/user.proto
  12. 1 1
      common/protocol/user_json.go
  13. 29 8
      proxy/shadowsocks/config.go
  14. 120 0
      proxy/shadowsocks/config.pb.go
  15. 23 0
      proxy/shadowsocks/config.proto
  16. 19 17
      proxy/shadowsocks/config_json.go
  17. 4 2
      proxy/shadowsocks/config_json_test.go
  18. 38 22
      proxy/shadowsocks/server.go
  19. 11 9
      proxy/socks/account.go
  20. 19 0
      proxy/socks/client_config.go
  21. 61 0
      proxy/socks/client_config.pb.go
  22. 9 0
      proxy/socks/client_config.proto
  23. 20 2
      proxy/socks/client_config_json.go
  24. 4 19
      proxy/socks/server_config.pb.go
  25. 46 0
      proxy/vmess/account.go
  26. 58 0
      proxy/vmess/account.pb.go
  27. 9 0
      proxy/vmess/account.proto
  28. 3 12
      proxy/vmess/account_json.go
  29. 7 3
      proxy/vmess/encoding/client.go
  30. 1 1
      proxy/vmess/encoding/commands.go
  31. 12 6
      proxy/vmess/encoding/encoding_test.go
  32. 6 2
      proxy/vmess/encoding/server.go
  33. 3 2
      proxy/vmess/inbound/command.go
  34. 1 1
      proxy/vmess/inbound/config.go
  35. 13 4
      proxy/vmess/inbound/config_json.go
  36. 13 9
      proxy/vmess/inbound/inbound.go
  37. 12 8
      proxy/vmess/outbound/command.go
  38. 10 3
      proxy/vmess/outbound/config_json.go
  39. 5 23
      proxy/vmess/vmess.go
  40. 1 1
      tools/release/proto-gen.sh

+ 8 - 1
.vscode/settings.json

@@ -2,5 +2,12 @@
 {
   "editor.tabSize": 2,
   
-  "go.buildTags": "json"
+  "go.buildTags": "json",
+
+  "protoc": {
+    "options": [
+      "--proto_path=$GOPATH/src/",
+      "--proto_path=$GOPATH/src/github.com/google/protobuf/src"
+    ]
+  }
 }

+ 6 - 0
common/protocol/account.go

@@ -3,3 +3,9 @@ package protocol
 type Account interface {
 	Equals(Account) bool
 }
+
+type AsAccount interface {
+	AsAccount() (Account, error)
+}
+
+type NewAccountFactory func() AsAccount

+ 1 - 1
common/protocol/headers.go

@@ -77,6 +77,6 @@ type CommandSwitchAccount struct {
 	Port     v2net.Port
 	ID       *uuid.UUID
 	AlterIds uint16
-	Level    UserLevel
+	Level    uint32
 	ValidMin byte
 }

+ 5 - 5
common/protocol/server_picker_test.go

@@ -13,9 +13,9 @@ func TestServerList(t *testing.T) {
 	assert := assert.On(t)
 
 	list := NewServerList()
-	list.AddServer(NewServerSpec(v2net.TCPDestination(v2net.LocalHostIP, v2net.Port(1)), AlwaysValid()))
+	list.AddServer(NewServerSpec(nil, v2net.TCPDestination(v2net.LocalHostIP, v2net.Port(1)), AlwaysValid()))
 	assert.Uint32(list.Size()).Equals(1)
-	list.AddServer(NewServerSpec(v2net.TCPDestination(v2net.LocalHostIP, v2net.Port(2)), BeforeTime(time.Now().Add(time.Second))))
+	list.AddServer(NewServerSpec(nil, v2net.TCPDestination(v2net.LocalHostIP, v2net.Port(2)), BeforeTime(time.Now().Add(time.Second))))
 	assert.Uint32(list.Size()).Equals(2)
 
 	server := list.GetServer(1)
@@ -32,9 +32,9 @@ func TestServerPicker(t *testing.T) {
 	assert := assert.On(t)
 
 	list := NewServerList()
-	list.AddServer(NewServerSpec(v2net.TCPDestination(v2net.LocalHostIP, v2net.Port(1)), AlwaysValid()))
-	list.AddServer(NewServerSpec(v2net.TCPDestination(v2net.LocalHostIP, v2net.Port(2)), BeforeTime(time.Now().Add(time.Second))))
-	list.AddServer(NewServerSpec(v2net.TCPDestination(v2net.LocalHostIP, v2net.Port(3)), BeforeTime(time.Now().Add(time.Second))))
+	list.AddServer(NewServerSpec(nil, v2net.TCPDestination(v2net.LocalHostIP, v2net.Port(1)), AlwaysValid()))
+	list.AddServer(NewServerSpec(nil, v2net.TCPDestination(v2net.LocalHostIP, v2net.Port(2)), BeforeTime(time.Now().Add(time.Second))))
+	list.AddServer(NewServerSpec(nil, v2net.TCPDestination(v2net.LocalHostIP, v2net.Port(3)), BeforeTime(time.Now().Add(time.Second))))
 
 	picker := NewRoundRobinServerPicker(list)
 	server := picker.PickServer()

+ 20 - 9
common/protocol/server_spec.go

@@ -45,19 +45,26 @@ func (this *TimeoutValidStrategy) Invalidate() {
 
 type ServerSpec struct {
 	sync.RWMutex
-	dest  v2net.Destination
-	users []*User
-	valid ValidationStrategy
+	dest       v2net.Destination
+	users      []*User
+	valid      ValidationStrategy
+	newAccount NewAccountFactory
 }
 
-func NewServerSpec(dest v2net.Destination, valid ValidationStrategy, users ...*User) *ServerSpec {
+func NewServerSpec(newAccount NewAccountFactory, dest v2net.Destination, valid ValidationStrategy, users ...*User) *ServerSpec {
 	return &ServerSpec{
-		dest:  dest,
-		users: users,
-		valid: valid,
+		dest:       dest,
+		users:      users,
+		valid:      valid,
+		newAccount: newAccount,
 	}
 }
 
+func NewServerSpecFromPB(newAccount NewAccountFactory, spec ServerSpecPB) *ServerSpec {
+	dest := v2net.TCPDestination(spec.Address.AsAddress(), v2net.Port(spec.Port))
+	return NewServerSpec(newAccount, dest, AlwaysValid(), spec.Users...)
+}
+
 func (this *ServerSpec) Destination() v2net.Destination {
 	return this.dest
 }
@@ -66,9 +73,13 @@ func (this *ServerSpec) HasUser(user *User) bool {
 	this.RLock()
 	defer this.RUnlock()
 
-	account := user.Account
+	accountA, err := user.GetTypedAccount(this.newAccount())
+	if err != nil {
+		return false
+	}
 	for _, u := range this.users {
-		if u.Account.Equals(account) {
+		accountB, err := u.GetTypedAccount(this.newAccount())
+		if err == nil && accountA.Equals(accountB) {
 			return true
 		}
 	}

+ 81 - 0
common/protocol/server_spec.pb.go

@@ -0,0 +1,81 @@
+// Code generated by protoc-gen-go.
+// source: v2ray.com/core/common/protocol/server_spec.proto
+// DO NOT EDIT!
+
+/*
+Package protocol is a generated protocol buffer package.
+
+It is generated from these files:
+	v2ray.com/core/common/protocol/server_spec.proto
+	v2ray.com/core/common/protocol/user.proto
+
+It has these top-level messages:
+	ServerSpecPB
+	User
+*/
+package protocol
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import com_v2ray_core_common_net "v2ray.com/core/common/net"
+
+// 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.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type ServerSpecPB struct {
+	Address *com_v2ray_core_common_net.AddressPB `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"`
+	Port    uint32                               `protobuf:"varint,2,opt,name=port" json:"port,omitempty"`
+	Users   []*User                              `protobuf:"bytes,3,rep,name=users" json:"users,omitempty"`
+}
+
+func (m *ServerSpecPB) Reset()                    { *m = ServerSpecPB{} }
+func (m *ServerSpecPB) String() string            { return proto.CompactTextString(m) }
+func (*ServerSpecPB) ProtoMessage()               {}
+func (*ServerSpecPB) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (m *ServerSpecPB) GetAddress() *com_v2ray_core_common_net.AddressPB {
+	if m != nil {
+		return m.Address
+	}
+	return nil
+}
+
+func (m *ServerSpecPB) GetUsers() []*User {
+	if m != nil {
+		return m.Users
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*ServerSpecPB)(nil), "com.v2ray.core.common.protocol.ServerSpecPB")
+}
+
+func init() { proto.RegisterFile("v2ray.com/core/common/protocol/server_spec.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 209 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x8e, 0x3f, 0x4b, 0xc5, 0x30,
+	0x14, 0xc5, 0x89, 0xf5, 0x1f, 0x51, 0x97, 0x4c, 0xa5, 0x83, 0x14, 0x11, 0xac, 0xcb, 0x8d, 0xd4,
+	0xcd, 0x41, 0xb0, 0x9f, 0xa0, 0xb4, 0xb8, 0xb8, 0x48, 0x4d, 0xef, 0x66, 0x7a, 0xc3, 0x4d, 0x2c,
+	0xf8, 0x65, 0xfc, 0xac, 0xf2, 0x92, 0x97, 0xad, 0xbc, 0xb7, 0x1d, 0x0e, 0xe7, 0x77, 0xce, 0x91,
+	0x4f, 0x6b, 0xcb, 0xd3, 0x2f, 0x18, 0xb2, 0xda, 0x10, 0xa3, 0x36, 0x64, 0x2d, 0x2d, 0xda, 0x31,
+	0x05, 0x32, 0xf4, 0xad, 0x3d, 0xf2, 0x8a, 0xfc, 0xe9, 0x1d, 0x1a, 0x88, 0xa6, 0xba, 0x35, 0x64,
+	0x21, 0x53, 0x8c, 0x90, 0x08, 0xc8, 0x44, 0xf5, 0xb0, 0xdd, 0xb8, 0x60, 0xd0, 0xd3, 0x3c, 0x33,
+	0x7a, 0x9f, 0xb2, 0xd5, 0xe3, 0x91, 0xe9, 0x1f, 0x8f, 0x9c, 0xa2, 0x77, 0x7f, 0x42, 0x5e, 0x8f,
+	0xf1, 0xc9, 0xe8, 0xd0, 0xf4, 0x9d, 0x7a, 0x95, 0x17, 0xfb, 0xb2, 0x52, 0xd4, 0xa2, 0xb9, 0x6a,
+	0xef, 0x61, 0xfb, 0xd6, 0x82, 0x01, 0xde, 0x52, 0xb2, 0xef, 0x86, 0x0c, 0x29, 0x25, 0x4f, 0x1d,
+	0x71, 0x28, 0x4f, 0x6a, 0xd1, 0xdc, 0x0c, 0x51, 0xab, 0x17, 0x79, 0xb6, 0x9b, 0xf4, 0x65, 0x51,
+	0x17, 0x07, 0x1a, 0xf3, 0x3f, 0x78, 0xf7, 0xc8, 0x43, 0x42, 0x3a, 0xf9, 0x71, 0x99, 0xfd, 0xaf,
+	0xf3, 0xa8, 0x9e, 0xff, 0x03, 0x00, 0x00, 0xff, 0xff, 0xde, 0x92, 0x28, 0x40, 0x5b, 0x01, 0x00,
+	0x00,
+}

+ 13 - 0
common/protocol/server_spec.proto

@@ -0,0 +1,13 @@
+syntax = "proto3";
+
+import "v2ray.com/core/common/net/address.proto";
+import "v2ray.com/core/common/protocol/user.proto";
+
+package com.v2ray.core.common.protocol;
+option go_package = "protocol";
+
+message ServerSpecPB {
+  com.v2ray.core.common.net.AddressPB address = 1;
+  uint32 port = 2;
+  repeated com.v2ray.core.common.protocol.User users = 3;
+}

+ 0 - 31
common/protocol/server_spec_test.go

@@ -4,41 +4,10 @@ import (
 	"testing"
 	"time"
 
-	v2net "v2ray.com/core/common/net"
 	. "v2ray.com/core/common/protocol"
 	"v2ray.com/core/testing/assert"
 )
 
-type TestAccount struct {
-	id int
-}
-
-func (this *TestAccount) Equals(account Account) bool {
-	return account.(*TestAccount).id == this.id
-}
-
-func TestServerSpecUser(t *testing.T) {
-	assert := assert.On(t)
-
-	account := &TestAccount{
-		id: 0,
-	}
-	user := NewUser(UserLevel(0), "")
-	user.Account = account
-	rec := NewServerSpec(v2net.TCPDestination(v2net.DomainAddress("v2ray.com"), 80), AlwaysValid(), user)
-	assert.Bool(rec.HasUser(user)).IsTrue()
-
-	account2 := &TestAccount{
-		id: 1,
-	}
-	user2 := NewUser(UserLevel(0), "")
-	user2.Account = account2
-	assert.Bool(rec.HasUser(user2)).IsFalse()
-
-	rec.AddUser(user2)
-	assert.Bool(rec.HasUser(user2)).IsTrue()
-}
-
 func TestAlwaysValidStrategy(t *testing.T) {
 	assert := assert.On(t)
 

+ 28 - 19
common/protocol/user.go

@@ -1,35 +1,44 @@
 package protocol
 
-type UserLevel byte
+import (
+	"errors"
 
-const (
-	UserLevelAdmin     = UserLevel(255)
-	UserLevelUntrusted = UserLevel(0)
+	"github.com/golang/protobuf/proto"
+	"github.com/golang/protobuf/ptypes"
 )
 
-type User struct {
-	Account Account
-	Level   UserLevel
-	Email   string
-}
+var (
+	ErrUserMissing    = errors.New("User is not specified.")
+	ErrAccountMissing = errors.New("Account is not specified.")
+	ErrNonMessageType = errors.New("Not a protobuf message.")
+)
 
-func NewUser(level UserLevel, email string) *User {
-	return &User{
-		Level: level,
-		Email: email,
+func (this *User) GetTypedAccount(account AsAccount) (Account, error) {
+	anyAccount := this.GetAccount()
+	if anyAccount == nil {
+		return nil, ErrAccountMissing
 	}
+	protoAccount, ok := account.(proto.Message)
+	if !ok {
+		return nil, ErrNonMessageType
+	}
+	err := ptypes.UnmarshalAny(anyAccount, protoAccount)
+	if err != nil {
+		return nil, err
+	}
+	return account.AsAccount()
 }
 
-type UserSettings struct {
-	PayloadReadTimeout uint32
-}
-
-func GetUserSettings(level UserLevel) UserSettings {
+func (this *User) GetSettings() UserSettings {
 	settings := UserSettings{
 		PayloadReadTimeout: 120,
 	}
-	if level > 0 {
+	if this.Level > 0 {
 		settings.PayloadReadTimeout = 0
 	}
 	return settings
 }
+
+type UserSettings struct {
+	PayloadReadTimeout uint32
+}

+ 55 - 0
common/protocol/user.pb.go

@@ -0,0 +1,55 @@
+// Code generated by protoc-gen-go.
+// source: v2ray.com/core/common/protocol/user.proto
+// DO NOT EDIT!
+
+package protocol
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import google_protobuf "github.com/golang/protobuf/ptypes/any"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+type User struct {
+	Level   uint32               `protobuf:"varint,1,opt,name=level" json:"level,omitempty"`
+	Email   string               `protobuf:"bytes,2,opt,name=email" json:"email,omitempty"`
+	Account *google_protobuf.Any `protobuf:"bytes,3,opt,name=account" json:"account,omitempty"`
+}
+
+func (m *User) Reset()                    { *m = User{} }
+func (m *User) String() string            { return proto.CompactTextString(m) }
+func (*User) ProtoMessage()               {}
+func (*User) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} }
+
+func (m *User) GetAccount() *google_protobuf.Any {
+	if m != nil {
+		return m.Account
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*User)(nil), "com.v2ray.core.common.protocol.User")
+}
+
+func init() { proto.RegisterFile("v2ray.com/core/common/protocol/user.proto", fileDescriptor1) }
+
+var fileDescriptor1 = []byte{
+	// 179 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x34, 0x8e, 0xcb, 0xaa, 0xc2, 0x30,
+	0x10, 0x86, 0xc9, 0x39, 0x5e, 0x23, 0x6e, 0x4a, 0x17, 0xd5, 0x85, 0x14, 0x57, 0x75, 0x33, 0x81,
+	0xfa, 0x04, 0xfa, 0x08, 0x05, 0x37, 0xee, 0xd2, 0x61, 0x2c, 0x42, 0x92, 0x91, 0xf4, 0x02, 0x79,
+	0x7b, 0xb1, 0x21, 0xbb, 0xf9, 0x66, 0xbe, 0xe1, 0xff, 0xe5, 0x65, 0xaa, 0xbd, 0x0e, 0x80, 0x6c,
+	0x15, 0xb2, 0x27, 0x85, 0x6c, 0x2d, 0x3b, 0xf5, 0xf1, 0x3c, 0x30, 0xb2, 0x51, 0x63, 0x4f, 0x1e,
+	0x66, 0xca, 0x4e, 0xc8, 0x16, 0x92, 0xee, 0x09, 0xa2, 0x0a, 0x49, 0x3d, 0x1e, 0x3a, 0xe6, 0xce,
+	0x50, 0xfc, 0x6d, 0xc7, 0x97, 0xd2, 0x2e, 0xc4, 0xeb, 0xb9, 0x95, 0x8b, 0x47, 0x4f, 0x3e, 0xcb,
+	0xe5, 0xd2, 0xd0, 0x44, 0xa6, 0x10, 0xa5, 0xa8, 0xf6, 0x4d, 0x84, 0xdf, 0x96, 0xac, 0x7e, 0x9b,
+	0xe2, 0xaf, 0x14, 0xd5, 0xb6, 0x89, 0x90, 0x81, 0x5c, 0x6b, 0x44, 0x1e, 0xdd, 0x50, 0xfc, 0x97,
+	0xa2, 0xda, 0xd5, 0x39, 0xc4, 0x00, 0x48, 0x01, 0x70, 0x73, 0xa1, 0x49, 0xd2, 0x5d, 0x3e, 0x37,
+	0xa9, 0x4a, 0xbb, 0x9a, 0xa7, 0xeb, 0x37, 0x00, 0x00, 0xff, 0xff, 0xfa, 0x7e, 0xbf, 0xfa, 0xde,
+	0x00, 0x00, 0x00,
+}

+ 12 - 0
common/protocol/user.proto

@@ -0,0 +1,12 @@
+syntax = "proto3";
+
+import "google/protobuf/any.proto";
+
+package com.v2ray.core.common.protocol;
+option go_package = "protocol";
+
+message User {
+  uint32 level = 1;
+  string email = 2;
+  google.protobuf.Any account = 3;
+}

+ 1 - 1
common/protocol/user_json.go

@@ -15,7 +15,7 @@ func (u *User) UnmarshalJSON(data []byte) error {
 	}
 
 	u.Email = rawUserValue.EmailString
-	u.Level = UserLevel(rawUserValue.LevelByte)
+	u.Level = uint32(rawUserValue.LevelByte)
 
 	return nil
 }

+ 29 - 8
proxy/shadowsocks/config.go

@@ -8,6 +8,35 @@ import (
 	"v2ray.com/core/common/protocol"
 )
 
+func (this *Config) GetCipher() Cipher {
+	switch this.Cipher {
+	case Config_AES_128_CFB:
+		return &AesCfb{KeyBytes: 16}
+	case Config_AES_256_CFB:
+		return &AesCfb{KeyBytes: 32}
+	case Config_CHACHA20:
+		return &ChaCha20{IVBytes: 8}
+	case Config_CHACHA20_IEFT:
+		return &ChaCha20{IVBytes: 12}
+	}
+	panic("Failed to create Cipher. Should not happen.")
+}
+
+func (this *Account) Equals(another protocol.Account) bool {
+	if account, ok := another.(*Account); ok {
+		return account.Password == this.Password
+	}
+	return false
+}
+
+func (this *Account) AsAccount() (protocol.Account, error) {
+	return this, nil
+}
+
+func (this *Account) GetCipherKey(size int) []byte {
+	return PasswordToCipherKey(this.Password, size)
+}
+
 type Cipher interface {
 	KeySize() int
 	IVSize() int
@@ -57,14 +86,6 @@ func (this *ChaCha20) NewDecodingStream(key []byte, iv []byte) (cipher.Stream, e
 	return crypto.NewChaCha20Stream(key, iv), nil
 }
 
-type Config struct {
-	Cipher Cipher
-	Key    []byte
-	UDP    bool
-	Level  protocol.UserLevel
-	Email  string
-}
-
 func PasswordToCipherKey(password string, keySize int) []byte {
 	pwdBytes := []byte(password)
 	key := make([]byte, 0, keySize)

+ 120 - 0
proxy/shadowsocks/config.pb.go

@@ -0,0 +1,120 @@
+// Code generated by protoc-gen-go.
+// source: v2ray.com/core/proxy/shadowsocks/config.proto
+// DO NOT EDIT!
+
+/*
+Package shadowsocks is a generated protocol buffer package.
+
+It is generated from these files:
+	v2ray.com/core/proxy/shadowsocks/config.proto
+
+It has these top-level messages:
+	Account
+	Config
+*/
+package shadowsocks
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import com_v2ray_core_common_protocol "v2ray.com/core/common/protocol"
+
+// 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.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type Config_Cipher int32
+
+const (
+	Config_UNKNOWN       Config_Cipher = 0
+	Config_AES_128_CFB   Config_Cipher = 1
+	Config_AES_256_CFB   Config_Cipher = 2
+	Config_CHACHA20      Config_Cipher = 3
+	Config_CHACHA20_IEFT Config_Cipher = 4
+)
+
+var Config_Cipher_name = map[int32]string{
+	0: "UNKNOWN",
+	1: "AES_128_CFB",
+	2: "AES_256_CFB",
+	3: "CHACHA20",
+	4: "CHACHA20_IEFT",
+}
+var Config_Cipher_value = map[string]int32{
+	"UNKNOWN":       0,
+	"AES_128_CFB":   1,
+	"AES_256_CFB":   2,
+	"CHACHA20":      3,
+	"CHACHA20_IEFT": 4,
+}
+
+func (x Config_Cipher) String() string {
+	return proto.EnumName(Config_Cipher_name, int32(x))
+}
+func (Config_Cipher) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} }
+
+type Account struct {
+	Password string `protobuf:"bytes,1,opt,name=password" json:"password,omitempty"`
+}
+
+func (m *Account) Reset()                    { *m = Account{} }
+func (m *Account) String() string            { return proto.CompactTextString(m) }
+func (*Account) ProtoMessage()               {}
+func (*Account) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+type Config struct {
+	Cipher     Config_Cipher                        `protobuf:"varint,1,opt,name=cipher,enum=com.v2ray.core.proxy.shadowsocks.Config_Cipher" json:"cipher,omitempty"`
+	UdpEnabled bool                                 `protobuf:"varint,2,opt,name=udp_enabled,json=udpEnabled" json:"udp_enabled,omitempty"`
+	User       *com_v2ray_core_common_protocol.User `protobuf:"bytes,3,opt,name=user" json:"user,omitempty"`
+}
+
+func (m *Config) Reset()                    { *m = Config{} }
+func (m *Config) String() string            { return proto.CompactTextString(m) }
+func (*Config) ProtoMessage()               {}
+func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+func (m *Config) GetUser() *com_v2ray_core_common_protocol.User {
+	if m != nil {
+		return m.User
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*Account)(nil), "com.v2ray.core.proxy.shadowsocks.Account")
+	proto.RegisterType((*Config)(nil), "com.v2ray.core.proxy.shadowsocks.Config")
+	proto.RegisterEnum("com.v2ray.core.proxy.shadowsocks.Config_Cipher", Config_Cipher_name, Config_Cipher_value)
+}
+
+func init() { proto.RegisterFile("v2ray.com/core/proxy/shadowsocks/config.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 308 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x8f, 0x41, 0x4f, 0xc2, 0x40,
+	0x14, 0x84, 0x2d, 0x90, 0x82, 0xaf, 0xa2, 0x75, 0x4f, 0x84, 0x8b, 0x0d, 0xd1, 0x04, 0x0f, 0xee,
+	0x6a, 0x8d, 0x86, 0x6b, 0x69, 0x40, 0x8c, 0x09, 0x26, 0x55, 0xa2, 0xf1, 0xd2, 0x94, 0xed, 0x2a,
+	0x44, 0xda, 0xd7, 0xec, 0x52, 0x91, 0x3f, 0xe0, 0xef, 0x36, 0x6e, 0xad, 0x21, 0x5c, 0x3c, 0xbe,
+	0xc9, 0x7c, 0x6f, 0x66, 0xe0, 0xec, 0xc3, 0x95, 0xd1, 0x9a, 0x72, 0x4c, 0x18, 0x47, 0x29, 0x58,
+	0x26, 0xf1, 0x73, 0xcd, 0xd4, 0x2c, 0x8a, 0x71, 0xa5, 0x90, 0xbf, 0x2b, 0xc6, 0x31, 0x7d, 0x9d,
+	0xbf, 0xd1, 0x4c, 0xe2, 0x12, 0x89, 0xc3, 0x31, 0xa1, 0x25, 0x22, 0x05, 0xd5, 0x76, 0xba, 0x61,
+	0x6f, 0x9f, 0x6e, 0x3d, 0xe4, 0x98, 0x24, 0x98, 0x32, 0x8d, 0x73, 0x5c, 0xb0, 0x5c, 0x09, 0x59,
+	0x3c, 0xeb, 0x9c, 0x40, 0xdd, 0xe3, 0x1c, 0xf3, 0x74, 0x49, 0xda, 0xd0, 0xc8, 0x22, 0xa5, 0x56,
+	0x28, 0xe3, 0x96, 0xe1, 0x18, 0xdd, 0xdd, 0xe0, 0xef, 0xee, 0x7c, 0x55, 0xc0, 0xf4, 0x75, 0x09,
+	0x72, 0x03, 0x26, 0x9f, 0x67, 0x33, 0x21, 0xb5, 0x69, 0xdf, 0x65, 0xf4, 0xbf, 0x3e, 0xb4, 0x20,
+	0xa9, 0xaf, 0xb1, 0xe0, 0x17, 0x27, 0x47, 0x60, 0xe5, 0x71, 0x16, 0x8a, 0x34, 0x9a, 0x2e, 0x44,
+	0xdc, 0xaa, 0x38, 0x46, 0xb7, 0x11, 0x40, 0x1e, 0x67, 0x83, 0x42, 0x21, 0x3d, 0xa8, 0xfd, 0x34,
+	0x6d, 0x55, 0x1d, 0xa3, 0x6b, 0xb9, 0xc7, 0xdb, 0x39, 0xc5, 0x2a, 0x5a, 0xae, 0xa2, 0x13, 0x25,
+	0x64, 0xa0, 0x89, 0xce, 0x33, 0x98, 0x45, 0x18, 0xb1, 0xa0, 0x3e, 0x19, 0xdf, 0x8d, 0xef, 0x9f,
+	0xc6, 0xf6, 0x0e, 0x39, 0x00, 0xcb, 0x1b, 0x3c, 0x84, 0x17, 0x6e, 0x2f, 0xf4, 0x87, 0x7d, 0xdb,
+	0x28, 0x05, 0xf7, 0xea, 0x5a, 0x0b, 0x15, 0xb2, 0x07, 0x0d, 0x7f, 0xe4, 0xf9, 0x23, 0xcf, 0x3d,
+	0xb7, 0xab, 0xe4, 0x10, 0x9a, 0xe5, 0x15, 0xde, 0x0e, 0x86, 0x8f, 0x76, 0xad, 0xdf, 0x7c, 0xb1,
+	0x36, 0x96, 0x4d, 0x4d, 0x9d, 0x7e, 0xf9, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x1d, 0xee, 0x41, 0x94,
+	0xc3, 0x01, 0x00, 0x00,
+}

+ 23 - 0
proxy/shadowsocks/config.proto

@@ -0,0 +1,23 @@
+syntax = "proto3";
+
+import "v2ray.com/core/common/protocol/user.proto";
+
+package com.v2ray.core.proxy.shadowsocks;
+option go_package = "shadowsocks";
+
+message Account {
+  string password = 1;
+}
+
+message Config {
+  enum Cipher {
+    UNKNOWN = 0;
+    AES_128_CFB = 1;
+    AES_256_CFB = 2;
+    CHACHA20 = 3;
+    CHACHA20_IEFT = 4;
+  }
+  Cipher cipher = 1;
+  bool udp_enabled = 2;
+  com.v2ray.core.common.protocol.User user = 3;
+}

+ 19 - 17
proxy/shadowsocks/config_json.go

@@ -11,6 +11,8 @@ import (
 	"v2ray.com/core/common/log"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/proxy/registry"
+
+	"github.com/golang/protobuf/ptypes"
 )
 
 func (this *Config) UnmarshalJSON(data []byte) error {
@@ -26,25 +28,17 @@ func (this *Config) UnmarshalJSON(data []byte) error {
 		return errors.New("Shadowsocks: Failed to parse config: " + err.Error())
 	}
 
-	this.UDP = jsonConfig.UDP
+	this.UdpEnabled = jsonConfig.UDP
 	jsonConfig.Cipher = strings.ToLower(jsonConfig.Cipher)
 	switch jsonConfig.Cipher {
 	case "aes-256-cfb":
-		this.Cipher = &AesCfb{
-			KeyBytes: 32,
-		}
+		this.Cipher = Config_AES_256_CFB
 	case "aes-128-cfb":
-		this.Cipher = &AesCfb{
-			KeyBytes: 16,
-		}
+		this.Cipher = Config_AES_128_CFB
 	case "chacha20":
-		this.Cipher = &ChaCha20{
-			IVBytes: 8,
-		}
+		this.Cipher = Config_CHACHA20
 	case "chacha20-ietf":
-		this.Cipher = &ChaCha20{
-			IVBytes: 12,
-		}
+		this.Cipher = Config_CHACHA20_IEFT
 	default:
 		log.Error("Shadowsocks: Unknown cipher method: ", jsonConfig.Cipher)
 		return common.ErrBadConfiguration
@@ -54,10 +48,18 @@ func (this *Config) UnmarshalJSON(data []byte) error {
 		log.Error("Shadowsocks: Password is not specified.")
 		return common.ErrBadConfiguration
 	}
-	this.Key = PasswordToCipherKey(jsonConfig.Password, this.Cipher.KeySize())
-
-	this.Level = protocol.UserLevel(jsonConfig.Level)
-	this.Email = jsonConfig.Email
+	account, err := ptypes.MarshalAny(&Account{
+		Password: jsonConfig.Password,
+	})
+	if err != nil {
+		log.Error("Shadowsocks: Failed to create account: ", err)
+		return common.ErrBadConfiguration
+	}
+	this.User = &protocol.User{
+		Email:   jsonConfig.Email,
+		Level:   uint32(jsonConfig.Level),
+		Account: account,
+	}
 
 	return nil
 }

+ 4 - 2
proxy/shadowsocks/config_json_test.go

@@ -21,6 +21,8 @@ func TestConfigParsing(t *testing.T) {
 	err := json.Unmarshal([]byte(rawJson), config)
 	assert.Error(err).IsNil()
 
-	assert.Int(config.Cipher.KeySize()).Equals(16)
-	assert.Bytes(config.Key).Equals([]byte{160, 224, 26, 2, 22, 110, 9, 80, 65, 52, 80, 20, 38, 243, 224, 241})
+	assert.Int(config.GetCipher().KeySize()).Equals(16)
+	account, err := config.User.GetTypedAccount(&Account{})
+	assert.Error(err).IsNil()
+	assert.Bytes(account.(*Account).GetCipherKey(config.GetCipher().KeySize())).Equals([]byte{160, 224, 26, 2, 22, 110, 9, 80, 65, 52, 80, 20, 38, 243, 224, 241})
 }

+ 38 - 22
proxy/shadowsocks/server.go

@@ -24,6 +24,8 @@ import (
 type Server struct {
 	packetDispatcher dispatcher.PacketDispatcher
 	config           *Config
+	cipher           Cipher
+	cipherKey        []byte
 	meta             *proxy.InboundHandlerMeta
 	accepting        bool
 	tcpHub           *internet.TCPHub
@@ -31,12 +33,31 @@ type Server struct {
 	udpServer        *udp.UDPServer
 }
 
-func NewServer(config *Config, packetDispatcher dispatcher.PacketDispatcher, meta *proxy.InboundHandlerMeta) *Server {
-	return &Server{
-		config:           config,
-		packetDispatcher: packetDispatcher,
-		meta:             meta,
+func NewServer(config *Config, space app.Space, meta *proxy.InboundHandlerMeta) (*Server, error) {
+	if config.GetUser() == nil {
+		return nil, protocol.ErrUserMissing
 	}
+	account := new(Account)
+	if _, err := config.GetUser().GetTypedAccount(account); err != nil {
+		return nil, err
+	}
+	cipher := config.GetCipher()
+	s := &Server{
+		config:    config,
+		meta:      meta,
+		cipher:    cipher,
+		cipherKey: account.GetCipherKey(cipher.KeySize()),
+	}
+
+	space.InitializeApplication(func() error {
+		if !space.HasApp(dispatcher.APP_ID) {
+			return app.ErrMissingApplication
+		}
+		s.packetDispatcher = space.GetApp(dispatcher.APP_ID).(dispatcher.PacketDispatcher)
+		return nil
+	})
+
+	return s, nil
 }
 
 func (this *Server) Port() v2net.Port {
@@ -70,7 +91,7 @@ func (this *Server) Start() error {
 	}
 	this.tcpHub = tcpHub
 
-	if this.config.UDP {
+	if this.config.UdpEnabled {
 		this.udpServer = udp.NewUDPServer(this.meta, this.packetDispatcher)
 		udpHub, err := udp.ListenUDP(this.meta.Address, this.meta.Port, udp.ListenOption{Callback: this.handlerUDPPayload})
 		if err != nil {
@@ -89,12 +110,11 @@ func (this *Server) handlerUDPPayload(payload *alloc.Buffer, session *proxy.Sess
 	defer payload.Release()
 
 	source := session.Source
-	ivLen := this.config.Cipher.IVSize()
+	ivLen := this.cipher.IVSize()
 	iv := payload.Value[:ivLen]
-	key := this.config.Key
 	payload.SliceFrom(ivLen)
 
-	stream, err := this.config.Cipher.NewDecodingStream(key, iv)
+	stream, err := this.cipher.NewDecodingStream(this.cipherKey, iv)
 	if err != nil {
 		log.Error("Shadowsocks: Failed to create decoding stream: ", err)
 		return
@@ -102,7 +122,7 @@ func (this *Server) handlerUDPPayload(payload *alloc.Buffer, session *proxy.Sess
 
 	reader := crypto.NewCryptionReader(stream, payload)
 
-	request, err := ReadRequest(reader, NewAuthenticator(HeaderKeyGenerator(key, iv)), true)
+	request, err := ReadRequest(reader, NewAuthenticator(HeaderKeyGenerator(this.cipherKey, iv)), true)
 	if err != nil {
 		if err != io.EOF {
 			log.Access(source, "", log.AccessRejected, err)
@@ -125,7 +145,7 @@ func (this *Server) handlerUDPPayload(payload *alloc.Buffer, session *proxy.Sess
 		rand.Read(response.Value)
 		respIv := response.Value
 
-		stream, err := this.config.Cipher.NewEncodingStream(key, respIv)
+		stream, err := this.cipher.NewEncodingStream(this.cipherKey, respIv)
 		if err != nil {
 			log.Error("Shadowsocks: Failed to create encoding stream: ", err)
 			return
@@ -149,7 +169,7 @@ func (this *Server) handlerUDPPayload(payload *alloc.Buffer, session *proxy.Sess
 		writer.Write(payload.Value)
 
 		if request.OTA {
-			respAuth := NewAuthenticator(HeaderKeyGenerator(key, respIv))
+			respAuth := NewAuthenticator(HeaderKeyGenerator(this.cipherKey, respIv))
 			respAuth.Authenticate(response.Value, response.Value[ivLen:])
 		}
 
@@ -169,7 +189,7 @@ func (this *Server) handleConnection(conn internet.Connection) {
 	bufferedReader := v2io.NewBufferedReader(timedReader)
 	defer bufferedReader.Release()
 
-	ivLen := this.config.Cipher.IVSize()
+	ivLen := this.cipher.IVSize()
 	_, err := io.ReadFull(bufferedReader, buffer.Value[:ivLen])
 	if err != nil {
 		if err != io.EOF {
@@ -180,9 +200,8 @@ func (this *Server) handleConnection(conn internet.Connection) {
 	}
 
 	iv := buffer.Value[:ivLen]
-	key := this.config.Key
 
-	stream, err := this.config.Cipher.NewDecodingStream(key, iv)
+	stream, err := this.cipher.NewDecodingStream(this.cipherKey, iv)
 	if err != nil {
 		log.Error("Shadowsocks: Failed to create decoding stream: ", err)
 		return
@@ -190,7 +209,7 @@ func (this *Server) handleConnection(conn internet.Connection) {
 
 	reader := crypto.NewCryptionReader(stream, bufferedReader)
 
-	request, err := ReadRequest(reader, NewAuthenticator(HeaderKeyGenerator(key, iv)), false)
+	request, err := ReadRequest(reader, NewAuthenticator(HeaderKeyGenerator(this.cipherKey, iv)), false)
 	if err != nil {
 		log.Access(conn.RemoteAddr(), "", log.AccessRejected, err)
 		log.Warning("Shadowsocks: Invalid request from ", conn.RemoteAddr(), ": ", err)
@@ -199,7 +218,7 @@ func (this *Server) handleConnection(conn internet.Connection) {
 	defer request.Release()
 	bufferedReader.SetCached(false)
 
-	userSettings := protocol.GetUserSettings(this.config.Level)
+	userSettings := this.config.GetUser().GetSettings()
 	timedReader.SetTimeOut(userSettings.PayloadReadTimeout)
 
 	dest := v2net.TCPDestination(request.Address, request.Port)
@@ -219,7 +238,7 @@ func (this *Server) handleConnection(conn internet.Connection) {
 			payload.SliceBack(ivLen)
 			rand.Read(payload.Value[:ivLen])
 
-			stream, err := this.config.Cipher.NewEncodingStream(key, payload.Value[:ivLen])
+			stream, err := this.cipher.NewEncodingStream(this.cipherKey, payload.Value[:ivLen])
 			if err != nil {
 				log.Error("Shadowsocks: Failed to create encoding stream: ", err)
 				return
@@ -264,10 +283,7 @@ func (this *ServerFactory) Create(space app.Space, rawConfig interface{}, meta *
 	if !space.HasApp(dispatcher.APP_ID) {
 		return nil, common.ErrBadConfiguration
 	}
-	return NewServer(
-		rawConfig.(*Config),
-		space.GetApp(dispatcher.APP_ID).(dispatcher.PacketDispatcher),
-		meta), nil
+	return NewServer(rawConfig.(*Config), space, meta)
 }
 
 func init() {

+ 11 - 9
proxy/socks/account.go

@@ -4,15 +4,17 @@ import (
 	"v2ray.com/core/common/protocol"
 )
 
-type Account struct {
-	Username string `json:"user"`
-	Password string `json:"pass"`
-}
-
 func (this *Account) Equals(another protocol.Account) bool {
-	socksAccount, ok := another.(*Account)
-	if !ok {
-		return false
+	if account, ok := another.(*Account); ok {
+		return this.Username == account.Username
 	}
-	return this.Username == socksAccount.Username
+	return false
+}
+
+func (this *Account) AsAccount() (protocol.Account, error) {
+	return this, nil
+}
+
+func NewAccount() protocol.AsAccount {
+	return &Account{}
 }

+ 19 - 0
proxy/socks/client_config.go

@@ -2,8 +2,27 @@ package socks
 
 import (
 	"v2ray.com/core/common/protocol"
+
+	"github.com/golang/protobuf/ptypes"
+	google_protobuf "github.com/golang/protobuf/ptypes/any"
 )
 
+func AccountEquals(a, b *google_protobuf.Any) bool {
+	accountA := new(Account)
+	if err := ptypes.UnmarshalAny(a, accountA); err != nil {
+		return false
+	}
+	accountB := new(Account)
+	if err := ptypes.UnmarshalAny(b, accountB); err != nil {
+		return false
+	}
+	return accountA.Equals(accountB)
+}
+
+func (this *Account) AsAny() (*google_protobuf.Any, error) {
+	return ptypes.MarshalAny(this)
+}
+
 type ClientConfig struct {
 	Servers []*protocol.ServerSpec
 }

+ 61 - 0
proxy/socks/client_config.pb.go

@@ -0,0 +1,61 @@
+// Code generated by protoc-gen-go.
+// source: v2ray.com/core/proxy/socks/client_config.proto
+// DO NOT EDIT!
+
+/*
+Package socks is a generated protocol buffer package.
+
+It is generated from these files:
+	v2ray.com/core/proxy/socks/client_config.proto
+	v2ray.com/core/proxy/socks/server_config.proto
+
+It has these top-level messages:
+	Account
+	ServerConfig
+*/
+package socks
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// 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.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type Account struct {
+	Username string `protobuf:"bytes,1,opt,name=username" json:"username,omitempty"`
+	Password string `protobuf:"bytes,2,opt,name=password" json:"password,omitempty"`
+}
+
+func (m *Account) Reset()                    { *m = Account{} }
+func (m *Account) String() string            { return proto.CompactTextString(m) }
+func (*Account) ProtoMessage()               {}
+func (*Account) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func init() {
+	proto.RegisterType((*Account)(nil), "com.v2ray.core.proxy.socks.Account")
+}
+
+func init() { proto.RegisterFile("v2ray.com/core/proxy/socks/client_config.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 146 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x2b, 0x33, 0x2a, 0x4a,
+	0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x2f, 0x28, 0xca, 0xaf, 0xa8,
+	0xd4, 0x2f, 0xce, 0x4f, 0xce, 0x2e, 0xd6, 0x4f, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0x89, 0x4f, 0xce,
+	0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x4a, 0xce, 0xcf, 0x85,
+	0xeb, 0x29, 0x4a, 0xd5, 0x03, 0xab, 0xd7, 0x03, 0xab, 0x57, 0x72, 0xe4, 0x62, 0x77, 0x4c, 0x4e,
+	0xce, 0x2f, 0xcd, 0x2b, 0x11, 0x92, 0xe2, 0xe2, 0x28, 0x2d, 0x4e, 0x2d, 0xca, 0x4b, 0xcc, 0x4d,
+	0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0xf3, 0x41, 0x72, 0x05, 0x89, 0xc5, 0xc5, 0xe5,
+	0xf9, 0x45, 0x29, 0x12, 0x4c, 0x10, 0x39, 0x18, 0xdf, 0x89, 0x3d, 0x8a, 0x15, 0x6c, 0x56, 0x12,
+	0x1b, 0xd8, 0x3a, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe9, 0xf0, 0x36, 0x26, 0xa0, 0x00,
+	0x00, 0x00,
+}

+ 9 - 0
proxy/socks/client_config.proto

@@ -0,0 +1,9 @@
+syntax = "proto3";
+
+package com.v2ray.core.proxy.socks;
+option go_package = "socks";
+
+message Account {
+  string username = 1;
+  string password = 2;
+}

+ 20 - 2
proxy/socks/client_config_json.go

@@ -11,6 +11,20 @@ import (
 	"v2ray.com/core/proxy/registry"
 )
 
+func (this *Account) UnmarshalJSON(data []byte) error {
+	type JsonConfig struct {
+		Username string `json:"user"`
+		Password string `json:"pass"`
+	}
+	jsonConfig := new(JsonConfig)
+	if err := json.Unmarshal(data, jsonConfig); err != nil {
+		return errors.New("Socks: Failed to parse account: " + err.Error())
+	}
+	this.Username = jsonConfig.Username
+	this.Password = jsonConfig.Password
+	return nil
+}
+
 func (this *ClientConfig) UnmarshalJSON(data []byte) error {
 	type ServerConfig struct {
 		Address *v2net.AddressPB  `json:"address"`
@@ -26,7 +40,7 @@ func (this *ClientConfig) UnmarshalJSON(data []byte) error {
 	}
 	this.Servers = make([]*protocol.ServerSpec, len(jsonConfig.Servers))
 	for idx, serverConfig := range jsonConfig.Servers {
-		server := protocol.NewServerSpec(v2net.TCPDestination(serverConfig.Address.AsAddress(), serverConfig.Port), protocol.AlwaysValid())
+		server := protocol.NewServerSpec(NewAccount, v2net.TCPDestination(serverConfig.Address.AsAddress(), serverConfig.Port), protocol.AlwaysValid())
 		for _, rawUser := range serverConfig.Users {
 			user := new(protocol.User)
 			if err := json.Unmarshal(rawUser, user); err != nil {
@@ -36,7 +50,11 @@ func (this *ClientConfig) UnmarshalJSON(data []byte) error {
 			if err := json.Unmarshal(rawUser, account); err != nil {
 				return errors.New("Socks|Client: Failed to parse socks account: " + err.Error())
 			}
-			user.Account = account
+			anyAccount, err := account.AsAny()
+			if err != nil {
+				return err
+			}
+			user.Account = anyAccount
 			server.AddUser(user)
 		}
 		this.Servers[idx] = server

+ 4 - 19
proxy/socks/server_config.pb.go

@@ -2,15 +2,6 @@
 // source: v2ray.com/core/proxy/socks/server_config.proto
 // DO NOT EDIT!
 
-/*
-Package socks is a generated protocol buffer package.
-
-It is generated from these files:
-	v2ray.com/core/proxy/socks/server_config.proto
-
-It has these top-level messages:
-	ServerConfig
-*/
 package socks
 
 import proto "github.com/golang/protobuf/proto"
@@ -23,12 +14,6 @@ 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.ProtoPackageIsVersion2 // please upgrade the proto package
-
 type ServerConfig_AuthType int32
 
 const (
@@ -48,7 +33,7 @@ var ServerConfig_AuthType_value = map[string]int32{
 func (x ServerConfig_AuthType) String() string {
 	return proto.EnumName(ServerConfig_AuthType_name, int32(x))
 }
-func (ServerConfig_AuthType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} }
+func (ServerConfig_AuthType) EnumDescriptor() ([]byte, []int) { return fileDescriptor1, []int{0, 0} }
 
 type ServerConfig struct {
 	AuthType   ServerConfig_AuthType                `protobuf:"varint,1,opt,name=auth_type,json=authType,enum=com.v2ray.core.proxy.socks.ServerConfig_AuthType" json:"auth_type,omitempty"`
@@ -61,7 +46,7 @@ type ServerConfig struct {
 func (m *ServerConfig) Reset()                    { *m = ServerConfig{} }
 func (m *ServerConfig) String() string            { return proto.CompactTextString(m) }
 func (*ServerConfig) ProtoMessage()               {}
-func (*ServerConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+func (*ServerConfig) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} }
 
 func (m *ServerConfig) GetAccounts() map[string]string {
 	if m != nil {
@@ -82,9 +67,9 @@ func init() {
 	proto.RegisterEnum("com.v2ray.core.proxy.socks.ServerConfig_AuthType", ServerConfig_AuthType_name, ServerConfig_AuthType_value)
 }
 
-func init() { proto.RegisterFile("v2ray.com/core/proxy/socks/server_config.proto", fileDescriptor0) }
+func init() { proto.RegisterFile("v2ray.com/core/proxy/socks/server_config.proto", fileDescriptor1) }
 
-var fileDescriptor0 = []byte{
+var fileDescriptor1 = []byte{
 	// 345 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x91, 0xd1, 0x4b, 0xeb, 0x30,
 	0x14, 0xc6, 0x6f, 0xd7, 0xbb, 0xdb, 0x2e, 0xdd, 0x2e, 0x23, 0xf8, 0x50, 0xf6, 0x62, 0x19, 0x8a,

+ 46 - 0
proxy/vmess/account.go

@@ -0,0 +1,46 @@
+package vmess
+
+import (
+	"v2ray.com/core/common/dice"
+	"v2ray.com/core/common/log"
+	"v2ray.com/core/common/protocol"
+	"v2ray.com/core/common/uuid"
+)
+
+type Account struct {
+	ID       *protocol.ID
+	AlterIDs []*protocol.ID
+}
+
+func NewAccount() protocol.AsAccount {
+	return &AccountPB{}
+}
+
+func (this *Account) AnyValidID() *protocol.ID {
+	if len(this.AlterIDs) == 0 {
+		return this.ID
+	}
+	return this.AlterIDs[dice.Roll(len(this.AlterIDs))]
+}
+
+func (this *Account) Equals(account protocol.Account) bool {
+	vmessAccount, ok := account.(*Account)
+	if !ok {
+		return false
+	}
+	// TODO: handle AlterIds difference
+	return this.ID.Equals(vmessAccount.ID)
+}
+
+func (this *AccountPB) AsAccount() (protocol.Account, error) {
+	id, err := uuid.ParseString(this.Id)
+	if err != nil {
+		log.Error("VMess: Failed to parse ID: ", err)
+		return nil, err
+	}
+	protoId := protocol.NewID(id)
+	return &Account{
+		ID:       protoId,
+		AlterIDs: protocol.NewAlterIDs(protoId, uint16(this.AlterId)),
+	}, nil
+}

+ 58 - 0
proxy/vmess/account.pb.go

@@ -0,0 +1,58 @@
+// Code generated by protoc-gen-go.
+// source: v2ray.com/core/proxy/vmess/account.proto
+// DO NOT EDIT!
+
+/*
+Package vmess is a generated protocol buffer package.
+
+It is generated from these files:
+	v2ray.com/core/proxy/vmess/account.proto
+
+It has these top-level messages:
+	AccountPB
+*/
+package vmess
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// 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.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type AccountPB struct {
+	Id      string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
+	AlterId uint32 `protobuf:"varint,2,opt,name=alterId" json:"alterId,omitempty"`
+}
+
+func (m *AccountPB) Reset()                    { *m = AccountPB{} }
+func (m *AccountPB) String() string            { return proto.CompactTextString(m) }
+func (*AccountPB) ProtoMessage()               {}
+func (*AccountPB) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func init() {
+	proto.RegisterType((*AccountPB)(nil), "com.v2ray.core.proxy.vmess.AccountPB")
+}
+
+func init() { proto.RegisterFile("v2ray.com/core/proxy/vmess/account.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 138 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x28, 0x33, 0x2a, 0x4a,
+	0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x2f, 0x28, 0xca, 0xaf, 0xa8,
+	0xd4, 0x2f, 0xcb, 0x4d, 0x2d, 0x2e, 0xd6, 0x4f, 0x4c, 0x4e, 0xce, 0x2f, 0xcd, 0x2b, 0xd1, 0x2b,
+	0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x4a, 0xce, 0xcf, 0xd5, 0x83, 0xa9, 0x2e, 0x4a, 0xd5, 0x03,
+	0xab, 0xd4, 0x03, 0xab, 0x54, 0x32, 0xe5, 0xe2, 0x74, 0x84, 0x28, 0x0e, 0x70, 0x12, 0xe2, 0xe3,
+	0x62, 0xca, 0x4c, 0x91, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x62, 0xca, 0x4c, 0x11, 0x92, 0xe0,
+	0x62, 0x4f, 0xcc, 0x29, 0x49, 0x2d, 0xf2, 0x4c, 0x91, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x0d, 0x82,
+	0x71, 0x9d, 0xd8, 0xa3, 0x58, 0xc1, 0xfa, 0x93, 0xd8, 0xc0, 0x56, 0x18, 0x03, 0x02, 0x00, 0x00,
+	0xff, 0xff, 0xe4, 0xea, 0x97, 0xa3, 0x8e, 0x00, 0x00, 0x00,
+}

+ 9 - 0
proxy/vmess/account.proto

@@ -0,0 +1,9 @@
+syntax = "proto3";
+
+package com.v2ray.core.proxy.vmess;
+option go_package = "vmess";
+
+message AccountPB {
+  string id = 1;
+  uint32 alterId = 2;
+}

+ 3 - 12
proxy/vmess/account_json.go

@@ -4,13 +4,9 @@ package vmess
 
 import (
 	"encoding/json"
-
-	"v2ray.com/core/common/log"
-	"v2ray.com/core/common/protocol"
-	"v2ray.com/core/common/uuid"
 )
 
-func (u *Account) UnmarshalJSON(data []byte) error {
+func (u *AccountPB) UnmarshalJSON(data []byte) error {
 	type JsonConfig struct {
 		ID       string `json:"id"`
 		AlterIds uint16 `json:"alterId"`
@@ -19,13 +15,8 @@ func (u *Account) UnmarshalJSON(data []byte) error {
 	if err := json.Unmarshal(data, &rawConfig); err != nil {
 		return err
 	}
-	id, err := uuid.ParseString(rawConfig.ID)
-	if err != nil {
-		log.Error("VMess: Failed to parse ID: ", err)
-		return err
-	}
-	u.ID = protocol.NewID(id)
-	u.AlterIDs = protocol.NewAlterIDs(u.ID, rawConfig.AlterIds)
+	u.Id = rawConfig.ID
+	u.AlterId = uint32(rawConfig.AlterIds)
 
 	return nil
 }

+ 7 - 3
proxy/vmess/encoding/client.go

@@ -52,7 +52,12 @@ func NewClientSession(idHash protocol.IDHash) *ClientSession {
 
 func (this *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writer io.Writer) {
 	timestamp := protocol.NewTimestampGenerator(protocol.NowTime(), 30)()
-	idHash := this.idHash(header.User.Account.(*vmess.Account).AnyValidID().Bytes())
+	account, err := header.User.GetTypedAccount(&vmess.AccountPB{})
+	if err != nil {
+		log.Error("VMess: Failed to get user account: ", err)
+		return
+	}
+	idHash := this.idHash(account.(*vmess.Account).AnyValidID().Bytes())
 	idHash.Write(timestamp.Bytes(nil))
 	writer.Write(idHash.Sum(nil))
 
@@ -83,8 +88,7 @@ func (this *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, w
 	timestampHash := md5.New()
 	timestampHash.Write(hashTimestamp(timestamp))
 	iv := timestampHash.Sum(nil)
-	account := header.User.Account.(*vmess.Account)
-	aesStream := crypto.NewAesEncryptionStream(account.ID.CmdKey(), iv)
+	aesStream := crypto.NewAesEncryptionStream(account.(*vmess.Account).ID.CmdKey(), iv)
 	aesStream.XORKeyStream(buffer, buffer)
 	writer.Write(buffer)
 

+ 1 - 1
proxy/vmess/encoding/commands.go

@@ -139,7 +139,7 @@ func (this *CommandSwitchAccountFactory) Unmarshal(data []byte) (interface{}, er
 	if len(data) < levelStart+1 {
 		return nil, transport.ErrCorruptedPacket
 	}
-	cmd.Level = protocol.UserLevel(data[levelStart])
+	cmd.Level = uint32(data[levelStart])
 	timeStart := levelStart + 1
 	if len(data) < timeStart {
 		return nil, transport.ErrCorruptedPacket

+ 12 - 6
proxy/vmess/encoding/encoding_test.go

@@ -10,18 +10,24 @@ import (
 	"v2ray.com/core/proxy/vmess"
 	. "v2ray.com/core/proxy/vmess/encoding"
 	"v2ray.com/core/testing/assert"
+
+	"github.com/golang/protobuf/ptypes"
 )
 
 func TestRequestSerialization(t *testing.T) {
 	assert := assert.On(t)
 
-	user := protocol.NewUser(
-		protocol.UserLevelUntrusted,
-		"test@v2ray.com")
-	user.Account = &vmess.Account{
-		ID:       protocol.NewID(uuid.New()),
-		AlterIDs: nil,
+	user := &protocol.User{
+		Level: 0,
+		Email: "test@v2ray.com",
+	}
+	account := &vmess.AccountPB{
+		Id:      uuid.New().String(),
+		AlterId: 0,
 	}
+	anyAccount, err := ptypes.MarshalAny(account)
+	assert.Error(err).IsNil()
+	user.Account = anyAccount
 
 	expectedRequest := &protocol.RequestHeader{
 		Version: 1,

+ 6 - 2
proxy/vmess/encoding/server.go

@@ -59,8 +59,12 @@ func (this *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Requ
 	timestampHash := md5.New()
 	timestampHash.Write(hashTimestamp(timestamp))
 	iv := timestampHash.Sum(nil)
-	account := user.Account.(*vmess.Account)
-	aesStream := crypto.NewAesDecryptionStream(account.ID.CmdKey(), iv)
+	account, err := user.GetTypedAccount(&vmess.AccountPB{})
+	if err != nil {
+		log.Error("Vmess: Failed to get user account: ", err)
+		return nil, err
+	}
+	aesStream := crypto.NewAesDecryptionStream(account.(*vmess.Account).ID.CmdKey(), iv)
 	decryptor := crypto.NewCryptionReader(aesStream, reader)
 
 	nBytes, err := io.ReadFull(decryptor, buffer[:41])

+ 3 - 2
proxy/vmess/inbound/command.go

@@ -22,10 +22,11 @@ func (this *VMessInboundHandler) generateCommand(request *protocol.RequestHeader
 				if user == nil {
 					return nil
 				}
+				account, _ := user.GetTypedAccount(&vmess.AccountPB{})
 				return &protocol.CommandSwitchAccount{
 					Port:     inboundHandler.Port(),
-					ID:       user.Account.(*vmess.Account).ID.UUID(),
-					AlterIds: uint16(len(user.Account.(*vmess.Account).AlterIDs)),
+					ID:       account.(*vmess.Account).ID.UUID(),
+					AlterIds: uint16(len(account.(*vmess.Account).AlterIDs)),
 					Level:    user.Level,
 					ValidMin: byte(availableMin),
 				}

+ 1 - 1
proxy/vmess/inbound/config.go

@@ -14,7 +14,7 @@ type FeaturesConfig struct {
 
 type DefaultConfig struct {
 	AlterIDs uint16
-	Level    protocol.UserLevel
+	Level    uint32
 }
 
 type Config struct {

+ 13 - 4
proxy/vmess/inbound/config_json.go

@@ -6,9 +6,13 @@ import (
 	"encoding/json"
 	"errors"
 
+	"v2ray.com/core/common"
+	"v2ray.com/core/common/log"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/proxy/registry"
 	"v2ray.com/core/proxy/vmess"
+
+	"github.com/golang/protobuf/ptypes"
 )
 
 func (this *DetourConfig) UnmarshalJSON(data []byte) error {
@@ -48,7 +52,7 @@ func (this *DefaultConfig) UnmarshalJSON(data []byte) error {
 	if this.AlterIDs == 0 {
 		this.AlterIDs = 32
 	}
-	this.Level = protocol.UserLevel(jsonConfig.Level)
+	this.Level = uint32(jsonConfig.Level)
 	return nil
 }
 
@@ -67,7 +71,7 @@ func (this *Config) UnmarshalJSON(data []byte) error {
 	this.Defaults = jsonConfig.Defaults
 	if this.Defaults == nil {
 		this.Defaults = &DefaultConfig{
-			Level:    protocol.UserLevel(0),
+			Level:    0,
 			AlterIDs: 32,
 		}
 	}
@@ -82,11 +86,16 @@ func (this *Config) UnmarshalJSON(data []byte) error {
 		if err := json.Unmarshal(rawData, user); err != nil {
 			return errors.New("VMess|Inbound: Invalid user: " + err.Error())
 		}
-		account := new(vmess.Account)
+		account := new(vmess.AccountPB)
 		if err := json.Unmarshal(rawData, account); err != nil {
 			return errors.New("VMess|Inbound: Invalid user: " + err.Error())
 		}
-		user.Account = account
+		anyAccount, err := ptypes.MarshalAny(account)
+		if err != nil {
+			log.Error("VMess|Inbound: Failed to create account: ", err)
+			return common.ErrBadConfiguration
+		}
+		user.Account = anyAccount
 		this.AllowedUsers[idx] = user
 	}
 

+ 13 - 9
proxy/vmess/inbound/inbound.go

@@ -20,12 +20,14 @@ import (
 	"v2ray.com/core/proxy/vmess/encoding"
 	vmessio "v2ray.com/core/proxy/vmess/io"
 	"v2ray.com/core/transport/internet"
+
+	"github.com/golang/protobuf/ptypes"
 )
 
 type userByEmail struct {
 	sync.RWMutex
 	cache           map[string]*protocol.User
-	defaultLevel    protocol.UserLevel
+	defaultLevel    uint32
 	defaultAlterIDs uint16
 }
 
@@ -51,14 +53,16 @@ func (this *userByEmail) Get(email string) (*protocol.User, bool) {
 		this.Lock()
 		user, found = this.cache[email]
 		if !found {
-			id := protocol.NewID(uuid.New())
-			alterIDs := protocol.NewAlterIDs(id, this.defaultAlterIDs)
-			account := &vmess.Account{
-				ID:       id,
-				AlterIDs: alterIDs,
+			account := &vmess.AccountPB{
+				Id:      uuid.New().String(),
+				AlterId: uint32(this.defaultAlterIDs),
+			}
+			anyAccount, _ := ptypes.MarshalAny(account)
+			user = &protocol.User{
+				Level:   this.defaultLevel,
+				Email:   email,
+				Account: anyAccount,
 			}
-			user = protocol.NewUser(this.defaultLevel, email)
-			user.Account = account
 			this.cache[email] = user
 		}
 		this.Unlock()
@@ -176,7 +180,7 @@ func (this *VMessInboundHandler) HandleConnection(connection internet.Connection
 	var readFinish sync.Mutex
 	readFinish.Lock()
 
-	userSettings := protocol.GetUserSettings(request.User.Level)
+	userSettings := request.User.GetSettings()
 	connReader.SetTimeOut(userSettings.PayloadReadTimeout)
 	reader.SetCached(false)
 

+ 12 - 8
proxy/vmess/outbound/command.go

@@ -6,20 +6,24 @@ import (
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/proxy/vmess"
+
+	"github.com/golang/protobuf/ptypes"
 )
 
 func (this *VMessOutboundHandler) handleSwitchAccount(cmd *protocol.CommandSwitchAccount) {
-	primary := protocol.NewID(cmd.ID)
-	alters := protocol.NewAlterIDs(primary, cmd.AlterIds)
-	account := &vmess.Account{
-		ID:       primary,
-		AlterIDs: alters,
+	account := &vmess.AccountPB{
+		Id:      cmd.ID.String(),
+		AlterId: uint32(cmd.AlterIds),
+	}
+	anyAccount, _ := ptypes.MarshalAny(account)
+	user := &protocol.User{
+		Email:   "",
+		Level:   cmd.Level,
+		Account: anyAccount,
 	}
-	user := protocol.NewUser(cmd.Level, "")
-	user.Account = account
 	dest := v2net.TCPDestination(cmd.Host, cmd.Port)
 	until := time.Now().Add(time.Duration(cmd.ValidMin) * time.Minute)
-	this.serverList.AddServer(protocol.NewServerSpec(dest, protocol.BeforeTime(until), user))
+	this.serverList.AddServer(protocol.NewServerSpec(vmess.NewAccount, dest, protocol.BeforeTime(until), user))
 }
 
 func (this *VMessOutboundHandler) handleCommand(dest v2net.Destination, cmd protocol.ResponseCommand) {

+ 10 - 3
proxy/vmess/outbound/config_json.go

@@ -13,6 +13,8 @@ import (
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/proxy/registry"
 	"v2ray.com/core/proxy/vmess"
+
+	"github.com/golang/protobuf/ptypes"
 )
 
 func (this *Config) UnmarshalJSON(data []byte) error {
@@ -48,19 +50,24 @@ func (this *Config) UnmarshalJSON(data []byte) error {
 				Ip: serial.Uint32ToBytes(757086633, nil),
 			}
 		}
-		spec := protocol.NewServerSpec(v2net.TCPDestination(rec.Address.AsAddress(), rec.Port), protocol.AlwaysValid())
+		spec := protocol.NewServerSpec(vmess.NewAccount, v2net.TCPDestination(rec.Address.AsAddress(), rec.Port), protocol.AlwaysValid())
 		for _, rawUser := range rec.Users {
 			user := new(protocol.User)
 			if err := json.Unmarshal(rawUser, user); err != nil {
 				log.Error("VMess|Outbound: Invalid user: ", err)
 				return err
 			}
-			account := new(vmess.Account)
+			account := new(vmess.AccountPB)
 			if err := json.Unmarshal(rawUser, account); err != nil {
 				log.Error("VMess|Outbound: Invalid user: ", err)
 				return err
 			}
-			user.Account = account
+			anyAccount, err := ptypes.MarshalAny(account)
+			if err != nil {
+				log.Error("VMess|Outbound: Failed to create account: ", err)
+				return common.ErrBadConfiguration
+			}
+			user.Account = anyAccount
 
 			spec.AddUser(user)
 		}

+ 5 - 23
proxy/vmess/vmess.go

@@ -9,32 +9,10 @@ import (
 	"sync"
 	"time"
 
-	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/signal"
 )
 
-type Account struct {
-	ID       *protocol.ID
-	AlterIDs []*protocol.ID
-}
-
-func (this *Account) AnyValidID() *protocol.ID {
-	if len(this.AlterIDs) == 0 {
-		return this.ID
-	}
-	return this.AlterIDs[dice.Roll(len(this.AlterIDs))]
-}
-
-func (this *Account) Equals(account protocol.Account) bool {
-	vmessAccount, ok := account.(*Account)
-	if !ok {
-		return false
-	}
-	// TODO: handle AlterIds difference
-	return this.ID.Equals(vmessAccount.ID)
-}
-
 const (
 	updateIntervalSec = 10
 	cacheDurationSec  = 120
@@ -140,7 +118,11 @@ L:
 func (this *TimedUserValidator) Add(user *protocol.User) error {
 	idx := len(this.validUsers)
 	this.validUsers = append(this.validUsers, user)
-	account := user.Account.(*Account)
+	rawAccount, err := user.GetTypedAccount(&AccountPB{})
+	if err != nil {
+		return err
+	}
+	account := rawAccount.(*Account)
 
 	nowSec := time.Now().Unix()
 

+ 1 - 1
tools/release/proto-gen.sh

@@ -5,7 +5,7 @@ for DIR in $(find ./v2ray.com/core -type d -not -path "*.git*"); do
   TEST_FILES=($DIR/*.proto)
   #echo ${TEST_FILES}
   if [ -f ${TEST_FILES[0]} ]; then
-    protoc --proto_path=. --go_out=. $DIR/*.proto
+    protoc --proto_path=. --proto_path=./github.com/google/protobuf/src --go_out=. $DIR/*.proto
   fi
 done
 popd