diff --git a/.vscode/settings.json b/.vscode/settings.json index 8d949fe0d..2dc556c5b 100644 --- a/.vscode/settings.json +++ b/.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" + ] + } } \ No newline at end of file diff --git a/common/protocol/account.go b/common/protocol/account.go index f78b0f49f..f251d5b35 100644 --- a/common/protocol/account.go +++ b/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 diff --git a/common/protocol/headers.go b/common/protocol/headers.go index 9fcbec9d5..597f7256d 100644 --- a/common/protocol/headers.go +++ b/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 } diff --git a/common/protocol/server_picker_test.go b/common/protocol/server_picker_test.go index bdb20cfca..8abbadcf4 100644 --- a/common/protocol/server_picker_test.go +++ b/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() diff --git a/common/protocol/server_spec.go b/common/protocol/server_spec.go index 989293998..113c67850 100644 --- a/common/protocol/server_spec.go +++ b/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 } } diff --git a/common/protocol/server_spec.pb.go b/common/protocol/server_spec.pb.go new file mode 100644 index 000000000..9f4a70b1e --- /dev/null +++ b/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, +} diff --git a/common/protocol/server_spec.proto b/common/protocol/server_spec.proto new file mode 100644 index 000000000..0a4f1b47c --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/common/protocol/server_spec_test.go b/common/protocol/server_spec_test.go index 23aa79676..beca89b9c 100644 --- a/common/protocol/server_spec_test.go +++ b/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) diff --git a/common/protocol/user.go b/common/protocol/user.go index d33642659..d4d166456 100644 --- a/common/protocol/user.go +++ b/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 (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() } -func NewUser(level UserLevel, email string) *User { - return &User{ - Level: level, - Email: email, +func (this *User) GetSettings() UserSettings { + settings := UserSettings{ + PayloadReadTimeout: 120, } + if this.Level > 0 { + settings.PayloadReadTimeout = 0 + } + return settings } type UserSettings struct { PayloadReadTimeout uint32 } - -func GetUserSettings(level UserLevel) UserSettings { - settings := UserSettings{ - PayloadReadTimeout: 120, - } - if level > 0 { - settings.PayloadReadTimeout = 0 - } - return settings -} diff --git a/common/protocol/user.pb.go b/common/protocol/user.pb.go new file mode 100644 index 000000000..49421c69a --- /dev/null +++ b/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, +} diff --git a/common/protocol/user.proto b/common/protocol/user.proto new file mode 100644 index 000000000..f7f674cad --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/common/protocol/user_json.go b/common/protocol/user_json.go index cbfc77274..6e51fa403 100644 --- a/common/protocol/user_json.go +++ b/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 } diff --git a/proxy/shadowsocks/config.go b/proxy/shadowsocks/config.go index 2f9f9193f..b6ee555fa 100644 --- a/proxy/shadowsocks/config.go +++ b/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) diff --git a/proxy/shadowsocks/config.pb.go b/proxy/shadowsocks/config.pb.go new file mode 100644 index 000000000..11ffdeef8 --- /dev/null +++ b/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, +} diff --git a/proxy/shadowsocks/config.proto b/proxy/shadowsocks/config.proto new file mode 100644 index 000000000..53c6990fb --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/proxy/shadowsocks/config_json.go b/proxy/shadowsocks/config_json.go index 66afda59a..5294249fa 100644 --- a/proxy/shadowsocks/config_json.go +++ b/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 } diff --git a/proxy/shadowsocks/config_json_test.go b/proxy/shadowsocks/config_json_test.go index 97baab413..e8a8828fa 100644 --- a/proxy/shadowsocks/config_json_test.go +++ b/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}) } diff --git a/proxy/shadowsocks/server.go b/proxy/shadowsocks/server.go index eb8e2e6e9..df4e64814 100644 --- a/proxy/shadowsocks/server.go +++ b/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() { diff --git a/proxy/socks/account.go b/proxy/socks/account.go index 451dd0a30..28e814e08 100644 --- a/proxy/socks/account.go +++ b/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 { + if account, ok := another.(*Account); ok { + return this.Username == account.Username + } + return false } -func (this *Account) Equals(another protocol.Account) bool { - socksAccount, ok := another.(*Account) - if !ok { - return false - } - return this.Username == socksAccount.Username +func (this *Account) AsAccount() (protocol.Account, error) { + return this, nil +} + +func NewAccount() protocol.AsAccount { + return &Account{} } diff --git a/proxy/socks/client_config.go b/proxy/socks/client_config.go index 26d0fbcc4..4afd4ee01 100644 --- a/proxy/socks/client_config.go +++ b/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 } diff --git a/proxy/socks/client_config.pb.go b/proxy/socks/client_config.pb.go new file mode 100644 index 000000000..af5abafeb --- /dev/null +++ b/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, +} diff --git a/proxy/socks/client_config.proto b/proxy/socks/client_config.proto new file mode 100644 index 000000000..09441529e --- /dev/null +++ b/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; +} diff --git a/proxy/socks/client_config_json.go b/proxy/socks/client_config_json.go index f5540ce73..69d538a3e 100644 --- a/proxy/socks/client_config_json.go +++ b/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 diff --git a/proxy/socks/server_config.pb.go b/proxy/socks/server_config.pb.go index 52abfec02..f910f26d8 100644 --- a/proxy/socks/server_config.pb.go +++ b/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, diff --git a/proxy/vmess/account.go b/proxy/vmess/account.go new file mode 100644 index 000000000..ded256364 --- /dev/null +++ b/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 +} diff --git a/proxy/vmess/account.pb.go b/proxy/vmess/account.pb.go new file mode 100644 index 000000000..4de848fff --- /dev/null +++ b/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, +} diff --git a/proxy/vmess/account.proto b/proxy/vmess/account.proto new file mode 100644 index 000000000..8257039b0 --- /dev/null +++ b/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; +} diff --git a/proxy/vmess/account_json.go b/proxy/vmess/account_json.go index 352088cbd..0b01b76c4 100644 --- a/proxy/vmess/account_json.go +++ b/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 } diff --git a/proxy/vmess/encoding/client.go b/proxy/vmess/encoding/client.go index 1bb0be986..3a6cb5674 100644 --- a/proxy/vmess/encoding/client.go +++ b/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) diff --git a/proxy/vmess/encoding/commands.go b/proxy/vmess/encoding/commands.go index 5ac1fbd80..bf72c3e5f 100644 --- a/proxy/vmess/encoding/commands.go +++ b/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 diff --git a/proxy/vmess/encoding/encoding_test.go b/proxy/vmess/encoding/encoding_test.go index 7954c634c..573c124e8 100644 --- a/proxy/vmess/encoding/encoding_test.go +++ b/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, diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index 9b6014e58..452308f3f 100644 --- a/proxy/vmess/encoding/server.go +++ b/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]) diff --git a/proxy/vmess/inbound/command.go b/proxy/vmess/inbound/command.go index cfe976a21..88774cf83 100644 --- a/proxy/vmess/inbound/command.go +++ b/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), } diff --git a/proxy/vmess/inbound/config.go b/proxy/vmess/inbound/config.go index 67f31d6c4..8e60c79a7 100644 --- a/proxy/vmess/inbound/config.go +++ b/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 { diff --git a/proxy/vmess/inbound/config_json.go b/proxy/vmess/inbound/config_json.go index 109202776..767f0e52e 100644 --- a/proxy/vmess/inbound/config_json.go +++ b/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 } diff --git a/proxy/vmess/inbound/inbound.go b/proxy/vmess/inbound/inbound.go index 9e810f168..bc935bbcb 100644 --- a/proxy/vmess/inbound/inbound.go +++ b/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) diff --git a/proxy/vmess/outbound/command.go b/proxy/vmess/outbound/command.go index cdad843af..0deb3b6ef 100644 --- a/proxy/vmess/outbound/command.go +++ b/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) { diff --git a/proxy/vmess/outbound/config_json.go b/proxy/vmess/outbound/config_json.go index 9f65aadef..3b34ab55a 100644 --- a/proxy/vmess/outbound/config_json.go +++ b/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) } diff --git a/proxy/vmess/vmess.go b/proxy/vmess/vmess.go index d52410c4c..61f8b1252 100644 --- a/proxy/vmess/vmess.go +++ b/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() diff --git a/tools/release/proto-gen.sh b/tools/release/proto-gen.sh index 5a6d10291..004a8b6ef 100755 --- a/tools/release/proto-gen.sh +++ b/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 \ No newline at end of file