mirror of
https://github.com/wyx2685/V2bX.git
synced 2025-02-08 17:18:13 -05:00
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:
parent
0ac7ea691d
commit
695da4f4c5
@ -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)
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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
20
api/panel/node_test.go
Normal 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())
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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"`
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user