From efcdffb20d64eefcfee84cd64d49ebe216839930 Mon Sep 17 00:00:00 2001 From: yuzuki999 Date: Thu, 28 Jul 2022 23:00:05 +0800 Subject: [PATCH] change project structure add across nodes ip limit add user ip recorder del config file watch --- .gitignore | 20 +- api/api.go | 19 +- app/{mydispatcher => dispatcher}/config.pb.go | 2 +- app/{mydispatcher => dispatcher}/config.proto | 0 app/{mydispatcher => dispatcher}/default.go | 2 +- .../dispatcher.go | 2 +- .../errors.generated.go | 2 +- .../fakednssniffer.go | 2 +- app/{mydispatcher => dispatcher}/sniffer.go | 2 +- app/{mydispatcher => dispatcher}/stats.go | 2 +- .../stats_test.go | 2 +- common/limiter/limiter.go | 108 ++++++--- conf/conf.go | 23 ++ conf/connetion.go | 19 ++ conf/log.go | 15 ++ service/controller/config.go => conf/node.go | 64 +++-- {main => example}/config.yml.example | 7 + {main => example}/custom_inbound.json | 0 {main => example}/custom_outbound.json | 0 {main => example}/dns.json | 0 {main => example}/geoip.dat | Bin {main => example}/geosite.dat | 0 {main => example}/route.json | 0 {main => example}/rulelist | 0 main/main.go => main.go | 63 +++-- .../controller => node}/inboundbuilder.go | 85 +++---- .../inboundbuilder_test.go | 4 +- .../controller/controller.go => node/node.go | 191 ++++++++------- .../controller_test.go => node/node_test.go | 6 +- .../controller => node}/outboundbuilder.go | 5 +- node/userbuilder.go | 118 ++++++++++ panel/config.go | 36 --- panel/defaultConfig.go | 30 --- panel/panel.go | 219 ------------------ service/controller/control.go | 170 -------------- service/controller/userbuilder.go | 154 ------------ service/service.go | 16 -- {main => xray}/distro/all/all.go | 12 +- xray/inbound.go | 61 +++++ xray/outbound.go | 30 +++ xray/rule.go | 24 ++ xray/user.go | 94 ++++++++ xray/xray.go | 161 +++++++++++++ 43 files changed, 877 insertions(+), 893 deletions(-) rename app/{mydispatcher => dispatcher}/config.pb.go (99%) rename app/{mydispatcher => dispatcher}/config.proto (100%) rename app/{mydispatcher => dispatcher}/default.go (99%) rename app/{mydispatcher => dispatcher}/dispatcher.go (87%) rename app/{mydispatcher => dispatcher}/errors.generated.go (90%) rename app/{mydispatcher => dispatcher}/fakednssniffer.go (99%) rename app/{mydispatcher => dispatcher}/sniffer.go (99%) rename app/{mydispatcher => dispatcher}/stats.go (95%) rename app/{mydispatcher => dispatcher}/stats_test.go (96%) create mode 100644 conf/conf.go create mode 100644 conf/connetion.go create mode 100644 conf/log.go rename service/controller/config.go => conf/node.go (58%) rename {main => example}/config.yml.example (93%) rename {main => example}/custom_inbound.json (100%) rename {main => example}/custom_outbound.json (100%) rename {main => example}/dns.json (100%) rename {main => example}/geoip.dat (100%) rename {main => example}/geosite.dat (100%) rename {main => example}/route.json (100%) rename {main => example}/rulelist (100%) rename main/main.go => main.go (67%) rename {service/controller => node}/inboundbuilder.go (68%) rename {service/controller => node}/inboundbuilder_test.go (96%) rename service/controller/controller.go => node/node.go (65%) rename service/controller/controller_test.go => node/node_test.go (93%) rename {service/controller => node}/outboundbuilder.go (87%) create mode 100644 node/userbuilder.go delete mode 100644 panel/config.go delete mode 100644 panel/defaultConfig.go delete mode 100644 panel/panel.go delete mode 100644 service/controller/control.go delete mode 100644 service/controller/userbuilder.go delete mode 100644 service/service.go rename {main => xray}/distro/all/all.go (84%) create mode 100644 xray/inbound.go create mode 100644 xray/outbound.go create mode 100644 xray/rule.go create mode 100644 xray/user.go create mode 100644 xray/xray.go diff --git a/.gitignore b/.gitignore index a8566f5..bd1bf3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,16 @@ -main/config.yml -main/main -main/XrayR -main/XrayR* -main/mytest -main/access.logo -main/error.log +example/config.yml +example/main +example/XrayR +example/XrayR* +example/mytest +example/access.logo +example/error.log api/chooseparser.go.bak common/Inboundbuilder/.lego/ common/legocmd/.lego/ .vscode/launch.json -main/.lego -main/cert -main/config.yml +example/.lego +example/cert +example/config.yml ./vscode .idea/* \ No newline at end of file diff --git a/api/api.go b/api/api.go index 3757cbe..5f697e9 100644 --- a/api/api.go +++ b/api/api.go @@ -4,6 +4,7 @@ package api import ( + "github.com/Yuzuki616/V2bX/conf" "github.com/go-resty/resty/v2" "log" "strconv" @@ -13,21 +14,6 @@ import ( // API is the interface for different panel's api. -type Config struct { - APIHost string `mapstructure:"ApiHost"` - NodeID int `mapstructure:"NodeID"` - Key string `mapstructure:"ApiKey"` - NodeType string `mapstructure:"NodeType"` - EnableVless bool `mapstructure:"EnableVless"` - EnableXTLS bool `mapstructure:"EnableXTLS"` - //EnableSS2022 bool `mapstructure:"EnableSS2022"` - Timeout int `mapstructure:"Timeout"` - SpeedLimit float64 `mapstructure:"SpeedLimit"` - DeviceLimit int `mapstructure:"DeviceLimit"` - RuleListPath string `mapstructure:"RuleListPath"` - DisableCustomConfig bool `mapstructure:"DisableCustomConfig"` -} - type ClientInfo struct { APIHost string NodeID int @@ -53,8 +39,7 @@ type Client struct { NodeRuleRspMd5 [16]byte } -func New(apiConfig *Config) API { - +func New(apiConfig *conf.ApiConfig) API { client := resty.New() client.SetRetryCount(3) if apiConfig.Timeout > 0 { diff --git a/app/mydispatcher/config.pb.go b/app/dispatcher/config.pb.go similarity index 99% rename from app/mydispatcher/config.pb.go rename to app/dispatcher/config.pb.go index 5a909d6..dfe83f0 100644 --- a/app/mydispatcher/config.pb.go +++ b/app/dispatcher/config.pb.go @@ -4,7 +4,7 @@ // protoc v3.19.4 // source: app/mydispatcher/config.proto -package mydispatcher +package dispatcher import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" diff --git a/app/mydispatcher/config.proto b/app/dispatcher/config.proto similarity index 100% rename from app/mydispatcher/config.proto rename to app/dispatcher/config.proto diff --git a/app/mydispatcher/default.go b/app/dispatcher/default.go similarity index 99% rename from app/mydispatcher/default.go rename to app/dispatcher/default.go index 891afe3..6659e57 100644 --- a/app/mydispatcher/default.go +++ b/app/dispatcher/default.go @@ -1,4 +1,4 @@ -package mydispatcher +package dispatcher //go:generate go run github.com/xtls/xray-core/common/errors/errorgen diff --git a/app/mydispatcher/dispatcher.go b/app/dispatcher/dispatcher.go similarity index 87% rename from app/mydispatcher/dispatcher.go rename to app/dispatcher/dispatcher.go index d7fdf22..29b954c 100644 --- a/app/mydispatcher/dispatcher.go +++ b/app/dispatcher/dispatcher.go @@ -1,4 +1,4 @@ // Package dispather implement the rate limiter and the onlie device counter -package mydispatcher +package dispatcher //go:generate go run github.com/xtls/xray-core/common/errors/errorgen diff --git a/app/mydispatcher/errors.generated.go b/app/dispatcher/errors.generated.go similarity index 90% rename from app/mydispatcher/errors.generated.go rename to app/dispatcher/errors.generated.go index 5350087..e13ee60 100644 --- a/app/mydispatcher/errors.generated.go +++ b/app/dispatcher/errors.generated.go @@ -1,4 +1,4 @@ -package mydispatcher +package dispatcher import "github.com/xtls/xray-core/common/errors" diff --git a/app/mydispatcher/fakednssniffer.go b/app/dispatcher/fakednssniffer.go similarity index 99% rename from app/mydispatcher/fakednssniffer.go rename to app/dispatcher/fakednssniffer.go index bf544fd..d6bbd63 100644 --- a/app/mydispatcher/fakednssniffer.go +++ b/app/dispatcher/fakednssniffer.go @@ -1,4 +1,4 @@ -package mydispatcher +package dispatcher import ( "context" diff --git a/app/mydispatcher/sniffer.go b/app/dispatcher/sniffer.go similarity index 99% rename from app/mydispatcher/sniffer.go rename to app/dispatcher/sniffer.go index 18c45e3..4cb8304 100644 --- a/app/mydispatcher/sniffer.go +++ b/app/dispatcher/sniffer.go @@ -1,4 +1,4 @@ -package mydispatcher +package dispatcher import ( "context" diff --git a/app/mydispatcher/stats.go b/app/dispatcher/stats.go similarity index 95% rename from app/mydispatcher/stats.go rename to app/dispatcher/stats.go index 5296ba6..8fac019 100644 --- a/app/mydispatcher/stats.go +++ b/app/dispatcher/stats.go @@ -1,4 +1,4 @@ -package mydispatcher +package dispatcher import ( "github.com/xtls/xray-core/common" diff --git a/app/mydispatcher/stats_test.go b/app/dispatcher/stats_test.go similarity index 96% rename from app/mydispatcher/stats_test.go rename to app/dispatcher/stats_test.go index 70745ae..6eca32a 100644 --- a/app/mydispatcher/stats_test.go +++ b/app/dispatcher/stats_test.go @@ -1,4 +1,4 @@ -package mydispatcher_test +package dispatcher_test import ( "testing" diff --git a/common/limiter/limiter.go b/common/limiter/limiter.go index 5df38b0..1ac9a5b 100644 --- a/common/limiter/limiter.go +++ b/common/limiter/limiter.go @@ -19,9 +19,9 @@ type UserInfo struct { type InboundInfo struct { Tag string NodeSpeedLimit uint64 - UserInfo *sync.Map // Key: Email value: UserInfo - BucketHub *sync.Map // key: Email, value: *ratelimit.Bucket - UserOnlineIP *sync.Map // Key: Email Value: *sync.Map: Key: IP, Value: UID + UserInfo *sync.Map // Key: Uid value: UserInfo + BucketHub *sync.Map // key: Uid, value: *ratelimit.Bucket + UserOnlineIP *sync.Map // Key: Uid Value: *sync.Map: Key: IP, Value: bool } type Limiter struct { @@ -49,7 +49,7 @@ func (l *Limiter) AddInboundLimiter(tag string, nodeInfo *api.NodeInfo, userList if (*userList)[i].DeviceLimit == 0 { (*userList)[i].DeviceLimit = nodeInfo.DeviceLimit }*/ - userMap.Store(fmt.Sprintf("%s|%s|%d", tag, (*userList)[i].GetUserEmail(), (*userList)[i].UID), UserInfo{ + userMap.Store(fmt.Sprintf("%s|%s|%d", tag, (*userList)[i].V2rayUser.Email, (*userList)[i].UID), UserInfo{ UID: (*userList)[i].UID, SpeedLimit: nodeInfo.SpeedLimit, DeviceLimit: nodeInfo.DeviceLimit, @@ -60,24 +60,19 @@ func (l *Limiter) AddInboundLimiter(tag string, nodeInfo *api.NodeInfo, userList return nil } -func (l *Limiter) UpdateInboundLimiter(tag string, nodeInfo *api.NodeInfo, updatedUserList *[]api.UserInfo, usersIndex *[]int) error { - +func (l *Limiter) UpdateInboundLimiter(tag string, nodeInfo *api.NodeInfo, updatedUserList *[]api.UserInfo) error { if value, ok := l.InboundInfo.Load(tag); ok { inboundInfo := value.(*InboundInfo) // Update User info - for _, u := range *usersIndex { - /*if (*updatedUserList)[u].SpeedLimit == 0 { - (*updatedUserList)[u].SpeedLimit = nodeInfo.SpeedLimit - } - if (*updatedUserList)[u].DeviceLimit == 0 { - (*updatedUserList)[u].DeviceLimit = nodeInfo.DeviceLimit - }*/ - inboundInfo.UserInfo.Store(fmt.Sprintf("%s|%s|%d", tag, (*updatedUserList)[u].GetUserEmail(), (*updatedUserList)[u].UID), UserInfo{ - UID: (*updatedUserList)[u].UID, + for i := range *updatedUserList { + inboundInfo.UserInfo.Store(fmt.Sprintf("%s|%s|%d", tag, + (*updatedUserList)[i].V2rayUser.Email, (*updatedUserList)[i].UID), UserInfo{ + UID: (*updatedUserList)[i].UID, SpeedLimit: nodeInfo.SpeedLimit, DeviceLimit: nodeInfo.DeviceLimit, }) - inboundInfo.BucketHub.Delete(fmt.Sprintf("%s|%s|%d", tag, (*updatedUserList)[u].GetUserEmail(), (*updatedUserList)[u].UID)) // Delete old limiter bucket + inboundInfo.BucketHub.Delete(fmt.Sprintf("%s|%s|%d", tag, + (*updatedUserList)[i].V2rayUser.Email, (*updatedUserList)[i].UID)) // Delete old limiter bucket } } else { return fmt.Errorf("no such inbound in limiter: %s", tag) @@ -90,34 +85,78 @@ func (l *Limiter) DeleteInboundLimiter(tag string) error { return nil } -func (l *Limiter) GetOnlineDevice(tag string) (*[]api.OnlineUser, error) { - onlineUser := make([]api.OnlineUser, 0) +type UserIp struct { + Uid int `json:"Uid"` + IPs []string `json:"Ips"` +} + +func (l *Limiter) GetOnlineUserIp(tag string) (*[]UserIp, error) { if value, ok := l.InboundInfo.Load(tag); ok { inboundInfo := value.(*InboundInfo) // Clear Speed Limiter bucket for users who are not online inboundInfo.BucketHub.Range(func(key, value interface{}) bool { - email := key.(string) - if _, exists := inboundInfo.UserOnlineIP.Load(email); !exists { - inboundInfo.BucketHub.Delete(email) + if _, exists := inboundInfo.UserOnlineIP.Load(key.(string)); !exists { + inboundInfo.BucketHub.Delete(key.(string)) } return true }) + onlineUser := make([]UserIp, 0) + var ipMap *sync.Map inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool { - ipMap := value.(*sync.Map) - ipMap.Range(func(key, value interface{}) bool { - ip := key.(string) - uid := value.(int) - onlineUser = append(onlineUser, api.OnlineUser{UID: uid, IP: ip}) + ipMap = value.(*sync.Map) + var ip []string + ipMap.Range(func(key, v interface{}) bool { + if v.(bool) { + ip = append(ip, key.(string)) + } return true }) - email := key.(string) - inboundInfo.UserOnlineIP.Delete(email) // Reset online device + if len(ip) > 0 { + if u, ok := inboundInfo.UserInfo.Load(key.(string)); ok { + onlineUser = append(onlineUser, UserIp{ + Uid: u.(UserInfo).UID, + IPs: ip, + }) + } + } return true }) + if len(onlineUser) == 0 { + return nil, nil + } + return &onlineUser, nil } else { return nil, fmt.Errorf("no such inbound in limiter: %s", tag) } - return &onlineUser, nil +} + +func (l *Limiter) UpdateOnlineUserIP(tag string, userIpList *[]UserIp) { + if v, ok := l.InboundInfo.Load(tag); ok { + inboundInfo := v.(*InboundInfo) + //Clear old IP + inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool { + inboundInfo.UserOnlineIP.Delete(key) + return true + }) + // Update User Online IP + for i := range *userIpList { + ipMap := new(sync.Map) + for _, userIp := range (*userIpList)[i].IPs { + ipMap.Store(userIp, false) + } + inboundInfo.UserOnlineIP.Store((*userIpList)[i].Uid, ipMap) + } + } +} + +func (l *Limiter) ClearOnlineUserIP(tag string) { + if v, ok := l.InboundInfo.Load(tag); ok { + inboundInfo := v.(*InboundInfo) + inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool { + inboundInfo.UserOnlineIP.Delete(key) + return true + }) + } } func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *ratelimit.Bucket, SpeedLimit bool, Reject bool) { @@ -126,21 +165,18 @@ func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *r nodeLimit := inboundInfo.NodeSpeedLimit var userLimit uint64 = 0 var deviceLimit = 0 - var uid = 0 if v, ok := inboundInfo.UserInfo.Load(email); ok { u := v.(UserInfo) - uid = u.UID userLimit = u.SpeedLimit deviceLimit = u.DeviceLimit } - // Report online device ipMap := new(sync.Map) - ipMap.Store(ip, uid) + ipMap.Store(ip, true) // If any device is online if v, ok := inboundInfo.UserOnlineIP.LoadOrStore(email, ipMap); ok { ipMap := v.(*sync.Map) // If this ip is a new device - if _, ok := ipMap.LoadOrStore(ip, uid); !ok { + if online, ok := ipMap.LoadOrStore(ip, true); !ok { counter := 0 ipMap.Range(func(key, value interface{}) bool { counter++ @@ -150,6 +186,10 @@ func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *r ipMap.Delete(ip) return nil, false, true } + } else { + if !online.(bool) { + ipMap.Store(ip, true) + } } } limit := determineRate(nodeLimit, userLimit) // If need the Speed limit diff --git a/conf/conf.go b/conf/conf.go new file mode 100644 index 0000000..884c8e8 --- /dev/null +++ b/conf/conf.go @@ -0,0 +1,23 @@ +package conf + +type Conf struct { + LogConfig *LogConfig `mapstructure:"Log"` + DnsConfigPath string `mapstructure:"DnsConfigPath"` + InboundConfigPath string `mapstructure:"InboundConfigPath"` + OutboundConfigPath string `mapstructure:"OutboundConfigPath"` + RouteConfigPath string `mapstructure:"RouteConfigPath"` + ConnectionConfig *ConnetionConfig `mapstructure:"ConnectionConfig"` + NodesConfig []*NodeConfig `mapstructure:"Nodes"` +} + +func New() *Conf { + return &Conf{ + LogConfig: NewLogConfig(), + DnsConfigPath: "", + InboundConfigPath: "", + OutboundConfigPath: "", + RouteConfigPath: "", + ConnectionConfig: NewConnetionConfig(), + NodesConfig: []*NodeConfig{}, + } +} diff --git a/conf/connetion.go b/conf/connetion.go new file mode 100644 index 0000000..39e7544 --- /dev/null +++ b/conf/connetion.go @@ -0,0 +1,19 @@ +package conf + +type ConnetionConfig struct { + Handshake uint32 `mapstructure:"handshake"` + ConnIdle uint32 `mapstructure:"connIdle"` + UplinkOnly uint32 `mapstructure:"uplinkOnly"` + DownlinkOnly uint32 `mapstructure:"downlinkOnly"` + BufferSize int32 `mapstructure:"bufferSize"` +} + +func NewConnetionConfig() *ConnetionConfig { + return &ConnetionConfig{ + Handshake: 4, + ConnIdle: 30, + UplinkOnly: 2, + DownlinkOnly: 4, + BufferSize: 64, + } +} diff --git a/conf/log.go b/conf/log.go new file mode 100644 index 0000000..24fea7a --- /dev/null +++ b/conf/log.go @@ -0,0 +1,15 @@ +package conf + +type LogConfig struct { + Level string `mapstructure:"Level"` + AccessPath string `mapstructure:"AccessPath"` + ErrorPath string `mapstructure:"ErrorPath"` +} + +func NewLogConfig() *LogConfig { + return &LogConfig{ + Level: "none", + AccessPath: "", + ErrorPath: "", + } +} diff --git a/service/controller/config.go b/conf/node.go similarity index 58% rename from service/controller/config.go rename to conf/node.go index 36907aa..d78bc28 100644 --- a/service/controller/config.go +++ b/conf/node.go @@ -1,20 +1,4 @@ -package controller - -type Config struct { - ListenIP string `mapstructure:"ListenIP"` - SendIP string `mapstructure:"SendIP"` - UpdatePeriodic int `mapstructure:"UpdatePeriodic"` - CertConfig *CertConfig `mapstructure:"CertConfig"` - EnableDNS bool `mapstructure:"EnableDNS"` - DNSType string `mapstructure:"DNSType"` - DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"` - DisableGetRule bool `mapstructure:"DisableGetRule"` - EnableProxyProtocol bool `mapstructure:"EnableProxyProtocol"` - EnableFallback bool `mapstructure:"EnableFallback"` - DisableIVCheck bool `mapstructure:"DisableIVCheck"` - DisableSniffing bool `mapstructure:"DisableSniffing"` - FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"` -} +package conf type CertConfig struct { CertMode string `mapstructure:"CertMode"` // none, file, http, dns @@ -34,3 +18,49 @@ type FallBackConfig struct { Dest string `mapstructure:"Dest"` ProxyProtocolVer uint64 `mapstructure:"ProxyProtocolVer"` } + +type IpReportConfig struct { + Url string `mapstructure:"Url"` + Token string `mapstructure:"Token"` + Periodic int `mapstructure:"Periodic"` + Timeout int `mapstructure:"Timeout"` + EnableIpSync bool `mapstructure:"EnableIpSync"` +} + +type ControllerConfig struct { + ListenIP string `mapstructure:"ListenIP"` + SendIP string `mapstructure:"SendIP"` + UpdatePeriodic int `mapstructure:"UpdatePeriodic"` + EnableDNS bool `mapstructure:"EnableDNS"` + DNSType string `mapstructure:"DNSType"` + DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"` + DisableGetRule bool `mapstructure:"DisableGetRule"` + EnableProxyProtocol bool `mapstructure:"EnableProxyProtocol"` + EnableFallback bool `mapstructure:"EnableFallback"` + DisableIVCheck bool `mapstructure:"DisableIVCheck"` + DisableSniffing bool `mapstructure:"DisableSniffing"` + FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"` + EnableIpRecorder bool `mapstructure:"EnableIpRecorder"` + IpRecorderConfig *IpReportConfig `mapstructure:"IpRecorderConfig"` + CertConfig *CertConfig `mapstructure:"CertConfig"` +} + +type ApiConfig struct { + APIHost string `mapstructure:"ApiHost"` + NodeID int `mapstructure:"NodeID"` + Key string `mapstructure:"ApiKey"` + NodeType string `mapstructure:"NodeType"` + EnableVless bool `mapstructure:"EnableVless"` + EnableXTLS bool `mapstructure:"EnableXTLS"` + //EnableSS2022 bool `mapstructure:"EnableSS2022"` + Timeout int `mapstructure:"Timeout"` + SpeedLimit float64 `mapstructure:"SpeedLimit"` + DeviceLimit int `mapstructure:"DeviceLimit"` + RuleListPath string `mapstructure:"RuleListPath"` + DisableCustomConfig bool `mapstructure:"DisableCustomConfig"` +} + +type NodeConfig struct { + ApiConfig *ApiConfig `mapstructure:"ApiConfig"` + ControllerConfig *ControllerConfig `mapstructure:"ControllerConfig"` +} diff --git a/main/config.yml.example b/example/config.yml.example similarity index 93% rename from main/config.yml.example rename to example/config.yml.example index 78129c2..008f3e0 100644 --- a/main/config.yml.example +++ b/example/config.yml.example @@ -40,6 +40,13 @@ Nodes: Path: # HTTP PATH, Empty for any Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details. ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for dsable + EnableIpRecorder: false # Enable online ip report + IpRecorderConfig: + Url: "http://127.0.0.1:123" # Report url + Token: "123" # Report token + Periodic: 60 # Report interval, sec. + Timeout: 10 # Report timeout, sec. + EnableIpSync: false # Enable online ip sync CertConfig: CertMode: dns # Option about how to get certificate: none, file, http, dns. Choose "none" will forcedly disable the tls config. CertDomain: "node1.test.com" # Domain to cert diff --git a/main/custom_inbound.json b/example/custom_inbound.json similarity index 100% rename from main/custom_inbound.json rename to example/custom_inbound.json diff --git a/main/custom_outbound.json b/example/custom_outbound.json similarity index 100% rename from main/custom_outbound.json rename to example/custom_outbound.json diff --git a/main/dns.json b/example/dns.json similarity index 100% rename from main/dns.json rename to example/dns.json diff --git a/main/geoip.dat b/example/geoip.dat similarity index 100% rename from main/geoip.dat rename to example/geoip.dat diff --git a/main/geosite.dat b/example/geosite.dat similarity index 100% rename from main/geosite.dat rename to example/geosite.dat diff --git a/main/route.json b/example/route.json similarity index 100% rename from main/route.json rename to example/route.json diff --git a/main/rulelist b/example/rulelist similarity index 100% rename from main/rulelist rename to example/rulelist diff --git a/main/main.go b/main.go similarity index 67% rename from main/main.go rename to main.go index 18bca9e..7b4ec92 100644 --- a/main/main.go +++ b/main.go @@ -3,6 +3,11 @@ package main import ( "flag" "fmt" + "github.com/Yuzuki616/V2bX/api" + "github.com/Yuzuki616/V2bX/conf" + "github.com/Yuzuki616/V2bX/node" + "github.com/Yuzuki616/V2bX/xray" + "github.com/spf13/viper" "log" "os" "os/signal" @@ -10,11 +15,6 @@ import ( "runtime" "strings" "syscall" - "time" - - "github.com/Yuzuki616/V2bX/panel" - "github.com/fsnotify/fsnotify" - "github.com/spf13/viper" ) var ( @@ -23,7 +23,7 @@ var ( ) var ( - version = "v0.0.2" + version = "v0.0.4" codename = "V2bX" intro = "A V2board backend based on Xray" ) @@ -34,7 +34,6 @@ func showVersion() { func getConfig() *viper.Viper { config := viper.New() - // Set custom path and name if *configFile != "" { configName := path.Base(*configFile) @@ -52,46 +51,44 @@ func getConfig() *viper.Viper { config.SetConfigName("config") config.SetConfigType("yml") config.AddConfigPath(".") - } - if err := config.ReadInConfig(); err != nil { log.Panicf("Fatal error config file: %s \n", err) } - - config.WatchConfig() // Watch the config - return config } +func startNodes(nodes []*conf.NodeConfig, core *xray.Xray) error { + for i, _ := range nodes { + var apiClient = api.New(nodes[i].ApiConfig) + // Register controller service + err := node.New(core, apiClient, nodes[i].ControllerConfig).Start() + if err != nil { + return fmt.Errorf("start node controller error: %v", err) + } + } + return nil +} + func main() { flag.Parse() showVersion() if *printVersion { return } - config := getConfig() - panelConfig := &panel.Config{} - config.Unmarshal(panelConfig) - p := panel.New(panelConfig) - lastTime := time.Now() - config.OnConfigChange(func(e fsnotify.Event) { - // Discarding event received within a short period of time after receiving an event. - if time.Now().After(lastTime.Add(3 * time.Second)) { - // Hot reload function - fmt.Println("Config file changed:", e.Name) - p.Close() - // Delete old instance and trigger GC - runtime.GC() - config.Unmarshal(panelConfig) - p.Start() - lastTime = time.Now() - } - }) - p.Start() - defer p.Close() - + c := conf.New() + err := config.Unmarshal(c) + if err != nil { + log.Panicf("can't unmarshal config file: %s \n", err) + } + x := xray.New(c) + x.Start() + defer x.Close() + err = startNodes(c.NodesConfig, x) + if err != nil { + log.Panicf("run nodes error: %v", err) + } //Explicitly triggering GC to remove garbage from config loading. runtime.GC() // Running backend diff --git a/service/controller/inboundbuilder.go b/node/inboundbuilder.go similarity index 68% rename from service/controller/inboundbuilder.go rename to node/inboundbuilder.go index 37ad2a4..c670d98 100644 --- a/service/controller/inboundbuilder.go +++ b/node/inboundbuilder.go @@ -1,19 +1,20 @@ -// Package controller the InbounderConfig used by add inbound -package controller +// Package node the InbounderConfig used by add inbound +package node import ( "encoding/json" "fmt" "github.com/Yuzuki616/V2bX/api" "github.com/Yuzuki616/V2bX/common/legoCmd" + "github.com/Yuzuki616/V2bX/conf" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/uuid" "github.com/xtls/xray-core/core" - "github.com/xtls/xray-core/infra/conf" + coreConf "github.com/xtls/xray-core/infra/conf" ) //InboundBuilder build Inbound config for different protocol -func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.InboundHandlerConfig, error) { +func InboundBuilder(config *conf.ControllerConfig, nodeInfo *api.NodeInfo, tag string) (*core.InboundHandlerConfig, error) { var proxySetting interface{} if nodeInfo.NodeType == "V2ray" { defer func() { @@ -25,7 +26,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I if config.EnableFallback { fallbackConfigs, err := buildVlessFallbacks(config.FallBackConfigs) if err == nil { - proxySetting = &conf.VLessInboundConfig{ + proxySetting = &coreConf.VLessInboundConfig{ Decryption: "none", Fallbacks: fallbackConfigs, } @@ -33,13 +34,13 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I return nil, err } } else { - proxySetting = &conf.VLessInboundConfig{ + proxySetting = &coreConf.VLessInboundConfig{ Decryption: "none", } } } else { nodeInfo.V2ray.Inbounds[0].Protocol = "vmess" - proxySetting = &conf.VMessInboundConfig{} + proxySetting = &coreConf.VMessInboundConfig{} } } else if nodeInfo.NodeType == "Trojan" { defer func() { @@ -47,55 +48,55 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I nodeInfo.Trojan = nil }() nodeInfo.V2ray = &api.V2rayConfig{} - nodeInfo.V2ray.Inbounds = make([]conf.InboundDetourConfig, 1) + nodeInfo.V2ray.Inbounds = make([]coreConf.InboundDetourConfig, 1) nodeInfo.V2ray.Inbounds[0].Protocol = "trojan" // Enable fallback if config.EnableFallback { fallbackConfigs, err := buildTrojanFallbacks(config.FallBackConfigs) if err == nil { - proxySetting = &conf.TrojanServerConfig{ + proxySetting = &coreConf.TrojanServerConfig{ Fallbacks: fallbackConfigs, } } else { return nil, err } } else { - proxySetting = &conf.TrojanServerConfig{} + proxySetting = &coreConf.TrojanServerConfig{} } - nodeInfo.V2ray.Inbounds[0].PortList = &conf.PortList{ - Range: []conf.PortRange{{From: uint32(nodeInfo.Trojan.LocalPort), To: uint32(nodeInfo.Trojan.LocalPort)}}, + nodeInfo.V2ray.Inbounds[0].PortList = &coreConf.PortList{ + Range: []coreConf.PortRange{{From: uint32(nodeInfo.Trojan.LocalPort), To: uint32(nodeInfo.Trojan.LocalPort)}}, } - t := conf.TransportProtocol(nodeInfo.Trojan.TransportProtocol) - nodeInfo.V2ray.Inbounds[0].StreamSetting = &conf.StreamConfig{Network: &t} + t := coreConf.TransportProtocol(nodeInfo.Trojan.TransportProtocol) + nodeInfo.V2ray.Inbounds[0].StreamSetting = &coreConf.StreamConfig{Network: &t} } else if nodeInfo.NodeType == "Shadowsocks" { defer func() { nodeInfo.V2ray = nil }() nodeInfo.V2ray = &api.V2rayConfig{} - nodeInfo.V2ray.Inbounds = []conf.InboundDetourConfig{{Protocol: "shadowsocks"}} - proxySetting = &conf.ShadowsocksServerConfig{} + nodeInfo.V2ray.Inbounds = []coreConf.InboundDetourConfig{{Protocol: "shadowsocks"}} + proxySetting = &coreConf.ShadowsocksServerConfig{} randomPasswd := uuid.New() - defaultSSuser := &conf.ShadowsocksUserConfig{ + defaultSSuser := &coreConf.ShadowsocksUserConfig{ Cipher: "aes-128-gcm", Password: randomPasswd.String(), } - proxySetting, _ := proxySetting.(*conf.ShadowsocksServerConfig) + proxySetting, _ := proxySetting.(*coreConf.ShadowsocksServerConfig) proxySetting.Users = append(proxySetting.Users, defaultSSuser) - proxySetting.NetworkList = &conf.NetworkList{"tcp", "udp"} + proxySetting.NetworkList = &coreConf.NetworkList{"tcp", "udp"} proxySetting.IVCheck = true if config.DisableIVCheck { proxySetting.IVCheck = false } - nodeInfo.V2ray.Inbounds[0].PortList = &conf.PortList{ - Range: []conf.PortRange{{From: uint32(nodeInfo.SS.Port), To: uint32(nodeInfo.SS.Port)}}, + nodeInfo.V2ray.Inbounds[0].PortList = &coreConf.PortList{ + Range: []coreConf.PortRange{{From: uint32(nodeInfo.SS.Port), To: uint32(nodeInfo.SS.Port)}}, } - t := conf.TransportProtocol(nodeInfo.SS.TransportProtocol) - nodeInfo.V2ray.Inbounds[0].StreamSetting = &conf.StreamConfig{Network: &t} + t := coreConf.TransportProtocol(nodeInfo.SS.TransportProtocol) + nodeInfo.V2ray.Inbounds[0].StreamSetting = &coreConf.StreamConfig{Network: &t} } else { return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks", nodeInfo.NodeType) } /*else if nodeInfo.NodeType == "dokodemo-door" { nodeInfo.V2ray = &api.V2rayConfig{} - nodeInfo.V2ray.Inbounds = make([]conf.InboundDetourConfig, 1) + nodeInfo.V2ray.Inbounds = make([]coreConf.InboundDetourConfig, 1) nodeInfo.V2ray.Inbounds[0].Protocol = "dokodemo-door" proxySetting = struct { Host string `json:"address"` @@ -107,11 +108,11 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I }*/ // Build Listen IP address ipAddress := net.ParseAddress(config.ListenIP) - nodeInfo.V2ray.Inbounds[0].ListenOn = &conf.Address{Address: ipAddress} + nodeInfo.V2ray.Inbounds[0].ListenOn = &coreConf.Address{Address: ipAddress} // SniffingConfig - sniffingConfig := &conf.SniffingConfig{ + sniffingConfig := &coreConf.SniffingConfig{ Enabled: true, - DestOverride: &conf.StringList{"http", "tls"}, + DestOverride: &coreConf.StringList{"http", "tls"}, } if config.DisableSniffing { sniffingConfig.Enabled = false @@ -127,16 +128,16 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err) } if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network == "tcp" { - if nodeInfo.NodeType == "V2ray" { + if nodeInfo.V2ray.Inbounds[0].StreamSetting.TCPSettings != nil { nodeInfo.V2ray.Inbounds[0].StreamSetting.TCPSettings.AcceptProxyProtocol = config.EnableProxyProtocol } else { - tcpSetting := &conf.TCPConfig{ + tcpSetting := &coreConf.TCPConfig{ AcceptProxyProtocol: config.EnableProxyProtocol, } nodeInfo.V2ray.Inbounds[0].StreamSetting.TCPSettings = tcpSetting } } else if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network == "ws" { - nodeInfo.V2ray.Inbounds[0].StreamSetting.WSSettings = &conf.WebSocketConfig{ + nodeInfo.V2ray.Inbounds[0].StreamSetting.WSSettings = &coreConf.WebSocketConfig{ AcceptProxyProtocol: config.EnableProxyProtocol} } // Build TLS and XTLS settings @@ -147,17 +148,17 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I return nil, err } if nodeInfo.TLSType == "tls" { - tlsSettings := &conf.TLSConfig{ + tlsSettings := &coreConf.TLSConfig{ RejectUnknownSNI: config.CertConfig.RejectUnknownSni, } - tlsSettings.Certs = append(tlsSettings.Certs, &conf.TLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600}) + tlsSettings.Certs = append(tlsSettings.Certs, &coreConf.TLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600}) nodeInfo.V2ray.Inbounds[0].StreamSetting.TLSSettings = tlsSettings } else if nodeInfo.TLSType == "xtls" { - xtlsSettings := &conf.XTLSConfig{ + xtlsSettings := &coreConf.XTLSConfig{ RejectUnknownSNI: config.CertConfig.RejectUnknownSni, } - xtlsSettings.Certs = append(xtlsSettings.Certs, &conf.XTLSCertConfig{ + xtlsSettings.Certs = append(xtlsSettings.Certs, &coreConf.XTLSCertConfig{ CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600}) @@ -168,7 +169,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network != "tcp" && *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network != "ws" && config.EnableProxyProtocol { - sockoptConfig := &conf.SocketConfig{ + sockoptConfig := &coreConf.SocketConfig{ AcceptProxyProtocol: config.EnableProxyProtocol, } nodeInfo.V2ray.Inbounds[0].StreamSetting.SocketSettings = sockoptConfig @@ -178,7 +179,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I return nodeInfo.V2ray.Inbounds[0].Build() } -func getCertFile(certConfig *CertConfig) (certFile string, keyFile string, err error) { +func getCertFile(certConfig *conf.CertConfig) (certFile string, keyFile string, err error) { if certConfig.CertMode == "file" { if certConfig.CertFile == "" || certConfig.KeyFile == "" { return "", "", fmt.Errorf("cert file path or key file path not exist") @@ -209,12 +210,12 @@ func getCertFile(certConfig *CertConfig) (certFile string, keyFile string, err e return "", "", fmt.Errorf("unsupported certmode: %s", certConfig.CertMode) } -func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboundFallback, error) { +func buildVlessFallbacks(fallbackConfigs []*conf.FallBackConfig) ([]*coreConf.VLessInboundFallback, error) { if fallbackConfigs == nil { return nil, fmt.Errorf("you must provide FallBackConfigs") } - vlessFallBacks := make([]*conf.VLessInboundFallback, len(fallbackConfigs)) + vlessFallBacks := make([]*coreConf.VLessInboundFallback, len(fallbackConfigs)) for i, c := range fallbackConfigs { if c.Dest == "" { @@ -226,7 +227,7 @@ func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboun if err != nil { return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err) } - vlessFallBacks[i] = &conf.VLessInboundFallback{ + vlessFallBacks[i] = &coreConf.VLessInboundFallback{ Name: c.SNI, Alpn: c.Alpn, Path: c.Path, @@ -237,12 +238,12 @@ func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboun return vlessFallBacks, nil } -func buildTrojanFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.TrojanInboundFallback, error) { +func buildTrojanFallbacks(fallbackConfigs []*conf.FallBackConfig) ([]*coreConf.TrojanInboundFallback, error) { if fallbackConfigs == nil { return nil, fmt.Errorf("you must provide FallBackConfigs") } - trojanFallBacks := make([]*conf.TrojanInboundFallback, len(fallbackConfigs)) + trojanFallBacks := make([]*coreConf.TrojanInboundFallback, len(fallbackConfigs)) for i, c := range fallbackConfigs { if c.Dest == "" { @@ -254,7 +255,7 @@ func buildTrojanFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.TrojanInbo if err != nil { return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err) } - trojanFallBacks[i] = &conf.TrojanInboundFallback{ + trojanFallBacks[i] = &coreConf.TrojanInboundFallback{ Name: c.SNI, Alpn: c.Alpn, Path: c.Path, diff --git a/service/controller/inboundbuilder_test.go b/node/inboundbuilder_test.go similarity index 96% rename from service/controller/inboundbuilder_test.go rename to node/inboundbuilder_test.go index 75540be..5c1f605 100644 --- a/service/controller/inboundbuilder_test.go +++ b/node/inboundbuilder_test.go @@ -1,10 +1,10 @@ -package controller_test +package node_test import ( + . "github.com/Yuzuki616/V2bX/node" "testing" "github.com/Yuzuki616/V2bX/api" - . "github.com/Yuzuki616/V2bX/service/controller" ) func TestBuildV2ray(t *testing.T) { diff --git a/service/controller/controller.go b/node/node.go similarity index 65% rename from service/controller/controller.go rename to node/node.go index 58ee3e9..ef909df 100644 --- a/service/controller/controller.go +++ b/node/node.go @@ -1,7 +1,12 @@ -package controller +package node import ( "fmt" + "github.com/Yuzuki616/V2bX/common/limiter" + "github.com/Yuzuki616/V2bX/conf" + "github.com/Yuzuki616/V2bX/xray" + "github.com/go-resty/resty/v2" + "github.com/goccy/go-json" "log" "math" "reflect" @@ -12,12 +17,11 @@ import ( "github.com/Yuzuki616/V2bX/common/legoCmd" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/task" - "github.com/xtls/xray-core/core" ) -type Controller struct { - server *core.Instance - config *Config +type Node struct { + server *xray.Xray + config *conf.ControllerConfig clientInfo api.ClientInfo apiClient api.API nodeInfo *api.NodeInfo @@ -25,12 +29,12 @@ type Controller struct { userList *[]api.UserInfo nodeInfoMonitorPeriodic *task.Periodic userReportPeriodic *task.Periodic - panelType string + onlineIpReportPeriodic *task.Periodic } -// New return a Controller service with default parameters. -func New(server *core.Instance, api api.API, config *Config) *Controller { - controller := &Controller{ +// New return a Node service with default parameters. +func New(server *xray.Xray, api api.API, config *conf.ControllerConfig) *Node { + controller := &Node{ server: server, config: config, apiClient: api, @@ -39,7 +43,7 @@ func New(server *core.Instance, api api.API, config *Config) *Controller { } // Start implement the Start() function of the service interface -func (c *Controller) Start() error { +func (c *Node) Start() error { c.clientInfo = c.apiClient.Describe() // First fetch Node Info newNodeInfo, err := c.apiClient.GetNodeInfo() @@ -66,7 +70,7 @@ func (c *Controller) Start() error { } //sync controller userList c.userList = userInfo - if err := c.AddInboundLimiter(c.Tag, userInfo); err != nil { + if err := c.server.AddInboundLimiter(c.Tag, c.nodeInfo, userInfo); err != nil { log.Print(err) } // Add Rule Manager @@ -74,11 +78,11 @@ func (c *Controller) Start() error { if ruleList, protocolRule, err := c.apiClient.GetNodeRule(); err != nil { log.Printf("Get rule list filed: %s", err) } else if len(*ruleList) > 0 { - if err := c.UpdateRule(c.Tag, *ruleList); err != nil { + if err := c.server.UpdateRule(c.Tag, *ruleList); err != nil { log.Print(err) } if len(*protocolRule) > 0 { - if err := c.UpdateProtocolRule(c.Tag, *protocolRule); err != nil { + if err := c.server.UpdateProtocolRule(c.Tag, *protocolRule); err != nil { log.Print(err) } } @@ -105,12 +109,24 @@ func (c *Controller) Start() error { time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second) _ = c.userReportPeriodic.Start() }() + if c.config.EnableIpRecorder { + c.onlineIpReportPeriodic = &task.Periodic{ + Interval: time.Duration(c.config.UpdatePeriodic) * 30, + Execute: c.onlineIpReport, + } + log.Printf("[%s: %d] Start report online ip", c.nodeInfo.NodeType, c.nodeInfo.NodeId) + // delay to start onlineIpReport + go func() { + time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second) + _ = c.onlineIpReportPeriodic.Start() + }() + } runtime.GC() return nil } // Close implement the Close() function of the service interface -func (c *Controller) Close() error { +func (c *Node) Close() error { if c.nodeInfoMonitorPeriodic != nil { err := c.nodeInfoMonitorPeriodic.Close() if err != nil { @@ -124,10 +140,16 @@ func (c *Controller) Close() error { log.Panicf("user report periodic close failed: %s", err) } } + if c.onlineIpReportPeriodic != nil { + err := c.onlineIpReportPeriodic.Close() + if err != nil { + log.Panicf("online ip report periodic close failed: %s", err) + } + } return nil } -func (c *Controller) nodeInfoMonitor() (err error) { +func (c *Node) nodeInfoMonitor() (err error) { // First fetch Node Info newNodeInfo, err := c.apiClient.GetNodeInfo() if err != nil { @@ -156,7 +178,7 @@ func (c *Controller) nodeInfoMonitor() (err error) { } nodeInfoChanged = true // Remove Old limiter - if err = c.DeleteInboundLimiter(oldtag); err != nil { + if err = c.server.DeleteInboundLimiter(oldtag); err != nil { log.Print(err) return nil } @@ -168,11 +190,11 @@ func (c *Controller) nodeInfoMonitor() (err error) { if ruleList, protocolRule, err := c.apiClient.GetNodeRule(); err != nil { log.Printf("Get rule list filed: %s", err) } else if len(*ruleList) > 0 { - if err := c.UpdateRule(c.Tag, *ruleList); err != nil { + if err := c.server.UpdateRule(c.Tag, *ruleList); err != nil { log.Print(err) } if len(*protocolRule) > 0 { - if err := c.UpdateProtocolRule(c.Tag, *protocolRule); err != nil { + if err := c.server.UpdateProtocolRule(c.Tag, *protocolRule); err != nil { log.Print(err) } } @@ -210,37 +232,37 @@ func (c *Controller) nodeInfoMonitor() (err error) { } newNodeInfo = nil // Add Limiter - if err := c.AddInboundLimiter(c.Tag, c.userList); err != nil { + if err := c.server.AddInboundLimiter(c.Tag, c.nodeInfo, c.userList); err != nil { log.Print(err) return nil } runtime.GC() } else { deleted, added := compareUserList(c.userList, newUserInfo) - if len(deleted) > 0 { - deletedEmail := make([]string, len(deleted)) - for i := range deleted { + if len(*deleted) > 0 { + deletedEmail := make([]string, len(*deleted)) + for i := range *deleted { deletedEmail[i] = fmt.Sprintf("%s|%s|%d", c.Tag, - (*c.userList)[deleted[i]].GetUserEmail(), - (*c.userList)[deleted[i]].UID) + (*deleted)[i].GetUserEmail(), + (*deleted)[i].UID) } - err := c.removeUsers(deletedEmail, c.Tag) + err := c.server.RemoveUsers(deletedEmail, c.Tag) if err != nil { log.Print(err) } } - if len(added) > 0 { - err = c.addNewUserFromIndex(newUserInfo, &added, c.nodeInfo) + if len(*added) > 0 { + err = c.addNewUser(added, newNodeInfo) if err != nil { log.Print(err) } // Update Limiter - if err := c.UpdateInboundLimiter(c.Tag, newUserInfo, &added); err != nil { + if err := c.server.UpdateInboundLimiter(c.Tag, c.nodeInfo, added); err != nil { log.Print(err) } } log.Printf("[%s: %d] %d user deleted, %d user added", c.nodeInfo.NodeType, c.nodeInfo.NodeId, - len(deleted), len(added)) + len(*deleted), len(*added)) c.userList = newUserInfo newUserInfo = nil runtime.GC() @@ -248,24 +270,24 @@ func (c *Controller) nodeInfoMonitor() (err error) { return nil } -func (c *Controller) removeOldTag(oldtag string) (err error) { - err = c.removeInbound(oldtag) +func (c *Node) removeOldTag(oldtag string) (err error) { + err = c.server.RemoveInbound(oldtag) if err != nil { return err } - err = c.removeOutbound(oldtag) + err = c.server.RemoveOutbound(oldtag) if err != nil { return err } return nil } -func (c *Controller) addNewTag(newNodeInfo *api.NodeInfo) (err error) { +func (c *Node) addNewTag(newNodeInfo *api.NodeInfo) (err error) { inboundConfig, err := InboundBuilder(c.config, newNodeInfo, c.Tag) if err != nil { return err } - err = c.addInbound(inboundConfig) + err = c.server.AddInbound(inboundConfig) if err != nil { return err @@ -275,7 +297,7 @@ func (c *Controller) addNewTag(newNodeInfo *api.NodeInfo) (err error) { return err } - err = c.addOutbound(outBoundConfig) + err = c.server.AddOutbound(outBoundConfig) if err != nil { return err @@ -283,7 +305,7 @@ func (c *Controller) addNewTag(newNodeInfo *api.NodeInfo) (err error) { return nil } -func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo) (err error) { +func (c *Node) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo) (err error) { users := make([]*protocol.User, 0) if nodeInfo.NodeType == "V2ray" { if nodeInfo.EnableVless { @@ -301,11 +323,11 @@ func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo } else if nodeInfo.NodeType == "Trojan" { users = c.buildTrojanUsers(userInfo) } else if nodeInfo.NodeType == "Shadowsocks" { - users = c.buildSSUsers(userInfo, nodeInfo.SS.CypherMethod) + users = c.buildSSUsers(userInfo, getCipherFromString(nodeInfo.SS.CypherMethod)) } else { return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType) } - err = c.addUsers(users, c.Tag) + err = c.server.AddUsers(users, c.Tag) if err != nil { return err } @@ -313,39 +335,7 @@ func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo return nil } -func (c *Controller) addNewUserFromIndex(userInfo *[]api.UserInfo, userIndex *[]int, nodeInfo *api.NodeInfo) (err error) { - users := make([]*protocol.User, 0, len(*userIndex)) - for _, v := range *userIndex { - if nodeInfo.NodeType == "V2ray" { - if nodeInfo.EnableVless { - users = append(users, c.buildVlessUser(&(*userInfo)[v])) - } else { - alterID := 0 - alterID = (*userInfo)[0].V2rayUser.AlterId - if alterID >= 0 && alterID < math.MaxUint16 { - users = append(users, c.buildVmessUser(&(*userInfo)[v], uint16(alterID))) - } else { - users = append(users, c.buildVmessUser(&(*userInfo)[v], 0)) - return fmt.Errorf("AlterID should between 0 to 1<<16 - 1, set it to 0 for now") - } - } - } else if nodeInfo.NodeType == "Trojan" { - users = append(users, c.buildTrojanUser(&(*userInfo)[v])) - } else if nodeInfo.NodeType == "Shadowsocks" { - users = append(users, c.buildSSUser(&(*userInfo)[v], nodeInfo.SS.CypherMethod)) - } else { - return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType) - } - } - err = c.addUsers(users, c.Tag) - if err != nil { - return err - } - log.Printf("[%s: %d] Added %d new users", c.nodeInfo.NodeType, c.nodeInfo.NodeId, len(*userIndex)) - return nil -} - -func compareUserList(old, new *[]api.UserInfo) (deleted, added []int) { +func compareUserList(old, new *[]api.UserInfo) (deleted, added *[]api.UserInfo) { tmp := map[string]struct{}{} tmp2 := map[string]struct{}{} for i := range *old { @@ -357,7 +347,7 @@ func compareUserList(old, new *[]api.UserInfo) (deleted, added []int) { tmp[e] = struct{}{} tmp2[e] = struct{}{} if l != len(tmp) { - added = append(added, i) + *added = append(*added, (*new)[i]) l++ } } @@ -366,18 +356,18 @@ func compareUserList(old, new *[]api.UserInfo) (deleted, added []int) { for i := range *old { tmp2[(*old)[i].GetUserEmail()] = struct{}{} if l != len(tmp2) { - deleted = append(deleted, i) + *deleted = append(*deleted, (*old)[i]) l++ } } return deleted, added } -func (c *Controller) userInfoMonitor() (err error) { +func (c *Node) userInfoMonitor() (err error) { // Get User traffic userTraffic := make([]api.UserTraffic, 0) for i := range *c.userList { - up, down := c.getTraffic(c.buildUserTag(&(*c.userList)[i])) + up, down := c.server.GetUserTraffic(c.buildUserTag(&(*c.userList)[i])) if up > 0 || down > 0 { userTraffic = append(userTraffic, api.UserTraffic{ UID: (*c.userList)[i].UID, @@ -389,26 +379,49 @@ func (c *Controller) userInfoMonitor() (err error) { err = c.apiClient.ReportUserTraffic(&userTraffic) if err != nil { log.Print(err) + } else { + log.Printf("[%s: %d] Report %d online users", c.nodeInfo.NodeType, c.nodeInfo.NodeId, len(userTraffic)) } } - - // Report Online info - if onlineDevice, err := c.GetOnlineDevice(c.Tag); err != nil { - log.Print(err) - } else { - log.Printf("[%s: %d] Report %d online users", c.nodeInfo.NodeType, c.nodeInfo.NodeId, len(*onlineDevice)) - } - // Report Illegal user - if detectResult, err := c.GetDetectResult(c.Tag); err != nil { - log.Print(err) - } else { - log.Printf("[%s: %d] Report %d illegal behaviors", c.nodeInfo.NodeType, c.nodeInfo.NodeId, len(*detectResult)) - } userTraffic = nil runtime.GC() return nil } -func (c *Controller) buildNodeTag() string { +func (c *Node) onlineIpReport() (err error) { + onlineIp, err := c.server.GetOnlineIps(c.Tag) + if err != nil { + log.Print(err) + return nil + } + rsp, err := resty.New().SetTimeout(time.Duration(c.config.IpRecorderConfig.Timeout) * time.Second). + R(). + SetBody(onlineIp). + Post(c.config.IpRecorderConfig.Url + + "/api/v1/SyncOnlineIp?token=" + + c.config.IpRecorderConfig.Token) + if err != nil { + log.Print(err) + c.server.ClearOnlineIps(c.Tag) + return nil + } + log.Printf("[Node: %d] Report %d online ip", c.nodeInfo.NodeId, len(*onlineIp)) + if rsp.StatusCode() == 200 { + onlineIp = &[]limiter.UserIp{} + err := json.Unmarshal(rsp.Body(), onlineIp) + if err != nil { + log.Print(err) + c.server.ClearOnlineIps(c.Tag) + return nil + } + c.server.UpdateOnlineIps(c.Tag, onlineIp) + log.Printf("[Node: %d] Updated %d online ip", c.nodeInfo.NodeId, len(*onlineIp)) + } else { + c.server.ClearOnlineIps(c.Tag) + } + return nil +} + +func (c *Node) buildNodeTag() string { return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.NodeId) } diff --git a/service/controller/controller_test.go b/node/node_test.go similarity index 93% rename from service/controller/controller_test.go rename to node/node_test.go index c4853c3..92325dc 100644 --- a/service/controller/controller_test.go +++ b/node/node_test.go @@ -1,7 +1,8 @@ -package controller_test +package node_test import ( "fmt" + . "github.com/Yuzuki616/V2bX/node" "os" "os/signal" "runtime" @@ -9,8 +10,7 @@ import ( "testing" "github.com/Yuzuki616/V2bX/api" - _ "github.com/Yuzuki616/V2bX/main/distro/all" - . "github.com/Yuzuki616/V2bX/service/controller" + _ "github.com/Yuzuki616/V2bX/example/distro/all" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/infra/conf" ) diff --git a/service/controller/outboundbuilder.go b/node/outboundbuilder.go similarity index 87% rename from service/controller/outboundbuilder.go rename to node/outboundbuilder.go index 4aefbe7..3a24631 100644 --- a/service/controller/outboundbuilder.go +++ b/node/outboundbuilder.go @@ -1,8 +1,9 @@ -package controller +package node import ( "encoding/json" "fmt" + conf2 "github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/api" "github.com/xtls/xray-core/common/net" @@ -11,7 +12,7 @@ import ( ) //OutboundBuilder build freedom outbund config for addoutbound -func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.OutboundHandlerConfig, error) { +func OutboundBuilder(config *conf2.ControllerConfig, nodeInfo *api.NodeInfo, tag string) (*core.OutboundHandlerConfig, error) { outboundDetourConfig := &conf.OutboundDetourConfig{} outboundDetourConfig.Protocol = "freedom" outboundDetourConfig.Tag = tag diff --git a/node/userbuilder.go b/node/userbuilder.go new file mode 100644 index 0000000..e209123 --- /dev/null +++ b/node/userbuilder.go @@ -0,0 +1,118 @@ +package node + +import ( + "fmt" + "strings" + + "github.com/Yuzuki616/V2bX/api" + "github.com/xtls/xray-core/common/protocol" + "github.com/xtls/xray-core/common/serial" + "github.com/xtls/xray-core/infra/conf" + "github.com/xtls/xray-core/proxy/shadowsocks" + "github.com/xtls/xray-core/proxy/trojan" + "github.com/xtls/xray-core/proxy/vless" +) + +func (c *Node) buildVmessUsers(userInfo *[]api.UserInfo, serverAlterID uint16) (users []*protocol.User) { + users = make([]*protocol.User, len(*userInfo)) + for i, user := range *userInfo { + users[i] = c.buildVmessUser(&user, serverAlterID) + } + return users +} + +func (c *Node) buildVmessUser(userInfo *api.UserInfo, serverAlterID uint16) (user *protocol.User) { + vmessAccount := &conf.VMessAccount{ + ID: userInfo.V2rayUser.Uuid, + AlterIds: serverAlterID, + Security: "auto", + } + user = &protocol.User{ + Level: 0, + Email: c.buildUserTag(userInfo), // Uid: InboundTag|email|uid + Account: serial.ToTypedMessage(vmessAccount.Build()), + } + return user +} + +func (c *Node) buildVlessUsers(userInfo *[]api.UserInfo) (users []*protocol.User) { + users = make([]*protocol.User, len(*userInfo)) + for i := range *userInfo { + users[i] = c.buildVlessUser(&(*userInfo)[i]) + } + return users +} + +func (c *Node) buildVlessUser(userInfo *api.UserInfo) (user *protocol.User) { + vlessAccount := &vless.Account{ + Id: userInfo.V2rayUser.Uuid, + Flow: "xtls-rprx-direct", + } + user = &protocol.User{ + Level: 0, + Email: c.buildUserTag(userInfo), + Account: serial.ToTypedMessage(vlessAccount), + } + return user +} + +func (c *Node) buildTrojanUsers(userInfo *[]api.UserInfo) (users []*protocol.User) { + users = make([]*protocol.User, len(*userInfo)) + for i := range *userInfo { + users[i] = c.buildTrojanUser(&(*userInfo)[i]) + } + return users +} + +func (c *Node) buildTrojanUser(userInfo *api.UserInfo) (user *protocol.User) { + trojanAccount := &trojan.Account{ + Password: userInfo.TrojanUser.Password, + Flow: "xtls-rprx-direct", + } + user = &protocol.User{ + Level: 0, + Email: c.buildUserTag(userInfo), + Account: serial.ToTypedMessage(trojanAccount), + } + return user +} + +func getCipherFromString(c string) shadowsocks.CipherType { + switch strings.ToLower(c) { + case "aes-128-gcm", "aead_aes_128_gcm": + return shadowsocks.CipherType_AES_128_GCM + case "aes-256-gcm", "aead_aes_256_gcm": + return shadowsocks.CipherType_AES_256_GCM + case "chacha20-poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305": + return shadowsocks.CipherType_CHACHA20_POLY1305 + case "none", "plain": + return shadowsocks.CipherType_NONE + default: + return shadowsocks.CipherType_UNKNOWN + } +} + +func (c *Node) buildSSUsers(userInfo *[]api.UserInfo, cypher shadowsocks.CipherType) (users []*protocol.User) { + users = make([]*protocol.User, len(*userInfo)) + for i := range *userInfo { + c.buildSSUser(&(*userInfo)[i], cypher) + } + return users +} + +func (c *Node) buildSSUser(userInfo *api.UserInfo, cypher shadowsocks.CipherType) (user *protocol.User) { + ssAccount := &shadowsocks.Account{ + Password: userInfo.Secret, + CipherType: cypher, + } + user = &protocol.User{ + Level: 0, + Email: c.buildUserTag(userInfo), + Account: serial.ToTypedMessage(ssAccount), + } + return user +} + +func (c *Node) buildUserTag(user *api.UserInfo) string { + return fmt.Sprintf("%s|%s|%d", c.Tag, user.GetUserEmail(), user.UID) +} diff --git a/panel/config.go b/panel/config.go deleted file mode 100644 index 021ad3e..0000000 --- a/panel/config.go +++ /dev/null @@ -1,36 +0,0 @@ -package panel - -import ( - "github.com/Yuzuki616/V2bX/api" - "github.com/Yuzuki616/V2bX/service/controller" -) - -type Config struct { - LogConfig *LogConfig `mapstructure:"Log"` - DnsConfigPath string `mapstructure:"DnsConfigPath"` - InboundConfigPath string `mapstructure:"InboundConfigPath"` - OutboundConfigPath string `mapstructure:"OutboundConfigPath"` - RouteConfigPath string `mapstructure:"RouteConfigPath"` - ConnetionConfig *ConnetionConfig `mapstructure:"ConnetionConfig"` - NodesConfig []*NodesConfig `mapstructure:"Nodes"` -} - -type NodesConfig struct { - //PanelType string `mapstructure:"PanelType"` - ApiConfig *api.Config `mapstructure:"ApiConfig"` - ControllerConfig *controller.Config `mapstructure:"ControllerConfig"` -} - -type LogConfig struct { - Level string `mapstructure:"Level"` - AccessPath string `mapstructure:"AccessPath"` - ErrorPath string `mapstructure:"ErrorPath"` -} - -type ConnetionConfig struct { - Handshake uint32 `mapstructure:"handshake"` - ConnIdle uint32 `mapstructure:"connIdle"` - UplinkOnly uint32 `mapstructure:"uplinkOnly"` - DownlinkOnly uint32 `mapstructure:"downlinkOnly"` - BufferSize int32 `mapstructure:"bufferSize"` -} diff --git a/panel/defaultConfig.go b/panel/defaultConfig.go deleted file mode 100644 index 1338e28..0000000 --- a/panel/defaultConfig.go +++ /dev/null @@ -1,30 +0,0 @@ -package panel - -import "github.com/Yuzuki616/V2bX/service/controller" - -func getDefaultLogConfig() *LogConfig { - return &LogConfig{ - Level: "none", - AccessPath: "", - ErrorPath: "", - } -} - -func getDefaultConnetionConfig() *ConnetionConfig { - return &ConnetionConfig{ - Handshake: 4, - ConnIdle: 30, - UplinkOnly: 2, - DownlinkOnly: 4, - BufferSize: 64, - } -} - -func getDefaultControllerConfig() *controller.Config { - return &controller.Config{ - ListenIP: "0.0.0.0", - SendIP: "0.0.0.0", - UpdatePeriodic: 60, - DNSType: "AsIs", - } -} diff --git a/panel/panel.go b/panel/panel.go deleted file mode 100644 index 184bd92..0000000 --- a/panel/panel.go +++ /dev/null @@ -1,219 +0,0 @@ -package panel - -import ( - "encoding/json" - "github.com/Yuzuki616/V2bX/app/mydispatcher" - io "io/ioutil" - "log" - "sync" - - "github.com/Yuzuki616/V2bX/api" - _ "github.com/Yuzuki616/V2bX/main/distro/all" - "github.com/Yuzuki616/V2bX/service" - "github.com/Yuzuki616/V2bX/service/controller" - "github.com/imdario/mergo" - "github.com/r3labs/diff/v2" - "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/infra/conf" -) - -// Panel Structure -type Panel struct { - access sync.Mutex - panelConfig *Config - Server *core.Instance - Service []service.Service - Running bool -} - -func New(panelConfig *Config) *Panel { - p := &Panel{panelConfig: panelConfig} - return p -} - -func (p *Panel) loadCore(panelConfig *Config) *core.Instance { - // Log Config - coreLogConfig := &conf.LogConfig{} - logConfig := getDefaultLogConfig() - if panelConfig.LogConfig != nil { - if _, err := diff.Merge(logConfig, panelConfig.LogConfig, logConfig); err != nil { - log.Panicf("Read Log config failed: %s", err) - } - } - coreLogConfig.LogLevel = logConfig.Level - coreLogConfig.AccessLog = logConfig.AccessPath - coreLogConfig.ErrorLog = logConfig.ErrorPath - - // DNS config - coreDnsConfig := &conf.DNSConfig{} - if panelConfig.DnsConfigPath != "" { - if data, err := io.ReadFile(panelConfig.DnsConfigPath); err != nil { - log.Panicf("Failed to read DNS config file at: %s", panelConfig.DnsConfigPath) - } else { - if err = json.Unmarshal(data, coreDnsConfig); err != nil { - log.Panicf("Failed to unmarshal DNS config: %s", panelConfig.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 := &conf.RouterConfig{} - if panelConfig.RouteConfigPath != "" { - if data, err := io.ReadFile(panelConfig.RouteConfigPath); err != nil { - log.Panicf("Failed to read Routing config file at: %s", panelConfig.RouteConfigPath) - } else { - if err = json.Unmarshal(data, coreRouterConfig); err != nil { - log.Panicf("Failed to unmarshal Routing config: %s", panelConfig.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 []conf.InboundDetourConfig - if panelConfig.InboundConfigPath != "" { - if data, err := io.ReadFile(panelConfig.InboundConfigPath); err != nil { - log.Panicf("Failed to read Custom Inbound config file at: %s", panelConfig.OutboundConfigPath) - } else { - if err = json.Unmarshal(data, &coreCustomInboundConfig); err != nil { - log.Panicf("Failed to unmarshal Custom Inbound config: %s", panelConfig.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 []conf.OutboundDetourConfig - if panelConfig.OutboundConfigPath != "" { - if data, err := io.ReadFile(panelConfig.OutboundConfigPath); err != nil { - log.Panicf("Failed to read Custom Outbound config file at: %s", panelConfig.OutboundConfigPath) - } else { - if err = json.Unmarshal(data, &coreCustomOutboundConfig); err != nil { - log.Panicf("Failed to unmarshal Custom Outbound config: %s", panelConfig.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(panelConfig.ConnetionConfig) - corePolicyConfig := &conf.PolicyConfig{} - corePolicyConfig.Levels = map[uint32]*conf.Policy{0: levelPolicyConfig} - policyConfig, _ := corePolicyConfig.Build() - // Build Core Config - config := &core.Config{ - App: []*serial.TypedMessage{ - serial.ToTypedMessage(coreLogConfig.Build()), - serial.ToTypedMessage(&mydispatcher.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("Xray Core Version: %s", core.Version()) - - return server -} - -// Start Start the panel -func (p *Panel) Start() { - p.access.Lock() - defer p.access.Unlock() - log.Print("Start the panel..") - // Load Core - server := p.loadCore(p.panelConfig) - if err := server.Start(); err != nil { - log.Panicf("Failed to start instance: %s", err) - } - p.Server = server - // Load Nodes config - for _, nodeConfig := range p.panelConfig.NodesConfig { - var apiClient = api.New(nodeConfig.ApiConfig) - var controllerService service.Service - // Register controller service - controllerConfig := getDefaultControllerConfig() - if nodeConfig.ControllerConfig != nil { - if err := mergo.Merge(controllerConfig, nodeConfig.ControllerConfig, mergo.WithOverride); err != nil { - log.Panicf("Read Controller Config Failed") - } - } - controllerService = controller.New(server, apiClient, controllerConfig) - p.Service = append(p.Service, controllerService) - - } - - // Start all the service - for _, s := range p.Service { - err := s.Start() - if err != nil { - log.Panicf("Panel Start fialed: %s", err) - } - } - p.Running = true - return -} - -// Close Close the panel -func (p *Panel) Close() { - p.access.Lock() - defer p.access.Unlock() - for _, s := range p.Service { - err := s.Close() - if err != nil { - log.Panicf("Panel Close fialed: %s", err) - } - } - p.Service = nil - p.Server.Close() - p.Running = false - return -} - -func parseConnectionConfig(c *ConnetionConfig) (policy *conf.Policy) { - connetionConfig := getDefaultConnetionConfig() - if c != nil { - if _, err := diff.Merge(connetionConfig, c, connetionConfig); err != nil { - log.Panicf("Read ConnetionConfig failed: %s", err) - } - } - policy = &conf.Policy{ - StatsUserUplink: true, - StatsUserDownlink: true, - Handshake: &connetionConfig.Handshake, - ConnectionIdle: &connetionConfig.ConnIdle, - UplinkOnly: &connetionConfig.UplinkOnly, - DownlinkOnly: &connetionConfig.DownlinkOnly, - BufferSize: &connetionConfig.BufferSize, - } - - return -} diff --git a/service/controller/control.go b/service/controller/control.go deleted file mode 100644 index da6301b..0000000 --- a/service/controller/control.go +++ /dev/null @@ -1,170 +0,0 @@ -package controller - -import ( - "context" - "fmt" - - "github.com/Yuzuki616/V2bX/api" - "github.com/Yuzuki616/V2bX/app/mydispatcher" - "github.com/xtls/xray-core/common/protocol" - "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" - "github.com/xtls/xray-core/features/stats" - "github.com/xtls/xray-core/proxy" -) - -func (c *Controller) removeInbound(tag string) error { - inboundManager := c.server.GetFeature(inbound.ManagerType()).(inbound.Manager) - err := inboundManager.RemoveHandler(context.Background(), tag) - return err -} - -func (c *Controller) removeOutbound(tag string) error { - outboundManager := c.server.GetFeature(outbound.ManagerType()).(outbound.Manager) - err := outboundManager.RemoveHandler(context.Background(), tag) - return err -} - -func (c *Controller) addInbound(config *core.InboundHandlerConfig) error { - inboundManager := c.server.GetFeature(inbound.ManagerType()).(inbound.Manager) - 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 := inboundManager.AddHandler(context.Background(), handler); err != nil { - return err - } - return nil -} - -func (c *Controller) addOutbound(config *core.OutboundHandlerConfig) error { - outboundManager := c.server.GetFeature(outbound.ManagerType()).(outbound.Manager) - 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 := outboundManager.AddHandler(context.Background(), handler); err != nil { - return err - } - return nil -} - -func (c *Controller) addUsers(users []*protocol.User, tag string) error { - inboundManager := c.server.GetFeature(inbound.ManagerType()).(inbound.Manager) - handler, err := inboundManager.GetHandler(context.Background(), tag) - if err != nil { - return fmt.Errorf("no such inbound tag: %s", err) - } - inboundInstance, ok := handler.(proxy.GetInbound) - if !ok { - return fmt.Errorf("handler %s is not implement proxy.GetInbound", tag) - } - - userManager, ok := inboundInstance.GetInbound().(proxy.UserManager) - if !ok { - return fmt.Errorf("handler %s is not implement proxy.UserManager", err) - } - for _, item := range users { - mUser, err := item.ToMemoryUser() - if err != nil { - return err - } - err = userManager.AddUser(context.Background(), mUser) - if err != nil { - return err - } - } - return nil -} - -func (c *Controller) removeUsers(users []string, tag string) error { - inboundManager := c.server.GetFeature(inbound.ManagerType()).(inbound.Manager) - handler, err := inboundManager.GetHandler(context.Background(), tag) - if err != nil { - return fmt.Errorf("no such inbound tag: %s", err) - } - inboundInstance, ok := handler.(proxy.GetInbound) - if !ok { - return fmt.Errorf("handler %s is not implement proxy.GetInbound", tag) - } - - userManager, ok := inboundInstance.GetInbound().(proxy.UserManager) - if !ok { - return fmt.Errorf("handler %s is not implement proxy.UserManager", err) - } - for _, email := range users { - err = userManager.RemoveUser(context.Background(), email) - if err != nil { - return err - } - } - return nil -} - -func (c *Controller) getTraffic(email string) (up int64, down int64) { - upName := "user>>>" + email + ">>>traffic>>>uplink" - downName := "user>>>" + email + ">>>traffic>>>downlink" - statsManager := c.server.GetFeature(stats.ManagerType()).(stats.Manager) - upCounter := statsManager.GetCounter(upName) - downCounter := statsManager.GetCounter(downName) - if upCounter != nil { - up = upCounter.Value() - upCounter.Set(0) - } - if downCounter != nil { - down = downCounter.Value() - downCounter.Set(0) - } - return up, down - -} - -func (c *Controller) AddInboundLimiter(tag string, userList *[]api.UserInfo) error { - dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher) - err := dispather.Limiter.AddInboundLimiter(tag, c.nodeInfo, userList) - return err -} - -func (c *Controller) UpdateInboundLimiter(tag string, updatedUserList *[]api.UserInfo, usersIndex *[]int) error { - dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher) - err := dispather.Limiter.UpdateInboundLimiter(tag, c.nodeInfo, updatedUserList, usersIndex) - return err -} - -func (c *Controller) DeleteInboundLimiter(tag string) error { - dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher) - err := dispather.Limiter.DeleteInboundLimiter(tag) - return err -} - -func (c *Controller) GetOnlineDevice(tag string) (*[]api.OnlineUser, error) { - dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher) - return dispather.Limiter.GetOnlineDevice(tag) -} - -func (c *Controller) UpdateRule(tag string, newRuleList []api.DetectRule) error { - dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher) - err := dispather.RuleManager.UpdateRule(tag, newRuleList) - return err -} - -func (c *Controller) UpdateProtocolRule(tag string, newRuleList []string) error { - dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher) - err := dispather.RuleManager.UpdateProtocolRule(tag, newRuleList) - return err -} - -func (c *Controller) GetDetectResult(tag string) (*[]api.DetectResult, error) { - dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher) - return dispather.RuleManager.GetDetectResult(tag) -} diff --git a/service/controller/userbuilder.go b/service/controller/userbuilder.go deleted file mode 100644 index ec05c7a..0000000 --- a/service/controller/userbuilder.go +++ /dev/null @@ -1,154 +0,0 @@ -package controller - -import ( - "fmt" - "strings" - - "github.com/Yuzuki616/V2bX/api" - "github.com/xtls/xray-core/common/protocol" - "github.com/xtls/xray-core/common/serial" - "github.com/xtls/xray-core/infra/conf" - "github.com/xtls/xray-core/proxy/shadowsocks" - "github.com/xtls/xray-core/proxy/trojan" - "github.com/xtls/xray-core/proxy/vless" -) - -func (c *Controller) buildVmessUsers(userInfo *[]api.UserInfo, serverAlterID uint16) (users []*protocol.User) { - users = make([]*protocol.User, len(*userInfo)) - for i, user := range *userInfo { - vmessAccount := &conf.VMessAccount{ - ID: user.V2rayUser.Uuid, - AlterIds: serverAlterID, - Security: "auto", - } - users[i] = &protocol.User{ - Level: 0, - Email: c.buildUserTag(&user), // Email: InboundTag|email|uid - Account: serial.ToTypedMessage(vmessAccount.Build()), - } - } - return users -} - -func (c *Controller) buildVmessUser(userInfo *api.UserInfo, serverAlterID uint16) (user *protocol.User) { - vmessAccount := &conf.VMessAccount{ - ID: userInfo.V2rayUser.Uuid, - AlterIds: serverAlterID, - Security: "auto", - } - user = &protocol.User{ - Level: 0, - Email: c.buildUserTag(userInfo), // Email: InboundTag|email|uid - Account: serial.ToTypedMessage(vmessAccount.Build()), - } - return user -} - -func (c *Controller) buildVlessUsers(userInfo *[]api.UserInfo) (users []*protocol.User) { - users = make([]*protocol.User, len(*userInfo)) - for i, user := range *userInfo { - vlessAccount := &vless.Account{ - Id: user.V2rayUser.Uuid, - Flow: "xtls-rprx-direct", - } - users[i] = &protocol.User{ - Level: 0, - Email: c.buildUserTag(&user), - Account: serial.ToTypedMessage(vlessAccount), - } - } - return users -} - -func (c *Controller) buildVlessUser(userInfo *api.UserInfo) (user *protocol.User) { - vlessAccount := &vless.Account{ - Id: userInfo.V2rayUser.Uuid, - Flow: "xtls-rprx-direct", - } - user = &protocol.User{ - Level: 0, - Email: c.buildUserTag(userInfo), - Account: serial.ToTypedMessage(vlessAccount), - } - return user -} - -func (c *Controller) buildTrojanUsers(userInfo *[]api.UserInfo) (users []*protocol.User) { - users = make([]*protocol.User, len(*userInfo)) - for i, user := range *userInfo { - trojanAccount := &trojan.Account{ - Password: user.TrojanUser.Password, - Flow: "xtls-rprx-direct", - } - users[i] = &protocol.User{ - Level: 0, - Email: c.buildUserTag(&user), - Account: serial.ToTypedMessage(trojanAccount), - } - } - return users -} - -func (c *Controller) buildTrojanUser(userInfo *api.UserInfo) (user *protocol.User) { - trojanAccount := &trojan.Account{ - Password: userInfo.TrojanUser.Password, - Flow: "xtls-rprx-direct", - } - user = &protocol.User{ - Level: 0, - Email: c.buildUserTag(userInfo), - Account: serial.ToTypedMessage(trojanAccount), - } - return user -} - -func cipherFromString(c string) shadowsocks.CipherType { - switch strings.ToLower(c) { - case "aes-128-gcm", "aead_aes_128_gcm": - return shadowsocks.CipherType_AES_128_GCM - case "aes-256-gcm", "aead_aes_256_gcm": - return shadowsocks.CipherType_AES_256_GCM - case "chacha20-poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305": - return shadowsocks.CipherType_CHACHA20_POLY1305 - case "none", "plain": - return shadowsocks.CipherType_NONE - default: - return shadowsocks.CipherType_UNKNOWN - } -} - -func (c *Controller) buildSSUsers(userInfo *[]api.UserInfo, method string) (users []*protocol.User) { - users = make([]*protocol.User, 0) - - cypherMethod := cipherFromString(method) - for _, user := range *userInfo { - ssAccount := &shadowsocks.Account{ - Password: user.Secret, - CipherType: cypherMethod, - } - users = append(users, &protocol.User{ - Level: 0, - Email: c.buildUserTag(&user), - Account: serial.ToTypedMessage(ssAccount), - }) - } - return users -} - -func (c *Controller) buildSSUser(userInfo *api.UserInfo, method string) (user *protocol.User) { - cypherMethod := cipherFromString(method) - ssAccount := &shadowsocks.Account{ - Password: userInfo.Secret, - CipherType: cypherMethod, - } - user = &protocol.User{ - Level: 0, - Email: c.buildUserTag(userInfo), - Account: serial.ToTypedMessage(ssAccount), - } - return user -} - -func (c *Controller) buildUserTag(user *api.UserInfo) string { - return fmt.Sprintf("%s|%s|%d", c.Tag, user.GetUserEmail(), user.UID) -} diff --git a/service/service.go b/service/service.go deleted file mode 100644 index 82a975e..0000000 --- a/service/service.go +++ /dev/null @@ -1,16 +0,0 @@ -// Package service contains all the services used by XrayR -// To implement an service, one needs to implement the interface below. -package service - -// Service is the interface of all the services running in the panel -type Service interface { - Start() error - Close() error - Restart -} - -// Restart the service -type Restart interface { - Start() error - Close() error -} diff --git a/main/distro/all/all.go b/xray/distro/all/all.go similarity index 84% rename from main/distro/all/all.go rename to xray/distro/all/all.go index 98bf938..ff493ff 100644 --- a/main/distro/all/all.go +++ b/xray/distro/all/all.go @@ -5,7 +5,7 @@ import ( // Required features. Can't remove unless there is replacements. // _ "github.com/xtls/xray-core/app/dispatcher" - _ "github.com/Yuzuki616/V2bX/app/mydispatcher" + _ "github.com/Yuzuki616/V2bX/app/dispatcher" _ "github.com/xtls/xray-core/app/proxyman/inbound" _ "github.com/xtls/xray-core/app/proxyman/outbound" @@ -29,13 +29,6 @@ import ( _ "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/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" @@ -66,7 +59,4 @@ import ( // Load config from file or http(s) _ "github.com/xtls/xray-core/main/confloader/external" - - // Commands - _ "github.com/xtls/xray-core/main/commands/all" ) diff --git a/xray/inbound.go b/xray/inbound.go new file mode 100644 index 0000000..551a4f2 --- /dev/null +++ b/xray/inbound.go @@ -0,0 +1,61 @@ +package xray + +import ( + "context" + "fmt" + "github.com/Yuzuki616/V2bX/api" + "github.com/Yuzuki616/V2bX/app/dispatcher" + "github.com/Yuzuki616/V2bX/common/limiter" + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/features/inbound" + "github.com/xtls/xray-core/features/routing" +) + +func (p *Xray) RemoveInbound(tag string) error { + inboundManager := p.Server.GetFeature(inbound.ManagerType()).(inbound.Manager) + err := inboundManager.RemoveHandler(context.Background(), tag) + return err +} + +func (p *Xray) AddInbound(config *core.InboundHandlerConfig) error { + inboundManager := p.Server.GetFeature(inbound.ManagerType()).(inbound.Manager) + rawHandler, err := core.CreateObject(p.Server, config) + if err != nil { + return err + } + handler, ok := rawHandler.(inbound.Handler) + if !ok { + return fmt.Errorf("not an InboundHandler: %s", err) + } + if err := inboundManager.AddHandler(context.Background(), handler); err != nil { + return err + } + return nil +} + +func (p *Xray) AddInboundLimiter(tag string, nodeInfo *api.NodeInfo, userList *[]api.UserInfo) error { + dispather := p.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) + err := dispather.Limiter.AddInboundLimiter(tag, nodeInfo, userList) + return err +} + +func (p *Xray) GetInboundLimiter(tag string) (*limiter.InboundInfo, error) { + dispather := p.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) + limit, ok := dispather.Limiter.InboundInfo.Load(tag) + if ok { + return limit.(*limiter.InboundInfo), nil + } + return nil, fmt.Errorf("not found limiter") +} + +func (p *Xray) UpdateInboundLimiter(tag string, nodeInfo *api.NodeInfo, updatedUserList *[]api.UserInfo) error { + dispather := p.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) + err := dispather.Limiter.UpdateInboundLimiter(tag, nodeInfo, updatedUserList) + return err +} + +func (p *Xray) DeleteInboundLimiter(tag string) error { + dispather := p.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) + err := dispather.Limiter.DeleteInboundLimiter(tag) + return err +} diff --git a/xray/outbound.go b/xray/outbound.go new file mode 100644 index 0000000..4d0cd95 --- /dev/null +++ b/xray/outbound.go @@ -0,0 +1,30 @@ +package xray + +import ( + "context" + "fmt" + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/features/outbound" +) + +func (p *Xray) RemoveOutbound(tag string) error { + outboundManager := p.Server.GetFeature(outbound.ManagerType()).(outbound.Manager) + err := outboundManager.RemoveHandler(context.Background(), tag) + return err +} + +func (p *Xray) AddOutbound(config *core.OutboundHandlerConfig) error { + outboundManager := p.Server.GetFeature(outbound.ManagerType()).(outbound.Manager) + rawHandler, err := core.CreateObject(p.Server, config) + if err != nil { + return err + } + handler, ok := rawHandler.(outbound.Handler) + if !ok { + return fmt.Errorf("not an InboundHandler: %s", err) + } + if err := outboundManager.AddHandler(context.Background(), handler); err != nil { + return err + } + return nil +} diff --git a/xray/rule.go b/xray/rule.go new file mode 100644 index 0000000..b215185 --- /dev/null +++ b/xray/rule.go @@ -0,0 +1,24 @@ +package xray + +import ( + "github.com/Yuzuki616/V2bX/api" + "github.com/Yuzuki616/V2bX/app/dispatcher" + "github.com/xtls/xray-core/features/routing" +) + +func (p *Xray) UpdateRule(tag string, newRuleList []api.DetectRule) error { + dispather := p.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) + err := dispather.RuleManager.UpdateRule(tag, newRuleList) + return err +} + +func (p *Xray) UpdateProtocolRule(tag string, newRuleList []string) error { + dispather := p.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) + err := dispather.RuleManager.UpdateProtocolRule(tag, newRuleList) + return err +} + +func (p *Xray) GetDetectResult(tag string) (*[]api.DetectResult, error) { + dispather := p.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) + return dispather.RuleManager.GetDetectResult(tag) +} diff --git a/xray/user.go b/xray/user.go new file mode 100644 index 0000000..c1b3acc --- /dev/null +++ b/xray/user.go @@ -0,0 +1,94 @@ +package xray + +import ( + "context" + "fmt" + "github.com/Yuzuki616/V2bX/app/dispatcher" + "github.com/Yuzuki616/V2bX/common/limiter" + "github.com/xtls/xray-core/common/protocol" + "github.com/xtls/xray-core/features/inbound" + "github.com/xtls/xray-core/features/routing" + "github.com/xtls/xray-core/features/stats" + "github.com/xtls/xray-core/proxy" +) + +func (p *Xray) GetUserManager(tag string) (proxy.UserManager, error) { + inboundManager := p.Server.GetFeature(inbound.ManagerType()).(inbound.Manager) + handler, err := inboundManager.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 (p *Xray) AddUsers(users []*protocol.User, tag string) error { + userManager, err := p.GetUserManager(tag) + if err != nil { + return fmt.Errorf("get user manager error: %s", err) + } + for _, item := range users { + mUser, err := item.ToMemoryUser() + if err != nil { + return err + } + err = userManager.AddUser(context.Background(), mUser) + if err != nil { + return err + } + } + return nil +} + +func (p *Xray) RemoveUsers(users []string, tag string) error { + userManager, err := p.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 (p *Xray) GetUserTraffic(email string) (up int64, down int64) { + upName := "user>>>" + email + ">>>traffic>>>uplink" + downName := "user>>>" + email + ">>>traffic>>>downlink" + statsManager := p.Server.GetFeature(stats.ManagerType()).(stats.Manager) + upCounter := statsManager.GetCounter(upName) + downCounter := statsManager.GetCounter(downName) + if upCounter != nil { + up = upCounter.Value() + upCounter.Set(0) + } + if downCounter != nil { + down = downCounter.Value() + downCounter.Set(0) + } + return up, down +} + +func (p *Xray) GetOnlineIps(tag string) (*[]limiter.UserIp, error) { + dispather := p.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) + return dispather.Limiter.GetOnlineUserIp(tag) +} + +func (p *Xray) UpdateOnlineIps(tag string, ips *[]limiter.UserIp) { + dispather := p.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) + dispather.Limiter.UpdateOnlineUserIP(tag, ips) +} + +func (p *Xray) ClearOnlineIps(tag string) { + dispather := p.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) + dispather.Limiter.ClearOnlineUserIP(tag) +} diff --git a/xray/xray.go b/xray/xray.go new file mode 100644 index 0000000..14c94b1 --- /dev/null +++ b/xray/xray.go @@ -0,0 +1,161 @@ +package xray + +import ( + "encoding/json" + "github.com/Yuzuki616/V2bX/app/dispatcher" + "github.com/Yuzuki616/V2bX/conf" + _ "github.com/Yuzuki616/V2bX/xray/distro/all" + "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" + coreConf "github.com/xtls/xray-core/infra/conf" + io "io/ioutil" + "log" + "sync" +) + +// Xray Structure +type Xray struct { + access sync.Mutex + Server *core.Instance +} + +func New(c *conf.Conf) *Xray { + return &Xray{Server: getCore(c)} +} + +func parseConnectionConfig(c *conf.ConnetionConfig) (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(v2bXConfig *conf.Conf) *core.Instance { + // Log Config + coreLogConfig := &coreConf.LogConfig{} + coreLogConfig.LogLevel = v2bXConfig.LogConfig.Level + coreLogConfig.AccessLog = v2bXConfig.LogConfig.AccessPath + coreLogConfig.ErrorLog = v2bXConfig.LogConfig.ErrorPath + // DNS config + coreDnsConfig := &coreConf.DNSConfig{} + if v2bXConfig.DnsConfigPath != "" { + if data, err := io.ReadFile(v2bXConfig.DnsConfigPath); err != nil { + log.Panicf("Failed to read DNS config file at: %s", v2bXConfig.DnsConfigPath) + } else { + if err = json.Unmarshal(data, coreDnsConfig); err != nil { + log.Panicf("Failed to unmarshal DNS config: %s", v2bXConfig.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 v2bXConfig.RouteConfigPath != "" { + if data, err := io.ReadFile(v2bXConfig.RouteConfigPath); err != nil { + log.Panicf("Failed to read Routing config file at: %s", v2bXConfig.RouteConfigPath) + } else { + if err = json.Unmarshal(data, coreRouterConfig); err != nil { + log.Panicf("Failed to unmarshal Routing config: %s", v2bXConfig.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 v2bXConfig.InboundConfigPath != "" { + if data, err := io.ReadFile(v2bXConfig.InboundConfigPath); err != nil { + log.Panicf("Failed to read Custom Inbound config file at: %s", v2bXConfig.OutboundConfigPath) + } else { + if err = json.Unmarshal(data, &coreCustomInboundConfig); err != nil { + log.Panicf("Failed to unmarshal Custom Inbound config: %s", v2bXConfig.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 v2bXConfig.OutboundConfigPath != "" { + if data, err := io.ReadFile(v2bXConfig.OutboundConfigPath); err != nil { + log.Panicf("Failed to read Custom Outbound config file at: %s", v2bXConfig.OutboundConfigPath) + } else { + if err = json.Unmarshal(data, &coreCustomOutboundConfig); err != nil { + log.Panicf("Failed to unmarshal Custom Outbound config: %s", v2bXConfig.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(v2bXConfig.ConnectionConfig) + corePolicyConfig := &coreConf.PolicyConfig{} + corePolicyConfig.Levels = map[uint32]*coreConf.Policy{0: levelPolicyConfig} + policyConfig, _ := corePolicyConfig.Build() + // Build Xray 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("Xray Version: %s", core.Version()) + + return server +} + +// Start the Xray +func (p *Xray) Start() { + p.access.Lock() + defer p.access.Unlock() + log.Print("Start the panel..") + if err := p.Server.Start(); err != nil { + log.Panicf("Failed to start instance: %s", err) + } + return +} + +// Close the core +func (p *Xray) Close() { + p.access.Lock() + defer p.access.Unlock() + p.Server.Close() + return +}