mirror of
https://github.com/wyx2685/V2bX.git
synced 2026-02-04 12:40:11 +00:00
update
add conditional compilation support add multi core support
This commit is contained in:
2
core/xray/app/app.go
Normal file
2
core/xray/app/app.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package app contains the third-party app used to replace the default app in xray-core
|
||||
package app
|
||||
205
core/xray/app/dispatcher/config.pb.go
Normal file
205
core/xray/app/dispatcher/config.pb.go
Normal file
@@ -0,0 +1,205 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.12
|
||||
// source: config.proto
|
||||
|
||||
package dispatcher
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type SessionConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *SessionConfig) Reset() {
|
||||
*x = SessionConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SessionConfig) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SessionConfig) ProtoMessage() {}
|
||||
|
||||
func (x *SessionConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SessionConfig.ProtoReflect.Descriptor instead.
|
||||
func (*SessionConfig) Descriptor() ([]byte, []int) {
|
||||
return file_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Settings *SessionConfig `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Config) GetSettings() *SessionConfig {
|
||||
if x != nil {
|
||||
return x.Settings
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_config_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_config_proto_rawDesc = []byte{
|
||||
0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x18,
|
||||
0x76, 0x32, 0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69,
|
||||
0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x15, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22,
|
||||
0x4d, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x43, 0x0a, 0x08, 0x73, 0x65, 0x74,
|
||||
0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x32,
|
||||
0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70,
|
||||
0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x6a,
|
||||
0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
|
||||
0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x50, 0x01,
|
||||
0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x59, 0x75, 0x7a,
|
||||
0x75, 0x6b, 0x69, 0x36, 0x31, 0x36, 0x2f, 0x56, 0x32, 0x62, 0x58, 0x2f, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0xaa,
|
||||
0x02, 0x18, 0x56, 0x32, 0x62, 0x58, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e,
|
||||
0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_config_proto_rawDescOnce sync.Once
|
||||
file_config_proto_rawDescData = file_config_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_config_proto_rawDescGZIP() []byte {
|
||||
file_config_proto_rawDescOnce.Do(func() {
|
||||
file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData)
|
||||
})
|
||||
return file_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_config_proto_goTypes = []interface{}{
|
||||
(*SessionConfig)(nil), // 0: v2bx.core.app.dispatcher.SessionConfig
|
||||
(*Config)(nil), // 1: v2bx.core.app.dispatcher.Config
|
||||
}
|
||||
var file_config_proto_depIdxs = []int32{
|
||||
0, // 0: v2bx.core.app.dispatcher.Config.settings:type_name -> v2bx.core.app.dispatcher.SessionConfig
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_config_proto_init() }
|
||||
func file_config_proto_init() {
|
||||
if File_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SessionConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_config_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_config_proto_goTypes,
|
||||
DependencyIndexes: file_config_proto_depIdxs,
|
||||
MessageInfos: file_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_config_proto = out.File
|
||||
file_config_proto_rawDesc = nil
|
||||
file_config_proto_goTypes = nil
|
||||
file_config_proto_depIdxs = nil
|
||||
}
|
||||
15
core/xray/app/dispatcher/config.proto
Normal file
15
core/xray/app/dispatcher/config.proto
Normal file
@@ -0,0 +1,15 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package v2bx.core.app.dispatcher;
|
||||
option csharp_namespace = "V2bX.core.app.dispatcher";
|
||||
option go_package = "github.com/Yuzuki616/V2bX/core/xray/app/dispatcher";
|
||||
option java_package = "com.v2bx.core.app.dispatcher";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message SessionConfig {
|
||||
reserved 1;
|
||||
}
|
||||
|
||||
message Config {
|
||||
SessionConfig settings = 1;
|
||||
}
|
||||
553
core/xray/app/dispatcher/default.go
Normal file
553
core/xray/app/dispatcher/default.go
Normal file
@@ -0,0 +1,553 @@
|
||||
package dispatcher
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/common/rate"
|
||||
"github.com/Yuzuki616/V2bX/limiter"
|
||||
routingSession "github.com/xtls/xray-core/features/routing/session"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
"github.com/xtls/xray-core/features/policy"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/features/stats"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/pipe"
|
||||
)
|
||||
|
||||
var errSniffingTimeout = newError("timeout on sniffing")
|
||||
|
||||
type cachedReader struct {
|
||||
sync.Mutex
|
||||
reader *pipe.Reader
|
||||
cache buf.MultiBuffer
|
||||
}
|
||||
|
||||
func (r *cachedReader) Cache(b *buf.Buffer) {
|
||||
mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100)
|
||||
r.Lock()
|
||||
if !mb.IsEmpty() {
|
||||
r.cache, _ = buf.MergeMulti(r.cache, mb)
|
||||
}
|
||||
b.Clear()
|
||||
rawBytes := b.Extend(buf.Size)
|
||||
n := r.cache.Copy(rawBytes)
|
||||
b.Resize(0, int32(n))
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *cachedReader) readInternal() buf.MultiBuffer {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if r.cache != nil && !r.cache.IsEmpty() {
|
||||
mb := r.cache
|
||||
r.cache = nil
|
||||
return mb
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *cachedReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
mb := r.readInternal()
|
||||
if mb != nil {
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
return r.reader.ReadMultiBuffer()
|
||||
}
|
||||
|
||||
func (r *cachedReader) ReadMultiBufferTimeout(timeout time.Duration) (buf.MultiBuffer, error) {
|
||||
mb := r.readInternal()
|
||||
if mb != nil {
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
return r.reader.ReadMultiBufferTimeout(timeout)
|
||||
}
|
||||
|
||||
func (r *cachedReader) Interrupt() {
|
||||
r.Lock()
|
||||
if r.cache != nil {
|
||||
r.cache = buf.ReleaseMulti(r.cache)
|
||||
}
|
||||
r.Unlock()
|
||||
r.reader.Interrupt()
|
||||
}
|
||||
|
||||
// DefaultDispatcher is a default implementation of Dispatcher.
|
||||
type DefaultDispatcher struct {
|
||||
ohm outbound.Manager
|
||||
router routing.Router
|
||||
policy policy.Manager
|
||||
stats stats.Manager
|
||||
dns dns.Client
|
||||
fdns dns.FakeDNSEngine
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
d := new(DefaultDispatcher)
|
||||
if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error {
|
||||
core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||
d.fdns = fdns
|
||||
})
|
||||
return d.Init(config.(*Config), om, router, pm, sm, dc)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d, nil
|
||||
}))
|
||||
}
|
||||
|
||||
// Init initializes DefaultDispatcher.
|
||||
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dns dns.Client) error {
|
||||
d.ohm = om
|
||||
d.router = router
|
||||
d.policy = pm
|
||||
d.stats = sm
|
||||
d.dns = dns
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type implements common.HasType.
|
||||
func (*DefaultDispatcher) Type() interface{} {
|
||||
return routing.DispatcherType()
|
||||
}
|
||||
|
||||
// Start implements common.Runnable.
|
||||
func (*DefaultDispatcher) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements common.Closable.
|
||||
func (*DefaultDispatcher) Close() error { return nil }
|
||||
|
||||
func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link, *limiter.Limiter, error) {
|
||||
downOpt := pipe.OptionsFromContext(ctx)
|
||||
upOpt := downOpt
|
||||
|
||||
if network == net.Network_UDP {
|
||||
var ip2domain *sync.Map // net.IP.String() => domain, this map is used by server side when client turn on fakedns
|
||||
// Client will send domain address in the buffer.UDP.Address, server record all possible target IP addrs.
|
||||
// When target replies, server will restore the domain and send back to client.
|
||||
// Note: this map is not global but per connection context
|
||||
upOpt = append(upOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer {
|
||||
for i, buffer := range mb {
|
||||
if buffer.UDP == nil {
|
||||
continue
|
||||
}
|
||||
addr := buffer.UDP.Address
|
||||
if addr.Family().IsIP() {
|
||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(addr) && sniffing.Enabled {
|
||||
domain := fkr0.GetDomainFromFakeDNS(addr)
|
||||
if len(domain) > 0 {
|
||||
buffer.UDP.Address = net.DomainAddress(domain)
|
||||
newError("[fakedns client] override with domain: ", domain, " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||
} else {
|
||||
newError("[fakedns client] failed to find domain! :", addr.String(), " for xUDP buffer at ", i).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ip2domain == nil {
|
||||
ip2domain = new(sync.Map)
|
||||
newError("[fakedns client] create a new map").WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
domain := addr.Domain()
|
||||
ips, err := d.dns.LookupIP(domain, dns.IPOption{true, true, false})
|
||||
if err == nil {
|
||||
for _, ip := range ips {
|
||||
ip2domain.Store(ip.String(), domain)
|
||||
}
|
||||
newError("[fakedns client] candidate ip: "+fmt.Sprintf("%v", ips), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||
} else {
|
||||
newError("[fakedns client] failed to look up IP for ", domain, " for xUDP buffer at ", i).Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mb
|
||||
}))
|
||||
downOpt = append(downOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer {
|
||||
for i, buffer := range mb {
|
||||
if buffer.UDP == nil {
|
||||
continue
|
||||
}
|
||||
addr := buffer.UDP.Address
|
||||
if addr.Family().IsIP() {
|
||||
if ip2domain == nil {
|
||||
continue
|
||||
}
|
||||
if domain, found := ip2domain.Load(addr.IP().String()); found {
|
||||
buffer.UDP.Address = net.DomainAddress(domain.(string))
|
||||
newError("[fakedns client] restore domain: ", domain.(string), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
} else {
|
||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok {
|
||||
fakeIp := fkr0.GetFakeIPForDomain(addr.Domain())
|
||||
buffer.UDP.Address = fakeIp[0]
|
||||
newError("[fakedns client] restore FakeIP: ", buffer.UDP, fmt.Sprintf("%v", fakeIp), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mb
|
||||
}))
|
||||
}
|
||||
uplinkReader, uplinkWriter := pipe.New(upOpt...)
|
||||
downlinkReader, downlinkWriter := pipe.New(downOpt...)
|
||||
|
||||
inboundLink := &transport.Link{
|
||||
Reader: downlinkReader,
|
||||
Writer: uplinkWriter,
|
||||
}
|
||||
|
||||
outboundLink := &transport.Link{
|
||||
Reader: uplinkReader,
|
||||
Writer: downlinkWriter,
|
||||
}
|
||||
|
||||
sessionInbound := session.InboundFromContext(ctx)
|
||||
var user *protocol.MemoryUser
|
||||
if sessionInbound != nil {
|
||||
user = sessionInbound.User
|
||||
}
|
||||
var limit *limiter.Limiter
|
||||
if user != nil && len(user.Email) > 0 {
|
||||
var err error
|
||||
limit, err = limiter.GetLimiter(sessionInbound.Tag)
|
||||
if err != nil {
|
||||
newError("Get limit info error: ", err).AtError().WriteToLog()
|
||||
common.Close(outboundLink.Writer)
|
||||
common.Close(inboundLink.Writer)
|
||||
common.Interrupt(outboundLink.Reader)
|
||||
common.Interrupt(inboundLink.Reader)
|
||||
return nil, nil, nil, newError("Get limit info error: ", err)
|
||||
}
|
||||
// Speed Limit and Device Limit
|
||||
w, reject := limit.CheckLimit(user.Email,
|
||||
sessionInbound.Source.Address.IP().String(),
|
||||
network == net.Network_TCP)
|
||||
if reject {
|
||||
newError("Limited ", user.Email, " by conn or ip").AtWarning().WriteToLog()
|
||||
common.Close(outboundLink.Writer)
|
||||
common.Close(inboundLink.Writer)
|
||||
common.Interrupt(outboundLink.Reader)
|
||||
common.Interrupt(inboundLink.Reader)
|
||||
return nil, nil, nil, newError("Limited ", user.Email, " by conn or ip")
|
||||
}
|
||||
if w != nil {
|
||||
inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w)
|
||||
outboundLink.Writer = rate.NewRateLimitWriter(outboundLink.Writer, w)
|
||||
}
|
||||
p := d.policy.ForLevel(user.Level)
|
||||
if p.Stats.UserUplink {
|
||||
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
|
||||
inboundLink.Writer = &SizeStatWriter{
|
||||
Counter: c,
|
||||
Writer: inboundLink.Writer,
|
||||
}
|
||||
}
|
||||
}
|
||||
if p.Stats.UserDownlink {
|
||||
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
|
||||
outboundLink.Writer = &SizeStatWriter{
|
||||
Counter: c,
|
||||
Writer: outboundLink.Writer,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inboundLink, outboundLink, limit, nil
|
||||
}
|
||||
|
||||
func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
|
||||
domain := result.Domain()
|
||||
if domain == "" {
|
||||
return false
|
||||
}
|
||||
for _, d := range request.ExcludeForDomain {
|
||||
if strings.ToLower(domain) == d {
|
||||
return false
|
||||
}
|
||||
}
|
||||
protocolString := result.Protocol()
|
||||
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||
protocolString = resComp.ProtocolForDomainResult()
|
||||
}
|
||||
for _, p := range request.OverrideDestinationForProtocol {
|
||||
if strings.HasPrefix(protocolString, p) {
|
||||
return true
|
||||
}
|
||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
|
||||
destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
|
||||
newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
|
||||
return true
|
||||
}
|
||||
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
|
||||
if resultSubset.IsProtoSubsetOf(p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Dispatch implements routing.Dispatcher.
|
||||
func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) {
|
||||
if !destination.IsValid() {
|
||||
panic("Dispatcher: Invalid destination.")
|
||||
}
|
||||
ob := &session.Outbound{
|
||||
Target: destination,
|
||||
}
|
||||
ctx = session.ContextWithOutbound(ctx, ob)
|
||||
content := session.ContentFromContext(ctx)
|
||||
if content == nil {
|
||||
content = new(session.Content)
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
}
|
||||
sniffingRequest := content.SniffingRequest
|
||||
inbound, outbound, l, err := d.getLink(ctx, destination.Network, sniffingRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !sniffingRequest.Enabled {
|
||||
go d.routedDispatch(ctx, outbound, destination, l)
|
||||
} else {
|
||||
go func() {
|
||||
cReader := &cachedReader{
|
||||
reader: outbound.Reader.(*pipe.Reader),
|
||||
}
|
||||
outbound.Reader = cReader
|
||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
|
||||
if err == nil {
|
||||
content.Protocol = result.Protocol()
|
||||
}
|
||||
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||
domain := result.Domain()
|
||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||
destination.Address = net.ParseAddress(domain)
|
||||
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
||||
ob.RouteTarget = destination
|
||||
} else {
|
||||
ob.Target = destination
|
||||
}
|
||||
}
|
||||
d.routedDispatch(ctx, outbound, destination, l)
|
||||
}()
|
||||
}
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
// DispatchLink implements routing.Dispatcher.
|
||||
func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
|
||||
if !destination.IsValid() {
|
||||
return newError("Dispatcher: Invalid destination.")
|
||||
}
|
||||
ob := &session.Outbound{
|
||||
Target: destination,
|
||||
}
|
||||
ctx = session.ContextWithOutbound(ctx, ob)
|
||||
content := session.ContentFromContext(ctx)
|
||||
if content == nil {
|
||||
content = new(session.Content)
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
}
|
||||
sniffingRequest := content.SniffingRequest
|
||||
if !sniffingRequest.Enabled {
|
||||
go d.routedDispatch(ctx, outbound, destination, nil)
|
||||
} else {
|
||||
go func() {
|
||||
cReader := &cachedReader{
|
||||
reader: outbound.Reader.(*pipe.Reader),
|
||||
}
|
||||
outbound.Reader = cReader
|
||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
|
||||
if err == nil {
|
||||
content.Protocol = result.Protocol()
|
||||
}
|
||||
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||
domain := result.Domain()
|
||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||
destination.Address = net.ParseAddress(domain)
|
||||
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
||||
ob.RouteTarget = destination
|
||||
} else {
|
||||
ob.Target = destination
|
||||
}
|
||||
}
|
||||
d.routedDispatch(ctx, outbound, destination, nil)
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {
|
||||
payload := buf.New()
|
||||
defer payload.Release()
|
||||
|
||||
sniffer := NewSniffer(ctx)
|
||||
|
||||
metaresult, metadataErr := sniffer.SniffMetadata(ctx)
|
||||
|
||||
if metadataOnly {
|
||||
return metaresult, metadataErr
|
||||
}
|
||||
|
||||
contentResult, contentErr := func() (SniffResult, error) {
|
||||
totalAttempt := 0
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
totalAttempt++
|
||||
if totalAttempt > 2 {
|
||||
return nil, errSniffingTimeout
|
||||
}
|
||||
|
||||
cReader.Cache(payload)
|
||||
if !payload.IsEmpty() {
|
||||
result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
|
||||
if err != common.ErrNoClue {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
if payload.IsFull() {
|
||||
return nil, errUnknownContent
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
if contentErr != nil && metadataErr == nil {
|
||||
return metaresult, nil
|
||||
}
|
||||
if contentErr == nil && metadataErr == nil {
|
||||
return CompositeResult(metaresult, contentResult), nil
|
||||
}
|
||||
return contentResult, contentErr
|
||||
}
|
||||
|
||||
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination, l *limiter.Limiter) {
|
||||
ob := session.OutboundFromContext(ctx)
|
||||
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
|
||||
proxied := hosts.LookupHosts(ob.Target.String())
|
||||
if proxied != nil {
|
||||
ro := ob.RouteTarget == destination
|
||||
destination.Address = *proxied
|
||||
if ro {
|
||||
ob.RouteTarget = destination
|
||||
} else {
|
||||
ob.Target = destination
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var handler outbound.Handler
|
||||
|
||||
// Check if domain and protocol hit the rule
|
||||
sessionInbound := session.InboundFromContext(ctx)
|
||||
// Whether the inbound connection contains a user
|
||||
if sessionInbound.User != nil {
|
||||
if l == nil {
|
||||
var err error
|
||||
l, err = limiter.GetLimiter(sessionInbound.Tag)
|
||||
if err != nil {
|
||||
newError("Get limiter error: ", err).AtError().WriteToLog()
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
}
|
||||
} else if destination.Network == net.Network_TCP {
|
||||
defer func() {
|
||||
l.ConnLimiter.DelConnCount(sessionInbound.User.Email, sessionInbound.Source.Address.IP().String())
|
||||
}()
|
||||
}
|
||||
if l.CheckDomainRule(destination.String()) {
|
||||
newError(fmt.Sprintf("User %s access %s reject by rule", sessionInbound.User.Email, destination.String())).AtError().WriteToLog()
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
routingLink := routingSession.AsRoutingContext(ctx)
|
||||
inTag := routingLink.GetInboundTag()
|
||||
isPickRoute := 0
|
||||
if forcedOutboundTag := session.GetForcedOutboundTagFromContext(ctx); forcedOutboundTag != "" {
|
||||
ctx = session.SetForcedOutboundTagToContext(ctx, "")
|
||||
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
|
||||
isPickRoute = 1
|
||||
newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||
handler = h
|
||||
} else {
|
||||
newError("non existing tag for platform initialized detour: ", forcedOutboundTag).AtError().WriteToLog(session.ExportIDToError(ctx))
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
}
|
||||
} else if d.router != nil {
|
||||
if route, err := d.router.PickRoute(routingLink); err == nil {
|
||||
outTag := route.GetOutboundTag()
|
||||
if h := d.ohm.GetHandler(outTag); h != nil {
|
||||
isPickRoute = 2
|
||||
newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||
handler = h
|
||||
} else {
|
||||
newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
} else {
|
||||
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
if handler == nil {
|
||||
handler = d.ohm.GetHandler(inTag) // Default outbound handier tag should be as same as the inbound tag
|
||||
}
|
||||
|
||||
// If there is no outbound with tag as same as the inbound tag
|
||||
if handler == nil {
|
||||
handler = d.ohm.GetDefaultHandler()
|
||||
}
|
||||
|
||||
if handler == nil {
|
||||
newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx))
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
}
|
||||
|
||||
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
|
||||
if tag := handler.Tag(); tag != "" {
|
||||
if inTag == "" {
|
||||
accessMessage.Detour = tag
|
||||
} else if isPickRoute == 1 {
|
||||
accessMessage.Detour = inTag + " ==> " + tag
|
||||
} else if isPickRoute == 2 {
|
||||
accessMessage.Detour = inTag + " -> " + tag
|
||||
} else {
|
||||
accessMessage.Detour = inTag + " >> " + tag
|
||||
}
|
||||
}
|
||||
log.Record(accessMessage)
|
||||
}
|
||||
|
||||
handler.Dispatch(ctx, link)
|
||||
}
|
||||
3
core/xray/app/dispatcher/dispatcher.go
Normal file
3
core/xray/app/dispatcher/dispatcher.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package dispatcher
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
9
core/xray/app/dispatcher/errors.generated.go
Normal file
9
core/xray/app/dispatcher/errors.generated.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package dispatcher
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
119
core/xray/app/dispatcher/fakednssniffer.go
Normal file
119
core/xray/app/dispatcher/fakednssniffer.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package dispatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
// newFakeDNSSniffer Creates a Fake DNS metadata sniffer
|
||||
func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error) {
|
||||
var fakeDNSEngine dns.FakeDNSEngine
|
||||
{
|
||||
fakeDNSEngineFeat := core.MustFromContext(ctx).GetFeature((*dns.FakeDNSEngine)(nil))
|
||||
if fakeDNSEngineFeat != nil {
|
||||
fakeDNSEngine = fakeDNSEngineFeat.(dns.FakeDNSEngine)
|
||||
}
|
||||
}
|
||||
|
||||
if fakeDNSEngine == nil {
|
||||
errNotInit := newError("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
|
||||
return protocolSnifferWithMetadata{}, errNotInit
|
||||
}
|
||||
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
||||
Target := session.OutboundFromContext(ctx).Target
|
||||
if Target.Network == net.Network_TCP || Target.Network == net.Network_UDP {
|
||||
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address)
|
||||
if domainFromFakeDNS != "" {
|
||||
newError("fake dns got domain: ", domainFromFakeDNS, " for ip: ", Target.Address.String()).WriteToLog(session.ExportIDToError(ctx))
|
||||
return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil {
|
||||
ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt)
|
||||
if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
|
||||
inPool := fkr0.IsIPInIPPool(Target.Address)
|
||||
ipAddressInRangeValue.addressInRange = &inPool
|
||||
}
|
||||
}
|
||||
|
||||
return nil, common.ErrNoClue
|
||||
}, metadataSniffer: true}, nil
|
||||
}
|
||||
|
||||
type fakeDNSSniffResult struct {
|
||||
domainName string
|
||||
}
|
||||
|
||||
func (fakeDNSSniffResult) Protocol() string {
|
||||
return "fakedns"
|
||||
}
|
||||
|
||||
func (f fakeDNSSniffResult) Domain() string {
|
||||
return f.domainName
|
||||
}
|
||||
|
||||
type fakeDNSExtraOpts int
|
||||
|
||||
const ipAddressInRange fakeDNSExtraOpts = 1
|
||||
|
||||
type ipAddressInRangeOpt struct {
|
||||
addressInRange *bool
|
||||
}
|
||||
|
||||
type DNSThenOthersSniffResult struct {
|
||||
domainName string
|
||||
protocolOriginalName string
|
||||
}
|
||||
|
||||
func (f DNSThenOthersSniffResult) IsProtoSubsetOf(protocolName string) bool {
|
||||
return strings.HasPrefix(protocolName, f.protocolOriginalName)
|
||||
}
|
||||
|
||||
func (DNSThenOthersSniffResult) Protocol() string {
|
||||
return "fakedns+others"
|
||||
}
|
||||
|
||||
func (f DNSThenOthersSniffResult) Domain() string {
|
||||
return f.domainName
|
||||
}
|
||||
|
||||
func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWithMetadata, others []protocolSnifferWithMetadata) (
|
||||
protocolSnifferWithMetadata, error,
|
||||
) { // nolint: unparam
|
||||
// ctx may be used in the future
|
||||
_ = ctx
|
||||
return protocolSnifferWithMetadata{
|
||||
protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
||||
ipAddressInRangeValue := &ipAddressInRangeOpt{}
|
||||
ctx = context.WithValue(ctx, ipAddressInRange, ipAddressInRangeValue)
|
||||
result, err := fakeDNSSniffer.protocolSniffer(ctx, bytes)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if ipAddressInRangeValue.addressInRange != nil {
|
||||
if *ipAddressInRangeValue.addressInRange {
|
||||
for _, v := range others {
|
||||
if v.metadataSniffer || bytes != nil {
|
||||
if result, err := v.protocolSniffer(ctx, bytes); err == nil {
|
||||
return DNSThenOthersSniffResult{domainName: result.Domain(), protocolOriginalName: result.Protocol()}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
newError("ip address not in fake dns range, return as is").AtDebug().WriteToLog()
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
newError("fake dns sniffer did not set address in range option, assume false.").AtWarning().WriteToLog()
|
||||
return nil, common.ErrNoClue
|
||||
},
|
||||
metadataSniffer: false,
|
||||
}, nil
|
||||
}
|
||||
137
core/xray/app/dispatcher/sniffer.go
Normal file
137
core/xray/app/dispatcher/sniffer.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package dispatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol/bittorrent"
|
||||
"github.com/xtls/xray-core/common/protocol/http"
|
||||
"github.com/xtls/xray-core/common/protocol/quic"
|
||||
"github.com/xtls/xray-core/common/protocol/tls"
|
||||
)
|
||||
|
||||
type SniffResult interface {
|
||||
Protocol() string
|
||||
Domain() string
|
||||
}
|
||||
|
||||
type protocolSniffer func(context.Context, []byte) (SniffResult, error)
|
||||
|
||||
type protocolSnifferWithMetadata struct {
|
||||
protocolSniffer protocolSniffer
|
||||
// A Metadata sniffer will be invoked on connection establishment only, with nil body,
|
||||
// for both TCP and UDP connections
|
||||
// It will not be shown as a traffic type for routing unless there is no other successful sniffing.
|
||||
metadataSniffer bool
|
||||
network net.Network
|
||||
}
|
||||
|
||||
type Sniffer struct {
|
||||
sniffer []protocolSnifferWithMetadata
|
||||
}
|
||||
|
||||
func NewSniffer(ctx context.Context) *Sniffer {
|
||||
ret := &Sniffer{
|
||||
sniffer: []protocolSnifferWithMetadata{
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffUTP(b) }, false, net.Network_UDP},
|
||||
},
|
||||
}
|
||||
if sniffer, err := newFakeDNSSniffer(ctx); err == nil {
|
||||
others := ret.sniffer
|
||||
ret.sniffer = append(ret.sniffer, sniffer)
|
||||
fakeDNSThenOthers, err := newFakeDNSThenOthers(ctx, sniffer, others)
|
||||
if err == nil {
|
||||
ret.sniffer = append([]protocolSnifferWithMetadata{fakeDNSThenOthers}, ret.sniffer...)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
var errUnknownContent = newError("unknown content")
|
||||
|
||||
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
|
||||
var pendingSniffer []protocolSnifferWithMetadata
|
||||
for _, si := range s.sniffer {
|
||||
s := si.protocolSniffer
|
||||
if si.metadataSniffer || si.network != network {
|
||||
continue
|
||||
}
|
||||
result, err := s(c, payload)
|
||||
if err == common.ErrNoClue {
|
||||
pendingSniffer = append(pendingSniffer, si)
|
||||
continue
|
||||
}
|
||||
|
||||
if err == nil && result != nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(pendingSniffer) > 0 {
|
||||
s.sniffer = pendingSniffer
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
|
||||
return nil, errUnknownContent
|
||||
}
|
||||
|
||||
func (s *Sniffer) SniffMetadata(c context.Context) (SniffResult, error) {
|
||||
var pendingSniffer []protocolSnifferWithMetadata
|
||||
for _, si := range s.sniffer {
|
||||
s := si.protocolSniffer
|
||||
if !si.metadataSniffer {
|
||||
pendingSniffer = append(pendingSniffer, si)
|
||||
continue
|
||||
}
|
||||
result, err := s(c, nil)
|
||||
if err == common.ErrNoClue {
|
||||
pendingSniffer = append(pendingSniffer, si)
|
||||
continue
|
||||
}
|
||||
|
||||
if err == nil && result != nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(pendingSniffer) > 0 {
|
||||
s.sniffer = pendingSniffer
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
|
||||
return nil, errUnknownContent
|
||||
}
|
||||
|
||||
func CompositeResult(domainResult SniffResult, protocolResult SniffResult) SniffResult {
|
||||
return &compositeResult{domainResult: domainResult, protocolResult: protocolResult}
|
||||
}
|
||||
|
||||
type compositeResult struct {
|
||||
domainResult SniffResult
|
||||
protocolResult SniffResult
|
||||
}
|
||||
|
||||
func (c compositeResult) Protocol() string {
|
||||
return c.protocolResult.Protocol()
|
||||
}
|
||||
|
||||
func (c compositeResult) Domain() string {
|
||||
return c.domainResult.Domain()
|
||||
}
|
||||
|
||||
func (c compositeResult) ProtocolForDomainResult() string {
|
||||
return c.domainResult.Protocol()
|
||||
}
|
||||
|
||||
type SnifferResultComposite interface {
|
||||
ProtocolForDomainResult() string
|
||||
}
|
||||
|
||||
type SnifferIsProtoSubsetOf interface {
|
||||
IsProtoSubsetOf(protocolName string) bool
|
||||
}
|
||||
25
core/xray/app/dispatcher/stats.go
Normal file
25
core/xray/app/dispatcher/stats.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package dispatcher
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/features/stats"
|
||||
)
|
||||
|
||||
type SizeStatWriter struct {
|
||||
Counter stats.Counter
|
||||
Writer buf.Writer
|
||||
}
|
||||
|
||||
func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
w.Counter.Add(int64(mb.Len()))
|
||||
return w.Writer.WriteMultiBuffer(mb)
|
||||
}
|
||||
|
||||
func (w *SizeStatWriter) Close() error {
|
||||
return common.Close(w.Writer)
|
||||
}
|
||||
|
||||
func (w *SizeStatWriter) Interrupt() {
|
||||
common.Interrupt(w.Writer)
|
||||
}
|
||||
44
core/xray/app/dispatcher/stats_test.go
Normal file
44
core/xray/app/dispatcher/stats_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package dispatcher_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/xtls/xray-core/app/dispatcher"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
)
|
||||
|
||||
type TestCounter int64
|
||||
|
||||
func (c *TestCounter) Value() int64 {
|
||||
return int64(*c)
|
||||
}
|
||||
|
||||
func (c *TestCounter) Add(v int64) int64 {
|
||||
x := int64(*c) + v
|
||||
*c = TestCounter(x)
|
||||
return x
|
||||
}
|
||||
|
||||
func (c *TestCounter) Set(v int64) int64 {
|
||||
*c = TestCounter(v)
|
||||
return v
|
||||
}
|
||||
|
||||
func TestStatsWriter(t *testing.T) {
|
||||
var c TestCounter
|
||||
writer := &SizeStatWriter{
|
||||
Counter: &c,
|
||||
Writer: buf.Discard,
|
||||
}
|
||||
|
||||
mb := buf.MergeBytes(nil, []byte("abcd"))
|
||||
common.Must(writer.WriteMultiBuffer(mb))
|
||||
|
||||
mb = buf.MergeBytes(nil, []byte("efg"))
|
||||
common.Must(writer.WriteMultiBuffer(mb))
|
||||
|
||||
if c.Value() != 7 {
|
||||
t.Fatal("unexpected counter value. want 7, but got ", c.Value())
|
||||
}
|
||||
}
|
||||
185
core/xray/core.go
Normal file
185
core/xray/core.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package xray
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
vCore "github.com/Yuzuki616/V2bX/core"
|
||||
"github.com/Yuzuki616/V2bX/core/xray/app/dispatcher"
|
||||
_ "github.com/Yuzuki616/V2bX/core/xray/distro/all"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/xtls/xray-core/app/proxyman"
|
||||
"github.com/xtls/xray-core/app/stats"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/inbound"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
statsFeature "github.com/xtls/xray-core/features/stats"
|
||||
coreConf "github.com/xtls/xray-core/infra/conf"
|
||||
)
|
||||
|
||||
func init() {
|
||||
vCore.RegisterCore("xray", New)
|
||||
}
|
||||
|
||||
// Core Structure
|
||||
type Core struct {
|
||||
access sync.Mutex
|
||||
Server *core.Instance
|
||||
ihm inbound.Manager
|
||||
ohm outbound.Manager
|
||||
shm statsFeature.Manager
|
||||
dispatcher *dispatcher.DefaultDispatcher
|
||||
}
|
||||
|
||||
func New(c *conf.CoreConfig) (vCore.Core, error) {
|
||||
return &Core{Server: getCore(c.XrayConfig)}, nil
|
||||
}
|
||||
|
||||
func parseConnectionConfig(c *conf.ConnectionConfig) (policy *coreConf.Policy) {
|
||||
policy = &coreConf.Policy{
|
||||
StatsUserUplink: true,
|
||||
StatsUserDownlink: true,
|
||||
Handshake: &c.Handshake,
|
||||
ConnectionIdle: &c.ConnIdle,
|
||||
UplinkOnly: &c.UplinkOnly,
|
||||
DownlinkOnly: &c.DownlinkOnly,
|
||||
BufferSize: &c.BufferSize,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getCore(c *conf.XrayConfig) *core.Instance {
|
||||
// Log Config
|
||||
coreLogConfig := &coreConf.LogConfig{}
|
||||
coreLogConfig.LogLevel = c.LogConfig.Level
|
||||
coreLogConfig.AccessLog = c.LogConfig.AccessPath
|
||||
coreLogConfig.ErrorLog = c.LogConfig.ErrorPath
|
||||
// DNS config
|
||||
coreDnsConfig := &coreConf.DNSConfig{}
|
||||
if c.DnsConfigPath != "" {
|
||||
if f, err := os.Open(c.DnsConfigPath); err != nil {
|
||||
log.Panicf("Failed to read DNS config file at: %s", c.DnsConfigPath)
|
||||
} else {
|
||||
if err = json.NewDecoder(f).Decode(coreDnsConfig); err != nil {
|
||||
log.Panicf("Failed to unmarshal DNS config: %s", c.DnsConfigPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
dnsConfig, err := coreDnsConfig.Build()
|
||||
if err != nil {
|
||||
log.Panicf("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help: %s", err)
|
||||
}
|
||||
// Routing config
|
||||
coreRouterConfig := &coreConf.RouterConfig{}
|
||||
if c.RouteConfigPath != "" {
|
||||
if f, err := os.Open(c.RouteConfigPath); err != nil {
|
||||
log.Panicf("Failed to read Routing config file at: %s", c.RouteConfigPath)
|
||||
} else {
|
||||
if err = json.NewDecoder(f).Decode(coreRouterConfig); err != nil {
|
||||
log.Panicf("Failed to unmarshal Routing config: %s", c.RouteConfigPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
routeConfig, err := coreRouterConfig.Build()
|
||||
if err != nil {
|
||||
log.Panicf("Failed to understand Routing config Please check: https://xtls.github.io/config/routing.html for help: %s", err)
|
||||
}
|
||||
// Custom Inbound config
|
||||
var coreCustomInboundConfig []coreConf.InboundDetourConfig
|
||||
if c.InboundConfigPath != "" {
|
||||
if f, err := os.Open(c.InboundConfigPath); err != nil {
|
||||
log.Panicf("Failed to read Custom Inbound config file at: %s", c.OutboundConfigPath)
|
||||
} else {
|
||||
if err = json.NewDecoder(f).Decode(&coreCustomInboundConfig); err != nil {
|
||||
log.Panicf("Failed to unmarshal Custom Inbound config: %s", c.OutboundConfigPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
var inBoundConfig []*core.InboundHandlerConfig
|
||||
for _, config := range coreCustomInboundConfig {
|
||||
oc, err := config.Build()
|
||||
if err != nil {
|
||||
log.Panicf("Failed to understand Inbound config, Please check: https://xtls.github.io/config/inbound.html for help: %s", err)
|
||||
}
|
||||
inBoundConfig = append(inBoundConfig, oc)
|
||||
}
|
||||
// Custom Outbound config
|
||||
var coreCustomOutboundConfig []coreConf.OutboundDetourConfig
|
||||
if c.OutboundConfigPath != "" {
|
||||
if f, err := os.Open(c.OutboundConfigPath); err != nil {
|
||||
log.Panicf("Failed to read Custom Outbound config file at: %s", c.OutboundConfigPath)
|
||||
} else {
|
||||
if err = json.NewDecoder(f).Decode(&coreCustomOutboundConfig); err != nil {
|
||||
log.Panicf("Failed to unmarshal Custom Outbound config: %s", c.OutboundConfigPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
var outBoundConfig []*core.OutboundHandlerConfig
|
||||
for _, config := range coreCustomOutboundConfig {
|
||||
oc, err := config.Build()
|
||||
if err != nil {
|
||||
log.Panicf("Failed to understand Outbound config, Please check: https://xtls.github.io/config/outbound.html for help: %s", err)
|
||||
}
|
||||
outBoundConfig = append(outBoundConfig, oc)
|
||||
}
|
||||
// Policy config
|
||||
levelPolicyConfig := parseConnectionConfig(c.ConnectionConfig)
|
||||
corePolicyConfig := &coreConf.PolicyConfig{}
|
||||
corePolicyConfig.Levels = map[uint32]*coreConf.Policy{0: levelPolicyConfig}
|
||||
policyConfig, _ := corePolicyConfig.Build()
|
||||
// Build Core conf
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(coreLogConfig.Build()),
|
||||
serial.ToTypedMessage(&dispatcher.Config{}),
|
||||
serial.ToTypedMessage(&stats.Config{}),
|
||||
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
||||
serial.ToTypedMessage(policyConfig),
|
||||
serial.ToTypedMessage(dnsConfig),
|
||||
serial.ToTypedMessage(routeConfig),
|
||||
},
|
||||
Inbound: inBoundConfig,
|
||||
Outbound: outBoundConfig,
|
||||
}
|
||||
server, err := core.New(config)
|
||||
if err != nil {
|
||||
log.Panicf("failed to create instance: %s", err)
|
||||
}
|
||||
log.Printf("Core Version: %s", core.Version())
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
// Start the Core
|
||||
func (c *Core) Start() error {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
if err := c.Server.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.shm = c.Server.GetFeature(statsFeature.ManagerType()).(statsFeature.Manager)
|
||||
c.ihm = c.Server.GetFeature(inbound.ManagerType()).(inbound.Manager)
|
||||
c.ohm = c.Server.GetFeature(outbound.ManagerType()).(outbound.Manager)
|
||||
c.dispatcher = c.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the core
|
||||
func (c *Core) Close() error {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
c.ihm = nil
|
||||
c.ohm = nil
|
||||
c.shm = nil
|
||||
c.dispatcher = nil
|
||||
err := c.Server.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
78
core/xray/distro/all/all.go
Normal file
78
core/xray/distro/all/all.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
// The following are necessary as they register handlers in their init functions.
|
||||
|
||||
// Mandatory features. Can't remove unless there are replacements.
|
||||
_ "github.com/xtls/xray-core/app/dispatcher"
|
||||
_ "github.com/xtls/xray-core/app/proxyman/inbound"
|
||||
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
||||
|
||||
// Default commander and all its services. This is an optional feature.
|
||||
//_ "github.com/xtls/xray-core/app/commander"
|
||||
//_ "github.com/xtls/xray-core/app/log/command"
|
||||
//_ "github.com/xtls/xray-core/app/proxyman/command"
|
||||
//_ "github.com/xtls/xray-core/app/stats/command"
|
||||
|
||||
// Developer preview services
|
||||
//_ "github.com/xtls/xray-core/app/observatory/command"
|
||||
|
||||
// Other optional features.
|
||||
_ "github.com/xtls/xray-core/app/dns"
|
||||
_ "github.com/xtls/xray-core/app/dns/fakedns"
|
||||
_ "github.com/xtls/xray-core/app/log"
|
||||
_ "github.com/xtls/xray-core/app/metrics"
|
||||
_ "github.com/xtls/xray-core/app/policy"
|
||||
_ "github.com/xtls/xray-core/app/reverse"
|
||||
_ "github.com/xtls/xray-core/app/router"
|
||||
_ "github.com/xtls/xray-core/app/stats"
|
||||
|
||||
// Fix dependency cycle caused by core import in internet package
|
||||
_ "github.com/xtls/xray-core/transport/internet/tagged/taggedimpl"
|
||||
|
||||
// Inbound and outbound proxies.
|
||||
_ "github.com/xtls/xray-core/proxy/blackhole"
|
||||
_ "github.com/xtls/xray-core/proxy/dns"
|
||||
_ "github.com/xtls/xray-core/proxy/dokodemo"
|
||||
_ "github.com/xtls/xray-core/proxy/freedom"
|
||||
_ "github.com/xtls/xray-core/proxy/http"
|
||||
_ "github.com/xtls/xray-core/proxy/loopback"
|
||||
_ "github.com/xtls/xray-core/proxy/mtproto"
|
||||
_ "github.com/xtls/xray-core/proxy/shadowsocks"
|
||||
_ "github.com/xtls/xray-core/proxy/socks"
|
||||
_ "github.com/xtls/xray-core/proxy/trojan"
|
||||
_ "github.com/xtls/xray-core/proxy/vless/inbound"
|
||||
_ "github.com/xtls/xray-core/proxy/vless/outbound"
|
||||
_ "github.com/xtls/xray-core/proxy/vmess/inbound"
|
||||
_ "github.com/xtls/xray-core/proxy/vmess/outbound"
|
||||
_ "github.com/xtls/xray-core/proxy/wireguard"
|
||||
|
||||
// Transports
|
||||
_ "github.com/xtls/xray-core/transport/internet/domainsocket"
|
||||
_ "github.com/xtls/xray-core/transport/internet/grpc"
|
||||
_ "github.com/xtls/xray-core/transport/internet/http"
|
||||
_ "github.com/xtls/xray-core/transport/internet/kcp"
|
||||
_ "github.com/xtls/xray-core/transport/internet/quic"
|
||||
_ "github.com/xtls/xray-core/transport/internet/reality"
|
||||
_ "github.com/xtls/xray-core/transport/internet/tcp"
|
||||
_ "github.com/xtls/xray-core/transport/internet/tls"
|
||||
_ "github.com/xtls/xray-core/transport/internet/udp"
|
||||
_ "github.com/xtls/xray-core/transport/internet/websocket"
|
||||
|
||||
// Transport headers
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/http"
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/noop"
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/srtp"
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/tls"
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/utp"
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/wechat"
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/wireguard"
|
||||
// JSON & TOML & YAML
|
||||
//_ "github.com/xtls/xray-core/main/json"
|
||||
//_ "github.com/xtls/xray-core/main/toml"
|
||||
//_ "github.com/xtls/xray-core/main/yaml"
|
||||
// Load config from file or http(s)
|
||||
//_ "github.com/xtls/xray-core/main/confloader/external"
|
||||
// Commands
|
||||
//_ "github.com/xtls/xray-core/main/commands/all"
|
||||
)
|
||||
83
core/xray/node.go
Normal file
83
core/xray/node.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package xray
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/common/builder"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/inbound"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
)
|
||||
|
||||
func (c *Core) AddNode(tag string, info *panel.NodeInfo, config *conf.ControllerConfig) error {
|
||||
inboundConfig, err := builder.BuildInbound(config, info, tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("build inbound error: %s", err)
|
||||
}
|
||||
err = c.addInbound(inboundConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add inbound error: %s", err)
|
||||
}
|
||||
outBoundConfig, err := builder.BuildOutbound(config, info, tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("build outbound error: %s", err)
|
||||
}
|
||||
err = c.addOutbound(outBoundConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add outbound error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) addInbound(config *core.InboundHandlerConfig) error {
|
||||
rawHandler, err := core.CreateObject(c.Server, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
handler, ok := rawHandler.(inbound.Handler)
|
||||
if !ok {
|
||||
return fmt.Errorf("not an InboundHandler: %s", err)
|
||||
}
|
||||
if err := c.ihm.AddHandler(context.Background(), handler); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) addOutbound(config *core.OutboundHandlerConfig) error {
|
||||
rawHandler, err := core.CreateObject(c.Server, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
handler, ok := rawHandler.(outbound.Handler)
|
||||
if !ok {
|
||||
return fmt.Errorf("not an InboundHandler: %s", err)
|
||||
}
|
||||
if err := c.ohm.AddHandler(context.Background(), handler); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) DelNode(tag string) error {
|
||||
err := c.removeInbound(tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("remove in error: %s", err)
|
||||
}
|
||||
err = c.removeOutbound(tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("remove out error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) removeInbound(tag string) error {
|
||||
return c.ihm.RemoveHandler(context.Background(), tag)
|
||||
}
|
||||
|
||||
func (c *Core) removeOutbound(tag string) error {
|
||||
err := c.ohm.RemoveHandler(context.Background(), tag)
|
||||
return err
|
||||
}
|
||||
100
core/xray/user.go
Normal file
100
core/xray/user.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package xray
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/builder"
|
||||
vCore "github.com/Yuzuki616/V2bX/core"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
)
|
||||
|
||||
func (c *Core) GetUserManager(tag string) (proxy.UserManager, error) {
|
||||
handler, err := c.ihm.GetHandler(context.Background(), tag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no such inbound tag: %s", err)
|
||||
}
|
||||
inboundInstance, ok := handler.(proxy.GetInbound)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("handler %s is not implement proxy.GetInbound", tag)
|
||||
}
|
||||
userManager, ok := inboundInstance.GetInbound().(proxy.UserManager)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("handler %s is not implement proxy.UserManager", tag)
|
||||
}
|
||||
return userManager, nil
|
||||
}
|
||||
|
||||
func (c *Core) DelUsers(users []string, tag string) error {
|
||||
userManager, err := c.GetUserManager(tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get user manager error: %s", err)
|
||||
}
|
||||
for _, email := range users {
|
||||
err = userManager.RemoveUser(context.Background(), email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) GetUserTraffic(email string, reset bool) (up int64, down int64) {
|
||||
upName := "user>>>" + email + ">>>traffic>>>uplink"
|
||||
downName := "user>>>" + email + ">>>traffic>>>downlink"
|
||||
upCounter := c.shm.GetCounter(upName)
|
||||
downCounter := c.shm.GetCounter(downName)
|
||||
if reset {
|
||||
if upCounter != nil {
|
||||
up = upCounter.Set(0)
|
||||
}
|
||||
if downCounter != nil {
|
||||
down = downCounter.Set(0)
|
||||
}
|
||||
} else {
|
||||
if upCounter != nil {
|
||||
up = upCounter.Value()
|
||||
}
|
||||
if downCounter != nil {
|
||||
down = downCounter.Value()
|
||||
}
|
||||
}
|
||||
return up, down
|
||||
}
|
||||
|
||||
func (c *Core) AddUsers(p *vCore.AddUsersParams) (added int, err error) {
|
||||
users := make([]*protocol.User, 0, len(p.UserInfo))
|
||||
switch p.NodeInfo.NodeType {
|
||||
case "v2ray":
|
||||
if p.Config.EnableVless {
|
||||
users = builder.BuildVlessUsers(p.Tag, p.UserInfo, p.Config.EnableXtls)
|
||||
} else {
|
||||
users = builder.BuildVmessUsers(p.Tag, p.UserInfo)
|
||||
}
|
||||
case "trojan":
|
||||
users = builder.BuildTrojanUsers(p.Tag, p.UserInfo)
|
||||
case "shadowsocks":
|
||||
users = builder.BuildSSUsers(p.Tag,
|
||||
p.UserInfo,
|
||||
p.NodeInfo.Cipher,
|
||||
p.NodeInfo.ServerKey)
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported node type: %s", p.NodeInfo.NodeType)
|
||||
}
|
||||
man, err := c.GetUserManager(p.Tag)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get user manager error: %s", err)
|
||||
}
|
||||
for _, u := range users {
|
||||
mUser, err := u.ToMemoryUser()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = man.AddUser(context.Background(), mUser)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return len(users), nil
|
||||
}
|
||||
Reference in New Issue
Block a user