mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-12-12 04:34:41 +04:00
Outbound: One endpoint and at most one user only (#5144)
https://github.com/XTLS/Xray-core/pull/5124#issuecomment-3281091009 Fixes https://github.com/XTLS/Xray-core/pull/5124#pullrequestreview-3218097421
This commit is contained in:
committed by
GitHub
parent
83c5370eec
commit
fe57507fd9
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/bitmask"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
@@ -71,14 +70,6 @@ type ResponseHeader struct {
|
||||
Command ResponseCommand
|
||||
}
|
||||
|
||||
type CommandSwitchAccount struct {
|
||||
Host net.Address
|
||||
Port net.Port
|
||||
ID uuid.UUID
|
||||
Level uint32
|
||||
ValidMin byte
|
||||
}
|
||||
|
||||
var (
|
||||
// Keep in sync with crypto/tls/cipher_suites.go.
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ServerList struct {
|
||||
sync.RWMutex
|
||||
servers []*ServerSpec
|
||||
}
|
||||
|
||||
func NewServerList() *ServerList {
|
||||
return &ServerList{}
|
||||
}
|
||||
|
||||
func (sl *ServerList) AddServer(server *ServerSpec) {
|
||||
sl.Lock()
|
||||
defer sl.Unlock()
|
||||
|
||||
sl.servers = append(sl.servers, server)
|
||||
}
|
||||
|
||||
func (sl *ServerList) Size() uint32 {
|
||||
sl.RLock()
|
||||
defer sl.RUnlock()
|
||||
|
||||
return uint32(len(sl.servers))
|
||||
}
|
||||
|
||||
func (sl *ServerList) GetServer(idx uint32) *ServerSpec {
|
||||
sl.Lock()
|
||||
defer sl.Unlock()
|
||||
|
||||
for {
|
||||
if idx >= uint32(len(sl.servers)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
server := sl.servers[idx]
|
||||
if !server.IsValid() {
|
||||
sl.removeServer(idx)
|
||||
continue
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
}
|
||||
|
||||
func (sl *ServerList) removeServer(idx uint32) {
|
||||
n := len(sl.servers)
|
||||
sl.servers[idx] = sl.servers[n-1]
|
||||
sl.servers = sl.servers[:n-1]
|
||||
}
|
||||
|
||||
type ServerPicker interface {
|
||||
PickServer() *ServerSpec
|
||||
}
|
||||
|
||||
type RoundRobinServerPicker struct {
|
||||
sync.Mutex
|
||||
serverlist *ServerList
|
||||
nextIndex uint32
|
||||
}
|
||||
|
||||
func NewRoundRobinServerPicker(serverlist *ServerList) *RoundRobinServerPicker {
|
||||
return &RoundRobinServerPicker{
|
||||
serverlist: serverlist,
|
||||
nextIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *RoundRobinServerPicker) PickServer() *ServerSpec {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
next := p.nextIndex
|
||||
server := p.serverlist.GetServer(next)
|
||||
if server == nil {
|
||||
next = 0
|
||||
server = p.serverlist.GetServer(0)
|
||||
}
|
||||
next++
|
||||
if next >= p.serverlist.Size() {
|
||||
next = 0
|
||||
}
|
||||
p.nextIndex = next
|
||||
|
||||
return server
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package protocol_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
. "github.com/xtls/xray-core/common/protocol"
|
||||
)
|
||||
|
||||
func TestServerList(t *testing.T) {
|
||||
list := NewServerList()
|
||||
list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(1)), AlwaysValid()))
|
||||
if list.Size() != 1 {
|
||||
t.Error("list size: ", list.Size())
|
||||
}
|
||||
list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(2)), BeforeTime(time.Now().Add(time.Second))))
|
||||
if list.Size() != 2 {
|
||||
t.Error("list.size: ", list.Size())
|
||||
}
|
||||
|
||||
server := list.GetServer(1)
|
||||
if server.Destination().Port != 2 {
|
||||
t.Error("server: ", server.Destination())
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
server = list.GetServer(1)
|
||||
if server != nil {
|
||||
t.Error("server: ", server)
|
||||
}
|
||||
|
||||
server = list.GetServer(0)
|
||||
if server.Destination().Port != 1 {
|
||||
t.Error("server: ", server.Destination())
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerPicker(t *testing.T) {
|
||||
list := NewServerList()
|
||||
list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(1)), AlwaysValid()))
|
||||
list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(2)), BeforeTime(time.Now().Add(time.Second))))
|
||||
list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(3)), BeforeTime(time.Now().Add(time.Second))))
|
||||
|
||||
picker := NewRoundRobinServerPicker(list)
|
||||
server := picker.PickServer()
|
||||
if server.Destination().Port != 1 {
|
||||
t.Error("server: ", server.Destination())
|
||||
}
|
||||
server = picker.PickServer()
|
||||
if server.Destination().Port != 2 {
|
||||
t.Error("server: ", server.Destination())
|
||||
}
|
||||
server = picker.PickServer()
|
||||
if server.Destination().Port != 3 {
|
||||
t.Error("server: ", server.Destination())
|
||||
}
|
||||
server = picker.PickServer()
|
||||
if server.Destination().Port != 1 {
|
||||
t.Error("server: ", server.Destination())
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
server = picker.PickServer()
|
||||
if server.Destination().Port != 1 {
|
||||
t.Error("server: ", server.Destination())
|
||||
}
|
||||
server = picker.PickServer()
|
||||
if server.Destination().Port != 1 {
|
||||
t.Error("server: ", server.Destination())
|
||||
}
|
||||
}
|
||||
@@ -1,122 +1,30 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
type ValidationStrategy interface {
|
||||
IsValid() bool
|
||||
Invalidate()
|
||||
}
|
||||
|
||||
type alwaysValidStrategy struct{}
|
||||
|
||||
func AlwaysValid() ValidationStrategy {
|
||||
return alwaysValidStrategy{}
|
||||
}
|
||||
|
||||
func (alwaysValidStrategy) IsValid() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (alwaysValidStrategy) Invalidate() {}
|
||||
|
||||
type timeoutValidStrategy struct {
|
||||
until time.Time
|
||||
}
|
||||
|
||||
func BeforeTime(t time.Time) ValidationStrategy {
|
||||
return &timeoutValidStrategy{
|
||||
until: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *timeoutValidStrategy) IsValid() bool {
|
||||
return s.until.After(time.Now())
|
||||
}
|
||||
|
||||
func (s *timeoutValidStrategy) Invalidate() {
|
||||
s.until = time.Time{}
|
||||
}
|
||||
|
||||
type ServerSpec struct {
|
||||
sync.RWMutex
|
||||
dest net.Destination
|
||||
users []*MemoryUser
|
||||
valid ValidationStrategy
|
||||
Destination net.Destination
|
||||
User *MemoryUser
|
||||
}
|
||||
|
||||
func NewServerSpec(dest net.Destination, valid ValidationStrategy, users ...*MemoryUser) *ServerSpec {
|
||||
func NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec {
|
||||
return &ServerSpec{
|
||||
dest: dest,
|
||||
users: users,
|
||||
valid: valid,
|
||||
Destination: dest,
|
||||
User: user,
|
||||
}
|
||||
}
|
||||
|
||||
func NewServerSpecFromPB(spec *ServerEndpoint) (*ServerSpec, error) {
|
||||
dest := net.TCPDestination(spec.Address.AsAddress(), net.Port(spec.Port))
|
||||
mUsers := make([]*MemoryUser, len(spec.User))
|
||||
for idx, u := range spec.User {
|
||||
mUser, err := u.ToMemoryUser()
|
||||
var dUser *MemoryUser
|
||||
if spec.User != nil {
|
||||
user, err := spec.User.ToMemoryUser()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mUsers[idx] = mUser
|
||||
dUser = user
|
||||
}
|
||||
return NewServerSpec(dest, AlwaysValid(), mUsers...), nil
|
||||
}
|
||||
|
||||
func (s *ServerSpec) Destination() net.Destination {
|
||||
return s.dest
|
||||
}
|
||||
|
||||
func (s *ServerSpec) HasUser(user *MemoryUser) bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
for _, u := range s.users {
|
||||
if u.Account.Equals(user.Account) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *ServerSpec) AddUser(user *MemoryUser) {
|
||||
if s.HasUser(user) {
|
||||
return
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.users = append(s.users, user)
|
||||
}
|
||||
|
||||
func (s *ServerSpec) PickUser() *MemoryUser {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
userCount := len(s.users)
|
||||
switch userCount {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return s.users[0]
|
||||
default:
|
||||
return s.users[dice.Roll(userCount)]
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerSpec) IsValid() bool {
|
||||
return s.valid.IsValid()
|
||||
}
|
||||
|
||||
func (s *ServerSpec) Invalidate() {
|
||||
s.valid.Invalidate()
|
||||
return NewServerSpec(dest, dUser), nil
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type ServerEndpoint struct {
|
||||
|
||||
Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||
Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
|
||||
User []*User `protobuf:"bytes,3,rep,name=user,proto3" json:"user,omitempty"`
|
||||
User *User `protobuf:"bytes,3,opt,name=user,proto3" json:"user,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ServerEndpoint) Reset() {
|
||||
@@ -75,7 +75,7 @@ func (x *ServerEndpoint) GetPort() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ServerEndpoint) GetUser() []*User {
|
||||
func (x *ServerEndpoint) GetUser() *User {
|
||||
if x != nil {
|
||||
return x.User
|
||||
}
|
||||
@@ -98,7 +98,7 @@ var file_common_protocol_server_spec_proto_rawDesc = []byte{
|
||||
0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2e, 0x0a,
|
||||
0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72,
|
||||
0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
||||
0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x42, 0x5e, 0x0a,
|
||||
0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
||||
|
||||
@@ -12,5 +12,5 @@ import "common/protocol/user.proto";
|
||||
message ServerEndpoint {
|
||||
xray.common.net.IPOrDomain address = 1;
|
||||
uint32 port = 2;
|
||||
repeated xray.common.protocol.User user = 3;
|
||||
xray.common.protocol.User user = 3;
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package protocol_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
. "github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/proxy/vmess"
|
||||
)
|
||||
|
||||
func TestAlwaysValidStrategy(t *testing.T) {
|
||||
strategy := AlwaysValid()
|
||||
if !strategy.IsValid() {
|
||||
t.Error("strategy not valid")
|
||||
}
|
||||
strategy.Invalidate()
|
||||
if !strategy.IsValid() {
|
||||
t.Error("strategy not valid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutValidStrategy(t *testing.T) {
|
||||
strategy := BeforeTime(time.Now().Add(2 * time.Second))
|
||||
if !strategy.IsValid() {
|
||||
t.Error("strategy not valid")
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
if strategy.IsValid() {
|
||||
t.Error("strategy is valid")
|
||||
}
|
||||
|
||||
strategy = BeforeTime(time.Now().Add(2 * time.Second))
|
||||
strategy.Invalidate()
|
||||
if strategy.IsValid() {
|
||||
t.Error("strategy is valid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserInServerSpec(t *testing.T) {
|
||||
uuid1 := uuid.New()
|
||||
uuid2 := uuid.New()
|
||||
|
||||
toAccount := func(a *vmess.Account) Account {
|
||||
account, err := a.AsAccount()
|
||||
common.Must(err)
|
||||
return account
|
||||
}
|
||||
|
||||
spec := NewServerSpec(net.Destination{}, AlwaysValid(), &MemoryUser{
|
||||
Email: "test1@example.com",
|
||||
Account: toAccount(&vmess.Account{Id: uuid1.String()}),
|
||||
})
|
||||
if spec.HasUser(&MemoryUser{
|
||||
Email: "test1@example.com",
|
||||
Account: toAccount(&vmess.Account{Id: uuid2.String()}),
|
||||
}) {
|
||||
t.Error("has user: ", uuid2)
|
||||
}
|
||||
|
||||
spec.AddUser(&MemoryUser{Email: "test2@example.com"})
|
||||
if !spec.HasUser(&MemoryUser{
|
||||
Email: "test1@example.com",
|
||||
Account: toAccount(&vmess.Account{Id: uuid1.String()}),
|
||||
}) {
|
||||
t.Error("not having user: ", uuid1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickUser(t *testing.T) {
|
||||
spec := NewServerSpec(net.Destination{}, AlwaysValid(), &MemoryUser{Email: "test1@example.com"}, &MemoryUser{Email: "test2@example.com"}, &MemoryUser{Email: "test3@example.com"})
|
||||
user := spec.PickUser()
|
||||
if !strings.HasSuffix(user.Email, "@example.com") {
|
||||
t.Error("user: ", user.Email)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user