From 695da4f4c546adfc2fa3b0fbb1061ada29a0ca63 Mon Sep 17 00:00:00 2001 From: Yuzuki999 Date: Sun, 18 Dec 2022 23:31:06 +0800 Subject: [PATCH] update to v1.1.0 change to uniproxy api refactor build inbound refactor limiter and rule add ss2022 support add speedlimit support and more... --- README.md | 23 +- api/panel/interface.go | 1 - api/panel/node.go | 292 ++++-------------- api/panel/node_test.go | 20 ++ api/panel/panel.go | 98 ++++-- api/panel/user.go | 71 +---- api/panel/utils.go | 6 +- conf/node.go | 32 +- core/app/dispatcher/limiter.go | 82 +++-- core/app/dispatcher/rule.go | 15 +- core/core.go | 2 +- core/inbound.go | 10 +- core/user.go | 4 +- example/config.yml.example | 2 - node/controller/inbound.go | 279 ++++++++++------- .../legoCmd/cmd/accounts_storage.go | 2 +- node/controller/legoCmd/cmd/certs_storage.go | 2 +- node/controller/legoCmd/cmd/cmd_list.go | 2 +- node/controller/node.go | 51 ++- node/controller/outbound.go | 2 +- node/controller/task.go | 148 +++++---- node/controller/user.go | 39 ++- node/node.go | 8 +- 23 files changed, 539 insertions(+), 652 deletions(-) create mode 100644 api/panel/node_test.go diff --git a/README.md b/README.md index 2e60299..0342081 100644 --- a/README.md +++ b/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) diff --git a/api/panel/interface.go b/api/panel/interface.go index 005a672..6399a64 100644 --- a/api/panel/interface.go +++ b/api/panel/interface.go @@ -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() } diff --git a/api/panel/node.go b/api/panel/node.go index 352fc8b..8d814a4 100644 --- a/api/panel/node.go +++ b/api/panel/node.go @@ -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 } diff --git a/api/panel/node_test.go b/api/panel/node_test.go new file mode 100644 index 0000000..b0c2b6e --- /dev/null +++ b/api/panel/node_test.go @@ -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()) +} diff --git a/api/panel/panel.go b/api/panel/panel.go index d7b3611..b61678f 100644 --- a/api/panel/panel.go +++ b/api/panel/panel.go @@ -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 } diff --git a/api/panel/user.go b/api/panel/user.go index 96b95c1..4d9b85d 100644 --- a/api/panel/user.go +++ b/api/panel/user.go @@ -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) diff --git a/api/panel/utils.go b/api/panel/utils.go index 5887bff..a664d27 100644 --- a/api/panel/utils.go +++ b/api/panel/utils.go @@ -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) diff --git a/conf/node.go b/conf/node.go index ce95f93..f29131e 100644 --- a/conf/node.go +++ b/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 { diff --git a/core/app/dispatcher/limiter.go b/core/app/dispatcher/limiter.go index 966be04..937814f 100644 --- a/core/app/dispatcher/limiter.go +++ b/core/app/dispatcher/limiter.go @@ -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 } } } diff --git a/core/app/dispatcher/rule.go b/core/app/dispatcher/rule.go index 94097a6..71e316d 100644 --- a/core/app/dispatcher/rule.go +++ b/core/app/dispatcher/rule.go @@ -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 } diff --git a/core/core.go b/core/core.go index c85bf9d..6ab555d 100644 --- a/core/core.go +++ b/core/core.go @@ -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" diff --git a/core/inbound.go b/core/inbound.go index a669535..f62b431 100644 --- a/core/inbound.go +++ b/core/inbound.go @@ -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) } diff --git a/core/user.go b/core/user.go index 6a60994..236ee43 100644 --- a/core/user.go +++ b/core/user.go @@ -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) { diff --git a/example/config.yml.example b/example/config.yml.example index 8358d95..5b10055 100644 --- a/example/config.yml.example +++ b/example/config.yml.example @@ -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 diff --git a/node/controller/inbound.go b/node/controller/inbound.go index cb8be55..f18506e 100644 --- a/node/controller/inbound.go +++ b/node/controller/inbound.go @@ -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 { diff --git a/node/controller/legoCmd/cmd/accounts_storage.go b/node/controller/legoCmd/cmd/accounts_storage.go index 6b05d5a..3db8a84 100644 --- a/node/controller/legoCmd/cmd/accounts_storage.go +++ b/node/controller/legoCmd/cmd/accounts_storage.go @@ -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" diff --git a/node/controller/legoCmd/cmd/certs_storage.go b/node/controller/legoCmd/cmd/certs_storage.go index e0f7f85..42fd2a6 100644 --- a/node/controller/legoCmd/cmd/certs_storage.go +++ b/node/controller/legoCmd/cmd/certs_storage.go @@ -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" diff --git a/node/controller/legoCmd/cmd/cmd_list.go b/node/controller/legoCmd/cmd/cmd_list.go index 68fd0c7..7e13f0c 100644 --- a/node/controller/legoCmd/cmd/cmd_list.go +++ b/node/controller/legoCmd/cmd/cmd_list.go @@ -1,8 +1,8 @@ package cmd import ( - "encoding/json" "fmt" + "github.com/goccy/go-json" "io/ioutil" "net/url" "path/filepath" diff --git a/node/controller/node.go b/node/controller/node.go index 6fe1263..cf2d88e 100644 --- a/node/controller/node.go +++ b/node/controller/node.go @@ -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) } diff --git a/node/controller/outbound.go b/node/controller/outbound.go index 1195c17..0e5e52e 100644 --- a/node/controller/outbound.go +++ b/node/controller/outbound.go @@ -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" diff --git a/node/controller/task.go b/node/controller/task.go index d38fdd4..dde3321 100644 --- a/node/controller/task.go +++ b/node/controller/task.go @@ -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) } diff --git a/node/controller/user.go b/node/controller/user.go index 0d1949c..cca611b 100644 --- a/node/controller/user.go +++ b/node/controller/user.go @@ -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) } diff --git a/node/node.go b/node/node.go index 1c4fea8..872a2d5 100644 --- a/node/node.go +++ b/node/node.go @@ -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 }