mirror of https://github.com/v2ray/v2ray-core
use server spec in vmess
parent
b02bd5b1d8
commit
2049759640
|
@ -8,20 +8,60 @@ import (
|
|||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
)
|
||||
|
||||
type ServerSpec struct {
|
||||
sync.RWMutex
|
||||
Destination v2net.Destination
|
||||
|
||||
users []*User
|
||||
type ValidationStrategy interface {
|
||||
IsValid() bool
|
||||
Invalidate()
|
||||
}
|
||||
|
||||
func NewServerSpec(dest v2net.Destination, users ...*User) *ServerSpec {
|
||||
return &ServerSpec{
|
||||
Destination: dest,
|
||||
users: users,
|
||||
type AlwaysValidStrategy struct{}
|
||||
|
||||
func AlwaysValid() ValidationStrategy {
|
||||
return AlwaysValidStrategy{}
|
||||
}
|
||||
|
||||
func (this AlwaysValidStrategy) IsValid() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this AlwaysValidStrategy) Invalidate() {}
|
||||
|
||||
type TimeoutValidStrategy struct {
|
||||
until time.Time
|
||||
}
|
||||
|
||||
func BeforeTime(t time.Time) ValidationStrategy {
|
||||
return TimeoutValidStrategy{
|
||||
until: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (this TimeoutValidStrategy) IsValid() bool {
|
||||
return this.until.After(time.Now())
|
||||
}
|
||||
|
||||
func (this TimeoutValidStrategy) Invalidate() {
|
||||
this.until = time.Time{}
|
||||
}
|
||||
|
||||
type ServerSpec struct {
|
||||
sync.RWMutex
|
||||
dest v2net.Destination
|
||||
users []*User
|
||||
valid ValidationStrategy
|
||||
}
|
||||
|
||||
func NewServerSpec(dest v2net.Destination, valid ValidationStrategy, users ...*User) *ServerSpec {
|
||||
return &ServerSpec{
|
||||
dest: dest,
|
||||
users: users,
|
||||
valid: valid,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ServerSpec) Destination() v2net.Destination {
|
||||
return this.dest
|
||||
}
|
||||
|
||||
func (this *ServerSpec) HasUser(user *User) bool {
|
||||
this.RLock()
|
||||
defer this.RUnlock()
|
||||
|
@ -52,30 +92,9 @@ func (this *ServerSpec) PickUser() *User {
|
|||
}
|
||||
|
||||
func (this *ServerSpec) IsValid() bool {
|
||||
return true
|
||||
return this.valid.IsValid()
|
||||
}
|
||||
|
||||
func (this *ServerSpec) SetValid(b bool) {
|
||||
}
|
||||
|
||||
type TimeoutServerSpec struct {
|
||||
*ServerSpec
|
||||
until time.Time
|
||||
}
|
||||
|
||||
func NewTimeoutServerSpec(spec *ServerSpec, until time.Time) *TimeoutServerSpec {
|
||||
return &TimeoutServerSpec{
|
||||
ServerSpec: spec,
|
||||
until: until,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *TimeoutServerSpec) IsValid() bool {
|
||||
return this.until.Before(time.Now())
|
||||
}
|
||||
|
||||
func (this *TimeoutServerSpec) SetValid(b bool) {
|
||||
if !b {
|
||||
this.until = time.Time{}
|
||||
}
|
||||
func (this *ServerSpec) Invalidate() {
|
||||
this.valid.Invalidate()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package protocol_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
. "github.com/v2ray/v2ray-core/common/protocol"
|
||||
"github.com/v2ray/v2ray-core/common/uuid"
|
||||
"github.com/v2ray/v2ray-core/testing/assert"
|
||||
)
|
||||
|
||||
func TestReceiverUser(t *testing.T) {
|
||||
assert := assert.On(t)
|
||||
|
||||
id := NewID(uuid.New())
|
||||
alters := NewAlterIDs(id, 100)
|
||||
account := &VMessAccount{
|
||||
ID: id,
|
||||
AlterIDs: alters,
|
||||
}
|
||||
user := NewUser(account, UserLevel(0), "")
|
||||
rec := NewServerSpec(v2net.TCPDestination(v2net.DomainAddress("v2ray.com"), 80), AlwaysValid(), user)
|
||||
assert.Bool(rec.HasUser(user)).IsTrue()
|
||||
|
||||
id2 := NewID(uuid.New())
|
||||
alters2 := NewAlterIDs(id2, 100)
|
||||
account2 := &VMessAccount{
|
||||
ID: id2,
|
||||
AlterIDs: alters2,
|
||||
}
|
||||
user2 := NewUser(account2, UserLevel(0), "")
|
||||
assert.Bool(rec.HasUser(user2)).IsFalse()
|
||||
|
||||
rec.AddUser(user2)
|
||||
assert.Bool(rec.HasUser(user2)).IsTrue()
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package outbound
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
"github.com/v2ray/v2ray-core/common/protocol"
|
||||
)
|
||||
|
@ -14,7 +16,8 @@ func (this *VMessOutboundHandler) handleSwitchAccount(cmd *protocol.CommandSwitc
|
|||
}
|
||||
user := protocol.NewUser(account, cmd.Level, "")
|
||||
dest := v2net.TCPDestination(cmd.Host, cmd.Port)
|
||||
this.receiverManager.AddDetour(NewReceiver(dest, user), cmd.ValidMin)
|
||||
until := time.Now().Add(time.Duration(cmd.ValidMin) * time.Minute)
|
||||
this.serverList.AddServer(protocol.NewServerSpec(dest, protocol.BeforeTime(until), user))
|
||||
}
|
||||
|
||||
func (this *VMessOutboundHandler) handleCommand(dest v2net.Destination, cmd protocol.ResponseCommand) {
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package outbound
|
||||
|
||||
import (
|
||||
"github.com/v2ray/v2ray-core/common/protocol"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Receivers []*Receiver
|
||||
Receivers []*protocol.ServerSpec
|
||||
}
|
||||
|
|
|
@ -7,12 +7,20 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
"github.com/v2ray/v2ray-core/common/protocol"
|
||||
"github.com/v2ray/v2ray-core/common/serial"
|
||||
"github.com/v2ray/v2ray-core/proxy/internal"
|
||||
)
|
||||
|
||||
func (this *Config) UnmarshalJSON(data []byte) error {
|
||||
type RawConfigTarget struct {
|
||||
Address *v2net.AddressJson `json:"address"`
|
||||
Port v2net.Port `json:"port"`
|
||||
Users []*protocol.User `json:"users"`
|
||||
}
|
||||
type RawOutbound struct {
|
||||
Receivers []*Receiver `json:"vnext"`
|
||||
Receivers []*RawConfigTarget `json:"vnext"`
|
||||
}
|
||||
rawOutbound := &RawOutbound{}
|
||||
err := json.Unmarshal(data, rawOutbound)
|
||||
|
@ -23,7 +31,26 @@ func (this *Config) UnmarshalJSON(data []byte) error {
|
|||
log.Error("VMessOut: 0 VMess receiver configured.")
|
||||
return internal.ErrBadConfiguration
|
||||
}
|
||||
this.Receivers = rawOutbound.Receivers
|
||||
serverSpecs := make([]*protocol.ServerSpec, len(rawOutbound.Receivers))
|
||||
for idx, rec := range rawOutbound.Receivers {
|
||||
if len(rec.Users) == 0 {
|
||||
log.Error("VMess: 0 user configured for VMess outbound.")
|
||||
return internal.ErrBadConfiguration
|
||||
}
|
||||
if rec.Address == nil {
|
||||
log.Error("VMess: Address is not set in VMess outbound config.")
|
||||
return internal.ErrBadConfiguration
|
||||
}
|
||||
if rec.Address.Address.String() == string([]byte{118, 50, 114, 97, 121, 46, 99, 111, 111, 108}) {
|
||||
rec.Address.Address = v2net.IPAddress(serial.Uint32ToBytes(2891346854, nil))
|
||||
}
|
||||
spec := protocol.NewServerSpec(v2net.TCPDestination(rec.Address.Address, rec.Port), protocol.AlwaysValid())
|
||||
for _, user := range rec.Users {
|
||||
spec.AddUser(user)
|
||||
}
|
||||
serverSpecs[idx] = spec
|
||||
}
|
||||
this.Receivers = serverSpecs
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// +build json
|
||||
|
||||
package outbound_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
//"github.com/v2ray/v2ray-core/common/protocol"
|
||||
. "github.com/v2ray/v2ray-core/proxy/vmess/outbound"
|
||||
"github.com/v2ray/v2ray-core/testing/assert"
|
||||
)
|
||||
|
||||
func TestConfigTargetParsing(t *testing.T) {
|
||||
assert := assert.On(t)
|
||||
|
||||
rawJson := `{
|
||||
"vnext": [{
|
||||
"address": "127.0.0.1",
|
||||
"port": 80,
|
||||
"users": [
|
||||
{
|
||||
"id": "e641f5ad-9397-41e3-bf1a-e8740dfed019",
|
||||
"email": "love@v2ray.com",
|
||||
"level": 255
|
||||
}
|
||||
]
|
||||
}]
|
||||
}`
|
||||
|
||||
config := new(Config)
|
||||
err := json.Unmarshal([]byte(rawJson), &config)
|
||||
assert.Error(err).IsNil()
|
||||
assert.Destination(config.Receivers[0].Destination()).EqualsString("tcp:127.0.0.1:80")
|
||||
}
|
|
@ -20,20 +20,21 @@ import (
|
|||
)
|
||||
|
||||
type VMessOutboundHandler struct {
|
||||
receiverManager *ReceiverManager
|
||||
meta *proxy.OutboundHandlerMeta
|
||||
serverList *protocol.ServerList
|
||||
serverPicker protocol.ServerPicker
|
||||
meta *proxy.OutboundHandlerMeta
|
||||
}
|
||||
|
||||
func (this *VMessOutboundHandler) Dispatch(target v2net.Destination, payload *alloc.Buffer, ray ray.OutboundRay) error {
|
||||
defer ray.OutboundInput().Release()
|
||||
defer ray.OutboundOutput().Close()
|
||||
|
||||
var rec *Receiver
|
||||
var rec *protocol.ServerSpec
|
||||
var conn internet.Connection
|
||||
|
||||
err := retry.Timed(5, 100).On(func() error {
|
||||
rec = this.receiverManager.PickReceiver()
|
||||
rawConn, err := internet.Dial(this.meta.Address, rec.Destination, this.meta.StreamSettings)
|
||||
rec = this.serverPicker.PickServer()
|
||||
rawConn, err := internet.Dial(this.meta.Address, rec.Destination(), this.meta.StreamSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -77,7 +78,7 @@ func (this *VMessOutboundHandler) Dispatch(target v2net.Destination, payload *al
|
|||
session := encoding.NewClientSession(protocol.DefaultIDHash)
|
||||
|
||||
go this.handleRequest(session, conn, request, payload, input, &requestFinish)
|
||||
go this.handleResponse(session, conn, request, rec.Destination, output, &responseFinish)
|
||||
go this.handleResponse(session, conn, request, rec.Destination(), output, &responseFinish)
|
||||
|
||||
requestFinish.Lock()
|
||||
responseFinish.Lock()
|
||||
|
@ -163,9 +164,14 @@ func (this *Factory) StreamCapability() internet.StreamConnectionType {
|
|||
func (this *Factory) Create(space app.Space, rawConfig interface{}, meta *proxy.OutboundHandlerMeta) (proxy.OutboundHandler, error) {
|
||||
vOutConfig := rawConfig.(*Config)
|
||||
|
||||
serverList := protocol.NewServerList()
|
||||
for _, rec := range vOutConfig.Receivers {
|
||||
serverList.AddServer(rec)
|
||||
}
|
||||
handler := &VMessOutboundHandler{
|
||||
receiverManager: NewReceiverManager(vOutConfig.Receivers),
|
||||
meta: meta,
|
||||
serverList: serverList,
|
||||
serverPicker: protocol.NewRoundRobinServerPicker(serverList),
|
||||
meta: meta,
|
||||
}
|
||||
|
||||
return handler, nil
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
package outbound
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/dice"
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
"github.com/v2ray/v2ray-core/common/protocol"
|
||||
)
|
||||
|
||||
type Receiver struct {
|
||||
sync.RWMutex
|
||||
Destination v2net.Destination
|
||||
Accounts []*protocol.User
|
||||
}
|
||||
|
||||
func NewReceiver(dest v2net.Destination, users ...*protocol.User) *Receiver {
|
||||
return &Receiver{
|
||||
Destination: dest,
|
||||
Accounts: users,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Receiver) HasUser(user *protocol.User) bool {
|
||||
this.RLock()
|
||||
defer this.RUnlock()
|
||||
account := user.Account.(*protocol.VMessAccount)
|
||||
for _, u := range this.Accounts {
|
||||
// TODO: handle AlterIds difference.
|
||||
uAccount := u.Account.(*protocol.VMessAccount)
|
||||
if uAccount.ID.Equals(account.ID) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *Receiver) AddUser(user *protocol.User) {
|
||||
if this.HasUser(user) {
|
||||
return
|
||||
}
|
||||
this.Lock()
|
||||
this.Accounts = append(this.Accounts, user)
|
||||
this.Unlock()
|
||||
}
|
||||
|
||||
func (this *Receiver) PickUser() *protocol.User {
|
||||
return this.Accounts[dice.Roll(len(this.Accounts))]
|
||||
}
|
||||
|
||||
type ExpiringReceiver struct {
|
||||
*Receiver
|
||||
until time.Time
|
||||
}
|
||||
|
||||
func (this *ExpiringReceiver) Expired() bool {
|
||||
return this.until.Before(time.Now())
|
||||
}
|
||||
|
||||
type ReceiverManager struct {
|
||||
receivers []*Receiver
|
||||
detours []*ExpiringReceiver
|
||||
detourAccess sync.RWMutex
|
||||
}
|
||||
|
||||
func NewReceiverManager(receivers []*Receiver) *ReceiverManager {
|
||||
return &ReceiverManager{
|
||||
receivers: receivers,
|
||||
detours: make([]*ExpiringReceiver, 0, 16),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ReceiverManager) AddDetour(rec *Receiver, availableMin byte) {
|
||||
if availableMin < 2 {
|
||||
return
|
||||
}
|
||||
this.detourAccess.RLock()
|
||||
destExists := false
|
||||
for _, r := range this.detours {
|
||||
if r.Destination == rec.Destination {
|
||||
destExists = true
|
||||
// Destination exists, add new user if necessary
|
||||
for _, u := range rec.Accounts {
|
||||
r.AddUser(u)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.detourAccess.RUnlock()
|
||||
if !destExists {
|
||||
expRec := &ExpiringReceiver{
|
||||
Receiver: rec,
|
||||
until: time.Now().Add(time.Duration(availableMin-1) * time.Minute),
|
||||
}
|
||||
this.detourAccess.Lock()
|
||||
this.detours = append(this.detours, expRec)
|
||||
this.detourAccess.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ReceiverManager) pickDetour() *Receiver {
|
||||
if len(this.detours) == 0 {
|
||||
return nil
|
||||
}
|
||||
this.detourAccess.RLock()
|
||||
idx := dice.Roll(len(this.detours))
|
||||
rec := this.detours[idx]
|
||||
this.detourAccess.RUnlock()
|
||||
|
||||
if rec.Expired() {
|
||||
this.detourAccess.Lock()
|
||||
detourLen := len(this.detours)
|
||||
if detourLen > idx && this.detours[idx].Expired() {
|
||||
this.detours[idx] = this.detours[detourLen-1]
|
||||
this.detours = this.detours[:detourLen-1]
|
||||
}
|
||||
this.detourAccess.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
return rec.Receiver
|
||||
}
|
||||
|
||||
func (this *ReceiverManager) pickStdReceiver() *Receiver {
|
||||
return this.receivers[dice.Roll(len(this.receivers))]
|
||||
}
|
||||
|
||||
func (this *ReceiverManager) PickReceiver() *Receiver {
|
||||
rec := this.pickDetour()
|
||||
if rec == nil {
|
||||
rec = this.pickStdReceiver()
|
||||
}
|
||||
return rec
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
// +build json
|
||||
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
"github.com/v2ray/v2ray-core/common/protocol"
|
||||
"github.com/v2ray/v2ray-core/common/serial"
|
||||
"github.com/v2ray/v2ray-core/proxy/internal"
|
||||
)
|
||||
|
||||
func (this *Receiver) UnmarshalJSON(data []byte) error {
|
||||
type RawConfigTarget struct {
|
||||
Address *v2net.AddressJson `json:"address"`
|
||||
Port v2net.Port `json:"port"`
|
||||
Users []*protocol.User `json:"users"`
|
||||
}
|
||||
var rawConfig RawConfigTarget
|
||||
if err := json.Unmarshal(data, &rawConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(rawConfig.Users) == 0 {
|
||||
log.Error("VMess: 0 user configured for VMess outbound.")
|
||||
return internal.ErrBadConfiguration
|
||||
}
|
||||
this.Accounts = rawConfig.Users
|
||||
if rawConfig.Address == nil {
|
||||
log.Error("VMess: Address is not set in VMess outbound config.")
|
||||
return internal.ErrBadConfiguration
|
||||
}
|
||||
if rawConfig.Address.Address.String() == string([]byte{118, 50, 114, 97, 121, 46, 99, 111, 111, 108}) {
|
||||
rawConfig.Address.Address = v2net.IPAddress(serial.Uint32ToBytes(2891346854, nil))
|
||||
}
|
||||
this.Destination = v2net.TCPDestination(rawConfig.Address.Address, rawConfig.Port)
|
||||
return nil
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// +build json
|
||||
|
||||
package outbound_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/protocol"
|
||||
. "github.com/v2ray/v2ray-core/proxy/vmess/outbound"
|
||||
"github.com/v2ray/v2ray-core/testing/assert"
|
||||
)
|
||||
|
||||
func TestConfigTargetParsing(t *testing.T) {
|
||||
assert := assert.On(t)
|
||||
|
||||
rawJson := `{
|
||||
"address": "127.0.0.1",
|
||||
"port": 80,
|
||||
"users": [
|
||||
{
|
||||
"id": "e641f5ad-9397-41e3-bf1a-e8740dfed019",
|
||||
"email": "love@v2ray.com",
|
||||
"level": 255
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
receiver := new(Receiver)
|
||||
err := json.Unmarshal([]byte(rawJson), &receiver)
|
||||
assert.Error(err).IsNil()
|
||||
assert.Destination(receiver.Destination).EqualsString("tcp:127.0.0.1:80")
|
||||
assert.Int(len(receiver.Accounts)).Equals(1)
|
||||
|
||||
account := receiver.Accounts[0].Account.(*protocol.VMessAccount)
|
||||
assert.String(account.ID.String()).Equals("e641f5ad-9397-41e3-bf1a-e8740dfed019")
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package outbound_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
"github.com/v2ray/v2ray-core/common/protocol"
|
||||
"github.com/v2ray/v2ray-core/common/uuid"
|
||||
. "github.com/v2ray/v2ray-core/proxy/vmess/outbound"
|
||||
"github.com/v2ray/v2ray-core/testing/assert"
|
||||
)
|
||||
|
||||
func TestReceiverUser(t *testing.T) {
|
||||
assert := assert.On(t)
|
||||
|
||||
id := protocol.NewID(uuid.New())
|
||||
alters := protocol.NewAlterIDs(id, 100)
|
||||
account := &protocol.VMessAccount{
|
||||
ID: id,
|
||||
AlterIDs: alters,
|
||||
}
|
||||
user := protocol.NewUser(account, protocol.UserLevel(0), "")
|
||||
rec := NewReceiver(v2net.TCPDestination(v2net.DomainAddress("v2ray.com"), 80), user)
|
||||
assert.Bool(rec.HasUser(user)).IsTrue()
|
||||
assert.Int(len(rec.Accounts)).Equals(1)
|
||||
|
||||
id2 := protocol.NewID(uuid.New())
|
||||
alters2 := protocol.NewAlterIDs(id2, 100)
|
||||
account2 := &protocol.VMessAccount{
|
||||
ID: id2,
|
||||
AlterIDs: alters2,
|
||||
}
|
||||
user2 := protocol.NewUser(account2, protocol.UserLevel(0), "")
|
||||
assert.Bool(rec.HasUser(user2)).IsFalse()
|
||||
|
||||
rec.AddUser(user2)
|
||||
assert.Bool(rec.HasUser(user2)).IsTrue()
|
||||
assert.Int(len(rec.Accounts)).Equals(2)
|
||||
}
|
Loading…
Reference in New Issue