feature: add support v2board new API

update: add eTag on fetch users for save resources
update: split cert monitor service
update: refactor log format
update: add support shadowsocks_2022

The cert monitor period = UpdatePeriodic * 60

The xray-core official is not implementation of proxy.UserManager feature. So it may change in the future.
This commit is contained in:
Senis Y 2022-11-22 14:49:36 +08:00
parent 2f10c3f6b8
commit 183b1be519
10 changed files with 656 additions and 95 deletions

View File

@ -42,6 +42,7 @@ type NodeInfo struct {
TLSType string
EnableVless bool
CypherMethod string
ServerKey string
ServiceName string
Header json.RawMessage
}

7
api/newV2board/model.go Normal file
View File

@ -0,0 +1,7 @@
package newV2board
type UserTraffic struct {
UID int `json:"user_id"`
Upload int64 `json:"u"`
Download int64 `json:"d"`
}

371
api/newV2board/v2board.go Normal file
View 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
}

View 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)
}

View File

@ -1,3 +1,4 @@
// Deprecated: after 2023.6.1
package v2board
type UserTraffic struct {

4
go.mod
View File

@ -11,6 +11,8 @@ require (
github.com/go-resty/resty/v2 v2.7.0
github.com/imdario/mergo v0.3.13
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/spf13/viper v1.14.0
github.com/stretchr/testify v1.8.1
@ -129,8 +131,6 @@ require (
github.com/sacloud/go-http v0.1.2 // indirect
github.com/sacloud/iaas-api-go v1.3.2 // 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/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/sirupsen/logrus v1.8.1 // indirect

View File

@ -6,6 +6,7 @@ import (
"os"
"sync"
"github.com/XrayR-project/XrayR/api/newV2board"
"github.com/XrayR-project/XrayR/app/mydispatcher"
"github.com/imdario/mergo"
@ -168,8 +169,11 @@ func (p *Panel) Start() {
switch nodeConfig.PanelType {
case "SSpanel":
apiClient = sspanel.New(nodeConfig.ApiConfig)
// todo Deprecated after 2023.6.1
case "V2board":
apiClient = v2board.New(nodeConfig.ApiConfig)
case "NewV2board":
apiClient = newV2board.New(nodeConfig.ApiConfig)
case "PMpanel":
apiClient = pmpanel.New(nodeConfig.ApiConfig)
case "Proxypanel":

View File

@ -37,6 +37,7 @@ type Controller struct {
userList *[]api.UserInfo
nodeInfoMonitorPeriodic *task.Periodic
userReportPeriodic *task.Periodic
renewCertPeriodic *task.Periodic
limitedUsers map[api.UserInfo]LimitInfo
warnedUsers map[api.UserInfo]int
panelType string
@ -118,6 +119,10 @@ func (c *Controller) Start() error {
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
Execute: c.userInfoMonitor,
}
c.renewCertPeriodic = &task.Periodic{
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second * 60,
Execute: c.certMonitor,
}
if c.config.AutoSpeedLimitConfig == nil {
c.config.AutoSpeedLimitConfig = &AutoSpeedLimitConfig{0, 0, 0, 0}
}
@ -127,13 +132,17 @@ func (c *Controller) Start() error {
}
// 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()
// 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()
// start cert monitor
log.Printf("%s Start monitor cert status", c.logPrefix())
go c.renewCertPeriodic.Start()
return nil
}
@ -142,14 +151,21 @@ func (c *Controller) Close() error {
if c.nodeInfoMonitorPeriodic != nil {
err := c.nodeInfoMonitorPeriodic.Close()
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 {
err := c.userReportPeriodic.Close()
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
@ -169,11 +185,17 @@ func (c *Controller) nodeInfoMonitor() (err error) {
}
// Update User
var usersChanged = true
newUserInfo, err := c.apiClient.GetUserList()
if err != nil {
if err.Error() == "users no change" {
usersChanged = false
newUserInfo = c.userList
} else {
log.Print(err)
return nil
}
}
var nodeInfoChanged = false
// If nodeInfo changed
@ -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 {
err = c.addNewUser(newUserInfo, newNodeInfo)
if err != nil {
@ -244,7 +253,9 @@ func (c *Controller) nodeInfoMonitor() (err error) {
return nil
}
} else {
deleted, added := compareUserList(c.userList, newUserInfo)
var deleted, added []api.UserInfo
if usersChanged {
deleted, added = compareUserList(c.userList, newUserInfo)
if len(deleted) > 0 {
deletedEmail := make([]string, len(deleted))
for i, u := range deleted {
@ -265,7 +276,8 @@ func (c *Controller) nodeInfoMonitor() (err error) {
log.Print(err)
}
}
log.Printf("[%s: %d] %d user deleted, %d user added", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(deleted), len(added))
}
log.Printf("%s %d user deleted, %d user added", c.logPrefix(), len(deleted), len(added))
}
c.userList = newUserInfo
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) {
users := make([]*protocol.User, 0)
if nodeInfo.NodeType == "V2ray" {
switch nodeInfo.NodeType {
case "V2ray":
if nodeInfo.EnableVless {
users = c.buildVlessUser(userInfo)
} else {
@ -378,20 +391,21 @@ func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo
}
users = c.buildVmessUser(userInfo, alterID)
}
} else if nodeInfo.NodeType == "Trojan" {
case "Trojan":
users = c.buildTrojanUser(userInfo)
} else if nodeInfo.NodeType == "Shadowsocks" {
case "Shadowsocks":
users = c.buildSSUser(userInfo, nodeInfo.CypherMethod)
} else if nodeInfo.NodeType == "Shadowsocks-Plugin" {
case "Shadowsocks-Plugin":
users = c.buildSSPluginUser(userInfo)
} else {
default:
return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType)
}
err = c.addUsers(users, c.Tag)
if err != nil {
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
}
@ -467,7 +481,7 @@ func (c *Controller) userInfoMonitor() (err error) {
}
// Unlock users
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)
for user, limitInfo := range c.limitedUsers {
if time.Now().Unix() > limitInfo.end {
@ -555,7 +569,7 @@ func (c *Controller) userInfoMonitor() (err error) {
if err = c.apiClient.ReportNodeOnlineUsers(onlineDevice); err != nil {
log.Print(err)
} 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
@ -565,7 +579,7 @@ func (c *Controller) userInfoMonitor() (err error) {
if err = c.apiClient.ReportIllegal(detectResult); err != nil {
log.Print(err)
} 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 {
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
}

View File

@ -2,11 +2,15 @@
package controller
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"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/uuid"
"github.com/xtls/xray-core/core"
"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
)
var proxySetting interface{}
var proxySetting any
// Build Protocol and Protocol setting
if nodeInfo.NodeType == "V2ray" {
switch nodeInfo.NodeType {
case "V2ray":
if nodeInfo.EnableVless {
protocol = "vless"
// Enable fallback
@ -74,7 +79,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
protocol = "vmess"
proxySetting = &conf.VMessInboundConfig{}
}
} else if nodeInfo.NodeType == "Trojan" {
case "Trojan":
protocol = "trojan"
// Enable fallback
if config.EnableFallback {
@ -89,23 +94,30 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
} else {
proxySetting = &conf.TrojanServerConfig{}
}
} else if nodeInfo.NodeType == "Shadowsocks" || nodeInfo.NodeType == "Shadowsocks-Plugin" {
case "Shadowsocks", "Shadowsocks-Plugin":
protocol = "shadowsocks"
proxySetting = &conf.ShadowsocksServerConfig{}
randomPasswd := uuid.New()
defaultSSuser := &conf.ShadowsocksUserConfig{
Cipher: "aes-128-gcm",
Password: randomPasswd.String(),
cipher := strings.ToLower(nodeInfo.CypherMethod)
proxySetting = &conf.ShadowsocksServerConfig{
Cipher: cipher,
Password: nodeInfo.ServerKey, // shadowsocks2022 shareKey
}
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.IVCheck = true
if config.DisableIVCheck {
proxySetting.IVCheck = false
}
} else if nodeInfo.NodeType == "dokodemo-door" {
case "dokodemo-door":
protocol = "dokodemo-door"
proxySetting = struct {
Host string `json:"address"`
@ -114,7 +126,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
Host: "v1.mux.cool",
NetworkList: []string{"tcp", "udp"},
}
} else {
default:
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 {
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
}
inboundDetourConfig.Protocol = protocol
inboundDetourConfig.Settings = &setting
// Build streamSettings
streamSetting = new(conf.StreamConfig)
@ -130,13 +144,15 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
if err != nil {
return nil, fmt.Errorf("convert TransportProtocol failed: %s", err)
}
if networkType == "tcp" {
switch networkType {
case "tcp":
tcpSetting := &conf.TCPConfig{
AcceptProxyProtocol: config.EnableProxyProtocol,
HeaderConfig: nodeInfo.Header,
}
streamSetting.TCPSettings = tcpSetting
} else if networkType == "websocket" {
case "websocket":
headers := make(map[string]string)
headers["Host"] = nodeInfo.Host
wsSettings := &conf.WebSocketConfig{
@ -145,14 +161,14 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
Headers: headers,
}
streamSetting.WSSettings = wsSettings
} else if networkType == "http" {
case "http":
hosts := conf.StringList{nodeInfo.Host}
httpSettings := &conf.HTTPConfig{
Host: &hosts,
Path: nodeInfo.Path,
}
streamSetting.HTTPSettings = httpSettings
} else if networkType == "grpc" {
case "grpc":
grpcSettings := &conf.GRPCConfig{
ServiceName: nodeInfo.ServiceName,
}
@ -160,6 +176,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
}
streamSetting.Network = &transportProtocol
// Build TLS and XTLS settings
if nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
streamSetting.Security = nodeInfo.TLSType
@ -182,6 +199,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
streamSetting.XTLSSettings = xtlsSettings
}
}
// Support ProxyProtocol for any transport protocol
if networkType != "tcp" && networkType != "ws" && config.EnableProxyProtocol {
sockoptConfig := &conf.SocketConfig{
@ -189,9 +207,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
}
streamSetting.SocketSettings = sockoptConfig
}
inboundDetourConfig.Protocol = protocol
inboundDetourConfig.StreamSetting = streamSetting
inboundDetourConfig.Settings = &setting
return inboundDetourConfig.Build()
}

View File

@ -4,6 +4,8 @@ import (
"fmt"
"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/serial"
"github.com/xtls/xray-core/infra/conf"
@ -14,7 +16,12 @@ import (
"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) {
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) {
users = make([]*protocol.User, 0)
users = make([]*protocol.User, len(*userInfo))
cypherMethod := cipherFromString(method)
for _, user := range *userInfo {
ssAccount := &shadowsocks.Account{
Password: user.Passwd,
CipherType: cypherMethod,
}
users = append(users, &protocol.User{
for i, user := range *userInfo {
// todo waiting xray-core complete proxy.UserManager in shadowsocks2022
if C.Contains(shadowaead_2022.List, strings.ToLower(method)) {
users[i] = &protocol.User{
Level: 0,
Email: c.buildUserTag(&user),
Account: serial.ToTypedMessage(ssAccount),
})
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),
}),
}
}
}
return users
}
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 {
// Check if the cypher method is AEAD
cypherMethod := cipherFromString(user.Method)
for _, aeadMethod := range AEADMethod {
if aeadMethod == cypherMethod {
ssAccount := &shadowsocks.Account{
Password: user.Passwd,
CipherType: cypherMethod,
}
users = append(users, &protocol.User{
for i, user := range *userInfo {
// todo waiting xray-core complete proxy.UserManager in shadowsocks2022
if C.Contains(shadowaead_2022.List, strings.ToLower(user.Method)) {
users[i] = &protocol.User{
Level: 0,
Email: c.buildUserTag(&user),
Account: serial.ToTypedMessage(ssAccount),
})
Account: serial.ToTypedMessage(&shadowsocks.Account{
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,
}),
}
}
}
}
return users
}