mirror of
https://github.com/wyx2685/V2bX.git
synced 2025-01-22 09:58:14 -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
23
README.md
23
README.md
@ -12,7 +12,7 @@ A V2board node server based on Xray-core, modified from XrayR
|
||||
|
||||
如对脚本不放心,可使用此沙箱先测一遍再使用: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之前的版本。**
|
||||
|
||||
## 免责声明
|
||||
|
||||
@ -32,26 +32,26 @@ A V2board node server based on Xray-core, modified from XrayR
|
||||
|
||||
## 功能介绍
|
||||
|
||||
| 功能 | v2ray | trojan | shadowsocks |
|
||||
| --------------- | ----- | ------ | ----------- |
|
||||
| 功能 | v2ray | trojan | shadowsocks |
|
||||
|-----------|-------|--------|-------------|
|
||||
| 获取节点信息 | √ | √ | √ |
|
||||
| 获取用户信息 | √ | √ | √ |
|
||||
| 用户流量统计 | √ | √ | √ |
|
||||
| 自动申请tls证书 | √ | √ | √ |
|
||||
| 自动续签tls证书 | √ | √ | √ |
|
||||
| 在线人数统计 | √ | √ | √ |
|
||||
| 在线IP数限制 | √ | √ | √ |
|
||||
| 跨节点IP数限制 | √ | √ | √ |
|
||||
| 审计规则 | √ | √ | √ |
|
||||
| 在线IP数限制 | √ | √ | √ |
|
||||
| 跨节点IP数限制 | √ | √ | √ |
|
||||
| 审计规则 | √ | √ | √ |
|
||||
| 按照用户限速 | √ | √ | √ |
|
||||
| 自定义DNS | √ | √ | √ |
|
||||
| 动态限速(未测试) | √ | √ | √
|
||||
| 自定义DNS | √ | √ | √ |
|
||||
| 动态限速(未测试) | √ | √ | √ |
|
||||
|
||||
## 支持前端
|
||||
|
||||
| 前端 | v2ray | trojan | shadowsocks |
|
||||
| ------------------------------------------------------ | ----- | ------ | ------------------------------ |
|
||||
| v2board | √ | √ | √ |
|
||||
| 前端 | v2ray | trojan | shadowsocks |
|
||||
|---------|-------|--------|-------------|
|
||||
| v2board | √ | √ | √ |
|
||||
|
||||
## TODO
|
||||
|
||||
@ -88,7 +88,6 @@ wget -N https://raw.githubusercontents.com/Yuzuki616/V2bX-script/master/install.
|
||||
|
||||
## Telgram
|
||||
|
||||
|
||||
## Stars 增长记录
|
||||
|
||||
[![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)
|
||||
ReportUserTraffic(userTraffic []UserTraffic) (err error)
|
||||
Describe() ClientInfo
|
||||
GetNodeRule() (ruleList *DetectRule, err error)
|
||||
Debug()
|
||||
}
|
||||
|
@ -1,266 +1,82 @@
|
||||
package panel
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
md52 "crypto/md5"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type DetectRule struct {
|
||||
ProtocolRule []string
|
||||
DestinationRule []DestinationRule
|
||||
type NodeInfo struct {
|
||||
Host string `json:"host"`
|
||||
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 {
|
||||
ID int
|
||||
Pattern *regexp.Regexp
|
||||
}
|
||||
|
||||
// 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
|
||||
type localNodeConfig struct {
|
||||
NodeId int
|
||||
NodeType string
|
||||
TLSType string
|
||||
EnableVless bool
|
||||
EnableTls bool
|
||||
//EnableSS2022 bool
|
||||
V2ray *V2rayConfig
|
||||
Trojan *TrojanConfig
|
||||
SS *SSConfig
|
||||
SpeedLimit int
|
||||
DeviceLimit int
|
||||
}
|
||||
|
||||
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) {
|
||||
var path string
|
||||
var res *resty.Response
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
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:
|
||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
const path = "/api/v1/server/UniProxy/config"
|
||||
r, err := c.client.R().Get(path)
|
||||
if err = c.checkResponse(r, path, err); err != nil {
|
||||
return
|
||||
}
|
||||
res, err = c.client.R().
|
||||
SetQueryParam("local_port", "1").
|
||||
ForceContentType("application/json").
|
||||
Get(path)
|
||||
err = c.checkResponse(res, path, err)
|
||||
err = json.Unmarshal(r.Body(), &nodeInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
c.access.Lock()
|
||||
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
|
||||
}
|
||||
}
|
||||
c.NodeInfoRspMd5 = md
|
||||
nodeInfo, err = c.ParseTrojanNodeResponse(res.Body())
|
||||
}
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetNodeRule() (*DetectRule, error) {
|
||||
ruleList := c.LocalRuleList
|
||||
if c.NodeType != "V2ray" || c.RemoteRuleCache == nil {
|
||||
if c.etag == r.Header().Get("ETag") { // node info not changed
|
||||
return nil, nil
|
||||
}
|
||||
// V2board only support the rule for v2ray
|
||||
// fix: reuse config response
|
||||
c.access.Lock()
|
||||
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)
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(c.RemoteRuleCache) >= 3 {
|
||||
for _, str := range (c.RemoteRuleCache)[2].Protocol {
|
||||
ruleList.ProtocolRule = append(ruleList.ProtocolRule, str)
|
||||
}
|
||||
nodeInfo.Routes = nil
|
||||
if _, ok := nodeInfo.BaseConfig.PullInterval.(int); !ok {
|
||||
i, _ := strconv.Atoi(nodeInfo.BaseConfig.PullInterval.(string))
|
||||
nodeInfo.BaseConfig.PullInterval = i
|
||||
}
|
||||
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
|
||||
if _, ok := nodeInfo.BaseConfig.PushInterval.(int); !ok {
|
||||
i, _ := strconv.Atoi(nodeInfo.BaseConfig.PushInterval.(string))
|
||||
nodeInfo.BaseConfig.PushInterval = i
|
||||
}
|
||||
c.etag = r.Header().Get("Etag")
|
||||
return
|
||||
}
|
||||
|
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
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -19,28 +23,22 @@ type ClientInfo struct {
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
client *resty.Client
|
||||
APIHost string
|
||||
NodeID int
|
||||
Key string
|
||||
NodeType string
|
||||
//EnableSS2022 bool
|
||||
EnableVless bool
|
||||
EnableXTLS bool
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
LocalRuleList *DetectRule
|
||||
RemoteRuleCache []Rule
|
||||
access sync.Mutex
|
||||
NodeInfoRspMd5 [16]byte
|
||||
NodeRuleRspMd5 [16]byte
|
||||
client *resty.Client
|
||||
APIHost string
|
||||
Key string
|
||||
NodeType string
|
||||
NodeId int
|
||||
SpeedLimit int
|
||||
DeviceLimit int
|
||||
LocalRuleList []DestinationRule
|
||||
etag string
|
||||
}
|
||||
|
||||
func New(apiConfig *conf.ApiConfig) Panel {
|
||||
func New(c *conf.ApiConfig) (Panel, error) {
|
||||
client := resty.New()
|
||||
client.SetRetryCount(3)
|
||||
if apiConfig.Timeout > 0 {
|
||||
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
|
||||
if c.Timeout > 0 {
|
||||
client.SetTimeout(time.Duration(c.Timeout) * time.Second)
|
||||
} else {
|
||||
client.SetTimeout(5 * time.Second)
|
||||
}
|
||||
@ -51,25 +49,57 @@ func New(apiConfig *conf.ApiConfig) Panel {
|
||||
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
|
||||
client.SetQueryParams(map[string]string{
|
||||
"node_id": strconv.Itoa(apiConfig.NodeID),
|
||||
"token": apiConfig.Key,
|
||||
"node_type": strings.ToLower(c.NodeType),
|
||||
"node_id": strconv.Itoa(c.NodeID),
|
||||
"token": c.Key,
|
||||
})
|
||||
// Read local rule list
|
||||
localRuleList := readLocalRuleList(apiConfig.RuleListPath)
|
||||
localRuleList := readLocalRuleList(c.RuleListPath)
|
||||
return &Client{
|
||||
client: client,
|
||||
NodeID: apiConfig.NodeID,
|
||||
Key: apiConfig.Key,
|
||||
APIHost: apiConfig.APIHost,
|
||||
NodeType: apiConfig.NodeType,
|
||||
//EnableSS2022: apiConfig.EnableSS2022,
|
||||
EnableVless: apiConfig.EnableVless,
|
||||
EnableXTLS: apiConfig.EnableXTLS,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
client: client,
|
||||
Key: c.Key,
|
||||
APIHost: c.APIHost,
|
||||
NodeType: c.NodeType,
|
||||
SpeedLimit: c.SpeedLimit,
|
||||
DeviceLimit: c.DeviceLimit,
|
||||
NodeId: c.NodeID,
|
||||
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 (
|
||||
"fmt"
|
||||
"github.com/goccy/go-json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type OnlineUser struct {
|
||||
@ -20,46 +19,22 @@ type TrojanUserInfo struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
type UserInfo struct {
|
||||
/*DeviceLimit int `json:"device_limit"`
|
||||
SpeedLimit uint64 `json:"speed_limit"`*/
|
||||
UID int `json:"id"`
|
||||
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
|
||||
Id int `json:"id"`
|
||||
Uuid string `json:"uuid"`
|
||||
Email string `json:"-"`
|
||||
SpeedLimit int `json:"speed_limit"`
|
||||
Traffic int64 `json:"-"`
|
||||
}
|
||||
|
||||
type UserListBody struct {
|
||||
//Msg string `json:"msg"`
|
||||
Data []UserInfo `json:"data"`
|
||||
Users []UserInfo `json:"users"`
|
||||
}
|
||||
|
||||
// GetUserList will pull user form sspanel
|
||||
func (c *Client) GetUserList() (UserList []UserInfo, err error) {
|
||||
var path string
|
||||
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)
|
||||
}
|
||||
const path = "/api/v1/server/UniProxy/user"
|
||||
res, err := c.client.R().
|
||||
ForceContentType("application/json").
|
||||
Get(path)
|
||||
err = c.checkResponse(res, path, err)
|
||||
if err != nil {
|
||||
@ -70,38 +45,24 @@ func (c *Client) GetUserList() (UserList []UserInfo, err error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal userlist error: %s", err)
|
||||
}
|
||||
return userList.Data, nil
|
||||
return userList.Users, nil
|
||||
}
|
||||
|
||||
type UserTraffic struct {
|
||||
UID int `json:"user_id"`
|
||||
Upload int64 `json:"u"`
|
||||
Download int64 `json:"d"`
|
||||
UID int
|
||||
Upload int64
|
||||
Download int64
|
||||
}
|
||||
|
||||
// ReportUserTraffic reports the user traffic
|
||||
func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error {
|
||||
var path string
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
path = "/api/v1/server/Deepbwork/submit"
|
||||
case "Trojan":
|
||||
path = "/api/v1/server/TrojanTidalab/submit"
|
||||
case "Shadowsocks":
|
||||
path = "/api/v1/server/ShadowsocksTidalab/submit"
|
||||
data := make(map[int][]int64, len(userTraffic))
|
||||
for i := range userTraffic {
|
||||
data[userTraffic[i].UID] = []int64{userTraffic[i].Upload, userTraffic[i].Download}
|
||||
}
|
||||
|
||||
data := make([]UserTraffic, len(userTraffic))
|
||||
for i, traffic := range userTraffic {
|
||||
data[i] = UserTraffic{
|
||||
UID: traffic.UID,
|
||||
Upload: traffic.Upload,
|
||||
Download: traffic.Download}
|
||||
}
|
||||
|
||||
const path = "/api/v1/server/UniProxy/user"
|
||||
res, err := c.client.R().
|
||||
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||
SetBody(data).
|
||||
SetBody(userTraffic).
|
||||
ForceContentType("application/json").
|
||||
Post(path)
|
||||
err = c.checkResponse(res, path, err)
|
||||
|
@ -3,11 +3,12 @@ package panel
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
path2 "path"
|
||||
)
|
||||
|
||||
// Describe return a description of the client
|
||||
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
|
||||
@ -16,13 +17,12 @@ func (c *Client) Debug() {
|
||||
}
|
||||
|
||||
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 {
|
||||
if err != nil {
|
||||
return fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
|
||||
}
|
||||
|
||||
if res.StatusCode() > 400 {
|
||||
body := res.Body()
|
||||
return fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err)
|
||||
|
32
conf/node.go
32
conf/node.go
@ -41,16 +41,15 @@ type IpReportConfig struct {
|
||||
}
|
||||
|
||||
type DynamicSpeedLimitConfig struct {
|
||||
Periodic int `yaml:"Periodic"`
|
||||
Traffic int64 `yaml:"Traffic"`
|
||||
SpeedLimit uint64 `yaml:"SpeedLimit"`
|
||||
ExpireTime int `yaml:"ExpireTime"`
|
||||
Periodic int `yaml:"Periodic"`
|
||||
Traffic int64 `yaml:"Traffic"`
|
||||
SpeedLimit int `yaml:"SpeedLimit"`
|
||||
ExpireTime int `yaml:"ExpireTime"`
|
||||
}
|
||||
|
||||
type ControllerConfig struct {
|
||||
ListenIP string `yaml:"ListenIP"`
|
||||
SendIP string `yaml:"SendIP"`
|
||||
UpdatePeriodic int `yaml:"UpdatePeriodic"`
|
||||
EnableDNS bool `yaml:"EnableDNS"`
|
||||
DNSType string `yaml:"DNSType"`
|
||||
DisableUploadTraffic bool `yaml:"DisableUploadTraffic"`
|
||||
@ -68,18 +67,17 @@ type ControllerConfig struct {
|
||||
}
|
||||
|
||||
type ApiConfig struct {
|
||||
APIHost string `yaml:"ApiHost"`
|
||||
NodeID int `yaml:"NodeID"`
|
||||
Key string `yaml:"ApiKey"`
|
||||
NodeType string `yaml:"NodeType"`
|
||||
EnableVless bool `yaml:"EnableVless"`
|
||||
EnableXTLS bool `yaml:"EnableXTLS"`
|
||||
//EnableSS2022 bool `yaml:"EnableSS2022"`
|
||||
Timeout int `yaml:"Timeout"`
|
||||
SpeedLimit float64 `yaml:"SpeedLimit"`
|
||||
DeviceLimit int `yaml:"DeviceLimit"`
|
||||
RuleListPath string `yaml:"RuleListPath"`
|
||||
DisableCustomConfig bool `yaml:"DisableCustomConfig"`
|
||||
APIHost string `yaml:"ApiHost"`
|
||||
NodeID int `yaml:"NodeID"`
|
||||
Key string `yaml:"ApiKey"`
|
||||
NodeType string `yaml:"NodeType"`
|
||||
EnableVless bool `yaml:"EnableVless"`
|
||||
EnableXTLS bool `yaml:"EnableXTLS"`
|
||||
Timeout int `yaml:"Timeout"`
|
||||
SpeedLimit int `yaml:"SpeedLimit"`
|
||||
DeviceLimit int `yaml:"DeviceLimit"`
|
||||
RuleListPath string `yaml:"RuleListPath"`
|
||||
DisableCustomConfig bool `yaml:"DisableCustomConfig"`
|
||||
}
|
||||
|
||||
type NodeConfig struct {
|
||||
|
@ -12,15 +12,15 @@ import (
|
||||
)
|
||||
|
||||
type UserLimitInfo struct {
|
||||
UID int
|
||||
SpeedLimit uint64
|
||||
ExpireTime int64
|
||||
//DeviceLimit int
|
||||
UID int
|
||||
SpeedLimit int
|
||||
DynamicSpeedLimit int
|
||||
ExpireTime int64
|
||||
}
|
||||
|
||||
type InboundInfo struct {
|
||||
Tag string
|
||||
NodeSpeedLimit uint64
|
||||
NodeSpeedLimit int
|
||||
NodeDeviceLimit int
|
||||
UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo
|
||||
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{
|
||||
Tag: tag,
|
||||
NodeSpeedLimit: nodeInfo.SpeedLimit,
|
||||
NodeDeviceLimit: nodeInfo.DeviceLimit,
|
||||
UserLimitInfo: new(sync.Map),
|
||||
SpeedLimiter: 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)
|
||||
l.InboundInfo.Store(tag, inboundInfo) // Replace the old inbound info
|
||||
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 {
|
||||
inboundInfo := value.(*InboundInfo)
|
||||
for i := range deleted {
|
||||
inboundInfo.UserLimitInfo.Delete(fmt.Sprintf("%s|%s|%d", tag,
|
||||
(deleted)[i].GetUserEmail(), (deleted)[i].UID))
|
||||
inboundInfo.SpeedLimiter.Delete(fmt.Sprintf("%s|%s|%d", tag,
|
||||
(deleted)[i].GetUserEmail(), (deleted)[i].UID)) // Delete limiter bucket
|
||||
(deleted)[i].Uuid, (deleted)[i].Id))
|
||||
}
|
||||
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 {
|
||||
return fmt.Errorf("no such inbound in limiter: %s", tag)
|
||||
@ -70,14 +90,14 @@ func (l *Limiter) DeleteInboundLimiter(tag string) error {
|
||||
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 {
|
||||
inboundInfo := value.(*InboundInfo)
|
||||
userLimit := &UserLimitInfo{
|
||||
SpeedLimit: limit,
|
||||
ExpireTime: time.Now().Add(time.Duration(expire) * time.Second).Unix(),
|
||||
DynamicSpeedLimit: limit,
|
||||
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
|
||||
} else {
|
||||
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 {
|
||||
inboundInfo := value.(*InboundInfo)
|
||||
nodeLimit := inboundInfo.NodeSpeedLimit
|
||||
var userLimit uint64 = 0
|
||||
userLimit := 0
|
||||
expired := false
|
||||
if v, ok := inboundInfo.UserLimitInfo.Load(email); ok {
|
||||
u := v.(*UserLimitInfo)
|
||||
if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 {
|
||||
userLimit = 0
|
||||
if u.SpeedLimit != 0 {
|
||||
userLimit = u.SpeedLimit
|
||||
}
|
||||
expired = true
|
||||
} else {
|
||||
userLimit = u.SpeedLimit
|
||||
userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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 expired {
|
||||
inboundInfo.SpeedLimiter.Store(email, limiter)
|
||||
@ -246,22 +268,22 @@ func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
}
|
||||
|
||||
// determineSpeedLimit returns the minimum non-zero rate
|
||||
func determineSpeedLimit(nodeLimit, userLimit uint64) (limit uint64) {
|
||||
if nodeLimit == 0 || userLimit == 0 {
|
||||
if nodeLimit > userLimit {
|
||||
return nodeLimit
|
||||
} else if nodeLimit < userLimit {
|
||||
return userLimit
|
||||
func determineSpeedLimit(limit1, limit2 int) (limit int) {
|
||||
if limit1 == 0 || limit2 == 0 {
|
||||
if limit1 > limit2 {
|
||||
return limit1
|
||||
} else if limit1 < limit2 {
|
||||
return limit2
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
if nodeLimit > userLimit {
|
||||
return userLimit
|
||||
} else if nodeLimit < userLimit {
|
||||
return nodeLimit
|
||||
if limit1 > limit2 {
|
||||
return limit2
|
||||
} else if limit1 < limit2 {
|
||||
return limit1
|
||||
} 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 {
|
||||
oldRuleList := value.([]panel.DestinationRule)
|
||||
if !reflect.DeepEqual(oldRuleList, newRuleList) {
|
||||
@ -30,20 +30,13 @@ func (r *Rule) Detect(tag string, destination string, protocol string) (reject b
|
||||
reject = false
|
||||
// If we have some rule for this inbound
|
||||
if value, ok := r.Rule.Load(tag); ok {
|
||||
ruleList := value.(*panel.DetectRule)
|
||||
for i, _ := range ruleList.DestinationRule {
|
||||
if ruleList.DestinationRule[i].Pattern.Match([]byte(destination)) {
|
||||
ruleList := value.([]panel.DestinationRule)
|
||||
for i := range ruleList {
|
||||
if ruleList[i].Pattern.Match([]byte(destination)) {
|
||||
reject = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !reject {
|
||||
for _, v := range ruleList.ProtocolRule {
|
||||
if v == protocol {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reject
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/Yuzuki616/V2bX/core/app/dispatcher"
|
||||
_ "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/stats"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
|
@ -28,8 +28,8 @@ func (p *Core) AddInbound(config *core.InboundHandlerConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Core) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo) error {
|
||||
return p.dispatcher.Limiter.AddInboundLimiter(tag, nodeInfo)
|
||||
func (p *Core) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo, users []panel.UserInfo) error {
|
||||
return p.dispatcher.Limiter.AddInboundLimiter(tag, nodeInfo, users)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
func (p *Core) UpdateInboundLimiter(tag string, deleted []panel.UserInfo) error {
|
||||
return p.dispatcher.Limiter.UpdateInboundLimiter(tag, deleted)
|
||||
func (p *Core) UpdateInboundLimiter(tag string, added []panel.UserInfo, deleted []panel.UserInfo) error {
|
||||
return p.dispatcher.Limiter.UpdateInboundLimiter(tag, added, deleted)
|
||||
}
|
||||
|
||||
func (p *Core) DeleteInboundLimiter(tag string) error {
|
||||
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)
|
||||
}
|
||||
|
@ -80,8 +80,8 @@ func (p *Core) GetUserTraffic(email string, reset bool) (up int64, down int64) {
|
||||
return up, down
|
||||
}
|
||||
|
||||
func (p *Core) AddUserSpeedLimit(tag string, user *panel.UserInfo, speedLimit uint64, expire int64) error {
|
||||
return p.dispatcher.Limiter.AddUserSpeedLimit(tag, user, speedLimit, expire)
|
||||
func (p *Core) AddUserSpeedLimit(tag string, user *panel.UserInfo, speedLimit int, expire int64) error {
|
||||
return p.dispatcher.Limiter.AddDynamicSpeedLimit(tag, user, speedLimit, expire)
|
||||
}
|
||||
|
||||
func (p *Core) ListOnlineIp(tag string) ([]dispatcher.UserIpList, error) {
|
||||
|
@ -28,7 +28,6 @@ Nodes:
|
||||
ControllerConfig:
|
||||
ListenIP: 0.0.0.0 # IP address you want to listen
|
||||
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
|
||||
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
|
||||
EnableProxyProtocol: false # Only works for WebSocket and TCP
|
||||
@ -83,7 +82,6 @@ Nodes:
|
||||
# DeviceLimit: 0 # Local settings will replace remote settings
|
||||
# ControllerConfig:
|
||||
# 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
|
||||
# CertConfig:
|
||||
# CertMode: dns # Option about how to get certificate: none, file, http, dns
|
||||
|
@ -1,108 +1,47 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/core"
|
||||
coreConf "github.com/xtls/xray-core/infra/conf"
|
||||
)
|
||||
|
||||
// buildInbound build Inbound config for different protocol
|
||||
func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag string) (*core.InboundHandlerConfig, error) {
|
||||
var proxySetting interface{}
|
||||
if nodeInfo.NodeType == "V2ray" {
|
||||
defer func() {
|
||||
//Clear v2ray config
|
||||
nodeInfo.V2ray = nil
|
||||
}()
|
||||
if nodeInfo.EnableVless {
|
||||
//Set vless
|
||||
nodeInfo.V2ray.Inbounds[0].Protocol = "vless"
|
||||
if config.EnableFallback {
|
||||
// Set fallback
|
||||
fallbackConfigs, err := buildVlessFallbacks(config.FallBackConfigs)
|
||||
if err == nil {
|
||||
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 {
|
||||
inbound := &coreConf.InboundDetourConfig{}
|
||||
// Set network protocol
|
||||
t := coreConf.TransportProtocol(nodeInfo.Network)
|
||||
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
|
||||
var err error
|
||||
switch nodeInfo.NodeType {
|
||||
case "V2ray":
|
||||
err = buildV2ray(config, nodeInfo, inbound)
|
||||
case "Trojan":
|
||||
err = buildTrojan(config, nodeInfo, inbound)
|
||||
case "Shadowsocks":
|
||||
err = buildShadowsocks(config, nodeInfo, inbound)
|
||||
default:
|
||||
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)
|
||||
nodeInfo.V2ray.Inbounds[0].ListenOn = &coreConf.Address{Address: ipAddress}
|
||||
// SniffingConfig
|
||||
inbound.ListenOn = &coreConf.Address{Address: ipAddress}
|
||||
// Set SniffingConfig
|
||||
sniffingConfig := &coreConf.SniffingConfig{
|
||||
Enabled: true,
|
||||
DestOverride: &coreConf.StringList{"http", "tls"},
|
||||
@ -110,31 +49,23 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
|
||||
if config.DisableSniffing {
|
||||
sniffingConfig.Enabled = false
|
||||
}
|
||||
nodeInfo.V2ray.Inbounds[0].SniffingConfig = sniffingConfig
|
||||
|
||||
var setting json.RawMessage
|
||||
|
||||
// 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
|
||||
inbound.SniffingConfig = sniffingConfig
|
||||
if nodeInfo.NodeType == "tcp" {
|
||||
if inbound.StreamSetting.TCPSettings != nil {
|
||||
inbound.StreamSetting.TCPSettings.AcceptProxyProtocol = config.EnableProxyProtocol
|
||||
} else {
|
||||
tcpSetting := &coreConf.TCPConfig{
|
||||
AcceptProxyProtocol: config.EnableProxyProtocol,
|
||||
} //Enable proxy protocol
|
||||
nodeInfo.V2ray.Inbounds[0].StreamSetting.TCPSettings = tcpSetting
|
||||
inbound.StreamSetting.TCPSettings = tcpSetting
|
||||
}
|
||||
} else if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network == "ws" {
|
||||
nodeInfo.V2ray.Inbounds[0].StreamSetting.WSSettings = &coreConf.WebSocketConfig{
|
||||
} else if nodeInfo.NodeType == "ws" {
|
||||
inbound.StreamSetting.WSSettings = &coreConf.WebSocketConfig{
|
||||
AcceptProxyProtocol: config.EnableProxyProtocol} //Enable proxy protocol
|
||||
}
|
||||
// Build TLS and XTLS settings
|
||||
// Set TLS and XTLS settings
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -144,7 +75,7 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
|
||||
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
|
||||
}
|
||||
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" {
|
||||
xtlsSettings := &coreConf.XTLSConfig{
|
||||
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
|
||||
@ -153,23 +84,139 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
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
|
||||
if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network != "tcp" &&
|
||||
*nodeInfo.V2ray.Inbounds[0].StreamSetting.Network != "ws" &&
|
||||
if *inbound.StreamSetting.Network != "tcp" &&
|
||||
*inbound.StreamSetting.Network != "ws" &&
|
||||
config.EnableProxyProtocol {
|
||||
sockoptConfig := &coreConf.SocketConfig{
|
||||
AcceptProxyProtocol: config.EnableProxyProtocol,
|
||||
} //Enable proxy protocol
|
||||
nodeInfo.V2ray.Inbounds[0].StreamSetting.SocketSettings = sockoptConfig
|
||||
inbound.StreamSetting.SocketSettings = sockoptConfig
|
||||
}
|
||||
nodeInfo.V2ray.Inbounds[0].Settings = &setting
|
||||
nodeInfo.V2ray.Inbounds[0].Tag = tag
|
||||
return nodeInfo.V2ray.Inbounds[0].Build()
|
||||
inbound.Tag = tag
|
||||
return inbound.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) {
|
||||
@ -199,7 +246,6 @@ func getCertFile(certConfig *conf.CertConfig) (certFile string, keyFile string,
|
||||
}
|
||||
return certPath, keyPath, err
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("unsupported certmode: %s", certConfig.CertMode)
|
||||
}
|
||||
|
||||
@ -207,14 +253,11 @@ func buildVlessFallbacks(fallbackConfigs []*conf.FallBackConfig) ([]*coreConf.VL
|
||||
if fallbackConfigs == nil {
|
||||
return nil, fmt.Errorf("you must provide FallBackConfigs")
|
||||
}
|
||||
|
||||
vlessFallBacks := make([]*coreConf.VLessInboundFallback, len(fallbackConfigs))
|
||||
for i, c := range fallbackConfigs {
|
||||
|
||||
if c.Dest == "" {
|
||||
return nil, fmt.Errorf("dest is required for fallback fialed")
|
||||
}
|
||||
|
||||
var dest json.RawMessage
|
||||
dest, err := json.Marshal(c.Dest)
|
||||
if err != nil {
|
||||
|
@ -3,11 +3,11 @@ package cmd
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"github.com/goccy/go-json"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -3,8 +3,8 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"github.com/goccy/go-json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -1,8 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/goccy/go-json"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
|
||||
type Node struct {
|
||||
server *core.Core
|
||||
config *conf.ControllerConfig
|
||||
clientInfo panel.ClientInfo
|
||||
apiClient panel.Panel
|
||||
nodeInfo *panel.NodeInfo
|
||||
@ -25,14 +24,15 @@ type Node struct {
|
||||
userReportPeriodic *task.Periodic
|
||||
onlineIpReportPeriodic *task.Periodic
|
||||
DynamicSpeedLimitPeriodic *task.Periodic
|
||||
*conf.ControllerConfig
|
||||
}
|
||||
|
||||
// New return a Node service with default parameters.
|
||||
func New(server *core.Core, api panel.Panel, config *conf.ControllerConfig) *Node {
|
||||
controller := &Node{
|
||||
server: server,
|
||||
config: config,
|
||||
apiClient: api,
|
||||
server: server,
|
||||
ControllerConfig: config,
|
||||
apiClient: api,
|
||||
}
|
||||
return controller
|
||||
}
|
||||
@ -64,71 +64,67 @@ func (c *Node) Start() error {
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
// Add Rule Manager
|
||||
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.Printf("Update rule filed: %s", err)
|
||||
}
|
||||
if !c.DisableGetRule {
|
||||
if err := c.server.UpdateRule(c.Tag, newNodeInfo.Rules); err != nil {
|
||||
log.Printf("Update rule filed: %s", err)
|
||||
}
|
||||
}
|
||||
// fetch node info task
|
||||
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,
|
||||
}
|
||||
// fetch user list task
|
||||
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,
|
||||
}
|
||||
log.Printf("[%s: %d] Start monitor node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
// delay to start nodeInfoMonitor
|
||||
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()
|
||||
}()
|
||||
|
||||
log.Printf("[%s: %d] Start report node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
// delay to start userReport
|
||||
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()
|
||||
}()
|
||||
if c.config.EnableDynamicSpeedLimit {
|
||||
if c.EnableDynamicSpeedLimit {
|
||||
// Check dynamic speed limit task
|
||||
c.DynamicSpeedLimitPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.config.DynamicSpeedLimitConfig.Periodic) * time.Second,
|
||||
Interval: time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second,
|
||||
Execute: c.dynamicSpeedLimit,
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.config.DynamicSpeedLimitConfig.Periodic) * time.Second)
|
||||
time.Sleep(time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second)
|
||||
_ = c.DynamicSpeedLimitPeriodic.Start()
|
||||
}()
|
||||
log.Printf("[%s: %d] Start dynamic speed limit", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
}
|
||||
if c.config.EnableIpRecorder {
|
||||
switch c.config.IpRecorderConfig.Type {
|
||||
if c.EnableIpRecorder {
|
||||
switch c.IpRecorderConfig.Type {
|
||||
case "Recorder":
|
||||
c.ipRecorder = iprecoder.NewRecorder(c.config.IpRecorderConfig.RecorderConfig)
|
||||
c.ipRecorder = iprecoder.NewRecorder(c.IpRecorderConfig.RecorderConfig)
|
||||
case "Redis":
|
||||
c.ipRecorder = iprecoder.NewRedis(c.config.IpRecorderConfig.RedisConfig)
|
||||
c.ipRecorder = iprecoder.NewRedis(c.IpRecorderConfig.RedisConfig)
|
||||
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
|
||||
}
|
||||
// report and fetch online ip list task
|
||||
c.onlineIpReportPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.config.IpRecorderConfig.Periodic) * time.Second,
|
||||
Interval: time.Duration(c.IpRecorderConfig.Periodic) * time.Second,
|
||||
Execute: c.reportOnlineIp,
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.config.IpRecorderConfig.Periodic) * time.Second)
|
||||
time.Sleep(time.Duration(c.IpRecorderConfig.Periodic) * time.Second)
|
||||
_ = c.onlineIpReportPeriodic.Start()
|
||||
}()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if c.nodeInfoMonitorPeriodic != nil {
|
||||
err := c.userReportPeriodic.Close()
|
||||
if err != nil {
|
||||
@ -167,5 +162,5 @@ func (c *Node) Close() error {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
conf2 "github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/core"
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"log"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -21,53 +21,41 @@ func (c *Node) nodeInfoMonitor() (err error) {
|
||||
var nodeInfoChanged = false
|
||||
// If nodeInfo changed
|
||||
if newNodeInfo != nil {
|
||||
if c.nodeInfo.SS == nil || !reflect.DeepEqual(c.nodeInfo.SS, newNodeInfo.SS) {
|
||||
// Remove old tag
|
||||
oldTag := c.Tag
|
||||
err := c.removeOldTag(oldTag)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
// Add new tag
|
||||
c.nodeInfo = newNodeInfo
|
||||
c.Tag = c.buildNodeTag()
|
||||
err = c.addNewTag(newNodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
nodeInfoChanged = true
|
||||
// Remove Old limiter
|
||||
if err = c.server.DeleteInboundLimiter(oldTag); err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
// Remove old tag
|
||||
oldTag := c.Tag
|
||||
err := c.removeOldTag(oldTag)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
// Add new tag
|
||||
c.nodeInfo = newNodeInfo
|
||||
c.Tag = c.buildNodeTag()
|
||||
err = c.addNewTag(newNodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
nodeInfoChanged = true
|
||||
// Remove Old limiter
|
||||
if err = c.server.DeleteInboundLimiter(oldTag); err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
if err := c.server.UpdateRule(c.Tag, newNodeInfo.Rules); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Check Cert
|
||||
if c.nodeInfo.EnableTls && c.config.CertConfig.CertMode != "none" &&
|
||||
(c.config.CertConfig.CertMode == "dns" || c.config.CertConfig.CertMode == "http") {
|
||||
if c.nodeInfo.EnableTls && c.CertConfig.CertMode != "none" &&
|
||||
(c.CertConfig.CertMode == "dns" || c.CertConfig.CertMode == "http") {
|
||||
lego, err := legoCmd.New()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
// Core-core supports the OcspStapling certification hot renew
|
||||
_, _, err = lego.RenewCert(c.config.CertConfig.CertDomain, c.config.CertConfig.Email,
|
||||
c.config.CertConfig.CertMode, c.config.CertConfig.Provider, c.config.CertConfig.DNSEnv)
|
||||
_, _, err = lego.RenewCert(c.CertConfig.CertDomain, c.CertConfig.Email,
|
||||
c.CertConfig.CertMode, c.CertConfig.Provider, c.CertConfig.DNSEnv)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
@ -80,27 +68,42 @@ func (c *Node) nodeInfoMonitor() (err error) {
|
||||
}
|
||||
if nodeInfoChanged {
|
||||
c.userList = newUserInfo
|
||||
newUserInfo = nil
|
||||
err = c.addNewUser(c.userList, newNodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
newNodeInfo = nil
|
||||
// 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)
|
||||
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 {
|
||||
deleted, added := compareUserList(c.userList, newUserInfo)
|
||||
if len(deleted) > 0 {
|
||||
deletedEmail := make([]string, len(deleted))
|
||||
for i := range deleted {
|
||||
deletedEmail[i] = fmt.Sprintf("%s|%s|%d", c.Tag,
|
||||
(deleted)[i].GetUserEmail(),
|
||||
(deleted)[i].UID)
|
||||
deletedEmail[i] = fmt.Sprintf("%s|%s|%d",
|
||||
c.Tag,
|
||||
(deleted)[i].Uuid,
|
||||
(deleted)[i].Id)
|
||||
}
|
||||
err := c.server.RemoveUsers(deletedEmail, c.Tag)
|
||||
if err != nil {
|
||||
@ -114,16 +117,14 @@ func (c *Node) nodeInfoMonitor() (err error) {
|
||||
}
|
||||
}
|
||||
if len(added) > 0 || len(deleted) > 0 {
|
||||
defer runtime.GC()
|
||||
// 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.Printf("[%s: %d] %d user deleted, %d user added", c.nodeInfo.NodeType, c.nodeInfo.NodeId,
|
||||
len(deleted), len(added))
|
||||
c.userList = newUserInfo
|
||||
newUserInfo = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -141,24 +142,21 @@ func (c *Node) removeOldTag(oldTag string) (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 {
|
||||
return err
|
||||
return fmt.Errorf("build inbound error: %s", err)
|
||||
}
|
||||
err = c.server.AddInbound(inboundConfig)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
return fmt.Errorf("add inbound error: %s", err)
|
||||
}
|
||||
outBoundConfig, err := buildOutbound(c.config, newNodeInfo, c.Tag)
|
||||
outBoundConfig, err := buildOutbound(c.ControllerConfig, newNodeInfo, c.Tag)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
return fmt.Errorf("build outbound error: %s", err)
|
||||
}
|
||||
err = c.server.AddOutbound(outBoundConfig)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
return fmt.Errorf("add outbound error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -174,13 +172,13 @@ func (c *Node) addNewUser(userInfo []panel.UserInfo, nodeInfo *panel.NodeInfo) (
|
||||
} else if nodeInfo.NodeType == "Trojan" {
|
||||
users = c.buildTrojanUsers(userInfo)
|
||||
} else if nodeInfo.NodeType == "Shadowsocks" {
|
||||
users = c.buildSSUsers(userInfo, getCipherFromString(nodeInfo.SS.CypherMethod))
|
||||
users = c.buildSSUsers(userInfo, getCipherFromString(nodeInfo.Cipher))
|
||||
} else {
|
||||
return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType)
|
||||
}
|
||||
err = c.server.AddUsers(users, c.Tag)
|
||||
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))
|
||||
return nil
|
||||
@ -190,24 +188,24 @@ func compareUserList(old, new []panel.UserInfo) (deleted, added []panel.UserInfo
|
||||
tmp := map[string]struct{}{}
|
||||
tmp2 := map[string]struct{}{}
|
||||
for i := range old {
|
||||
tmp[(old)[i].GetUserEmail()] = struct{}{}
|
||||
tmp[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{}
|
||||
}
|
||||
l := len(tmp)
|
||||
for i := range new {
|
||||
e := (new)[i].GetUserEmail()
|
||||
e := new[i].Uuid + strconv.Itoa(new[i].SpeedLimit)
|
||||
tmp[e] = struct{}{}
|
||||
tmp2[e] = struct{}{}
|
||||
if l != len(tmp) {
|
||||
added = append(added, (new)[i])
|
||||
added = append(added, new[i])
|
||||
l++
|
||||
}
|
||||
}
|
||||
tmp = nil
|
||||
l = len(tmp2)
|
||||
for i := range old {
|
||||
tmp2[(old)[i].GetUserEmail()] = struct{}{}
|
||||
tmp2[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{}
|
||||
if l != len(tmp2) {
|
||||
deleted = append(deleted, (old)[i])
|
||||
deleted = append(deleted, old[i])
|
||||
l++
|
||||
}
|
||||
}
|
||||
@ -220,16 +218,16 @@ func (c *Node) reportUserTraffic() (err error) {
|
||||
for i := range c.userList {
|
||||
up, down := c.server.GetUserTraffic(c.buildUserTag(&(c.userList)[i]), true)
|
||||
if up > 0 || down > 0 {
|
||||
if c.config.EnableDynamicSpeedLimit {
|
||||
if c.EnableDynamicSpeedLimit {
|
||||
c.userList[i].Traffic += up + down
|
||||
}
|
||||
userTraffic = append(userTraffic, panel.UserTraffic{
|
||||
UID: (c.userList)[i].UID,
|
||||
UID: (c.userList)[i].Id,
|
||||
Upload: up,
|
||||
Download: down})
|
||||
}
|
||||
}
|
||||
if len(userTraffic) > 0 && !c.config.DisableUploadTraffic {
|
||||
if len(userTraffic) > 0 && !c.DisableUploadTraffic {
|
||||
err = c.apiClient.ReportUserTraffic(userTraffic)
|
||||
if err != nil {
|
||||
log.Printf("Report user traffic faild: %s", err)
|
||||
@ -238,7 +236,7 @@ func (c *Node) reportUserTraffic() (err error) {
|
||||
}
|
||||
}
|
||||
userTraffic = nil
|
||||
if !c.config.EnableIpRecorder {
|
||||
if !c.EnableIpRecorder {
|
||||
c.server.ClearOnlineIp(c.Tag)
|
||||
}
|
||||
runtime.GC()
|
||||
@ -256,7 +254,7 @@ func (c *Node) reportOnlineIp() (err error) {
|
||||
log.Print("Report online ip error: ", err)
|
||||
c.server.ClearOnlineIp(c.Tag)
|
||||
}
|
||||
if c.config.IpRecorderConfig.EnableIpSync {
|
||||
if c.IpRecorderConfig.EnableIpSync {
|
||||
c.server.UpdateOnlineIp(c.Tag, 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 {
|
||||
if c.config.EnableDynamicSpeedLimit {
|
||||
if c.EnableDynamicSpeedLimit {
|
||||
for i := range c.userList {
|
||||
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,
|
||||
&c.userList[i],
|
||||
c.config.DynamicSpeedLimitConfig.SpeedLimit,
|
||||
time.Now().Add(time.Second*time.Duration(c.config.DynamicSpeedLimitConfig.ExpireTime)).Unix())
|
||||
c.DynamicSpeedLimitConfig.SpeedLimit,
|
||||
time.Now().Add(time.Second*time.Duration(c.DynamicSpeedLimitConfig.ExpireTime)).Unix())
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
@ -3,14 +3,14 @@ package controller
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
"github.com/xtls/xray-core/proxy/shadowsocks"
|
||||
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
|
||||
"github.com/xtls/xray-core/proxy/trojan"
|
||||
"github.com/xtls/xray-core/proxy/vless"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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) {
|
||||
vmessAccount := &conf.VMessAccount{
|
||||
ID: userInfo.V2rayUser.Uuid,
|
||||
ID: userInfo.Uuid,
|
||||
AlterIds: serverAlterID,
|
||||
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) {
|
||||
vlessAccount := &vless.Account{
|
||||
Id: userInfo.V2rayUser.Uuid,
|
||||
Id: userInfo.Uuid,
|
||||
Flow: "xtls-rprx-direct",
|
||||
}
|
||||
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) {
|
||||
trojanAccount := &trojan.Account{
|
||||
Password: userInfo.TrojanUser.Password,
|
||||
Password: userInfo.Uuid,
|
||||
Flow: "xtls-rprx-direct",
|
||||
}
|
||||
return &protocol.User{
|
||||
@ -98,17 +98,28 @@ func (c *Node) buildSSUsers(userInfo []panel.UserInfo, cypher shadowsocks.Cipher
|
||||
}
|
||||
|
||||
func (c *Node) buildSSUser(userInfo *panel.UserInfo, cypher shadowsocks.CipherType) (user *protocol.User) {
|
||||
ssAccount := &shadowsocks.Account{
|
||||
Password: userInfo.Secret,
|
||||
CipherType: cypher,
|
||||
}
|
||||
return &protocol.User{
|
||||
Level: 0,
|
||||
Email: c.buildUserTag(userInfo),
|
||||
Account: serial.ToTypedMessage(ssAccount),
|
||||
if c.nodeInfo.ServerKey == "" {
|
||||
ssAccount := &shadowsocks.Account{
|
||||
Password: userInfo.Uuid,
|
||||
CipherType: cypher,
|
||||
}
|
||||
return &protocol.User{
|
||||
Level: 0,
|
||||
Email: c.buildUserTag(userInfo),
|
||||
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 {
|
||||
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 {
|
||||
n.controllers = make([]*controller.Node, len(nodes))
|
||||
for i, c := range nodes {
|
||||
p, err := panel.New(c.ApiConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Register controller service
|
||||
n.controllers[i] = controller.New(core, panel.New(c.ApiConfig), c.ControllerConfig)
|
||||
err := n.controllers[i].Start()
|
||||
n.controllers[i] = controller.New(core, p, c.ControllerConfig)
|
||||
err = n.controllers[i].Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user