XrayR/api/sspanel/sspanel.go
Senis John 5fe18e020d
Refactor sspanel and test files in the API
Fixed typos, changed variable naming to follow camel case convention, simplified the error handling, and refactored the function names in sspanel.go for improved readability and consistency. Accordingly, updated the test function names in sspanel_test.go to match the changes made in the main file. Additionally, the if condition in ParseSSPanelNodeInfo was streamlined to improve the function's readability and error handling. The changes will help in better understanding the code while debugging or adding new features.
2023-10-04 09:45:52 +08:00

861 lines
23 KiB
Go

package sspanel
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"reflect"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/go-resty/resty/v2"
"github.com/XrayR-project/XrayR/api"
)
var (
firstPortRe = regexp.MustCompile(`(?m)port=(?P<outport>\d+)#?`) // First Port
secondPortRe = regexp.MustCompile(`(?m)port=\d+#(\d+)`) // Second Port
hostRe = regexp.MustCompile(`(?m)host=([\w.]+)\|?`) // Host
)
// APIClient create a api client to the panel.
type APIClient struct {
client *resty.Client
APIHost string
NodeID int
Key string
NodeType string
EnableVless bool
VlessFlow string
SpeedLimit float64
DeviceLimit int
DisableCustomConfig bool
LocalRuleList []api.DetectRule
LastReportOnline map[int]int
access sync.Mutex
version string
eTags map[string]string
}
// New create api instance
func New(apiConfig *api.Config) *APIClient {
client := resty.New()
client.SetRetryCount(3)
if apiConfig.Timeout > 0 {
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
} else {
client.SetTimeout(5 * time.Second)
}
client.OnError(func(req *resty.Request, err error) {
var v *resty.ResponseError
if errors.As(err, &v) {
// v.Response contains the last response from the server
// v.Err contains the original error
log.Print(v.Err)
}
})
client.SetBaseURL(apiConfig.APIHost)
// Create Key for each requests
client.SetQueryParam("key", apiConfig.Key)
// Add support for muKey
client.SetQueryParam("muKey", apiConfig.Key)
// Read local rule list
localRuleList := readLocalRuleList(apiConfig.RuleListPath)
return &APIClient{
client: client,
NodeID: apiConfig.NodeID,
Key: apiConfig.Key,
APIHost: apiConfig.APIHost,
NodeType: apiConfig.NodeType,
EnableVless: apiConfig.EnableVless,
VlessFlow: apiConfig.VlessFlow,
SpeedLimit: apiConfig.SpeedLimit,
DeviceLimit: apiConfig.DeviceLimit,
LocalRuleList: localRuleList,
DisableCustomConfig: apiConfig.DisableCustomConfig,
LastReportOnline: make(map[int]int),
eTags: make(map[string]string),
}
}
// readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
LocalRuleList = make([]api.DetectRule, 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 LocalRuleList
}
fileScanner := bufio.NewScanner(file)
// read line by line
for fileScanner.Scan() {
LocalRuleList = append(LocalRuleList, api.DetectRule{
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
}
file.Close()
}
return LocalRuleList
}
// Describe return a description of the client
func (c *APIClient) Describe() api.ClientInfo {
return api.ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType}
}
// Debug set the client debug for client
func (c *APIClient) Debug() {
c.client.SetDebug(true)
}
func (c *APIClient) assembleURL(path string) string {
return c.APIHost + path
}
func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (*Response, error) {
if err != nil {
return nil, fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
}
if res.StatusCode() > 400 {
body := res.Body()
return nil, fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err)
}
response := res.Result().(*Response)
if response.Ret != 1 {
res, _ := json.Marshal(&response)
return nil, fmt.Errorf("ret %s invalid", string(res))
}
return response, nil
}
// GetNodeInfo will pull NodeInfo Config from ssPanel
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
res, err := c.client.R().
SetResult(&Response{}).
SetHeader("If-None-Match", c.eTags["node"]).
ForceContentType("application/json").
Get(path)
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
if res.StatusCode() == 304 {
return nil, errors.New(api.NodeNotModified)
}
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["node"] {
c.eTags["node"] = res.Header().Get("ETag")
}
response, err := c.parseResponse(res, path, err)
if err != nil {
return nil, err
}
nodeInfoResponse := new(NodeInfoResponse)
if err := json.Unmarshal(response.Data, nodeInfoResponse); err != nil {
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
}
// New ssPanel API
c.version = nodeInfoResponse.Version
if !c.DisableCustomConfig {
nodeInfo, err = c.ParseSSPanelNodeInfo(nodeInfoResponse)
if err != nil {
res, _ := json.Marshal(nodeInfoResponse)
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s, \nPlease check the doc of custom_config for help: https://xrayr-project.github.io/XrayR-doc/dui-jie-sspanel/sspanel/sspanel_custom_config", string(res), err)
}
} else {
switch c.NodeType {
case "V2ray":
nodeInfo, err = c.ParseV2rayNodeResponse(nodeInfoResponse)
case "Trojan":
nodeInfo, err = c.ParseTrojanNodeResponse(nodeInfoResponse)
case "Shadowsocks":
nodeInfo, err = c.ParseSSNodeResponse(nodeInfoResponse)
case "Shadowsocks-Plugin":
nodeInfo, err = c.ParseSSPluginNodeResponse(nodeInfoResponse)
default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
}
if err != nil {
res, _ := json.Marshal(nodeInfoResponse)
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s", string(res), err)
}
return nodeInfo, nil
}
// GetUserList will pull user form ssPanel
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
path := "/mod_mu/users"
res, err := c.client.R().
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
SetHeader("If-None-Match", c.eTags["users"]).
SetResult(&Response{}).
ForceContentType("application/json").
Get(path)
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
if res.StatusCode() == 304 {
return nil, errors.New(api.UserNotModified)
}
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["users"] {
c.eTags["users"] = res.Header().Get("ETag")
}
response, err := c.parseResponse(res, path, err)
if err != nil {
return nil, err
}
userListResponse := new([]UserResponse)
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
}
userList, err := c.ParseUserListResponse(userListResponse)
if err != nil {
res, _ := json.Marshal(userListResponse)
return nil, fmt.Errorf("parse user list failed: %s", string(res))
}
return userList, nil
}
// ReportNodeStatus reports the node status to the ssPanel
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
// Determine whether a status report is in need
if compareVersion(c.version, "2023.2") == -1 {
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
systemLoad := SystemLoad{
Uptime: strconv.FormatUint(nodeStatus.Uptime, 10),
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.Mem/100, nodeStatus.Disk/100),
}
res, err := c.client.R().
SetBody(systemLoad).
SetResult(&Response{}).
ForceContentType("application/json").
Post(path)
_, err = c.parseResponse(res, path, err)
if err != nil {
return err
}
}
return nil
}
// ReportNodeOnlineUsers reports online user ip
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
c.access.Lock()
defer c.access.Unlock()
reportOnline := make(map[int]int)
data := make([]OnlineUser, len(*onlineUserList))
for i, user := range *onlineUserList {
data[i] = OnlineUser{UID: user.UID, IP: user.IP}
if _, ok := reportOnline[user.UID]; ok {
reportOnline[user.UID]++
} else {
reportOnline[user.UID] = 1
}
}
c.LastReportOnline = reportOnline // Update LastReportOnline
postData := &PostData{Data: data}
path := fmt.Sprintf("/mod_mu/users/aliveip")
res, err := c.client.R().
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
SetBody(postData).
SetResult(&Response{}).
ForceContentType("application/json").
Post(path)
_, err = c.parseResponse(res, path, err)
if err != nil {
return err
}
return nil
}
// ReportUserTraffic reports the user traffic
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
data := make([]UserTraffic, len(*userTraffic))
for i, traffic := range *userTraffic {
data[i] = UserTraffic{
UID: traffic.UID,
Upload: traffic.Upload,
Download: traffic.Download}
}
postData := &PostData{Data: data}
path := "/mod_mu/users/traffic"
res, err := c.client.R().
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
SetBody(postData).
SetResult(&Response{}).
ForceContentType("application/json").
Post(path)
_, err = c.parseResponse(res, path, err)
if err != nil {
return err
}
return nil
}
// GetNodeRule will pull the audit rule form ssPanel
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
ruleList := c.LocalRuleList
path := "/mod_mu/func/detect_rules"
res, err := c.client.R().
SetResult(&Response{}).
SetHeader("If-None-Match", c.eTags["rules"]).
ForceContentType("application/json").
Get(path)
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
if res.StatusCode() == 304 {
return nil, errors.New(api.RuleNotModified)
}
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["rules"] {
c.eTags["rules"] = res.Header().Get("ETag")
}
response, err := c.parseResponse(res, path, err)
if err != nil {
return nil, err
}
ruleListResponse := new([]RuleItem)
if err := json.Unmarshal(response.Data, ruleListResponse); err != nil {
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
}
for _, r := range *ruleListResponse {
ruleList = append(ruleList, api.DetectRule{
ID: r.ID,
Pattern: regexp.MustCompile(r.Content),
})
}
return &ruleList, nil
}
// ReportIllegal reports the user illegal behaviors
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
data := make([]IllegalItem, len(*detectResultList))
for i, r := range *detectResultList {
data[i] = IllegalItem{
ID: r.RuleID,
UID: r.UID,
}
}
postData := &PostData{Data: data}
path := "/mod_mu/users/detectlog"
res, err := c.client.R().
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
SetBody(postData).
SetResult(&Response{}).
ForceContentType("application/json").
Post(path)
_, err = c.parseResponse(res, path, err)
if err != nil {
return err
}
return nil
}
// ParseV2rayNodeResponse parse the response for the given node info format
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
var enableTLS bool
var path, host, transportProtocol, serviceName, HeaderType string
var header json.RawMessage
var speedLimit uint64 = 0
if nodeInfoResponse.RawServerString == "" {
return nil, fmt.Errorf("no server info in response")
}
// nodeInfo.RawServerString = strings.ToLower(nodeInfo.RawServerString)
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
if err != nil {
return nil, err
}
port := uint32(parsedPort)
parsedAlterID, err := strconv.ParseInt(serverConf[2], 10, 16)
if err != nil {
return nil, err
}
alterID := uint16(parsedAlterID)
// Compatible with more node types config
for _, value := range serverConf[3:5] {
switch value {
case "tls":
enableTLS = true
default:
if value != "" {
transportProtocol = value
}
}
}
extraServerConf := strings.Split(serverConf[5], "|")
serviceName = ""
for _, item := range extraServerConf {
conf := strings.Split(item, "=")
key := conf[0]
if key == "" {
continue
}
value := conf[1]
switch key {
case "path":
rawPath := strings.Join(conf[1:], "=") // In case of the path strings contains the "="
path = rawPath
case "host":
host = value
case "servicename":
serviceName = value
case "headerType":
HeaderType = value
}
}
if c.SpeedLimit > 0 {
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
} else {
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
}
if HeaderType != "" {
headers := map[string]string{"type": HeaderType}
header, err = json.Marshal(headers)
}
if err != nil {
return nil, fmt.Errorf("marshal Header Type %s into config fialed: %s", header, err)
}
// Create GeneralNodeInfo
nodeInfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: port,
SpeedLimit: speedLimit,
AlterID: alterID,
TransportProtocol: transportProtocol,
EnableTLS: enableTLS,
Path: path,
Host: host,
EnableVless: c.EnableVless,
VlessFlow: c.VlessFlow,
ServiceName: serviceName,
Header: header,
}
return nodeInfo, nil
}
// ParseSSNodeResponse parse the response for the given node info format
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
var port uint32 = 0
var speedLimit uint64 = 0
var method string
path := "/mod_mu/users"
res, err := c.client.R().
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
SetResult(&Response{}).
ForceContentType("application/json").
Get(path)
response, err := c.parseResponse(res, path, err)
if err != nil {
return nil, err
}
userListResponse := new([]UserResponse)
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
}
// Find the multi-user
for _, u := range *userListResponse {
if u.MultiUser > 0 {
port = u.Port
method = u.Method
break
}
}
if port == 0 || method == "" {
return nil, fmt.Errorf("cant find the single port multi user")
}
if c.SpeedLimit > 0 {
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
} else {
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
}
// Create GeneralNodeInfo
nodeInfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: port,
SpeedLimit: speedLimit,
TransportProtocol: "tcp",
CypherMethod: method,
}
return nodeInfo, nil
}
// ParseSSPluginNodeResponse parse the response for the given node info format
func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
var enableTLS bool
var path, host, transportProtocol string
var speedLimit uint64 = 0
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
if err != nil {
return nil, err
}
port := uint32(parsedPort)
port = port - 1 // Shadowsocks-Plugin requires two ports, one for ss the other for other stream protocol
if port <= 0 {
return nil, fmt.Errorf("Shadowsocks-Plugin listen port must bigger than 1")
}
// Compatible with more node types config
for _, value := range serverConf[3:5] {
switch value {
case "tls":
enableTLS = true
case "ws":
transportProtocol = "ws"
case "obfs":
transportProtocol = "tcp"
}
}
extraServerConf := strings.Split(serverConf[5], "|")
for _, item := range extraServerConf {
conf := strings.Split(item, "=")
key := conf[0]
if key == "" {
continue
}
value := conf[1]
switch key {
case "path":
rawPath := strings.Join(conf[1:], "=") // In case of the path strings contains the "="
path = rawPath
case "host":
host = value
}
}
if c.SpeedLimit > 0 {
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
} else {
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
}
// Create GeneralNodeInfo
nodeInfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: port,
SpeedLimit: speedLimit,
TransportProtocol: transportProtocol,
EnableTLS: enableTLS,
Path: path,
Host: host,
}
return nodeInfo, nil
}
// ParseTrojanNodeResponse parse the response for the given node info format
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
// 域名或IP;port=连接端口#偏移端口|host=xx
// gz.aaa.com;port=443#12345|host=hk.aaa.com
var p, host, outsidePort, insidePort, transportProtocol, serviceName string
var speedLimit uint64 = 0
if nodeInfoResponse.RawServerString == "" {
return nil, fmt.Errorf("no server info in response")
}
if result := firstPortRe.FindStringSubmatch(nodeInfoResponse.RawServerString); len(result) > 1 {
outsidePort = result[1]
}
if result := secondPortRe.FindStringSubmatch(nodeInfoResponse.RawServerString); len(result) > 1 {
insidePort = result[1]
}
if result := hostRe.FindStringSubmatch(nodeInfoResponse.RawServerString); len(result) > 1 {
host = result[1]
}
if insidePort != "" {
p = insidePort
} else {
p = outsidePort
}
parsedPort, err := strconv.ParseInt(p, 10, 32)
if err != nil {
return nil, err
}
port := uint32(parsedPort)
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
extraServerConf := strings.Split(serverConf[1], "|")
transportProtocol = "tcp"
serviceName = ""
for _, item := range extraServerConf {
conf := strings.Split(item, "=")
key := conf[0]
if key == "" {
continue
}
value := conf[1]
switch key {
case "grpc":
transportProtocol = "grpc"
case "servicename":
serviceName = value
}
}
if c.SpeedLimit > 0 {
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
} else {
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
}
// Create GeneralNodeInfo
nodeInfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: port,
SpeedLimit: speedLimit,
TransportProtocol: transportProtocol,
EnableTLS: true,
Host: host,
ServiceName: serviceName,
}
return nodeInfo, nil
}
// ParseUserListResponse parse the response for the given node info format
func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]api.UserInfo, error) {
c.access.Lock()
// Clear Last report log
defer func() {
c.LastReportOnline = make(map[int]int)
c.access.Unlock()
}()
var deviceLimit, localDeviceLimit int = 0, 0
var speedLimit uint64 = 0
var userList []api.UserInfo
for _, user := range *userInfoResponse {
if c.DeviceLimit > 0 {
deviceLimit = c.DeviceLimit
} else {
deviceLimit = user.DeviceLimit
}
// If there is still device available, add the user
if deviceLimit > 0 && user.AliveIP > 0 {
lastOnline := 0
if v, ok := c.LastReportOnline[user.ID]; ok {
lastOnline = v
}
// If there are any available device.
if localDeviceLimit = deviceLimit - user.AliveIP + lastOnline; localDeviceLimit > 0 {
deviceLimit = localDeviceLimit
// If this backend server has reported any user in the last reporting period.
} else if lastOnline > 0 {
deviceLimit = lastOnline
// Remove this user.
} else {
continue
}
}
if c.SpeedLimit > 0 {
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
} else {
speedLimit = uint64((user.SpeedLimit * 1000000) / 8)
}
userList = append(userList, api.UserInfo{
UID: user.ID,
Email: user.Email,
UUID: user.UUID,
Passwd: user.Passwd,
SpeedLimit: speedLimit,
DeviceLimit: deviceLimit,
Port: user.Port,
Method: user.Method,
Protocol: user.Protocol,
ProtocolParam: user.ProtocolParam,
Obfs: user.Obfs,
ObfsParam: user.ObfsParam,
})
}
return &userList, nil
}
// ParseSSPanelNodeInfo parse the response for the given node info format
// Only used for SSPanel version >= 2021.11
func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
var speedLimit uint64 = 0
var EnableTLS, EnableVless bool
var AlterID uint16 = 0
var TLSType, transportProtocol string
if nodeInfoResponse.Version == "" {
return nil, errors.New("panel version must be 2021.11 or above")
}
// Check if custom_config is valid
if len(nodeInfoResponse.CustomConfig) == 0 {
return nil, errors.New("custom_config is empty, disable custom config")
}
nodeConfig := new(CustomConfig)
err := json.Unmarshal(nodeInfoResponse.CustomConfig, nodeConfig)
if err != nil {
return nil, fmt.Errorf("custom_config is error: %v", err)
}
if c.SpeedLimit > 0 {
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
} else {
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
}
parsedPort, err := strconv.ParseInt(nodeConfig.OffsetPortNode, 10, 32)
if err != nil {
return nil, err
}
port := uint32(parsedPort)
if c.NodeType == "Shadowsocks" {
transportProtocol = "tcp"
}
if c.NodeType == "V2ray" {
transportProtocol = nodeConfig.Network
TLSType = nodeConfig.Security
if parsedAlterID, err := strconv.ParseInt(nodeConfig.AlterID, 10, 16); err != nil {
return nil, err
} else {
AlterID = uint16(parsedAlterID)
}
if TLSType == "tls" || TLSType == "xtls" {
EnableTLS = true
}
if nodeConfig.EnableVless == "1" {
EnableVless = true
}
}
if c.NodeType == "Trojan" {
EnableTLS = true
TLSType = "tls"
transportProtocol = "tcp"
// Select security type
if nodeConfig.EnableXtls == "1" {
TLSType = "xtls"
} else if nodeConfig.Security != "" {
TLSType = nodeConfig.Security // try to read security from config
}
// Select transport protocol
if nodeConfig.Grpc == "1" {
transportProtocol = "grpc"
} else if nodeConfig.Network != "" {
transportProtocol = nodeConfig.Network // try to read transport protocol from config
}
}
// Create GeneralNodeInfo
nodeInfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: port,
SpeedLimit: speedLimit,
AlterID: AlterID,
TransportProtocol: transportProtocol,
Host: nodeConfig.Host,
Path: nodeConfig.Path,
EnableTLS: EnableTLS,
EnableVless: EnableVless,
VlessFlow: nodeConfig.Flow,
CypherMethod: nodeConfig.MuEncryption,
ServiceName: nodeConfig.Servicename,
Header: nodeConfig.Header,
}
return nodeInfo, nil
}
func compareVersion(version1, version2 string) int {
n, m := len(version1), len(version2)
i, j := 0, 0
for i < n || j < m {
x := 0
for ; i < n && version1[i] != '.'; i++ {
x = x*10 + int(version1[i]-'0')
}
i++ // jump dot
y := 0
for ; j < m && version2[j] != '.'; j++ {
y = y*10 + int(version2[j]-'0')
}
j++ // jump dot
if x > y {
return 1
}
if x < y {
return -1
}
}
return 0
}