diff --git a/go.mod b/go.mod index 5529e2f7..7aecdb6b 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( golang.org/x/time v0.2.0 // indirect golang.org/x/tools v0.3.0 // indirect google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.1.7 // indirect diff --git a/go.sum b/go.sum index 95b85802..599a465d 100644 --- a/go.sum +++ b/go.sum @@ -330,7 +330,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/proxy/shadowsocks_2022/config.go b/proxy/shadowsocks_2022/config.go new file mode 100644 index 00000000..8a66406c --- /dev/null +++ b/proxy/shadowsocks_2022/config.go @@ -0,0 +1,29 @@ +package shadowsocks_2022 + +import ( + "github.com/xtls/xray-core/common/protocol" +) + +// MemoryAccount is an account type converted from Account. +type MemoryAccount struct { + Key string + Email string + Level int32 +} + +// AsAccount implements protocol.AsAccount. +func (u *User) AsAccount() (protocol.Account, error) { + return &MemoryAccount{ + Key: u.GetKey(), + Email: u.GetEmail(), + Level: u.GetLevel(), + }, nil +} + +// Equals implements protocol.Account.Equals(). +func (a *MemoryAccount) Equals(another protocol.Account) bool { + if account, ok := another.(*MemoryAccount); ok { + return a.Key == account.Key + } + return false +} diff --git a/proxy/shadowsocks_2022/inbound.go b/proxy/shadowsocks_2022/inbound.go index 55bdda9f..52b0a798 100644 --- a/proxy/shadowsocks_2022/inbound.go +++ b/proxy/shadowsocks_2022/inbound.go @@ -11,6 +11,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/log" diff --git a/proxy/shadowsocks_2022/inbound_multi.go b/proxy/shadowsocks_2022/inbound_multi.go index 8b130e60..b9a258f6 100644 --- a/proxy/shadowsocks_2022/inbound_multi.go +++ b/proxy/shadowsocks_2022/inbound_multi.go @@ -4,6 +4,8 @@ import ( "context" "encoding/base64" "strconv" + "strings" + "sync" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" C "github.com/sagernet/sing/common" @@ -13,6 +15,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/log" @@ -31,6 +34,7 @@ func init() { } type MultiUserInbound struct { + sync.Mutex networks []net.Network users []*User service *shadowaead_2022.MultiService[int] @@ -78,6 +82,73 @@ func NewMultiServer(ctx context.Context, config *MultiUserServerConfig) (*MultiU return inbound, nil } +// AddUser implements proxy.UserManager.AddUser(). +func (i *MultiUserInbound) AddUser(ctx context.Context, u *protocol.MemoryUser) error { + i.Lock() + defer i.Unlock() + + account := u.Account.(*MemoryAccount) + if account.Email != "" { + for idx := range i.users { + if i.users[idx].Email == account.Email { + return newError("User ", account.Email, " already exists.") + } + } + } + i.users = append(i.users, &User{ + Key: account.Key, + Email: strings.ToLower(account.Email), + Level: account.Level, + }) + + // sync to multi service + // Considering implements shadowsocks2022 in xray-core may have better performance. + i.service.UpdateUsersWithPasswords( + C.MapIndexed(i.users, func(index int, it *User) int { return index }), + C.Map(i.users, func(it *User) string { return it.Key }), + ) + + return nil +} + +// RemoveUser implements proxy.UserManager.RemoveUser(). +func (i *MultiUserInbound) RemoveUser(ctx context.Context, email string) error { + if email == "" { + return newError("Email must not be empty.") + } + + i.Lock() + defer i.Unlock() + + email = strings.ToLower(email) + idx := -1 + for ii, u := range i.users { + if strings.EqualFold(u.Email, email) { + idx = ii + break + } + } + + if idx == -1 { + return newError("User ", email, " not found.") + } + + ulen := len(i.users) + + i.users[idx] = i.users[ulen-1] + i.users[ulen-1] = nil + i.users = i.users[:ulen-1] + + // sync to multi service + // Considering implements shadowsocks2022 in xray-core may have better performance. + i.service.UpdateUsersWithPasswords( + C.MapIndexed(i.users, func(index int, it *User) int { return index }), + C.Map(i.users, func(it *User) string { return it.Key }), + ) + + return nil +} + func (i *MultiUserInbound) Network() []net.Network { return i.networks }