update to v1.1.0

change to uniproxy api
refactor build inbound
refactor limiter and rule
add ss2022 support
add speedlimit support
and more...
This commit is contained in:
Yuzuki999 2022-12-18 23:31:06 +08:00
parent 0ac7ea691d
commit 695da4f4c5
23 changed files with 539 additions and 652 deletions

View File

@ -12,7 +12,7 @@ A V2board node server based on Xray-core, modified from XrayR
如对脚本不放心可使用此沙箱先测一遍再使用https://killercoda.com/playgrounds/scenario/ubuntu 如对脚本不放心可使用此沙箱先测一遍再使用https://killercoda.com/playgrounds/scenario/ubuntu
目前可以结合 [IpRecorder](https://github.com/Yuzuki616/IpRecorder) 实现跨节点IP数限制和每日IP连接地区数超限提醒请参考 [配置文件说明](https://yuzuki-1.gitbook.io/v2bx-doc/v2bx-pei-zhi-wen-jian-shuo-ming/config#wai-bu-ji-lu-qi-pei-zhi) 配置IpRecorder。 **注意1.1.0将更换为V2board1.7.0之后新增的Api原Api将被移除请1.7.0之前的用户使用1.1.0之前的版本。**
## 免责声明 ## 免责声明
@ -33,7 +33,7 @@ A V2board node server based on Xray-core, modified from XrayR
## 功能介绍 ## 功能介绍
| 功能 | v2ray | trojan | shadowsocks | | 功能 | v2ray | trojan | shadowsocks |
| --------------- | ----- | ------ | ----------- | |-----------|-------|--------|-------------|
| 获取节点信息 | √ | √ | √ | | 获取节点信息 | √ | √ | √ |
| 获取用户信息 | √ | √ | √ | | 获取用户信息 | √ | √ | √ |
| 用户流量统计 | √ | √ | √ | | 用户流量统计 | √ | √ | √ |
@ -45,12 +45,12 @@ A V2board node server based on Xray-core, modified from XrayR
| 审计规则 | √ | √ | √ | | 审计规则 | √ | √ | √ |
| 按照用户限速 | √ | √ | √ | | 按照用户限速 | √ | √ | √ |
| 自定义DNS | √ | √ | √ | | 自定义DNS | √ | √ | √ |
| 动态限速(未测试) | √ | √ | √ | 动态限速(未测试) | √ | √ | √ |
## 支持前端 ## 支持前端
| 前端 | v2ray | trojan | shadowsocks | | 前端 | v2ray | trojan | shadowsocks |
| ------------------------------------------------------ | ----- | ------ | ------------------------------ | |---------|-------|--------|-------------|
| v2board | √ | √ | √ | | v2board | √ | √ | √ |
## TODO ## TODO
@ -88,7 +88,6 @@ wget -N https://raw.githubusercontents.com/Yuzuki616/V2bX-script/master/install.
## Telgram ## Telgram
## Stars 增长记录 ## Stars 增长记录
[![Stargazers over time](https://starchart.cc/Yuzuki616/V2bX.svg)](https://starchart.cc/Yuzuki616/V2bX) [![Stargazers over time](https://starchart.cc/Yuzuki616/V2bX.svg)](https://starchart.cc/Yuzuki616/V2bX)

View File

@ -5,6 +5,5 @@ type Panel interface {
GetUserList() (userList []UserInfo, err error) GetUserList() (userList []UserInfo, err error)
ReportUserTraffic(userTraffic []UserTraffic) (err error) ReportUserTraffic(userTraffic []UserTraffic) (err error)
Describe() ClientInfo Describe() ClientInfo
GetNodeRule() (ruleList *DetectRule, err error)
Debug() Debug()
} }

View File

@ -1,266 +1,82 @@
package panel package panel
import ( import (
"bufio"
"bytes"
md52 "crypto/md5"
"fmt"
"github.com/go-resty/resty/v2"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"github.com/xtls/xray-core/infra/conf"
"log"
"os"
"regexp" "regexp"
"strconv"
) )
type DetectRule struct { type NodeInfo struct {
ProtocolRule []string Host string `json:"host"`
DestinationRule []DestinationRule ServerPort int `json:"server_port"`
ServerName string `json:"server_name"`
Network string `json:"network"`
NetworkSettings json.RawMessage `json:"networkSettings"`
Cipher string `json:"cipher"`
ServerKey string `json:"server_key"`
Tls int `json:"tls"`
Routes []Route `json:"routes"`
BaseConfig *BaseConfig `json:"base_config"`
Rules []DestinationRule `json:"-"`
localNodeConfig `json:"-"`
}
type Route struct {
Id int `json:"id"`
Match string `json:"match"`
Action string `json:"action"`
//ActionValue interface{} `json:"action_value"`
}
type BaseConfig struct {
PushInterval any `json:"push_interval"`
PullInterval any `json:"pull_interval"`
} }
type DestinationRule struct { type DestinationRule struct {
ID int ID int
Pattern *regexp.Regexp Pattern *regexp.Regexp
} }
type localNodeConfig struct {
// readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList *DetectRule) {
LocalRuleList = &DetectRule{}
if path != "" {
// open the file
file, err := os.Open(path)
//handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return
}
fileScanner := bufio.NewScanner(file)
// read line by line
for fileScanner.Scan() {
LocalRuleList.DestinationRule = append(LocalRuleList.DestinationRule, DestinationRule{
ID: -1,
Pattern: regexp.MustCompile(fileScanner.Text()),
})
}
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return
}
}
return
}
type NodeInfo struct {
DeviceLimit int
SpeedLimit uint64
NodeType string
NodeId int NodeId int
NodeType string
TLSType string TLSType string
EnableVless bool EnableVless bool
EnableTls bool EnableTls bool
//EnableSS2022 bool SpeedLimit int
V2ray *V2rayConfig DeviceLimit int
Trojan *TrojanConfig
SS *SSConfig
} }
type SSConfig struct {
Port int `json:"port"`
TransportProtocol string `json:"transportProtocol"`
CypherMethod string `json:"cypher"`
}
type V2rayConfig struct {
Inbounds []conf.InboundDetourConfig `json:"inbounds"`
Routing *struct {
Rules json.RawMessage `json:"rules"`
} `json:"routing"`
}
type Rule struct {
Type string `json:"type"`
InboundTag string `json:"inboundTag,omitempty"`
OutboundTag string `json:"outboundTag"`
Domain []string `json:"domain,omitempty"`
Protocol []string `json:"protocol,omitempty"`
}
type TrojanConfig struct {
LocalPort int `json:"local_port"`
Password []interface{} `json:"password"`
TransportProtocol string
Ssl struct {
Sni string `json:"sni"`
} `json:"ssl"`
}
// GetNodeInfo will pull NodeInfo Config from v2board
func (c *Client) GetNodeInfo() (nodeInfo *NodeInfo, err error) { func (c *Client) GetNodeInfo() (nodeInfo *NodeInfo, err error) {
var path string const path = "/api/v1/server/UniProxy/config"
var res *resty.Response r, err := c.client.R().Get(path)
switch c.NodeType { if err = c.checkResponse(r, path, err); err != nil {
case "V2ray": return
path = "/api/v1/server/Deepbwork/config"
case "Trojan":
path = "/api/v1/server/TrojanTidalab/config"
case "Shadowsocks":
if nodeInfo, err = c.ParseSSNodeResponse(); err == nil {
return nodeInfo, nil
} else {
return nil, err
} }
default: err = json.Unmarshal(r.Body(), &nodeInfo)
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
res, err = c.client.R().
SetQueryParam("local_port", "1").
ForceContentType("application/json").
Get(path)
err = c.checkResponse(res, path, err)
if err != nil { if err != nil {
return nil, err return
} }
c.access.Lock() if c.etag == r.Header().Get("ETag") { // node info not changed
defer c.access.Unlock()
switch c.NodeType {
case "V2ray":
i := bytes.Index(res.Body(), []byte("outbo"))
md := md52.Sum(res.Body()[:i])
nodeNotIsChange := true
if c.NodeInfoRspMd5 == [16]byte{} {
nodeNotIsChange = false
c.NodeInfoRspMd5 = md
} else {
if c.NodeInfoRspMd5 != md {
nodeNotIsChange = false
c.NodeInfoRspMd5 = md
}
}
md2 := md52.Sum(res.Body()[i:])
ruleIsChange := false
if c.NodeRuleRspMd5 != md2 {
ruleIsChange = true
c.NodeRuleRspMd5 = md2
}
nodeInfo, err = c.ParseV2rayNodeResponse(res.Body(), nodeNotIsChange, ruleIsChange)
case "Trojan":
md := md52.Sum(res.Body())
if c.NodeInfoRspMd5 != [16]byte{} {
if c.NodeInfoRspMd5 == md {
return nil, nil return nil, nil
} }
nodeInfo.NodeId = c.NodeId
nodeInfo.NodeType = c.NodeType
for i := range nodeInfo.Routes { // parse rules from routes
r := &nodeInfo.Routes[i]
if r.Action == "block" {
nodeInfo.Rules = append(nodeInfo.Rules, DestinationRule{
ID: r.Id,
Pattern: regexp.MustCompile(r.Match),
})
} }
c.NodeInfoRspMd5 = md
nodeInfo, err = c.ParseTrojanNodeResponse(res.Body())
} }
return nodeInfo, nil nodeInfo.Routes = nil
} if _, ok := nodeInfo.BaseConfig.PullInterval.(int); !ok {
i, _ := strconv.Atoi(nodeInfo.BaseConfig.PullInterval.(string))
func (c *Client) GetNodeRule() (*DetectRule, error) { nodeInfo.BaseConfig.PullInterval = i
ruleList := c.LocalRuleList }
if c.NodeType != "V2ray" || c.RemoteRuleCache == nil { if _, ok := nodeInfo.BaseConfig.PushInterval.(int); !ok {
return nil, nil i, _ := strconv.Atoi(nodeInfo.BaseConfig.PushInterval.(string))
} nodeInfo.BaseConfig.PushInterval = i
// V2board only support the rule for v2ray }
// fix: reuse config response c.etag = r.Header().Get("Etag")
c.access.Lock() return
defer c.access.Unlock()
if len(c.RemoteRuleCache) >= 2 {
for i, rule := range (c.RemoteRuleCache)[1].Domain {
ruleListItem := DestinationRule{
ID: i,
Pattern: regexp.MustCompile(rule),
}
ruleList.DestinationRule = append(ruleList.DestinationRule, ruleListItem)
}
}
if len(c.RemoteRuleCache) >= 3 {
for _, str := range (c.RemoteRuleCache)[2].Protocol {
ruleList.ProtocolRule = append(ruleList.ProtocolRule, str)
}
}
c.RemoteRuleCache = nil
return ruleList, nil
}
// ParseTrojanNodeResponse parse the response for the given node info format
func (c *Client) ParseTrojanNodeResponse(body []byte) (*NodeInfo, error) {
node := &NodeInfo{Trojan: &TrojanConfig{}}
var err = json.Unmarshal(body, node.Trojan)
if err != nil {
return nil, fmt.Errorf("unmarshal nodeinfo error: %s", err)
}
node.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
node.DeviceLimit = c.DeviceLimit
node.NodeId = c.NodeID
node.NodeType = c.NodeType
node.Trojan.TransportProtocol = "tcp"
return node, nil
}
// ParseSSNodeResponse parse the response for the given node info format
func (c *Client) ParseSSNodeResponse() (*NodeInfo, error) {
var port int
var method string
userInfo, err := c.GetUserList()
if err != nil {
return nil, err
}
if len(userInfo) > 0 {
port = userInfo[0].Port
method = userInfo[0].Cipher
} else {
return nil, fmt.Errorf("shadowsocks node need a active user")
}
if err != nil {
return nil, err
}
node := &NodeInfo{
SpeedLimit: uint64(c.SpeedLimit * 1000000 / 8),
DeviceLimit: c.DeviceLimit,
//EnableSS2022: c.EnableSS2022,
NodeType: c.NodeType,
NodeId: c.NodeID,
SS: &SSConfig{
Port: port,
TransportProtocol: "tcp",
CypherMethod: method,
},
}
return node, nil
}
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
func (c *Client) ParseV2rayNodeResponse(body []byte, notParseNode, parseRule bool) (*NodeInfo, error) {
if notParseNode && !parseRule {
return nil, nil
}
node := &NodeInfo{V2ray: &V2rayConfig{}}
err := json.Unmarshal(body, node.V2ray)
if err != nil {
return nil, fmt.Errorf("unmarshal nodeinfo error: %s", err)
}
if parseRule {
c.RemoteRuleCache = []Rule{}
err := json.Unmarshal(node.V2ray.Routing.Rules, &c.RemoteRuleCache)
if err != nil {
log.Println(err)
}
if notParseNode {
return nil, nil
}
}
node.V2ray.Routing = nil
node.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
node.DeviceLimit = c.DeviceLimit
node.NodeType = c.NodeType
node.NodeId = c.NodeID
if c.EnableXTLS {
node.TLSType = "xtls"
} else {
node.TLSType = "tls"
}
node.EnableVless = c.EnableVless
node.EnableTls = node.V2ray.Inbounds[0].StreamSetting.Security == "tls"
return node, nil
} }

20
api/panel/node_test.go Normal file
View File

@ -0,0 +1,20 @@
package panel
import (
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
"testing"
)
func TestClient_GetNodeInfo(t *testing.T) {
c, err := New(&conf.ApiConfig{
APIHost: "http://127.0.0.1",
Key: "token",
NodeType: "V2ray",
NodeID: 1,
})
if err != nil {
log.Print(err)
}
log.Println(c.GetNodeInfo())
}

View File

@ -1,11 +1,15 @@
package panel package panel
import ( import (
"bufio"
"fmt"
"github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/conf"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"log" "log"
"os"
"regexp"
"strconv" "strconv"
"sync" "strings"
"time" "time"
) )
@ -21,26 +25,20 @@ type ClientInfo struct {
type Client struct { type Client struct {
client *resty.Client client *resty.Client
APIHost string APIHost string
NodeID int
Key string Key string
NodeType string NodeType string
//EnableSS2022 bool NodeId int
EnableVless bool SpeedLimit int
EnableXTLS bool
SpeedLimit float64
DeviceLimit int DeviceLimit int
LocalRuleList *DetectRule LocalRuleList []DestinationRule
RemoteRuleCache []Rule etag string
access sync.Mutex
NodeInfoRspMd5 [16]byte
NodeRuleRspMd5 [16]byte
} }
func New(apiConfig *conf.ApiConfig) Panel { func New(c *conf.ApiConfig) (Panel, error) {
client := resty.New() client := resty.New()
client.SetRetryCount(3) client.SetRetryCount(3)
if apiConfig.Timeout > 0 { if c.Timeout > 0 {
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second) client.SetTimeout(time.Duration(c.Timeout) * time.Second)
} else { } else {
client.SetTimeout(5 * time.Second) client.SetTimeout(5 * time.Second)
} }
@ -51,25 +49,57 @@ func New(apiConfig *conf.ApiConfig) Panel {
log.Print(v.Err) log.Print(v.Err)
} }
}) })
client.SetBaseURL(apiConfig.APIHost) client.SetBaseURL(c.APIHost)
// Check node type
if c.NodeType != "V2ray" &&
c.NodeType != "Trojan" &&
c.NodeType != "Shadowsocks" {
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
// Create Key for each requests // Create Key for each requests
client.SetQueryParams(map[string]string{ client.SetQueryParams(map[string]string{
"node_id": strconv.Itoa(apiConfig.NodeID), "node_type": strings.ToLower(c.NodeType),
"token": apiConfig.Key, "node_id": strconv.Itoa(c.NodeID),
"token": c.Key,
}) })
// Read local rule list // Read local rule list
localRuleList := readLocalRuleList(apiConfig.RuleListPath) localRuleList := readLocalRuleList(c.RuleListPath)
return &Client{ return &Client{
client: client, client: client,
NodeID: apiConfig.NodeID, Key: c.Key,
Key: apiConfig.Key, APIHost: c.APIHost,
APIHost: apiConfig.APIHost, NodeType: c.NodeType,
NodeType: apiConfig.NodeType, SpeedLimit: c.SpeedLimit,
//EnableSS2022: apiConfig.EnableSS2022, DeviceLimit: c.DeviceLimit,
EnableVless: apiConfig.EnableVless, NodeId: c.NodeID,
EnableXTLS: apiConfig.EnableXTLS,
SpeedLimit: apiConfig.SpeedLimit,
DeviceLimit: apiConfig.DeviceLimit,
LocalRuleList: localRuleList, LocalRuleList: localRuleList,
} }, nil
}
// readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList []DestinationRule) {
LocalRuleList = make([]DestinationRule, 0)
if path != "" {
// open the file
file, err := os.Open(path)
//handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return
}
fileScanner := bufio.NewScanner(file)
// read line by line
for fileScanner.Scan() {
LocalRuleList = append(LocalRuleList, DestinationRule{
ID: -1,
Pattern: regexp.MustCompile(fileScanner.Text()),
})
}
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return
}
}
return
} }

View File

@ -3,7 +3,6 @@ package panel
import ( import (
"fmt" "fmt"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"strconv"
) )
type OnlineUser struct { type OnlineUser struct {
@ -20,46 +19,22 @@ type TrojanUserInfo struct {
Password string `json:"password"` Password string `json:"password"`
} }
type UserInfo struct { type UserInfo struct {
/*DeviceLimit int `json:"device_limit"` Id int `json:"id"`
SpeedLimit uint64 `json:"speed_limit"`*/ Uuid string `json:"uuid"`
UID int `json:"id"` Email string `json:"-"`
SpeedLimit int `json:"speed_limit"`
Traffic int64 `json:"-"` Traffic int64 `json:"-"`
Port int `json:"port"`
Cipher string `json:"cipher"`
Secret string `json:"secret"`
V2rayUser *V2RayUserInfo `json:"v2ray_user"`
TrojanUser *TrojanUserInfo `json:"trojan_user"`
}
func (p *UserInfo) GetUserEmail() string {
if p.V2rayUser != nil {
return p.V2rayUser.Email
} else if p.TrojanUser != nil {
return p.TrojanUser.Password
}
return p.Secret
} }
type UserListBody struct { type UserListBody struct {
//Msg string `json:"msg"` //Msg string `json:"msg"`
Data []UserInfo `json:"data"` Users []UserInfo `json:"users"`
} }
// GetUserList will pull user form sspanel // GetUserList will pull user form sspanel
func (c *Client) GetUserList() (UserList []UserInfo, err error) { func (c *Client) GetUserList() (UserList []UserInfo, err error) {
var path string const path = "/api/v1/server/UniProxy/user"
switch c.NodeType {
case "V2ray":
path = "/api/v1/server/Deepbwork/user"
case "Trojan":
path = "/api/v1/server/TrojanTidalab/user"
case "Shadowsocks":
path = "/api/v1/server/ShadowsocksTidalab/user"
default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
res, err := c.client.R(). res, err := c.client.R().
ForceContentType("application/json").
Get(path) Get(path)
err = c.checkResponse(res, path, err) err = c.checkResponse(res, path, err)
if err != nil { if err != nil {
@ -70,38 +45,24 @@ func (c *Client) GetUserList() (UserList []UserInfo, err error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("unmarshal userlist error: %s", err) return nil, fmt.Errorf("unmarshal userlist error: %s", err)
} }
return userList.Data, nil return userList.Users, nil
} }
type UserTraffic struct { type UserTraffic struct {
UID int `json:"user_id"` UID int
Upload int64 `json:"u"` Upload int64
Download int64 `json:"d"` Download int64
} }
// ReportUserTraffic reports the user traffic // ReportUserTraffic reports the user traffic
func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error { func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error {
var path string data := make(map[int][]int64, len(userTraffic))
switch c.NodeType { for i := range userTraffic {
case "V2ray": data[userTraffic[i].UID] = []int64{userTraffic[i].Upload, userTraffic[i].Download}
path = "/api/v1/server/Deepbwork/submit"
case "Trojan":
path = "/api/v1/server/TrojanTidalab/submit"
case "Shadowsocks":
path = "/api/v1/server/ShadowsocksTidalab/submit"
} }
const path = "/api/v1/server/UniProxy/user"
data := make([]UserTraffic, len(userTraffic))
for i, traffic := range userTraffic {
data[i] = UserTraffic{
UID: traffic.UID,
Upload: traffic.Upload,
Download: traffic.Download}
}
res, err := c.client.R(). res, err := c.client.R().
SetQueryParam("node_id", strconv.Itoa(c.NodeID)). SetBody(userTraffic).
SetBody(data).
ForceContentType("application/json"). ForceContentType("application/json").
Post(path) Post(path)
err = c.checkResponse(res, path, err) err = c.checkResponse(res, path, err)

View File

@ -3,11 +3,12 @@ package panel
import ( import (
"fmt" "fmt"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
path2 "path"
) )
// Describe return a description of the client // Describe return a description of the client
func (c *Client) Describe() ClientInfo { func (c *Client) Describe() ClientInfo {
return ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType} return ClientInfo{APIHost: c.APIHost, NodeID: c.NodeId, Key: c.Key, NodeType: c.NodeType}
} }
// Debug set the client debug for client // Debug set the client debug for client
@ -16,13 +17,12 @@ func (c *Client) Debug() {
} }
func (c *Client) assembleURL(path string) string { func (c *Client) assembleURL(path string) string {
return c.APIHost + path return path2.Join(c.APIHost + path)
} }
func (c *Client) checkResponse(res *resty.Response, path string, err error) error { func (c *Client) checkResponse(res *resty.Response, path string, err error) error {
if err != nil { if err != nil {
return fmt.Errorf("request %s failed: %s", c.assembleURL(path), err) return fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
} }
if res.StatusCode() > 400 { if res.StatusCode() > 400 {
body := res.Body() body := res.Body()
return fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err) return fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err)

View File

@ -43,14 +43,13 @@ type IpReportConfig struct {
type DynamicSpeedLimitConfig struct { type DynamicSpeedLimitConfig struct {
Periodic int `yaml:"Periodic"` Periodic int `yaml:"Periodic"`
Traffic int64 `yaml:"Traffic"` Traffic int64 `yaml:"Traffic"`
SpeedLimit uint64 `yaml:"SpeedLimit"` SpeedLimit int `yaml:"SpeedLimit"`
ExpireTime int `yaml:"ExpireTime"` ExpireTime int `yaml:"ExpireTime"`
} }
type ControllerConfig struct { type ControllerConfig struct {
ListenIP string `yaml:"ListenIP"` ListenIP string `yaml:"ListenIP"`
SendIP string `yaml:"SendIP"` SendIP string `yaml:"SendIP"`
UpdatePeriodic int `yaml:"UpdatePeriodic"`
EnableDNS bool `yaml:"EnableDNS"` EnableDNS bool `yaml:"EnableDNS"`
DNSType string `yaml:"DNSType"` DNSType string `yaml:"DNSType"`
DisableUploadTraffic bool `yaml:"DisableUploadTraffic"` DisableUploadTraffic bool `yaml:"DisableUploadTraffic"`
@ -74,9 +73,8 @@ type ApiConfig struct {
NodeType string `yaml:"NodeType"` NodeType string `yaml:"NodeType"`
EnableVless bool `yaml:"EnableVless"` EnableVless bool `yaml:"EnableVless"`
EnableXTLS bool `yaml:"EnableXTLS"` EnableXTLS bool `yaml:"EnableXTLS"`
//EnableSS2022 bool `yaml:"EnableSS2022"`
Timeout int `yaml:"Timeout"` Timeout int `yaml:"Timeout"`
SpeedLimit float64 `yaml:"SpeedLimit"` SpeedLimit int `yaml:"SpeedLimit"`
DeviceLimit int `yaml:"DeviceLimit"` DeviceLimit int `yaml:"DeviceLimit"`
RuleListPath string `yaml:"RuleListPath"` RuleListPath string `yaml:"RuleListPath"`
DisableCustomConfig bool `yaml:"DisableCustomConfig"` DisableCustomConfig bool `yaml:"DisableCustomConfig"`

View File

@ -13,14 +13,14 @@ import (
type UserLimitInfo struct { type UserLimitInfo struct {
UID int UID int
SpeedLimit uint64 SpeedLimit int
DynamicSpeedLimit int
ExpireTime int64 ExpireTime int64
//DeviceLimit int
} }
type InboundInfo struct { type InboundInfo struct {
Tag string Tag string
NodeSpeedLimit uint64 NodeSpeedLimit int
NodeDeviceLimit int NodeDeviceLimit int
UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo
SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket
@ -37,27 +37,47 @@ func NewLimiter() *Limiter {
} }
} }
func (l *Limiter) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo) error { func (l *Limiter) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo, users []panel.UserInfo) error {
inboundInfo := &InboundInfo{ inboundInfo := &InboundInfo{
Tag: tag, Tag: tag,
NodeSpeedLimit: nodeInfo.SpeedLimit, NodeSpeedLimit: nodeInfo.SpeedLimit,
NodeDeviceLimit: nodeInfo.DeviceLimit, NodeDeviceLimit: nodeInfo.DeviceLimit,
UserLimitInfo: new(sync.Map),
SpeedLimiter: new(sync.Map), SpeedLimiter: new(sync.Map),
UserOnlineIP: new(sync.Map), UserOnlineIP: new(sync.Map),
} }
for i := range users {
if users[i].SpeedLimit != 0 {
userLimit := &UserLimitInfo{
UID: users[i].Id,
SpeedLimit: users[i].SpeedLimit,
ExpireTime: 0,
}
inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, users[i].Uuid, users[i].Id), userLimit)
}
}
inboundInfo.UserLimitInfo = new(sync.Map) inboundInfo.UserLimitInfo = new(sync.Map)
l.InboundInfo.Store(tag, inboundInfo) // Replace the old inbound info l.InboundInfo.Store(tag, inboundInfo) // Replace the old inbound info
return nil return nil
} }
func (l *Limiter) UpdateInboundLimiter(tag string, deleted []panel.UserInfo) error { func (l *Limiter) UpdateInboundLimiter(tag string, added []panel.UserInfo, deleted []panel.UserInfo) error {
if value, ok := l.InboundInfo.Load(tag); ok { if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo) inboundInfo := value.(*InboundInfo)
for i := range deleted { for i := range deleted {
inboundInfo.UserLimitInfo.Delete(fmt.Sprintf("%s|%s|%d", tag, inboundInfo.UserLimitInfo.Delete(fmt.Sprintf("%s|%s|%d", tag,
(deleted)[i].GetUserEmail(), (deleted)[i].UID)) (deleted)[i].Uuid, (deleted)[i].Id))
inboundInfo.SpeedLimiter.Delete(fmt.Sprintf("%s|%s|%d", tag, }
(deleted)[i].GetUserEmail(), (deleted)[i].UID)) // Delete limiter bucket for i := range added {
if added[i].SpeedLimit != 0 {
userLimit := &UserLimitInfo{
UID: added[i].Id,
SpeedLimit: added[i].SpeedLimit,
ExpireTime: 0,
}
inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag,
(added)[i].Uuid, (added)[i].Id), userLimit)
}
} }
} else { } else {
return fmt.Errorf("no such inbound in limiter: %s", tag) return fmt.Errorf("no such inbound in limiter: %s", tag)
@ -70,14 +90,14 @@ func (l *Limiter) DeleteInboundLimiter(tag string) error {
return nil return nil
} }
func (l *Limiter) AddUserSpeedLimit(tag string, userInfo *panel.UserInfo, limit uint64, expire int64) error { func (l *Limiter) AddDynamicSpeedLimit(tag string, userInfo *panel.UserInfo, limit int, expire int64) error {
if value, ok := l.InboundInfo.Load(tag); ok { if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo) inboundInfo := value.(*InboundInfo)
userLimit := &UserLimitInfo{ userLimit := &UserLimitInfo{
SpeedLimit: limit, DynamicSpeedLimit: limit,
ExpireTime: time.Now().Add(time.Duration(expire) * time.Second).Unix(), ExpireTime: time.Now().Add(time.Duration(expire) * time.Second).Unix(),
} }
inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, userInfo.GetUserEmail(), userInfo.UID), userLimit) inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, userInfo.Uuid, userInfo.Id), userLimit)
return nil return nil
} else { } else {
return fmt.Errorf("no such inbound in limiter: %s", tag) return fmt.Errorf("no such inbound in limiter: %s", tag)
@ -167,15 +187,17 @@ func (l *Limiter) CheckSpeedAndDeviceLimit(tag string, email string, ip string)
if value, ok := l.InboundInfo.Load(tag); ok { if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo) inboundInfo := value.(*InboundInfo)
nodeLimit := inboundInfo.NodeSpeedLimit nodeLimit := inboundInfo.NodeSpeedLimit
var userLimit uint64 = 0 userLimit := 0
expired := false expired := false
if v, ok := inboundInfo.UserLimitInfo.Load(email); ok { if v, ok := inboundInfo.UserLimitInfo.Load(email); ok {
u := v.(*UserLimitInfo) u := v.(*UserLimitInfo)
if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 { if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 {
userLimit = 0 if u.SpeedLimit != 0 {
userLimit = u.SpeedLimit
}
expired = true expired = true
} else { } else {
userLimit = u.SpeedLimit userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
} }
} }
ipMap := new(sync.Map) ipMap := new(sync.Map)
@ -200,9 +222,9 @@ func (l *Limiter) CheckSpeedAndDeviceLimit(tag string, email string, ip string)
} }
} }
} }
limit := determineSpeedLimit(nodeLimit, userLimit) // If you need the Speed limit limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit
if limit > 0 { if limit > 0 {
limiter := ratelimit.NewBucketWithQuantum(time.Second, int64(limit), int64(limit)) // Byte/s limiter := ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s
if v, ok := inboundInfo.SpeedLimiter.LoadOrStore(email, limiter); ok { if v, ok := inboundInfo.SpeedLimiter.LoadOrStore(email, limiter); ok {
if expired { if expired {
inboundInfo.SpeedLimiter.Store(email, limiter) inboundInfo.SpeedLimiter.Store(email, limiter)
@ -246,22 +268,22 @@ func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
} }
// determineSpeedLimit returns the minimum non-zero rate // determineSpeedLimit returns the minimum non-zero rate
func determineSpeedLimit(nodeLimit, userLimit uint64) (limit uint64) { func determineSpeedLimit(limit1, limit2 int) (limit int) {
if nodeLimit == 0 || userLimit == 0 { if limit1 == 0 || limit2 == 0 {
if nodeLimit > userLimit { if limit1 > limit2 {
return nodeLimit return limit1
} else if nodeLimit < userLimit { } else if limit1 < limit2 {
return userLimit return limit2
} else { } else {
return 0 return 0
} }
} else { } else {
if nodeLimit > userLimit { if limit1 > limit2 {
return userLimit return limit2
} else if nodeLimit < userLimit { } else if limit1 < limit2 {
return nodeLimit return limit1
} else { } else {
return nodeLimit return limit1
} }
} }
} }

View File

@ -16,7 +16,7 @@ func NewRule() *Rule {
} }
} }
func (r *Rule) UpdateRule(tag string, newRuleList *panel.DetectRule) error { func (r *Rule) UpdateRule(tag string, newRuleList []panel.DestinationRule) error {
if value, ok := r.Rule.LoadOrStore(tag, newRuleList); ok { if value, ok := r.Rule.LoadOrStore(tag, newRuleList); ok {
oldRuleList := value.([]panel.DestinationRule) oldRuleList := value.([]panel.DestinationRule)
if !reflect.DeepEqual(oldRuleList, newRuleList) { if !reflect.DeepEqual(oldRuleList, newRuleList) {
@ -30,20 +30,13 @@ func (r *Rule) Detect(tag string, destination string, protocol string) (reject b
reject = false reject = false
// If we have some rule for this inbound // If we have some rule for this inbound
if value, ok := r.Rule.Load(tag); ok { if value, ok := r.Rule.Load(tag); ok {
ruleList := value.(*panel.DetectRule) ruleList := value.([]panel.DestinationRule)
for i, _ := range ruleList.DestinationRule { for i := range ruleList {
if ruleList.DestinationRule[i].Pattern.Match([]byte(destination)) { if ruleList[i].Pattern.Match([]byte(destination)) {
reject = true reject = true
break break
} }
} }
if !reject {
for _, v := range ruleList.ProtocolRule {
if v == protocol {
return true
}
}
}
} }
return reject return reject
} }

View File

@ -1,10 +1,10 @@
package core package core
import ( import (
"encoding/json"
"github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/core/app/dispatcher" "github.com/Yuzuki616/V2bX/core/app/dispatcher"
_ "github.com/Yuzuki616/V2bX/core/distro/all" _ "github.com/Yuzuki616/V2bX/core/distro/all"
"github.com/goccy/go-json"
"github.com/xtls/xray-core/app/proxyman" "github.com/xtls/xray-core/app/proxyman"
"github.com/xtls/xray-core/app/stats" "github.com/xtls/xray-core/app/stats"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"

View File

@ -28,8 +28,8 @@ func (p *Core) AddInbound(config *core.InboundHandlerConfig) error {
return nil return nil
} }
func (p *Core) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo) error { func (p *Core) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo, users []panel.UserInfo) error {
return p.dispatcher.Limiter.AddInboundLimiter(tag, nodeInfo) return p.dispatcher.Limiter.AddInboundLimiter(tag, nodeInfo, users)
} }
func (p *Core) GetInboundLimiter(tag string) (*dispatcher.InboundInfo, error) { func (p *Core) GetInboundLimiter(tag string) (*dispatcher.InboundInfo, error) {
@ -40,14 +40,14 @@ func (p *Core) GetInboundLimiter(tag string) (*dispatcher.InboundInfo, error) {
return nil, fmt.Errorf("not found limiter") return nil, fmt.Errorf("not found limiter")
} }
func (p *Core) UpdateInboundLimiter(tag string, deleted []panel.UserInfo) error { func (p *Core) UpdateInboundLimiter(tag string, added []panel.UserInfo, deleted []panel.UserInfo) error {
return p.dispatcher.Limiter.UpdateInboundLimiter(tag, deleted) return p.dispatcher.Limiter.UpdateInboundLimiter(tag, added, deleted)
} }
func (p *Core) DeleteInboundLimiter(tag string) error { func (p *Core) DeleteInboundLimiter(tag string) error {
return p.dispatcher.Limiter.DeleteInboundLimiter(tag) return p.dispatcher.Limiter.DeleteInboundLimiter(tag)
} }
func (p *Core) UpdateRule(tag string, newRuleList *panel.DetectRule) error { func (p *Core) UpdateRule(tag string, newRuleList []panel.DestinationRule) error {
return p.dispatcher.RuleManager.UpdateRule(tag, newRuleList) return p.dispatcher.RuleManager.UpdateRule(tag, newRuleList)
} }

View File

@ -80,8 +80,8 @@ func (p *Core) GetUserTraffic(email string, reset bool) (up int64, down int64) {
return up, down return up, down
} }
func (p *Core) AddUserSpeedLimit(tag string, user *panel.UserInfo, speedLimit uint64, expire int64) error { func (p *Core) AddUserSpeedLimit(tag string, user *panel.UserInfo, speedLimit int, expire int64) error {
return p.dispatcher.Limiter.AddUserSpeedLimit(tag, user, speedLimit, expire) return p.dispatcher.Limiter.AddDynamicSpeedLimit(tag, user, speedLimit, expire)
} }
func (p *Core) ListOnlineIp(tag string) ([]dispatcher.UserIpList, error) { func (p *Core) ListOnlineIp(tag string) ([]dispatcher.UserIpList, error) {

View File

@ -28,7 +28,6 @@ Nodes:
ControllerConfig: ControllerConfig:
ListenIP: 0.0.0.0 # IP address you want to listen ListenIP: 0.0.0.0 # IP address you want to listen
SendIP: 0.0.0.0 # IP address you want to send pacakage SendIP: 0.0.0.0 # IP address you want to send pacakage
UpdatePeriodic: 60 # Time to update the nodeinfo, how many sec.
EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
EnableProxyProtocol: false # Only works for WebSocket and TCP EnableProxyProtocol: false # Only works for WebSocket and TCP
@ -83,7 +82,6 @@ Nodes:
# DeviceLimit: 0 # Local settings will replace remote settings # DeviceLimit: 0 # Local settings will replace remote settings
# ControllerConfig: # ControllerConfig:
# ListenIP: 0.0.0.0 # IP address you want to listen # ListenIP: 0.0.0.0 # IP address you want to listen
# UpdatePeriodic: 10 # Time to update the nodeinfo, how many sec.
# EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well # EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
# CertConfig: # CertConfig:
# CertMode: dns # Option about how to get certificate: none, file, http, dns # CertMode: dns # Option about how to get certificate: none, file, http, dns

View File

@ -1,108 +1,47 @@
package controller package controller
import ( import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt" "fmt"
"github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/node/controller/legoCmd" "github.com/Yuzuki616/V2bX/node/controller/legoCmd"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"github.com/xtls/xray-core/common/net" "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/core"
coreConf "github.com/xtls/xray-core/infra/conf" coreConf "github.com/xtls/xray-core/infra/conf"
) )
// buildInbound build Inbound config for different protocol // buildInbound build Inbound config for different protocol
func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag string) (*core.InboundHandlerConfig, error) { func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag string) (*core.InboundHandlerConfig, error) {
var proxySetting interface{} inbound := &coreConf.InboundDetourConfig{}
if nodeInfo.NodeType == "V2ray" { // Set network protocol
defer func() { t := coreConf.TransportProtocol(nodeInfo.Network)
//Clear v2ray config inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
nodeInfo.V2ray = nil var err error
}() switch nodeInfo.NodeType {
if nodeInfo.EnableVless { case "V2ray":
//Set vless err = buildV2ray(config, nodeInfo, inbound)
nodeInfo.V2ray.Inbounds[0].Protocol = "vless" case "Trojan":
if config.EnableFallback { err = buildTrojan(config, nodeInfo, inbound)
// Set fallback case "Shadowsocks":
fallbackConfigs, err := buildVlessFallbacks(config.FallBackConfigs) err = buildShadowsocks(config, nodeInfo, inbound)
if err == nil { default:
proxySetting = &coreConf.VLessInboundConfig{
Decryption: "none",
Fallbacks: fallbackConfigs,
}
} else {
return nil, err
}
} else {
proxySetting = &coreConf.VLessInboundConfig{
Decryption: "none",
}
}
} else {
// Set vmess
nodeInfo.V2ray.Inbounds[0].Protocol = "vmess"
proxySetting = &coreConf.VMessInboundConfig{}
}
} else if nodeInfo.NodeType == "Trojan" {
defer func() {
//clear trojan and v2ray config
nodeInfo.V2ray = nil
nodeInfo.Trojan = nil
}()
nodeInfo.V2ray = &panel.V2rayConfig{}
nodeInfo.V2ray.Inbounds = make([]coreConf.InboundDetourConfig, 1)
nodeInfo.V2ray.Inbounds[0].Protocol = "trojan"
if config.EnableFallback {
// Set fallback
fallbackConfigs, err := buildTrojanFallbacks(config.FallBackConfigs)
if err == nil {
proxySetting = &coreConf.TrojanServerConfig{
Fallbacks: fallbackConfigs,
}
} else {
return nil, err
}
} else {
proxySetting = &coreConf.TrojanServerConfig{}
}
nodeInfo.V2ray.Inbounds[0].PortList = &coreConf.PortList{
Range: []coreConf.PortRange{{From: uint32(nodeInfo.Trojan.LocalPort), To: uint32(nodeInfo.Trojan.LocalPort)}},
}
t := coreConf.TransportProtocol(nodeInfo.Trojan.TransportProtocol)
nodeInfo.V2ray.Inbounds[0].StreamSetting = &coreConf.StreamConfig{Network: &t}
} else if nodeInfo.NodeType == "Shadowsocks" {
defer func() {
//Clear v2ray config
nodeInfo.V2ray = nil
}()
nodeInfo.V2ray = &panel.V2rayConfig{}
nodeInfo.V2ray.Inbounds = []coreConf.InboundDetourConfig{{Protocol: "shadowsocks"}}
proxySetting = &coreConf.ShadowsocksServerConfig{}
randomPasswd := uuid.New()
defaultSSuser := &coreConf.ShadowsocksUserConfig{
Cipher: "aes-128-gcm",
Password: randomPasswd.String(),
}
proxySetting, _ := proxySetting.(*coreConf.ShadowsocksServerConfig)
proxySetting.Users = append(proxySetting.Users, defaultSSuser)
proxySetting.NetworkList = &coreConf.NetworkList{"tcp", "udp"}
proxySetting.IVCheck = true
if config.DisableIVCheck {
proxySetting.IVCheck = false
}
nodeInfo.V2ray.Inbounds[0].PortList = &coreConf.PortList{
Range: []coreConf.PortRange{{From: uint32(nodeInfo.SS.Port), To: uint32(nodeInfo.SS.Port)}},
}
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) return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks", nodeInfo.NodeType)
} }
// Build Listen IP address if err != nil {
return nil, err
}
// Set server port
inbound.PortList = &coreConf.PortList{
Range: []coreConf.PortRange{{From: uint32(nodeInfo.ServerPort), To: uint32(nodeInfo.ServerPort)}},
}
// Set Listen IP address
ipAddress := net.ParseAddress(config.ListenIP) ipAddress := net.ParseAddress(config.ListenIP)
nodeInfo.V2ray.Inbounds[0].ListenOn = &coreConf.Address{Address: ipAddress} inbound.ListenOn = &coreConf.Address{Address: ipAddress}
// SniffingConfig // Set SniffingConfig
sniffingConfig := &coreConf.SniffingConfig{ sniffingConfig := &coreConf.SniffingConfig{
Enabled: true, Enabled: true,
DestOverride: &coreConf.StringList{"http", "tls"}, DestOverride: &coreConf.StringList{"http", "tls"},
@ -110,31 +49,23 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
if config.DisableSniffing { if config.DisableSniffing {
sniffingConfig.Enabled = false sniffingConfig.Enabled = false
} }
nodeInfo.V2ray.Inbounds[0].SniffingConfig = sniffingConfig inbound.SniffingConfig = sniffingConfig
if nodeInfo.NodeType == "tcp" {
var setting json.RawMessage if inbound.StreamSetting.TCPSettings != nil {
inbound.StreamSetting.TCPSettings.AcceptProxyProtocol = config.EnableProxyProtocol
// Build Protocol and Protocol setting
setting, err := json.Marshal(proxySetting)
if err != nil {
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
}
if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network == "tcp" {
if nodeInfo.V2ray.Inbounds[0].StreamSetting.TCPSettings != nil {
nodeInfo.V2ray.Inbounds[0].StreamSetting.TCPSettings.AcceptProxyProtocol = config.EnableProxyProtocol
} else { } else {
tcpSetting := &coreConf.TCPConfig{ tcpSetting := &coreConf.TCPConfig{
AcceptProxyProtocol: config.EnableProxyProtocol, AcceptProxyProtocol: config.EnableProxyProtocol,
} //Enable proxy protocol } //Enable proxy protocol
nodeInfo.V2ray.Inbounds[0].StreamSetting.TCPSettings = tcpSetting inbound.StreamSetting.TCPSettings = tcpSetting
} }
} else if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network == "ws" { } else if nodeInfo.NodeType == "ws" {
nodeInfo.V2ray.Inbounds[0].StreamSetting.WSSettings = &coreConf.WebSocketConfig{ inbound.StreamSetting.WSSettings = &coreConf.WebSocketConfig{
AcceptProxyProtocol: config.EnableProxyProtocol} //Enable proxy protocol AcceptProxyProtocol: config.EnableProxyProtocol} //Enable proxy protocol
} }
// Build TLS and XTLS settings // Set TLS and XTLS settings
if nodeInfo.EnableTls && config.CertConfig.CertMode != "none" { if nodeInfo.EnableTls && config.CertConfig.CertMode != "none" {
nodeInfo.V2ray.Inbounds[0].StreamSetting.Security = nodeInfo.TLSType inbound.StreamSetting.Security = nodeInfo.TLSType
certFile, keyFile, err := getCertFile(config.CertConfig) certFile, keyFile, err := getCertFile(config.CertConfig)
if err != nil { if err != nil {
return nil, err return nil, err
@ -144,7 +75,7 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
RejectUnknownSNI: config.CertConfig.RejectUnknownSni, RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
} }
tlsSettings.Certs = append(tlsSettings.Certs, &coreConf.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 inbound.StreamSetting.TLSSettings = tlsSettings
} else if nodeInfo.TLSType == "xtls" { } else if nodeInfo.TLSType == "xtls" {
xtlsSettings := &coreConf.XTLSConfig{ xtlsSettings := &coreConf.XTLSConfig{
RejectUnknownSNI: config.CertConfig.RejectUnknownSni, RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
@ -153,23 +84,139 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
CertFile: certFile, CertFile: certFile,
KeyFile: keyFile, KeyFile: keyFile,
OcspStapling: 3600}) OcspStapling: 3600})
nodeInfo.V2ray.Inbounds[0].StreamSetting.XTLSSettings = xtlsSettings inbound.StreamSetting.XTLSSettings = xtlsSettings
} }
} else if nodeInfo.NodeType == "V2ray" {
nodeInfo.V2ray.Inbounds[0].StreamSetting.Security = "none"
} }
// Support ProxyProtocol for any transport protocol // Support ProxyProtocol for any transport protocol
if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network != "tcp" && if *inbound.StreamSetting.Network != "tcp" &&
*nodeInfo.V2ray.Inbounds[0].StreamSetting.Network != "ws" && *inbound.StreamSetting.Network != "ws" &&
config.EnableProxyProtocol { config.EnableProxyProtocol {
sockoptConfig := &coreConf.SocketConfig{ sockoptConfig := &coreConf.SocketConfig{
AcceptProxyProtocol: config.EnableProxyProtocol, AcceptProxyProtocol: config.EnableProxyProtocol,
} //Enable proxy protocol } //Enable proxy protocol
nodeInfo.V2ray.Inbounds[0].StreamSetting.SocketSettings = sockoptConfig inbound.StreamSetting.SocketSettings = sockoptConfig
} }
nodeInfo.V2ray.Inbounds[0].Settings = &setting inbound.Tag = tag
nodeInfo.V2ray.Inbounds[0].Tag = tag return inbound.Build()
return nodeInfo.V2ray.Inbounds[0].Build() }
func buildV2ray(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
if nodeInfo.EnableVless {
//Set vless
inbound.Protocol = "vless"
if config.EnableFallback {
// Set fallback
fallbackConfigs, err := buildVlessFallbacks(config.FallBackConfigs)
if err != nil {
return err
}
s, err := json.Marshal(&coreConf.VLessInboundConfig{
Decryption: "none",
Fallbacks: fallbackConfigs,
})
if err != nil {
return fmt.Errorf("marshal vless fallback config error: %s", err)
}
inbound.Settings = (*json.RawMessage)(&s)
} else {
var err error
s, err := json.Marshal(&coreConf.VLessInboundConfig{
Decryption: "none",
})
if err != nil {
return fmt.Errorf("marshal vless config error: %s", err)
}
inbound.Settings = (*json.RawMessage)(&s)
}
} else {
// Set vmess
inbound.Protocol = "vmess"
var err error
s, err := json.Marshal(&coreConf.VMessInboundConfig{})
if err != nil {
return fmt.Errorf("marshal vmess settings error: %s", err)
}
inbound.Settings = (*json.RawMessage)(&s)
}
switch nodeInfo.Network {
case "tcp":
err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.TCPSettings)
if err != nil {
return fmt.Errorf("unmarshal tcp settings error: %s", err)
}
case "ws":
err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.WSSettings)
if err != nil {
return fmt.Errorf("unmarshal ws settings error: %s", err)
}
case "grpc":
err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.GRPCConfig)
if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err)
}
}
return nil
}
func buildTrojan(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
inbound.Protocol = "trojan"
if config.EnableFallback {
// Set fallback
fallbackConfigs, err := buildTrojanFallbacks(config.FallBackConfigs)
if err != nil {
return err
}
s, err := json.Marshal(&coreConf.TrojanServerConfig{
Fallbacks: fallbackConfigs,
})
inbound.Settings = (*json.RawMessage)(&s)
if err != nil {
return fmt.Errorf("marshal trojan fallback config error: %s", err)
}
} else {
s := []byte("{}")
inbound.Settings = (*json.RawMessage)(&s)
}
t := coreConf.TransportProtocol(nodeInfo.Network)
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
return nil
}
func buildShadowsocks(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
inbound.Protocol = "shadowsocks"
settings := &coreConf.ShadowsocksServerConfig{
Cipher: nodeInfo.Cipher,
}
p := make([]byte, 32)
_, err := rand.Read(p)
if err != nil {
return fmt.Errorf("generate random password error: %s", err)
}
randomPasswd := hex.EncodeToString(p)
cipher := nodeInfo.Cipher
if nodeInfo.ServerKey != "" {
settings.Password = nodeInfo.ServerKey
randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd))
cipher = ""
}
defaultSSuser := &coreConf.ShadowsocksUserConfig{
Cipher: cipher,
Password: randomPasswd,
}
settings.Users = append(settings.Users, defaultSSuser)
settings.NetworkList = &coreConf.NetworkList{"tcp", "udp"}
settings.IVCheck = true
if config.DisableIVCheck {
settings.IVCheck = false
}
t := coreConf.TransportProtocol("tcp")
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
s, err := json.Marshal(settings)
inbound.Settings = (*json.RawMessage)(&s)
if err != nil {
return fmt.Errorf("marshal shadowsocks settings error: %s", err)
}
return nil
} }
func getCertFile(certConfig *conf.CertConfig) (certFile string, keyFile string, err error) { func getCertFile(certConfig *conf.CertConfig) (certFile string, keyFile string, err error) {
@ -199,7 +246,6 @@ func getCertFile(certConfig *conf.CertConfig) (certFile string, keyFile string,
} }
return certPath, keyPath, err return certPath, keyPath, err
} }
return "", "", fmt.Errorf("unsupported certmode: %s", certConfig.CertMode) return "", "", fmt.Errorf("unsupported certmode: %s", certConfig.CertMode)
} }
@ -207,14 +253,11 @@ func buildVlessFallbacks(fallbackConfigs []*conf.FallBackConfig) ([]*coreConf.VL
if fallbackConfigs == nil { if fallbackConfigs == nil {
return nil, fmt.Errorf("you must provide FallBackConfigs") return nil, fmt.Errorf("you must provide FallBackConfigs")
} }
vlessFallBacks := make([]*coreConf.VLessInboundFallback, len(fallbackConfigs)) vlessFallBacks := make([]*coreConf.VLessInboundFallback, len(fallbackConfigs))
for i, c := range fallbackConfigs { for i, c := range fallbackConfigs {
if c.Dest == "" { if c.Dest == "" {
return nil, fmt.Errorf("dest is required for fallback fialed") return nil, fmt.Errorf("dest is required for fallback fialed")
} }
var dest json.RawMessage var dest json.RawMessage
dest, err := json.Marshal(c.Dest) dest, err := json.Marshal(c.Dest)
if err != nil { if err != nil {

View File

@ -3,11 +3,11 @@ package cmd
import ( import (
"crypto" "crypto"
"crypto/x509" "crypto/x509"
"encoding/json"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log" "github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
"github.com/goccy/go-json"
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"os" "os"

View File

@ -3,8 +3,8 @@ package cmd
import ( import (
"bytes" "bytes"
"crypto/x509" "crypto/x509"
"encoding/json"
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log" "github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
"github.com/goccy/go-json"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"

View File

@ -1,8 +1,8 @@
package cmd package cmd
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/goccy/go-json"
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"path/filepath" "path/filepath"

View File

@ -14,7 +14,6 @@ import (
type Node struct { type Node struct {
server *core.Core server *core.Core
config *conf.ControllerConfig
clientInfo panel.ClientInfo clientInfo panel.ClientInfo
apiClient panel.Panel apiClient panel.Panel
nodeInfo *panel.NodeInfo nodeInfo *panel.NodeInfo
@ -25,13 +24,14 @@ type Node struct {
userReportPeriodic *task.Periodic userReportPeriodic *task.Periodic
onlineIpReportPeriodic *task.Periodic onlineIpReportPeriodic *task.Periodic
DynamicSpeedLimitPeriodic *task.Periodic DynamicSpeedLimitPeriodic *task.Periodic
*conf.ControllerConfig
} }
// New return a Node service with default parameters. // New return a Node service with default parameters.
func New(server *core.Core, api panel.Panel, config *conf.ControllerConfig) *Node { func New(server *core.Core, api panel.Panel, config *conf.ControllerConfig) *Node {
controller := &Node{ controller := &Node{
server: server, server: server,
config: config, ControllerConfig: config,
apiClient: api, apiClient: api,
} }
return controller return controller
@ -64,71 +64,67 @@ func (c *Node) Start() error {
if err != nil { if err != nil {
return err return err
} }
if err := c.server.AddInboundLimiter(c.Tag, c.nodeInfo); err != nil { if err := c.server.AddInboundLimiter(c.Tag, c.nodeInfo, c.userList); err != nil {
return fmt.Errorf("add inbound limiter failed: %s", err) return fmt.Errorf("add inbound limiter failed: %s", err)
} }
// Add Rule Manager // Add Rule Manager
if !c.config.DisableGetRule { if !c.DisableGetRule {
if ruleList, err := c.apiClient.GetNodeRule(); err != nil { if err := c.server.UpdateRule(c.Tag, newNodeInfo.Rules); err != nil {
log.Printf("Get rule list filed: %s", err)
} else if ruleList != nil {
if err := c.server.UpdateRule(c.Tag, ruleList); err != nil {
log.Printf("Update rule filed: %s", err) log.Printf("Update rule filed: %s", err)
} }
} }
}
// fetch node info task // fetch node info task
c.nodeInfoMonitorPeriodic = &task.Periodic{ c.nodeInfoMonitorPeriodic = &task.Periodic{
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second, Interval: time.Duration(c.nodeInfo.BaseConfig.PullInterval.(int)) * time.Second,
Execute: c.nodeInfoMonitor, Execute: c.nodeInfoMonitor,
} }
// fetch user list task // fetch user list task
c.userReportPeriodic = &task.Periodic{ c.userReportPeriodic = &task.Periodic{
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second, Interval: time.Duration(c.nodeInfo.BaseConfig.PushInterval.(int)) * time.Second,
Execute: c.reportUserTraffic, Execute: c.reportUserTraffic,
} }
log.Printf("[%s: %d] Start monitor node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId) log.Printf("[%s: %d] Start monitor node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
// delay to start nodeInfoMonitor // delay to start nodeInfoMonitor
go func() { go func() {
time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second) time.Sleep(time.Duration(c.nodeInfo.BaseConfig.PullInterval.(int)) * time.Second)
_ = c.nodeInfoMonitorPeriodic.Start() _ = c.nodeInfoMonitorPeriodic.Start()
}() }()
log.Printf("[%s: %d] Start report node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId) log.Printf("[%s: %d] Start report node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
// delay to start userReport // delay to start userReport
go func() { go func() {
time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second) time.Sleep(time.Duration(c.nodeInfo.BaseConfig.PushInterval.(int)) * time.Second)
_ = c.userReportPeriodic.Start() _ = c.userReportPeriodic.Start()
}() }()
if c.config.EnableDynamicSpeedLimit { if c.EnableDynamicSpeedLimit {
// Check dynamic speed limit task // Check dynamic speed limit task
c.DynamicSpeedLimitPeriodic = &task.Periodic{ c.DynamicSpeedLimitPeriodic = &task.Periodic{
Interval: time.Duration(c.config.DynamicSpeedLimitConfig.Periodic) * time.Second, Interval: time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second,
Execute: c.dynamicSpeedLimit, Execute: c.dynamicSpeedLimit,
} }
go func() { go func() {
time.Sleep(time.Duration(c.config.DynamicSpeedLimitConfig.Periodic) * time.Second) time.Sleep(time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second)
_ = c.DynamicSpeedLimitPeriodic.Start() _ = c.DynamicSpeedLimitPeriodic.Start()
}() }()
log.Printf("[%s: %d] Start dynamic speed limit", c.nodeInfo.NodeType, c.nodeInfo.NodeId) log.Printf("[%s: %d] Start dynamic speed limit", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
} }
if c.config.EnableIpRecorder { if c.EnableIpRecorder {
switch c.config.IpRecorderConfig.Type { switch c.IpRecorderConfig.Type {
case "Recorder": case "Recorder":
c.ipRecorder = iprecoder.NewRecorder(c.config.IpRecorderConfig.RecorderConfig) c.ipRecorder = iprecoder.NewRecorder(c.IpRecorderConfig.RecorderConfig)
case "Redis": case "Redis":
c.ipRecorder = iprecoder.NewRedis(c.config.IpRecorderConfig.RedisConfig) c.ipRecorder = iprecoder.NewRedis(c.IpRecorderConfig.RedisConfig)
default: default:
log.Printf("recorder type: %s is not vail, disable recorder", c.config.IpRecorderConfig.Type) log.Printf("recorder type: %s is not vail, disable recorder", c.IpRecorderConfig.Type)
return nil return nil
} }
// report and fetch online ip list task // report and fetch online ip list task
c.onlineIpReportPeriodic = &task.Periodic{ c.onlineIpReportPeriodic = &task.Periodic{
Interval: time.Duration(c.config.IpRecorderConfig.Periodic) * time.Second, Interval: time.Duration(c.IpRecorderConfig.Periodic) * time.Second,
Execute: c.reportOnlineIp, Execute: c.reportOnlineIp,
} }
go func() { go func() {
time.Sleep(time.Duration(c.config.IpRecorderConfig.Periodic) * time.Second) time.Sleep(time.Duration(c.IpRecorderConfig.Periodic) * time.Second)
_ = c.onlineIpReportPeriodic.Start() _ = c.onlineIpReportPeriodic.Start()
}() }()
log.Printf("[%s: %d] Start report online ip", c.nodeInfo.NodeType, c.nodeInfo.NodeId) log.Printf("[%s: %d] Start report online ip", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
@ -144,7 +140,6 @@ func (c *Node) Close() error {
log.Panicf("node info periodic close failed: %s", err) log.Panicf("node info periodic close failed: %s", err)
} }
} }
if c.nodeInfoMonitorPeriodic != nil { if c.nodeInfoMonitorPeriodic != nil {
err := c.userReportPeriodic.Close() err := c.userReportPeriodic.Close()
if err != nil { if err != nil {
@ -167,5 +162,5 @@ func (c *Node) Close() error {
} }
func (c *Node) buildNodeTag() string { func (c *Node) buildNodeTag() string {
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.NodeId) return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.ListenIP, c.nodeInfo.NodeId)
} }

View File

@ -1,10 +1,10 @@
package controller package controller
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/api/panel"
conf2 "github.com/Yuzuki616/V2bX/conf" conf2 "github.com/Yuzuki616/V2bX/conf"
"github.com/goccy/go-json"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"

View File

@ -6,8 +6,8 @@ import (
"github.com/Yuzuki616/V2bX/node/controller/legoCmd" "github.com/Yuzuki616/V2bX/node/controller/legoCmd"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"log" "log"
"reflect"
"runtime" "runtime"
"strconv"
"time" "time"
) )
@ -21,7 +21,6 @@ func (c *Node) nodeInfoMonitor() (err error) {
var nodeInfoChanged = false var nodeInfoChanged = false
// If nodeInfo changed // If nodeInfo changed
if newNodeInfo != nil { if newNodeInfo != nil {
if c.nodeInfo.SS == nil || !reflect.DeepEqual(c.nodeInfo.SS, newNodeInfo.SS) {
// Remove old tag // Remove old tag
oldTag := c.Tag oldTag := c.Tag
err := c.removeOldTag(oldTag) err := c.removeOldTag(oldTag)
@ -43,31 +42,20 @@ func (c *Node) nodeInfoMonitor() (err error) {
log.Print(err) log.Print(err)
return nil return nil
} }
} if err := c.server.UpdateRule(c.Tag, newNodeInfo.Rules); err != nil {
}
// Check Rule
if !c.config.DisableGetRule {
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
log.Printf("Get rule list filed: %s", err)
} else if ruleList != nil {
if err := c.server.UpdateRule(c.Tag, ruleList); err != nil {
log.Print(err) log.Print(err)
} }
} }
}
// Check Cert // Check Cert
if c.nodeInfo.EnableTls && c.config.CertConfig.CertMode != "none" && if c.nodeInfo.EnableTls && c.CertConfig.CertMode != "none" &&
(c.config.CertConfig.CertMode == "dns" || c.config.CertConfig.CertMode == "http") { (c.CertConfig.CertMode == "dns" || c.CertConfig.CertMode == "http") {
lego, err := legoCmd.New() lego, err := legoCmd.New()
if err != nil { if err != nil {
log.Print(err) log.Print(err)
} }
// Core-core supports the OcspStapling certification hot renew // Core-core supports the OcspStapling certification hot renew
_, _, err = lego.RenewCert(c.config.CertConfig.CertDomain, c.config.CertConfig.Email, _, _, err = lego.RenewCert(c.CertConfig.CertDomain, c.CertConfig.Email,
c.config.CertConfig.CertMode, c.config.CertConfig.Provider, c.config.CertConfig.DNSEnv) c.CertConfig.CertMode, c.CertConfig.Provider, c.CertConfig.DNSEnv)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
} }
@ -80,27 +68,42 @@ func (c *Node) nodeInfoMonitor() (err error) {
} }
if nodeInfoChanged { if nodeInfoChanged {
c.userList = newUserInfo c.userList = newUserInfo
newUserInfo = nil
err = c.addNewUser(c.userList, newNodeInfo) err = c.addNewUser(c.userList, newNodeInfo)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
return nil return nil
} }
newNodeInfo = nil
// Add Limiter // Add Limiter
if err := c.server.AddInboundLimiter(c.Tag, c.nodeInfo); err != nil { if err := c.server.AddInboundLimiter(c.Tag, newNodeInfo, newUserInfo); err != nil {
log.Print(err) log.Print(err)
return nil return nil
} }
runtime.GC() // Check interval
if c.nodeInfoMonitorPeriodic.Interval != time.Duration(newNodeInfo.BaseConfig.PullInterval.(int))*time.Second {
c.nodeInfoMonitorPeriodic.Interval = time.Duration(newNodeInfo.BaseConfig.PullInterval.(int)) * time.Second
_ = c.nodeInfoMonitorPeriodic.Close()
go func() {
time.Sleep(c.nodeInfoMonitorPeriodic.Interval)
_ = c.nodeInfoMonitorPeriodic.Start()
}()
}
if c.userReportPeriodic.Interval != time.Duration(newNodeInfo.BaseConfig.PushInterval.(int))*time.Second {
c.userReportPeriodic.Interval = time.Duration(newNodeInfo.BaseConfig.PushInterval.(int)) * time.Second
_ = c.userReportPeriodic.Close()
go func() {
time.Sleep(c.userReportPeriodic.Interval)
_ = c.userReportPeriodic.Start()
}()
}
} else { } else {
deleted, added := compareUserList(c.userList, newUserInfo) deleted, added := compareUserList(c.userList, newUserInfo)
if len(deleted) > 0 { if len(deleted) > 0 {
deletedEmail := make([]string, len(deleted)) deletedEmail := make([]string, len(deleted))
for i := range deleted { for i := range deleted {
deletedEmail[i] = fmt.Sprintf("%s|%s|%d", c.Tag, deletedEmail[i] = fmt.Sprintf("%s|%s|%d",
(deleted)[i].GetUserEmail(), c.Tag,
(deleted)[i].UID) (deleted)[i].Uuid,
(deleted)[i].Id)
} }
err := c.server.RemoveUsers(deletedEmail, c.Tag) err := c.server.RemoveUsers(deletedEmail, c.Tag)
if err != nil { if err != nil {
@ -114,16 +117,14 @@ func (c *Node) nodeInfoMonitor() (err error) {
} }
} }
if len(added) > 0 || len(deleted) > 0 { if len(added) > 0 || len(deleted) > 0 {
defer runtime.GC()
// Update Limiter // Update Limiter
if err := c.server.UpdateInboundLimiter(c.Tag, deleted); err != nil { if err := c.server.UpdateInboundLimiter(c.Tag, added, deleted); err != nil {
log.Print(err) log.Print(err)
} }
} }
log.Printf("[%s: %d] %d user deleted, %d user added", c.nodeInfo.NodeType, c.nodeInfo.NodeId, 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 c.userList = newUserInfo
newUserInfo = nil
} }
return nil return nil
} }
@ -141,24 +142,21 @@ func (c *Node) removeOldTag(oldTag string) (err error) {
} }
func (c *Node) addNewTag(newNodeInfo *panel.NodeInfo) (err error) { func (c *Node) addNewTag(newNodeInfo *panel.NodeInfo) (err error) {
inboundConfig, err := buildInbound(c.config, newNodeInfo, c.Tag) inboundConfig, err := buildInbound(c.ControllerConfig, newNodeInfo, c.Tag)
if err != nil { if err != nil {
return err return fmt.Errorf("build inbound error: %s", err)
} }
err = c.server.AddInbound(inboundConfig) err = c.server.AddInbound(inboundConfig)
if err != nil { if err != nil {
return fmt.Errorf("add inbound error: %s", err)
return err
} }
outBoundConfig, err := buildOutbound(c.config, newNodeInfo, c.Tag) outBoundConfig, err := buildOutbound(c.ControllerConfig, newNodeInfo, c.Tag)
if err != nil { if err != nil {
return fmt.Errorf("build outbound error: %s", err)
return err
} }
err = c.server.AddOutbound(outBoundConfig) err = c.server.AddOutbound(outBoundConfig)
if err != nil { if err != nil {
return fmt.Errorf("add outbound error: %s", err)
return err
} }
return nil return nil
} }
@ -174,13 +172,13 @@ func (c *Node) addNewUser(userInfo []panel.UserInfo, nodeInfo *panel.NodeInfo) (
} else if nodeInfo.NodeType == "Trojan" { } else if nodeInfo.NodeType == "Trojan" {
users = c.buildTrojanUsers(userInfo) users = c.buildTrojanUsers(userInfo)
} else if nodeInfo.NodeType == "Shadowsocks" { } else if nodeInfo.NodeType == "Shadowsocks" {
users = c.buildSSUsers(userInfo, getCipherFromString(nodeInfo.SS.CypherMethod)) users = c.buildSSUsers(userInfo, getCipherFromString(nodeInfo.Cipher))
} else { } else {
return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType) return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType)
} }
err = c.server.AddUsers(users, c.Tag) err = c.server.AddUsers(users, c.Tag)
if err != nil { if err != nil {
return err return fmt.Errorf("add users error: %s", err)
} }
log.Printf("[%s: %d] Added %d new users", c.nodeInfo.NodeType, c.nodeInfo.NodeId, len(userInfo)) log.Printf("[%s: %d] Added %d new users", c.nodeInfo.NodeType, c.nodeInfo.NodeId, len(userInfo))
return nil return nil
@ -190,24 +188,24 @@ func compareUserList(old, new []panel.UserInfo) (deleted, added []panel.UserInfo
tmp := map[string]struct{}{} tmp := map[string]struct{}{}
tmp2 := map[string]struct{}{} tmp2 := map[string]struct{}{}
for i := range old { for i := range old {
tmp[(old)[i].GetUserEmail()] = struct{}{} tmp[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{}
} }
l := len(tmp) l := len(tmp)
for i := range new { for i := range new {
e := (new)[i].GetUserEmail() e := new[i].Uuid + strconv.Itoa(new[i].SpeedLimit)
tmp[e] = struct{}{} tmp[e] = struct{}{}
tmp2[e] = struct{}{} tmp2[e] = struct{}{}
if l != len(tmp) { if l != len(tmp) {
added = append(added, (new)[i]) added = append(added, new[i])
l++ l++
} }
} }
tmp = nil tmp = nil
l = len(tmp2) l = len(tmp2)
for i := range old { for i := range old {
tmp2[(old)[i].GetUserEmail()] = struct{}{} tmp2[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{}
if l != len(tmp2) { if l != len(tmp2) {
deleted = append(deleted, (old)[i]) deleted = append(deleted, old[i])
l++ l++
} }
} }
@ -220,16 +218,16 @@ func (c *Node) reportUserTraffic() (err error) {
for i := range c.userList { for i := range c.userList {
up, down := c.server.GetUserTraffic(c.buildUserTag(&(c.userList)[i]), true) up, down := c.server.GetUserTraffic(c.buildUserTag(&(c.userList)[i]), true)
if up > 0 || down > 0 { if up > 0 || down > 0 {
if c.config.EnableDynamicSpeedLimit { if c.EnableDynamicSpeedLimit {
c.userList[i].Traffic += up + down c.userList[i].Traffic += up + down
} }
userTraffic = append(userTraffic, panel.UserTraffic{ userTraffic = append(userTraffic, panel.UserTraffic{
UID: (c.userList)[i].UID, UID: (c.userList)[i].Id,
Upload: up, Upload: up,
Download: down}) Download: down})
} }
} }
if len(userTraffic) > 0 && !c.config.DisableUploadTraffic { if len(userTraffic) > 0 && !c.DisableUploadTraffic {
err = c.apiClient.ReportUserTraffic(userTraffic) err = c.apiClient.ReportUserTraffic(userTraffic)
if err != nil { if err != nil {
log.Printf("Report user traffic faild: %s", err) log.Printf("Report user traffic faild: %s", err)
@ -238,7 +236,7 @@ func (c *Node) reportUserTraffic() (err error) {
} }
} }
userTraffic = nil userTraffic = nil
if !c.config.EnableIpRecorder { if !c.EnableIpRecorder {
c.server.ClearOnlineIp(c.Tag) c.server.ClearOnlineIp(c.Tag)
} }
runtime.GC() runtime.GC()
@ -256,7 +254,7 @@ func (c *Node) reportOnlineIp() (err error) {
log.Print("Report online ip error: ", err) log.Print("Report online ip error: ", err)
c.server.ClearOnlineIp(c.Tag) c.server.ClearOnlineIp(c.Tag)
} }
if c.config.IpRecorderConfig.EnableIpSync { if c.IpRecorderConfig.EnableIpSync {
c.server.UpdateOnlineIp(c.Tag, onlineIp) c.server.UpdateOnlineIp(c.Tag, onlineIp)
log.Printf("[Node: %d] Updated %d online ip", c.nodeInfo.NodeId, len(onlineIp)) log.Printf("[Node: %d] Updated %d online ip", c.nodeInfo.NodeId, len(onlineIp))
} }
@ -265,14 +263,14 @@ func (c *Node) reportOnlineIp() (err error) {
} }
func (c *Node) dynamicSpeedLimit() error { func (c *Node) dynamicSpeedLimit() error {
if c.config.EnableDynamicSpeedLimit { if c.EnableDynamicSpeedLimit {
for i := range c.userList { for i := range c.userList {
up, down := c.server.GetUserTraffic(c.buildUserTag(&(c.userList)[i]), false) up, down := c.server.GetUserTraffic(c.buildUserTag(&(c.userList)[i]), false)
if c.userList[i].Traffic+down+up/1024/1024 > c.config.DynamicSpeedLimitConfig.Traffic { if c.userList[i].Traffic+down+up/1024/1024 > c.DynamicSpeedLimitConfig.Traffic {
err := c.server.AddUserSpeedLimit(c.Tag, err := c.server.AddUserSpeedLimit(c.Tag,
&c.userList[i], &c.userList[i],
c.config.DynamicSpeedLimitConfig.SpeedLimit, c.DynamicSpeedLimitConfig.SpeedLimit,
time.Now().Add(time.Second*time.Duration(c.config.DynamicSpeedLimitConfig.ExpireTime)).Unix()) time.Now().Add(time.Second*time.Duration(c.DynamicSpeedLimitConfig.ExpireTime)).Unix())
if err != nil { if err != nil {
log.Print(err) log.Print(err)
} }

View File

@ -3,14 +3,14 @@ package controller
import ( import (
"fmt" "fmt"
"github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/api/panel"
"strings"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/infra/conf" "github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/proxy/shadowsocks" "github.com/xtls/xray-core/proxy/shadowsocks"
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
"github.com/xtls/xray-core/proxy/trojan" "github.com/xtls/xray-core/proxy/trojan"
"github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless"
"strings"
) )
func (c *Node) buildVmessUsers(userInfo []panel.UserInfo) (users []*protocol.User) { func (c *Node) buildVmessUsers(userInfo []panel.UserInfo) (users []*protocol.User) {
@ -23,7 +23,7 @@ func (c *Node) buildVmessUsers(userInfo []panel.UserInfo) (users []*protocol.Use
func (c *Node) buildVmessUser(userInfo *panel.UserInfo, serverAlterID uint16) (user *protocol.User) { func (c *Node) buildVmessUser(userInfo *panel.UserInfo, serverAlterID uint16) (user *protocol.User) {
vmessAccount := &conf.VMessAccount{ vmessAccount := &conf.VMessAccount{
ID: userInfo.V2rayUser.Uuid, ID: userInfo.Uuid,
AlterIds: serverAlterID, AlterIds: serverAlterID,
Security: "auto", Security: "auto",
} }
@ -44,7 +44,7 @@ func (c *Node) buildVlessUsers(userInfo []panel.UserInfo) (users []*protocol.Use
func (c *Node) buildVlessUser(userInfo *panel.UserInfo) (user *protocol.User) { func (c *Node) buildVlessUser(userInfo *panel.UserInfo) (user *protocol.User) {
vlessAccount := &vless.Account{ vlessAccount := &vless.Account{
Id: userInfo.V2rayUser.Uuid, Id: userInfo.Uuid,
Flow: "xtls-rprx-direct", Flow: "xtls-rprx-direct",
} }
return &protocol.User{ return &protocol.User{
@ -64,7 +64,7 @@ func (c *Node) buildTrojanUsers(userInfo []panel.UserInfo) (users []*protocol.Us
func (c *Node) buildTrojanUser(userInfo *panel.UserInfo) (user *protocol.User) { func (c *Node) buildTrojanUser(userInfo *panel.UserInfo) (user *protocol.User) {
trojanAccount := &trojan.Account{ trojanAccount := &trojan.Account{
Password: userInfo.TrojanUser.Password, Password: userInfo.Uuid,
Flow: "xtls-rprx-direct", Flow: "xtls-rprx-direct",
} }
return &protocol.User{ return &protocol.User{
@ -98,8 +98,9 @@ func (c *Node) buildSSUsers(userInfo []panel.UserInfo, cypher shadowsocks.Cipher
} }
func (c *Node) buildSSUser(userInfo *panel.UserInfo, cypher shadowsocks.CipherType) (user *protocol.User) { func (c *Node) buildSSUser(userInfo *panel.UserInfo, cypher shadowsocks.CipherType) (user *protocol.User) {
if c.nodeInfo.ServerKey == "" {
ssAccount := &shadowsocks.Account{ ssAccount := &shadowsocks.Account{
Password: userInfo.Secret, Password: userInfo.Uuid,
CipherType: cypher, CipherType: cypher,
} }
return &protocol.User{ return &protocol.User{
@ -107,8 +108,18 @@ func (c *Node) buildSSUser(userInfo *panel.UserInfo, cypher shadowsocks.CipherTy
Email: c.buildUserTag(userInfo), Email: c.buildUserTag(userInfo),
Account: serial.ToTypedMessage(ssAccount), Account: serial.ToTypedMessage(ssAccount),
} }
} else {
ssAccount := &shadowsocks_2022.User{
Key: userInfo.Uuid,
}
return &protocol.User{
Level: 0,
Email: c.buildUserTag(userInfo),
Account: serial.ToTypedMessage(ssAccount),
}
}
} }
func (c *Node) buildUserTag(user *panel.UserInfo) string { func (c *Node) buildUserTag(user *panel.UserInfo) string {
return fmt.Sprintf("%s|%s|%d", c.Tag, user.GetUserEmail(), user.UID) return fmt.Sprintf("%s|%s|%d", c.Tag, user.Uuid, user.Id)
} }

View File

@ -18,9 +18,13 @@ func New() *Node {
func (n *Node) Start(nodes []*conf.NodeConfig, core *core.Core) error { func (n *Node) Start(nodes []*conf.NodeConfig, core *core.Core) error {
n.controllers = make([]*controller.Node, len(nodes)) n.controllers = make([]*controller.Node, len(nodes))
for i, c := range nodes { for i, c := range nodes {
p, err := panel.New(c.ApiConfig)
if err != nil {
return err
}
// Register controller service // Register controller service
n.controllers[i] = controller.New(core, panel.New(c.ApiConfig), c.ControllerConfig) n.controllers[i] = controller.New(core, p, c.ControllerConfig)
err := n.controllers[i].Start() err = n.controllers[i].Start()
if err != nil { if err != nil {
return err return err
} }