mirror of
https://github.com/XrayR-project/XrayR.git
synced 2025-06-08 13:29:54 +00:00
Merge branch 'master' of https://github.com/Github-Aiko/XrayR-Real
This commit is contained in:
commit
c04330d0bf
@ -42,6 +42,7 @@ type NodeInfo struct {
|
|||||||
TLSType string
|
TLSType string
|
||||||
EnableVless bool
|
EnableVless bool
|
||||||
CypherMethod string
|
CypherMethod string
|
||||||
|
ServerKey string
|
||||||
ServiceName string
|
ServiceName string
|
||||||
Header json.RawMessage
|
Header json.RawMessage
|
||||||
}
|
}
|
||||||
|
1
api/newV2board/model.go
Normal file
1
api/newV2board/model.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package newV2board
|
371
api/newV2board/v2board.go
Normal file
371
api/newV2board/v2board.go
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
package newV2board
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bitly/go-simplejson"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIClient create an api client to the panel.
|
||||||
|
type APIClient struct {
|
||||||
|
client *resty.Client
|
||||||
|
APIHost string
|
||||||
|
NodeID int
|
||||||
|
Key string
|
||||||
|
NodeType string
|
||||||
|
EnableVless bool
|
||||||
|
EnableXTLS bool
|
||||||
|
SpeedLimit float64
|
||||||
|
DeviceLimit int
|
||||||
|
LocalRuleList []api.DetectRule
|
||||||
|
resp atomic.Value
|
||||||
|
eTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New create an 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) {
|
||||||
|
if v, ok := err.(*resty.ResponseError); ok {
|
||||||
|
// 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.SetQueryParams(map[string]string{
|
||||||
|
"node_id": strconv.Itoa(apiConfig.NodeID),
|
||||||
|
"node_type": strings.ToLower(apiConfig.NodeType),
|
||||||
|
"token": apiConfig.Key,
|
||||||
|
})
|
||||||
|
// Read local rule list
|
||||||
|
localRuleList := readLocalRuleList(apiConfig.RuleListPath)
|
||||||
|
apiClient := &APIClient{
|
||||||
|
client: client,
|
||||||
|
NodeID: apiConfig.NodeID,
|
||||||
|
Key: apiConfig.Key,
|
||||||
|
APIHost: apiConfig.APIHost,
|
||||||
|
NodeType: apiConfig.NodeType,
|
||||||
|
EnableVless: apiConfig.EnableVless,
|
||||||
|
EnableXTLS: apiConfig.EnableXTLS,
|
||||||
|
SpeedLimit: apiConfig.SpeedLimit,
|
||||||
|
DeviceLimit: apiConfig.DeviceLimit,
|
||||||
|
LocalRuleList: localRuleList,
|
||||||
|
}
|
||||||
|
return apiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) (*simplejson.Json, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode() > 399 {
|
||||||
|
body := res.Body()
|
||||||
|
return nil, fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err)
|
||||||
|
}
|
||||||
|
rtn, err := simplejson.NewJson(res.Body())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ret %s invalid", res.String())
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodeInfo will pull NodeInfo Config from panel
|
||||||
|
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||||
|
path := "/api/v1/server/UniProxy/config"
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Get(path)
|
||||||
|
|
||||||
|
response, err := c.parseResponse(res, path, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.resp.Store(response)
|
||||||
|
|
||||||
|
switch c.NodeType {
|
||||||
|
case "V2ray":
|
||||||
|
nodeInfo, err = c.parseV2rayNodeResponse(response)
|
||||||
|
case "Trojan":
|
||||||
|
nodeInfo, err = c.parseTrojanNodeResponse(response)
|
||||||
|
case "Shadowsocks":
|
||||||
|
nodeInfo, err = c.parseSSNodeResponse(response)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
res, _ := response.MarshalJSON()
|
||||||
|
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s", string(res), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserList will pull user form panel
|
||||||
|
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||||
|
path := "/api/v1/server/UniProxy/user"
|
||||||
|
|
||||||
|
switch c.NodeType {
|
||||||
|
case "V2ray", "Trojan", "Shadowsocks":
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetHeader("If-None-Match", c.eTag).
|
||||||
|
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("users no change")
|
||||||
|
}
|
||||||
|
// update etag
|
||||||
|
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTag {
|
||||||
|
c.eTag = res.Header().Get("Etag")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.parseResponse(res, path, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
numOfUsers := len(response.Get("users").MustArray())
|
||||||
|
userList := make([]api.UserInfo, numOfUsers)
|
||||||
|
for i := 0; i < numOfUsers; i++ {
|
||||||
|
user := response.Get("users").GetIndex(i)
|
||||||
|
u := api.UserInfo{}
|
||||||
|
u.UID = user.Get("id").MustInt()
|
||||||
|
u.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8) // todo waiting v2board send configuration
|
||||||
|
u.DeviceLimit = c.DeviceLimit // todo waiting v2board send configuration
|
||||||
|
u.UUID = user.Get("uuid").MustString()
|
||||||
|
u.Email = u.UUID + "@v2board.user"
|
||||||
|
if c.NodeType == "Shadowsocks" {
|
||||||
|
u.Passwd = u.UUID
|
||||||
|
}
|
||||||
|
userList[i] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportUserTraffic reports the user traffic
|
||||||
|
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||||
|
path := "/api/v1/server/UniProxy/push"
|
||||||
|
|
||||||
|
// json structure: {uid1: [u, d], uid2: [u, d], uid1: [u, d], uid3: [u, d]}
|
||||||
|
data := make(map[int][]int64, len(*userTraffic))
|
||||||
|
for _, traffic := range *userTraffic {
|
||||||
|
data[traffic.UID] = []int64{traffic.Upload, traffic.Download}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetBody(data).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Post(path)
|
||||||
|
_, err = c.parseResponse(res, path, err)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodeRule implements the API interface
|
||||||
|
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||||
|
ruleList := c.LocalRuleList
|
||||||
|
if c.NodeType != "V2ray" {
|
||||||
|
return &ruleList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// V2board only support the rule for v2ray
|
||||||
|
nodeInfoResponse := c.resp.Load().(*simplejson.Json) // todo waiting v2board send configuration
|
||||||
|
for i, rule := range nodeInfoResponse.Get("rules").MustStringArray() {
|
||||||
|
rule = strings.TrimPrefix(rule, "regexp:")
|
||||||
|
ruleListItem := api.DetectRule{
|
||||||
|
ID: i,
|
||||||
|
Pattern: regexp.MustCompile(rule),
|
||||||
|
}
|
||||||
|
ruleList = append(ruleList, ruleListItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ruleList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportNodeStatus implements the API interface
|
||||||
|
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportNodeOnlineUsers implements the API interface
|
||||||
|
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportIllegal implements the API interface
|
||||||
|
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTrojanNodeResponse parse the response for the given nodeInfo format
|
||||||
|
func (c *APIClient) parseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||||
|
var TLSType = "tls"
|
||||||
|
if c.EnableXTLS {
|
||||||
|
TLSType = "xtls"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create GeneralNodeInfo
|
||||||
|
nodeInfo := &api.NodeInfo{
|
||||||
|
NodeType: c.NodeType,
|
||||||
|
NodeID: c.NodeID,
|
||||||
|
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
||||||
|
TransportProtocol: "tcp",
|
||||||
|
EnableTLS: true,
|
||||||
|
TLSType: TLSType,
|
||||||
|
Host: nodeInfoResponse.Get("host").MustString(),
|
||||||
|
ServiceName: nodeInfoResponse.Get("server_name").MustString(),
|
||||||
|
}
|
||||||
|
return nodeInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSSNodeResponse parse the response for the given nodeInfo format
|
||||||
|
func (c *APIClient) parseSSNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||||
|
// Create GeneralNodeInfo
|
||||||
|
return &api.NodeInfo{
|
||||||
|
NodeType: c.NodeType,
|
||||||
|
NodeID: c.NodeID,
|
||||||
|
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
||||||
|
TransportProtocol: "tcp",
|
||||||
|
CypherMethod: nodeInfoResponse.Get("cipher").MustString(),
|
||||||
|
ServerKey: nodeInfoResponse.Get("server_key").MustString(), // shadowsocks2022 share key
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseV2rayNodeResponse parse the response for the given nodeInfo format
|
||||||
|
func (c *APIClient) parseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||||
|
var (
|
||||||
|
TLSType = "tls"
|
||||||
|
path, host, serviceName string
|
||||||
|
header json.RawMessage
|
||||||
|
enableTLS bool
|
||||||
|
alterID uint16 = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.EnableXTLS {
|
||||||
|
TLSType = "xtls"
|
||||||
|
}
|
||||||
|
|
||||||
|
transportProtocol := nodeInfoResponse.Get("network").MustString()
|
||||||
|
|
||||||
|
switch transportProtocol {
|
||||||
|
case "ws":
|
||||||
|
path = nodeInfoResponse.Get("networkSettings").Get("path").MustString()
|
||||||
|
host = nodeInfoResponse.Get("networkSettings").Get("headers").Get("Host").MustString()
|
||||||
|
case "grpc":
|
||||||
|
if data, ok := nodeInfoResponse.Get("networkSettings").CheckGet("serviceName"); ok {
|
||||||
|
serviceName = data.MustString()
|
||||||
|
}
|
||||||
|
case "tcp":
|
||||||
|
if data, ok := nodeInfoResponse.Get("networkSettings").CheckGet("headers"); ok {
|
||||||
|
if httpHeader, err := data.MarshalJSON(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
header = httpHeader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeInfoResponse.Get("tls").MustInt() == 1 {
|
||||||
|
enableTLS = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create GeneralNodeInfo
|
||||||
|
return &api.NodeInfo{
|
||||||
|
NodeType: c.NodeType,
|
||||||
|
NodeID: c.NodeID,
|
||||||
|
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
||||||
|
AlterID: alterID,
|
||||||
|
TransportProtocol: transportProtocol,
|
||||||
|
EnableTLS: enableTLS,
|
||||||
|
TLSType: TLSType,
|
||||||
|
Path: path,
|
||||||
|
Host: host,
|
||||||
|
EnableVless: c.EnableVless,
|
||||||
|
ServiceName: serviceName,
|
||||||
|
Header: header,
|
||||||
|
}, nil
|
||||||
|
}
|
101
api/newV2board/v2board_test.go
Normal file
101
api/newV2board/v2board_test.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package newV2board_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/api/newV2board"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateClient() api.API {
|
||||||
|
apiConfig := &api.Config{
|
||||||
|
APIHost: "http://localhost:9897",
|
||||||
|
Key: "qwertyuiopasdfghjkl",
|
||||||
|
NodeID: 1,
|
||||||
|
NodeType: "V2ray",
|
||||||
|
}
|
||||||
|
client := newV2board.New(apiConfig)
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetV2rayNodeInfo(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(nodeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSSNodeInfo(t *testing.T) {
|
||||||
|
apiConfig := &api.Config{
|
||||||
|
APIHost: "http://127.0.0.1:668",
|
||||||
|
Key: "qwertyuiopasdfghjkl",
|
||||||
|
NodeID: 1,
|
||||||
|
NodeType: "Shadowsocks",
|
||||||
|
}
|
||||||
|
client := newV2board.New(apiConfig)
|
||||||
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(nodeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTrojanNodeInfo(t *testing.T) {
|
||||||
|
apiConfig := &api.Config{
|
||||||
|
APIHost: "http://127.0.0.1:668",
|
||||||
|
Key: "qwertyuiopasdfghjkl",
|
||||||
|
NodeID: 1,
|
||||||
|
NodeType: "Trojan",
|
||||||
|
}
|
||||||
|
client := newV2board.New(apiConfig)
|
||||||
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(nodeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserList(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
|
||||||
|
userList, err := client.GetUserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(userList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReportReportUserTraffic(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
userList, err := client.GetUserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
generalUserTraffic := make([]api.UserTraffic, len(*userList))
|
||||||
|
for i, userInfo := range *userList {
|
||||||
|
generalUserTraffic[i] = api.UserTraffic{
|
||||||
|
UID: userInfo.UID,
|
||||||
|
Upload: 114514,
|
||||||
|
Download: 114514,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// client.Debug()
|
||||||
|
err = client.ReportUserTraffic(&generalUserTraffic)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNodeRule(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
client.Debug()
|
||||||
|
ruleList, err := client.GetNodeRule()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(ruleList)
|
||||||
|
}
|
@ -241,7 +241,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
|||||||
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
||||||
systemload := SystemLoad{
|
systemload := SystemLoad{
|
||||||
Uptime: strconv.FormatUint(nodeStatus.Uptime, 10),
|
Uptime: strconv.FormatUint(nodeStatus.Uptime, 10),
|
||||||
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.CPU/100, nodeStatus.CPU/100),
|
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.Mem/100, nodeStatus.Disk/100),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// Deprecated: after 2023.6.1
|
||||||
package v2board
|
package v2board
|
||||||
|
|
||||||
type UserTraffic struct {
|
type UserTraffic struct {
|
||||||
|
@ -108,7 +108,7 @@ func (l *Limiter) UpdateInboundLimiter(tag string, updatedUserList *[]api.UserIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return newError("no such inbound in limiter: %s", tag).AtError()
|
return fmt.Errorf("no such inbound in limiter: %s", tag)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -143,7 +143,7 @@ func (l *Limiter) GetOnlineDevice(tag string) (*[]api.OnlineUser, error) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return nil, newError("no such inbound in limiter: %s", tag).AtError()
|
return nil, fmt.Errorf("no such inbound in limiter: %s", tag)
|
||||||
}
|
}
|
||||||
return &onlineUser, nil
|
return &onlineUser, nil
|
||||||
}
|
}
|
||||||
|
4
go.mod
4
go.mod
@ -11,6 +11,8 @@ require (
|
|||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/go-resty/resty/v2 v2.7.0
|
||||||
github.com/imdario/mergo v0.3.13
|
github.com/imdario/mergo v0.3.13
|
||||||
github.com/r3labs/diff/v2 v2.15.1
|
github.com/r3labs/diff/v2 v2.15.1
|
||||||
|
github.com/sagernet/sing v0.0.0-20220801112236-1bb95f9661fc
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1
|
||||||
github.com/shirou/gopsutil/v3 v3.22.10
|
github.com/shirou/gopsutil/v3 v3.22.10
|
||||||
github.com/spf13/viper v1.14.0
|
github.com/spf13/viper v1.14.0
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
@ -129,8 +131,6 @@ require (
|
|||||||
github.com/sacloud/go-http v0.1.2 // indirect
|
github.com/sacloud/go-http v0.1.2 // indirect
|
||||||
github.com/sacloud/iaas-api-go v1.3.2 // indirect
|
github.com/sacloud/iaas-api-go v1.3.2 // indirect
|
||||||
github.com/sacloud/packages-go v0.0.5 // indirect
|
github.com/sacloud/packages-go v0.0.5 // indirect
|
||||||
github.com/sagernet/sing v0.0.0-20220801112236-1bb95f9661fc // indirect
|
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1 // indirect
|
|
||||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9 // indirect
|
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9 // indirect
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
|
@ -14,7 +14,7 @@ ConnectionConfig:
|
|||||||
BufferSize: 64 # The internal cache size of each connection, kB
|
BufferSize: 64 # The internal cache size of each connection, kB
|
||||||
Nodes:
|
Nodes:
|
||||||
-
|
-
|
||||||
PanelType: "SSpanel" # Panel type: SSpanel, V2board, PMpanel, Proxypanel, V2RaySocks
|
PanelType: "SSpanel" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
|
||||||
ApiConfig:
|
ApiConfig:
|
||||||
ApiHost: "http://127.0.0.1:667"
|
ApiHost: "http://127.0.0.1:667"
|
||||||
ApiKey: "123"
|
ApiKey: "123"
|
||||||
@ -64,7 +64,7 @@ Nodes:
|
|||||||
ALICLOUD_ACCESS_KEY: aaa
|
ALICLOUD_ACCESS_KEY: aaa
|
||||||
ALICLOUD_SECRET_KEY: bbb
|
ALICLOUD_SECRET_KEY: bbb
|
||||||
# -
|
# -
|
||||||
# PanelType: "V2board" # Panel type: SSpanel, V2board
|
# PanelType: "NewV2board" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
|
||||||
# ApiConfig:
|
# ApiConfig:
|
||||||
# ApiHost: "http://127.0.0.1:668"
|
# ApiHost: "http://127.0.0.1:668"
|
||||||
# ApiKey: "123"
|
# ApiKey: "123"
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api/newV2board"
|
||||||
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
@ -168,8 +169,11 @@ func (p *Panel) Start() {
|
|||||||
switch nodeConfig.PanelType {
|
switch nodeConfig.PanelType {
|
||||||
case "SSpanel":
|
case "SSpanel":
|
||||||
apiClient = sspanel.New(nodeConfig.ApiConfig)
|
apiClient = sspanel.New(nodeConfig.ApiConfig)
|
||||||
|
// todo Deprecated after 2023.6.1
|
||||||
case "V2board":
|
case "V2board":
|
||||||
apiClient = v2board.New(nodeConfig.ApiConfig)
|
apiClient = v2board.New(nodeConfig.ApiConfig)
|
||||||
|
case "NewV2board":
|
||||||
|
apiClient = newV2board.New(nodeConfig.ApiConfig)
|
||||||
case "PMpanel":
|
case "PMpanel":
|
||||||
apiClient = pmpanel.New(nodeConfig.ApiConfig)
|
apiClient = pmpanel.New(nodeConfig.ApiConfig)
|
||||||
case "Proxypanel":
|
case "Proxypanel":
|
||||||
|
@ -37,6 +37,7 @@ type Controller struct {
|
|||||||
userList *[]api.UserInfo
|
userList *[]api.UserInfo
|
||||||
nodeInfoMonitorPeriodic *task.Periodic
|
nodeInfoMonitorPeriodic *task.Periodic
|
||||||
userReportPeriodic *task.Periodic
|
userReportPeriodic *task.Periodic
|
||||||
|
renewCertPeriodic *task.Periodic
|
||||||
limitedUsers map[api.UserInfo]LimitInfo
|
limitedUsers map[api.UserInfo]LimitInfo
|
||||||
warnedUsers map[api.UserInfo]int
|
warnedUsers map[api.UserInfo]int
|
||||||
panelType string
|
panelType string
|
||||||
@ -118,6 +119,10 @@ func (c *Controller) Start() error {
|
|||||||
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
|
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
|
||||||
Execute: c.userInfoMonitor,
|
Execute: c.userInfoMonitor,
|
||||||
}
|
}
|
||||||
|
c.renewCertPeriodic = &task.Periodic{
|
||||||
|
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second * 60,
|
||||||
|
Execute: c.certMonitor,
|
||||||
|
}
|
||||||
if c.config.AutoSpeedLimitConfig == nil {
|
if c.config.AutoSpeedLimitConfig == nil {
|
||||||
c.config.AutoSpeedLimitConfig = &AutoSpeedLimitConfig{0, 0, 0, 0}
|
c.config.AutoSpeedLimitConfig = &AutoSpeedLimitConfig{0, 0, 0, 0}
|
||||||
}
|
}
|
||||||
@ -127,13 +132,17 @@ func (c *Controller) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start nodeInfoMonitor
|
// start nodeInfoMonitor
|
||||||
log.Printf("[%s: %d] Start monitor node status", c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
log.Printf("%s Start monitor node status", c.logPrefix())
|
||||||
go c.nodeInfoMonitorPeriodic.Start()
|
go c.nodeInfoMonitorPeriodic.Start()
|
||||||
|
|
||||||
// start userReport
|
// start userReport
|
||||||
log.Printf("[%s: %d] Start report node status", c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
log.Printf("%s Start report user status", c.logPrefix())
|
||||||
go c.userReportPeriodic.Start()
|
go c.userReportPeriodic.Start()
|
||||||
|
|
||||||
|
// start cert monitor
|
||||||
|
log.Printf("%s Start monitor cert status", c.logPrefix())
|
||||||
|
go c.renewCertPeriodic.Start()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,14 +151,21 @@ func (c *Controller) Close() error {
|
|||||||
if c.nodeInfoMonitorPeriodic != nil {
|
if c.nodeInfoMonitorPeriodic != nil {
|
||||||
err := c.nodeInfoMonitorPeriodic.Close()
|
err := c.nodeInfoMonitorPeriodic.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("node info periodic close failed: %s", err)
|
log.Panicf("%s node info periodic close failed: %s", c.logPrefix(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.nodeInfoMonitorPeriodic != nil {
|
if c.nodeInfoMonitorPeriodic != nil {
|
||||||
err := c.userReportPeriodic.Close()
|
err := c.userReportPeriodic.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("user report periodic close failed: %s", err)
|
log.Panicf("%s user report periodic close failed: %s", c.logPrefix(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.renewCertPeriodic != nil {
|
||||||
|
err := c.renewCertPeriodic.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("%s renew cert periodic close failed: %s", c.logPrefix(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -169,10 +185,16 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update User
|
// Update User
|
||||||
|
var usersChanged = true
|
||||||
newUserInfo, err := c.apiClient.GetUserList()
|
newUserInfo, err := c.apiClient.GetUserList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
if err.Error() == "users no change" {
|
||||||
return nil
|
usersChanged = false
|
||||||
|
newUserInfo = c.userList
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodeInfoChanged = false
|
var nodeInfoChanged = false
|
||||||
@ -219,19 +241,6 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Cert
|
|
||||||
if c.nodeInfo.EnableTLS && (c.config.CertConfig.CertMode == "dns" || c.config.CertConfig.CertMode == "http") {
|
|
||||||
lego, err := mylego.New(c.config.CertConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
// Xray-core supports the OcspStapling certification hot renew
|
|
||||||
_, _, _, err = lego.RenewCert()
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nodeInfoChanged {
|
if nodeInfoChanged {
|
||||||
err = c.addNewUser(newUserInfo, newNodeInfo)
|
err = c.addNewUser(newUserInfo, newNodeInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -244,28 +253,31 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deleted, added := compareUserList(c.userList, newUserInfo)
|
var deleted, added []api.UserInfo
|
||||||
if len(deleted) > 0 {
|
if usersChanged {
|
||||||
deletedEmail := make([]string, len(deleted))
|
deleted, added = compareUserList(c.userList, newUserInfo)
|
||||||
for i, u := range deleted {
|
if len(deleted) > 0 {
|
||||||
deletedEmail[i] = fmt.Sprintf("%s|%s|%d", c.Tag, u.Email, u.UID)
|
deletedEmail := make([]string, len(deleted))
|
||||||
|
for i, u := range deleted {
|
||||||
|
deletedEmail[i] = fmt.Sprintf("%s|%s|%d", c.Tag, u.Email, u.UID)
|
||||||
|
}
|
||||||
|
err := c.removeUsers(deletedEmail, c.Tag)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err := c.removeUsers(deletedEmail, c.Tag)
|
if len(added) > 0 {
|
||||||
if err != nil {
|
err = c.addNewUser(&added, c.nodeInfo)
|
||||||
log.Print(err)
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
// Update Limiter
|
||||||
|
if err := c.UpdateInboundLimiter(c.Tag, &added); err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(added) > 0 {
|
log.Printf("%s %d user deleted, %d user added", c.logPrefix(), len(deleted), len(added))
|
||||||
err = c.addNewUser(&added, c.nodeInfo)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
// Update Limiter
|
|
||||||
if err := c.UpdateInboundLimiter(c.Tag, &added); 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
|
c.userList = newUserInfo
|
||||||
return nil
|
return nil
|
||||||
@ -365,7 +377,8 @@ func (c *Controller) addInboundForSSPlugin(newNodeInfo api.NodeInfo) (err error)
|
|||||||
|
|
||||||
func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo) (err error) {
|
func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo) (err error) {
|
||||||
users := make([]*protocol.User, 0)
|
users := make([]*protocol.User, 0)
|
||||||
if nodeInfo.NodeType == "V2ray" {
|
switch nodeInfo.NodeType {
|
||||||
|
case "V2ray":
|
||||||
if nodeInfo.EnableVless {
|
if nodeInfo.EnableVless {
|
||||||
users = c.buildVlessUser(userInfo)
|
users = c.buildVlessUser(userInfo)
|
||||||
} else {
|
} else {
|
||||||
@ -378,20 +391,21 @@ func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo
|
|||||||
}
|
}
|
||||||
users = c.buildVmessUser(userInfo, alterID)
|
users = c.buildVmessUser(userInfo, alterID)
|
||||||
}
|
}
|
||||||
} else if nodeInfo.NodeType == "Trojan" {
|
case "Trojan":
|
||||||
users = c.buildTrojanUser(userInfo)
|
users = c.buildTrojanUser(userInfo)
|
||||||
} else if nodeInfo.NodeType == "Shadowsocks" {
|
case "Shadowsocks":
|
||||||
users = c.buildSSUser(userInfo, nodeInfo.CypherMethod)
|
users = c.buildSSUser(userInfo, nodeInfo.CypherMethod)
|
||||||
} else if nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
case "Shadowsocks-Plugin":
|
||||||
users = c.buildSSPluginUser(userInfo)
|
users = c.buildSSPluginUser(userInfo)
|
||||||
} else {
|
default:
|
||||||
return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType)
|
return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.addUsers(users, c.Tag)
|
err = c.addUsers(users, c.Tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("[%s: %d] Added %d new users", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(*userInfo))
|
log.Printf("%s Added %d new users", c.logPrefix(), len(*userInfo))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,7 +481,7 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
}
|
}
|
||||||
// Unlock users
|
// Unlock users
|
||||||
if c.config.AutoSpeedLimitConfig.Limit > 0 && len(c.limitedUsers) > 0 {
|
if c.config.AutoSpeedLimitConfig.Limit > 0 && len(c.limitedUsers) > 0 {
|
||||||
log.Printf("Limited users:")
|
log.Printf("%s Limited users:", c.logPrefix())
|
||||||
toReleaseUsers := make([]api.UserInfo, 0)
|
toReleaseUsers := make([]api.UserInfo, 0)
|
||||||
for user, limitInfo := range c.limitedUsers {
|
for user, limitInfo := range c.limitedUsers {
|
||||||
if time.Now().Unix() > limitInfo.end {
|
if time.Now().Unix() > limitInfo.end {
|
||||||
@ -555,7 +569,7 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
if err = c.apiClient.ReportNodeOnlineUsers(onlineDevice); err != nil {
|
if err = c.apiClient.ReportNodeOnlineUsers(onlineDevice); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[%s: %d] Report %d online users", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(*onlineDevice))
|
log.Printf("%s Report %d online users", c.logPrefix(), len(*onlineDevice))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Report Illegal user
|
// Report Illegal user
|
||||||
@ -565,7 +579,7 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
if err = c.apiClient.ReportIllegal(detectResult); err != nil {
|
if err = c.apiClient.ReportIllegal(detectResult); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[%s: %d] Report %d illegal behaviors", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(*detectResult))
|
log.Printf("%s Report %d illegal behaviors", c.logPrefix(), len(*detectResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -575,3 +589,26 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
func (c *Controller) buildNodeTag() string {
|
func (c *Controller) buildNodeTag() string {
|
||||||
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.Port)
|
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) logPrefix() string {
|
||||||
|
return fmt.Sprintf("[%s] %s(ID=%d)", c.clientInfo.APIHost, c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Cert
|
||||||
|
func (c *Controller) certMonitor() error {
|
||||||
|
if c.nodeInfo.EnableTLS {
|
||||||
|
switch c.config.CertConfig.CertMode {
|
||||||
|
case "dns", "http", "tls":
|
||||||
|
lego, err := mylego.New(c.config.CertConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
// Xray-core supports the OcspStapling certification hot renew
|
||||||
|
_, _, _, err = lego.RenewCert()
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -2,11 +2,15 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||||
|
C "github.com/sagernet/sing/common"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/uuid"
|
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
|
||||||
@ -49,9 +53,10 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
setting json.RawMessage
|
setting json.RawMessage
|
||||||
)
|
)
|
||||||
|
|
||||||
var proxySetting interface{}
|
var proxySetting any
|
||||||
// Build Protocol and Protocol setting
|
// Build Protocol and Protocol setting
|
||||||
if nodeInfo.NodeType == "V2ray" {
|
switch nodeInfo.NodeType {
|
||||||
|
case "V2ray":
|
||||||
if nodeInfo.EnableVless {
|
if nodeInfo.EnableVless {
|
||||||
protocol = "vless"
|
protocol = "vless"
|
||||||
// Enable fallback
|
// Enable fallback
|
||||||
@ -74,7 +79,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
protocol = "vmess"
|
protocol = "vmess"
|
||||||
proxySetting = &conf.VMessInboundConfig{}
|
proxySetting = &conf.VMessInboundConfig{}
|
||||||
}
|
}
|
||||||
} else if nodeInfo.NodeType == "Trojan" {
|
case "Trojan":
|
||||||
protocol = "trojan"
|
protocol = "trojan"
|
||||||
// Enable fallback
|
// Enable fallback
|
||||||
if config.EnableFallback {
|
if config.EnableFallback {
|
||||||
@ -89,23 +94,30 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
} else {
|
} else {
|
||||||
proxySetting = &conf.TrojanServerConfig{}
|
proxySetting = &conf.TrojanServerConfig{}
|
||||||
}
|
}
|
||||||
} else if nodeInfo.NodeType == "Shadowsocks" || nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
case "Shadowsocks", "Shadowsocks-Plugin":
|
||||||
protocol = "shadowsocks"
|
protocol = "shadowsocks"
|
||||||
proxySetting = &conf.ShadowsocksServerConfig{}
|
cipher := strings.ToLower(nodeInfo.CypherMethod)
|
||||||
randomPasswd := uuid.New()
|
|
||||||
defaultSSuser := &conf.ShadowsocksUserConfig{
|
proxySetting = &conf.ShadowsocksServerConfig{
|
||||||
Cipher: "aes-128-gcm",
|
Cipher: cipher,
|
||||||
Password: randomPasswd.String(),
|
Password: nodeInfo.ServerKey, // shadowsocks2022 shareKey
|
||||||
}
|
}
|
||||||
|
|
||||||
proxySetting, _ := proxySetting.(*conf.ShadowsocksServerConfig)
|
proxySetting, _ := proxySetting.(*conf.ShadowsocksServerConfig)
|
||||||
proxySetting.Users = append(proxySetting.Users, defaultSSuser)
|
// shadowsocks must have a random password
|
||||||
|
if !C.Contains(shadowaead_2022.List, cipher) {
|
||||||
|
b := make([]byte, 16)
|
||||||
|
rand.Read(b)
|
||||||
|
proxySetting.Password = hex.EncodeToString(b)
|
||||||
|
}
|
||||||
|
|
||||||
proxySetting.NetworkList = &conf.NetworkList{"tcp", "udp"}
|
proxySetting.NetworkList = &conf.NetworkList{"tcp", "udp"}
|
||||||
proxySetting.IVCheck = true
|
proxySetting.IVCheck = true
|
||||||
if config.DisableIVCheck {
|
if config.DisableIVCheck {
|
||||||
proxySetting.IVCheck = false
|
proxySetting.IVCheck = false
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if nodeInfo.NodeType == "dokodemo-door" {
|
case "dokodemo-door":
|
||||||
protocol = "dokodemo-door"
|
protocol = "dokodemo-door"
|
||||||
proxySetting = struct {
|
proxySetting = struct {
|
||||||
Host string `json:"address"`
|
Host string `json:"address"`
|
||||||
@ -114,7 +126,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
Host: "v1.mux.cool",
|
Host: "v1.mux.cool",
|
||||||
NetworkList: []string{"tcp", "udp"},
|
NetworkList: []string{"tcp", "udp"},
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks, and Shadowsocks-Plugin", nodeInfo.NodeType)
|
return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks, and Shadowsocks-Plugin", nodeInfo.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +134,8 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
||||||
}
|
}
|
||||||
|
inboundDetourConfig.Protocol = protocol
|
||||||
|
inboundDetourConfig.Settings = &setting
|
||||||
|
|
||||||
// Build streamSettings
|
// Build streamSettings
|
||||||
streamSetting = new(conf.StreamConfig)
|
streamSetting = new(conf.StreamConfig)
|
||||||
@ -130,13 +144,15 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("convert TransportProtocol failed: %s", err)
|
return nil, fmt.Errorf("convert TransportProtocol failed: %s", err)
|
||||||
}
|
}
|
||||||
if networkType == "tcp" {
|
|
||||||
|
switch networkType {
|
||||||
|
case "tcp":
|
||||||
tcpSetting := &conf.TCPConfig{
|
tcpSetting := &conf.TCPConfig{
|
||||||
AcceptProxyProtocol: config.EnableProxyProtocol,
|
AcceptProxyProtocol: config.EnableProxyProtocol,
|
||||||
HeaderConfig: nodeInfo.Header,
|
HeaderConfig: nodeInfo.Header,
|
||||||
}
|
}
|
||||||
streamSetting.TCPSettings = tcpSetting
|
streamSetting.TCPSettings = tcpSetting
|
||||||
} else if networkType == "websocket" {
|
case "websocket":
|
||||||
headers := make(map[string]string)
|
headers := make(map[string]string)
|
||||||
headers["Host"] = nodeInfo.Host
|
headers["Host"] = nodeInfo.Host
|
||||||
wsSettings := &conf.WebSocketConfig{
|
wsSettings := &conf.WebSocketConfig{
|
||||||
@ -145,14 +161,14 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
Headers: headers,
|
Headers: headers,
|
||||||
}
|
}
|
||||||
streamSetting.WSSettings = wsSettings
|
streamSetting.WSSettings = wsSettings
|
||||||
} else if networkType == "http" {
|
case "http":
|
||||||
hosts := conf.StringList{nodeInfo.Host}
|
hosts := conf.StringList{nodeInfo.Host}
|
||||||
httpSettings := &conf.HTTPConfig{
|
httpSettings := &conf.HTTPConfig{
|
||||||
Host: &hosts,
|
Host: &hosts,
|
||||||
Path: nodeInfo.Path,
|
Path: nodeInfo.Path,
|
||||||
}
|
}
|
||||||
streamSetting.HTTPSettings = httpSettings
|
streamSetting.HTTPSettings = httpSettings
|
||||||
} else if networkType == "grpc" {
|
case "grpc":
|
||||||
grpcSettings := &conf.GRPCConfig{
|
grpcSettings := &conf.GRPCConfig{
|
||||||
ServiceName: nodeInfo.ServiceName,
|
ServiceName: nodeInfo.ServiceName,
|
||||||
}
|
}
|
||||||
@ -160,6 +176,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
streamSetting.Network = &transportProtocol
|
streamSetting.Network = &transportProtocol
|
||||||
|
|
||||||
// Build TLS and XTLS settings
|
// Build TLS and XTLS settings
|
||||||
if nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
|
if nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
|
||||||
streamSetting.Security = nodeInfo.TLSType
|
streamSetting.Security = nodeInfo.TLSType
|
||||||
@ -182,6 +199,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
streamSetting.XTLSSettings = xtlsSettings
|
streamSetting.XTLSSettings = xtlsSettings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support ProxyProtocol for any transport protocol
|
// Support ProxyProtocol for any transport protocol
|
||||||
if networkType != "tcp" && networkType != "ws" && config.EnableProxyProtocol {
|
if networkType != "tcp" && networkType != "ws" && config.EnableProxyProtocol {
|
||||||
sockoptConfig := &conf.SocketConfig{
|
sockoptConfig := &conf.SocketConfig{
|
||||||
@ -189,9 +207,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
}
|
}
|
||||||
streamSetting.SocketSettings = sockoptConfig
|
streamSetting.SocketSettings = sockoptConfig
|
||||||
}
|
}
|
||||||
inboundDetourConfig.Protocol = protocol
|
|
||||||
inboundDetourConfig.StreamSetting = streamSetting
|
inboundDetourConfig.StreamSetting = streamSetting
|
||||||
inboundDetourConfig.Settings = &setting
|
|
||||||
|
|
||||||
return inboundDetourConfig.Build()
|
return inboundDetourConfig.Build()
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||||
|
C "github.com/sagernet/sing/common"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
@ -14,7 +16,12 @@ import (
|
|||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AEADMethod = []shadowsocks.CipherType{shadowsocks.CipherType_AES_128_GCM, shadowsocks.CipherType_AES_256_GCM, shadowsocks.CipherType_CHACHA20_POLY1305, shadowsocks.CipherType_XCHACHA20_POLY1305}
|
var AEADMethod = map[shadowsocks.CipherType]uint8{
|
||||||
|
shadowsocks.CipherType_AES_128_GCM: 0,
|
||||||
|
shadowsocks.CipherType_AES_256_GCM: 0,
|
||||||
|
shadowsocks.CipherType_CHACHA20_POLY1305: 0,
|
||||||
|
shadowsocks.CipherType_XCHACHA20_POLY1305: 0,
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) buildVmessUser(userInfo *[]api.UserInfo, serverAlterID uint16) (users []*protocol.User) {
|
func (c *Controller) buildVmessUser(userInfo *[]api.UserInfo, serverAlterID uint16) (users []*protocol.User) {
|
||||||
users = make([]*protocol.User, len(*userInfo))
|
users = make([]*protocol.User, len(*userInfo))
|
||||||
@ -66,43 +73,59 @@ func (c *Controller) buildTrojanUser(userInfo *[]api.UserInfo) (users []*protoco
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) buildSSUser(userInfo *[]api.UserInfo, method string) (users []*protocol.User) {
|
func (c *Controller) buildSSUser(userInfo *[]api.UserInfo, method string) (users []*protocol.User) {
|
||||||
users = make([]*protocol.User, 0)
|
users = make([]*protocol.User, len(*userInfo))
|
||||||
|
|
||||||
cypherMethod := cipherFromString(method)
|
for i, user := range *userInfo {
|
||||||
for _, user := range *userInfo {
|
// todo waiting xray-core complete proxy.UserManager in shadowsocks2022
|
||||||
ssAccount := &shadowsocks.Account{
|
if C.Contains(shadowaead_2022.List, strings.ToLower(method)) {
|
||||||
Password: user.Passwd,
|
users[i] = &protocol.User{
|
||||||
CipherType: cypherMethod,
|
Level: 0,
|
||||||
|
Email: c.buildUserTag(&user),
|
||||||
|
Account: serial.ToTypedMessage(&shadowsocks.Account{
|
||||||
|
Password: user.Passwd,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
users[i] = &protocol.User{
|
||||||
|
Level: 0,
|
||||||
|
Email: c.buildUserTag(&user),
|
||||||
|
Account: serial.ToTypedMessage(&shadowsocks.Account{
|
||||||
|
Password: user.Passwd,
|
||||||
|
CipherType: cipherFromString(method),
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
users = append(users, &protocol.User{
|
|
||||||
Level: 0,
|
|
||||||
Email: c.buildUserTag(&user),
|
|
||||||
Account: serial.ToTypedMessage(ssAccount),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) buildSSPluginUser(userInfo *[]api.UserInfo) (users []*protocol.User) {
|
func (c *Controller) buildSSPluginUser(userInfo *[]api.UserInfo) (users []*protocol.User) {
|
||||||
users = make([]*protocol.User, 0)
|
users = make([]*protocol.User, len(*userInfo))
|
||||||
|
|
||||||
for _, user := range *userInfo {
|
for i, user := range *userInfo {
|
||||||
// Check if the cypher method is AEAD
|
// todo waiting xray-core complete proxy.UserManager in shadowsocks2022
|
||||||
cypherMethod := cipherFromString(user.Method)
|
if C.Contains(shadowaead_2022.List, strings.ToLower(user.Method)) {
|
||||||
for _, aeadMethod := range AEADMethod {
|
users[i] = &protocol.User{
|
||||||
if aeadMethod == cypherMethod {
|
Level: 0,
|
||||||
ssAccount := &shadowsocks.Account{
|
Email: c.buildUserTag(&user),
|
||||||
Password: user.Passwd,
|
Account: serial.ToTypedMessage(&shadowsocks.Account{
|
||||||
CipherType: cypherMethod,
|
Password: user.Passwd,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if the cypher method is AEAD
|
||||||
|
cypherMethod := cipherFromString(user.Method)
|
||||||
|
if _, ok := AEADMethod[cypherMethod]; ok {
|
||||||
|
users[i] = &protocol.User{
|
||||||
|
Level: 0,
|
||||||
|
Email: c.buildUserTag(&user),
|
||||||
|
Account: serial.ToTypedMessage(&shadowsocks.Account{
|
||||||
|
Password: user.Passwd,
|
||||||
|
CipherType: cypherMethod,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
users = append(users, &protocol.User{
|
|
||||||
Level: 0,
|
|
||||||
Email: c.buildUserTag(&user),
|
|
||||||
Account: serial.ToTypedMessage(ssAccount),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user