update to v1.1.0

change to uniproxy api
refactor build inbound
refactor limiter and rule
add ss2022 support
add speedlimit support
and more...
This commit is contained in:
Yuzuki999 2022-12-18 23:31:06 +08:00
parent 0ac7ea691d
commit 695da4f4c5
23 changed files with 539 additions and 652 deletions

View File

@ -12,7 +12,7 @@ A V2board node server based on Xray-core, modified from XrayR
如对脚本不放心可使用此沙箱先测一遍再使用https://killercoda.com/playgrounds/scenario/ubuntu
目前可以结合 [IpRecorder](https://github.com/Yuzuki616/IpRecorder) 实现跨节点IP数限制和每日IP连接地区数超限提醒请参考 [配置文件说明](https://yuzuki-1.gitbook.io/v2bx-doc/v2bx-pei-zhi-wen-jian-shuo-ming/config#wai-bu-ji-lu-qi-pei-zhi) 配置IpRecorder。
**注意1.1.0将更换为V2board1.7.0之后新增的Api原Api将被移除请1.7.0之前的用户使用1.1.0之前的版本。**
## 免责声明
@ -32,26 +32,26 @@ A V2board node server based on Xray-core, modified from XrayR
## 功能介绍
| 功能 | v2ray | trojan | shadowsocks |
| --------------- | ----- | ------ | ----------- |
| 功能 | v2ray | trojan | shadowsocks |
|-----------|-------|--------|-------------|
| 获取节点信息 | √ | √ | √ |
| 获取用户信息 | √ | √ | √ |
| 用户流量统计 | √ | √ | √ |
| 自动申请tls证书 | √ | √ | √ |
| 自动续签tls证书 | √ | √ | √ |
| 在线人数统计 | √ | √ | √ |
| 在线IP数限制 | √ | √ | √ |
| 跨节点IP数限制 | √ | √ | √ |
| 审计规则 | √ | √ | √ |
| 在线IP数限制 | √ | √ | √ |
| 跨节点IP数限制 | √ | √ | √ |
| 审计规则 | √ | √ | √ |
| 按照用户限速 | √ | √ | √ |
| 自定义DNS | √ | √ | √ |
| 动态限速(未测试) | √ | √ | √
| 自定义DNS | √ | √ | √ |
| 动态限速(未测试) | √ | √ | √ |
## 支持前端
| 前端 | v2ray | trojan | shadowsocks |
| ------------------------------------------------------ | ----- | ------ | ------------------------------ |
| v2board | √ | √ | √ |
| 前端 | v2ray | trojan | shadowsocks |
|---------|-------|--------|-------------|
| v2board | √ | √ | √ |
## TODO
@ -88,7 +88,6 @@ wget -N https://raw.githubusercontents.com/Yuzuki616/V2bX-script/master/install.
## Telgram
## Stars 增长记录
[![Stargazers over time](https://starchart.cc/Yuzuki616/V2bX.svg)](https://starchart.cc/Yuzuki616/V2bX)

View File

@ -5,6 +5,5 @@ type Panel interface {
GetUserList() (userList []UserInfo, err error)
ReportUserTraffic(userTraffic []UserTraffic) (err error)
Describe() ClientInfo
GetNodeRule() (ruleList *DetectRule, err error)
Debug()
}

View File

@ -1,266 +1,82 @@
package panel
import (
"bufio"
"bytes"
md52 "crypto/md5"
"fmt"
"github.com/go-resty/resty/v2"
"github.com/goccy/go-json"
"github.com/xtls/xray-core/infra/conf"
"log"
"os"
"regexp"
"strconv"
)
type DetectRule struct {
ProtocolRule []string
DestinationRule []DestinationRule
type NodeInfo struct {
Host string `json:"host"`
ServerPort int `json:"server_port"`
ServerName string `json:"server_name"`
Network string `json:"network"`
NetworkSettings json.RawMessage `json:"networkSettings"`
Cipher string `json:"cipher"`
ServerKey string `json:"server_key"`
Tls int `json:"tls"`
Routes []Route `json:"routes"`
BaseConfig *BaseConfig `json:"base_config"`
Rules []DestinationRule `json:"-"`
localNodeConfig `json:"-"`
}
type Route struct {
Id int `json:"id"`
Match string `json:"match"`
Action string `json:"action"`
//ActionValue interface{} `json:"action_value"`
}
type BaseConfig struct {
PushInterval any `json:"push_interval"`
PullInterval any `json:"pull_interval"`
}
type DestinationRule struct {
ID int
Pattern *regexp.Regexp
}
// readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList *DetectRule) {
LocalRuleList = &DetectRule{}
if path != "" {
// open the file
file, err := os.Open(path)
//handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return
}
fileScanner := bufio.NewScanner(file)
// read line by line
for fileScanner.Scan() {
LocalRuleList.DestinationRule = append(LocalRuleList.DestinationRule, DestinationRule{
ID: -1,
Pattern: regexp.MustCompile(fileScanner.Text()),
})
}
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return
}
}
return
}
type NodeInfo struct {
DeviceLimit int
SpeedLimit uint64
NodeType string
type localNodeConfig struct {
NodeId int
NodeType string
TLSType string
EnableVless bool
EnableTls bool
//EnableSS2022 bool
V2ray *V2rayConfig
Trojan *TrojanConfig
SS *SSConfig
SpeedLimit int
DeviceLimit int
}
type SSConfig struct {
Port int `json:"port"`
TransportProtocol string `json:"transportProtocol"`
CypherMethod string `json:"cypher"`
}
type V2rayConfig struct {
Inbounds []conf.InboundDetourConfig `json:"inbounds"`
Routing *struct {
Rules json.RawMessage `json:"rules"`
} `json:"routing"`
}
type Rule struct {
Type string `json:"type"`
InboundTag string `json:"inboundTag,omitempty"`
OutboundTag string `json:"outboundTag"`
Domain []string `json:"domain,omitempty"`
Protocol []string `json:"protocol,omitempty"`
}
type TrojanConfig struct {
LocalPort int `json:"local_port"`
Password []interface{} `json:"password"`
TransportProtocol string
Ssl struct {
Sni string `json:"sni"`
} `json:"ssl"`
}
// GetNodeInfo will pull NodeInfo Config from v2board
func (c *Client) GetNodeInfo() (nodeInfo *NodeInfo, err error) {
var path string
var res *resty.Response
switch c.NodeType {
case "V2ray":
path = "/api/v1/server/Deepbwork/config"
case "Trojan":
path = "/api/v1/server/TrojanTidalab/config"
case "Shadowsocks":
if nodeInfo, err = c.ParseSSNodeResponse(); err == nil {
return nodeInfo, nil
} else {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
const path = "/api/v1/server/UniProxy/config"
r, err := c.client.R().Get(path)
if err = c.checkResponse(r, path, err); err != nil {
return
}
res, err = c.client.R().
SetQueryParam("local_port", "1").
ForceContentType("application/json").
Get(path)
err = c.checkResponse(res, path, err)
err = json.Unmarshal(r.Body(), &nodeInfo)
if err != nil {
return nil, err
return
}
c.access.Lock()
defer c.access.Unlock()
switch c.NodeType {
case "V2ray":
i := bytes.Index(res.Body(), []byte("outbo"))
md := md52.Sum(res.Body()[:i])
nodeNotIsChange := true
if c.NodeInfoRspMd5 == [16]byte{} {
nodeNotIsChange = false
c.NodeInfoRspMd5 = md
} else {
if c.NodeInfoRspMd5 != md {
nodeNotIsChange = false
c.NodeInfoRspMd5 = md
}
}
md2 := md52.Sum(res.Body()[i:])
ruleIsChange := false
if c.NodeRuleRspMd5 != md2 {
ruleIsChange = true
c.NodeRuleRspMd5 = md2
}
nodeInfo, err = c.ParseV2rayNodeResponse(res.Body(), nodeNotIsChange, ruleIsChange)
case "Trojan":
md := md52.Sum(res.Body())
if c.NodeInfoRspMd5 != [16]byte{} {
if c.NodeInfoRspMd5 == md {
return nil, nil
}
}
c.NodeInfoRspMd5 = md
nodeInfo, err = c.ParseTrojanNodeResponse(res.Body())
}
return nodeInfo, nil
}
func (c *Client) GetNodeRule() (*DetectRule, error) {
ruleList := c.LocalRuleList
if c.NodeType != "V2ray" || c.RemoteRuleCache == nil {
if c.etag == r.Header().Get("ETag") { // node info not changed
return nil, nil
}
// V2board only support the rule for v2ray
// fix: reuse config response
c.access.Lock()
defer c.access.Unlock()
if len(c.RemoteRuleCache) >= 2 {
for i, rule := range (c.RemoteRuleCache)[1].Domain {
ruleListItem := DestinationRule{
ID: i,
Pattern: regexp.MustCompile(rule),
}
ruleList.DestinationRule = append(ruleList.DestinationRule, ruleListItem)
nodeInfo.NodeId = c.NodeId
nodeInfo.NodeType = c.NodeType
for i := range nodeInfo.Routes { // parse rules from routes
r := &nodeInfo.Routes[i]
if r.Action == "block" {
nodeInfo.Rules = append(nodeInfo.Rules, DestinationRule{
ID: r.Id,
Pattern: regexp.MustCompile(r.Match),
})
}
}
if len(c.RemoteRuleCache) >= 3 {
for _, str := range (c.RemoteRuleCache)[2].Protocol {
ruleList.ProtocolRule = append(ruleList.ProtocolRule, str)
}
nodeInfo.Routes = nil
if _, ok := nodeInfo.BaseConfig.PullInterval.(int); !ok {
i, _ := strconv.Atoi(nodeInfo.BaseConfig.PullInterval.(string))
nodeInfo.BaseConfig.PullInterval = i
}
c.RemoteRuleCache = nil
return ruleList, nil
}
// ParseTrojanNodeResponse parse the response for the given node info format
func (c *Client) ParseTrojanNodeResponse(body []byte) (*NodeInfo, error) {
node := &NodeInfo{Trojan: &TrojanConfig{}}
var err = json.Unmarshal(body, node.Trojan)
if err != nil {
return nil, fmt.Errorf("unmarshal nodeinfo error: %s", err)
}
node.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
node.DeviceLimit = c.DeviceLimit
node.NodeId = c.NodeID
node.NodeType = c.NodeType
node.Trojan.TransportProtocol = "tcp"
return node, nil
}
// ParseSSNodeResponse parse the response for the given node info format
func (c *Client) ParseSSNodeResponse() (*NodeInfo, error) {
var port int
var method string
userInfo, err := c.GetUserList()
if err != nil {
return nil, err
}
if len(userInfo) > 0 {
port = userInfo[0].Port
method = userInfo[0].Cipher
} else {
return nil, fmt.Errorf("shadowsocks node need a active user")
}
if err != nil {
return nil, err
}
node := &NodeInfo{
SpeedLimit: uint64(c.SpeedLimit * 1000000 / 8),
DeviceLimit: c.DeviceLimit,
//EnableSS2022: c.EnableSS2022,
NodeType: c.NodeType,
NodeId: c.NodeID,
SS: &SSConfig{
Port: port,
TransportProtocol: "tcp",
CypherMethod: method,
},
}
return node, nil
}
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
func (c *Client) ParseV2rayNodeResponse(body []byte, notParseNode, parseRule bool) (*NodeInfo, error) {
if notParseNode && !parseRule {
return nil, nil
}
node := &NodeInfo{V2ray: &V2rayConfig{}}
err := json.Unmarshal(body, node.V2ray)
if err != nil {
return nil, fmt.Errorf("unmarshal nodeinfo error: %s", err)
}
if parseRule {
c.RemoteRuleCache = []Rule{}
err := json.Unmarshal(node.V2ray.Routing.Rules, &c.RemoteRuleCache)
if err != nil {
log.Println(err)
}
if notParseNode {
return nil, nil
}
}
node.V2ray.Routing = nil
node.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
node.DeviceLimit = c.DeviceLimit
node.NodeType = c.NodeType
node.NodeId = c.NodeID
if c.EnableXTLS {
node.TLSType = "xtls"
} else {
node.TLSType = "tls"
}
node.EnableVless = c.EnableVless
node.EnableTls = node.V2ray.Inbounds[0].StreamSetting.Security == "tls"
return node, nil
if _, ok := nodeInfo.BaseConfig.PushInterval.(int); !ok {
i, _ := strconv.Atoi(nodeInfo.BaseConfig.PushInterval.(string))
nodeInfo.BaseConfig.PushInterval = i
}
c.etag = r.Header().Get("Etag")
return
}

20
api/panel/node_test.go Normal file
View File

@ -0,0 +1,20 @@
package panel
import (
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
"testing"
)
func TestClient_GetNodeInfo(t *testing.T) {
c, err := New(&conf.ApiConfig{
APIHost: "http://127.0.0.1",
Key: "token",
NodeType: "V2ray",
NodeID: 1,
})
if err != nil {
log.Print(err)
}
log.Println(c.GetNodeInfo())
}

View File

@ -1,11 +1,15 @@
package panel
import (
"bufio"
"fmt"
"github.com/Yuzuki616/V2bX/conf"
"github.com/go-resty/resty/v2"
"log"
"os"
"regexp"
"strconv"
"sync"
"strings"
"time"
)
@ -19,28 +23,22 @@ type ClientInfo struct {
}
type Client struct {
client *resty.Client
APIHost string
NodeID int
Key string
NodeType string
//EnableSS2022 bool
EnableVless bool
EnableXTLS bool
SpeedLimit float64
DeviceLimit int
LocalRuleList *DetectRule
RemoteRuleCache []Rule
access sync.Mutex
NodeInfoRspMd5 [16]byte
NodeRuleRspMd5 [16]byte
client *resty.Client
APIHost string
Key string
NodeType string
NodeId int
SpeedLimit int
DeviceLimit int
LocalRuleList []DestinationRule
etag string
}
func New(apiConfig *conf.ApiConfig) Panel {
func New(c *conf.ApiConfig) (Panel, error) {
client := resty.New()
client.SetRetryCount(3)
if apiConfig.Timeout > 0 {
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
if c.Timeout > 0 {
client.SetTimeout(time.Duration(c.Timeout) * time.Second)
} else {
client.SetTimeout(5 * time.Second)
}
@ -51,25 +49,57 @@ func New(apiConfig *conf.ApiConfig) Panel {
log.Print(v.Err)
}
})
client.SetBaseURL(apiConfig.APIHost)
client.SetBaseURL(c.APIHost)
// Check node type
if c.NodeType != "V2ray" &&
c.NodeType != "Trojan" &&
c.NodeType != "Shadowsocks" {
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
// Create Key for each requests
client.SetQueryParams(map[string]string{
"node_id": strconv.Itoa(apiConfig.NodeID),
"token": apiConfig.Key,
"node_type": strings.ToLower(c.NodeType),
"node_id": strconv.Itoa(c.NodeID),
"token": c.Key,
})
// Read local rule list
localRuleList := readLocalRuleList(apiConfig.RuleListPath)
localRuleList := readLocalRuleList(c.RuleListPath)
return &Client{
client: client,
NodeID: apiConfig.NodeID,
Key: apiConfig.Key,
APIHost: apiConfig.APIHost,
NodeType: apiConfig.NodeType,
//EnableSS2022: apiConfig.EnableSS2022,
EnableVless: apiConfig.EnableVless,
EnableXTLS: apiConfig.EnableXTLS,
SpeedLimit: apiConfig.SpeedLimit,
DeviceLimit: apiConfig.DeviceLimit,
client: client,
Key: c.Key,
APIHost: c.APIHost,
NodeType: c.NodeType,
SpeedLimit: c.SpeedLimit,
DeviceLimit: c.DeviceLimit,
NodeId: c.NodeID,
LocalRuleList: localRuleList,
}
}, nil
}
// readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList []DestinationRule) {
LocalRuleList = make([]DestinationRule, 0)
if path != "" {
// open the file
file, err := os.Open(path)
//handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return
}
fileScanner := bufio.NewScanner(file)
// read line by line
for fileScanner.Scan() {
LocalRuleList = append(LocalRuleList, DestinationRule{
ID: -1,
Pattern: regexp.MustCompile(fileScanner.Text()),
})
}
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return
}
}
return
}

View File

@ -3,7 +3,6 @@ package panel
import (
"fmt"
"github.com/goccy/go-json"
"strconv"
)
type OnlineUser struct {
@ -20,46 +19,22 @@ type TrojanUserInfo struct {
Password string `json:"password"`
}
type UserInfo struct {
/*DeviceLimit int `json:"device_limit"`
SpeedLimit uint64 `json:"speed_limit"`*/
UID int `json:"id"`
Traffic int64 `json:"-"`
Port int `json:"port"`
Cipher string `json:"cipher"`
Secret string `json:"secret"`
V2rayUser *V2RayUserInfo `json:"v2ray_user"`
TrojanUser *TrojanUserInfo `json:"trojan_user"`
}
func (p *UserInfo) GetUserEmail() string {
if p.V2rayUser != nil {
return p.V2rayUser.Email
} else if p.TrojanUser != nil {
return p.TrojanUser.Password
}
return p.Secret
Id int `json:"id"`
Uuid string `json:"uuid"`
Email string `json:"-"`
SpeedLimit int `json:"speed_limit"`
Traffic int64 `json:"-"`
}
type UserListBody struct {
//Msg string `json:"msg"`
Data []UserInfo `json:"data"`
Users []UserInfo `json:"users"`
}
// GetUserList will pull user form sspanel
func (c *Client) GetUserList() (UserList []UserInfo, err error) {
var path string
switch c.NodeType {
case "V2ray":
path = "/api/v1/server/Deepbwork/user"
case "Trojan":
path = "/api/v1/server/TrojanTidalab/user"
case "Shadowsocks":
path = "/api/v1/server/ShadowsocksTidalab/user"
default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
const path = "/api/v1/server/UniProxy/user"
res, err := c.client.R().
ForceContentType("application/json").
Get(path)
err = c.checkResponse(res, path, err)
if err != nil {
@ -70,38 +45,24 @@ func (c *Client) GetUserList() (UserList []UserInfo, err error) {
if err != nil {
return nil, fmt.Errorf("unmarshal userlist error: %s", err)
}
return userList.Data, nil
return userList.Users, nil
}
type UserTraffic struct {
UID int `json:"user_id"`
Upload int64 `json:"u"`
Download int64 `json:"d"`
UID int
Upload int64
Download int64
}
// ReportUserTraffic reports the user traffic
func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error {
var path string
switch c.NodeType {
case "V2ray":
path = "/api/v1/server/Deepbwork/submit"
case "Trojan":
path = "/api/v1/server/TrojanTidalab/submit"
case "Shadowsocks":
path = "/api/v1/server/ShadowsocksTidalab/submit"
data := make(map[int][]int64, len(userTraffic))
for i := range userTraffic {
data[userTraffic[i].UID] = []int64{userTraffic[i].Upload, userTraffic[i].Download}
}
data := make([]UserTraffic, len(userTraffic))
for i, traffic := range userTraffic {
data[i] = UserTraffic{
UID: traffic.UID,
Upload: traffic.Upload,
Download: traffic.Download}
}
const path = "/api/v1/server/UniProxy/user"
res, err := c.client.R().
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
SetBody(data).
SetBody(userTraffic).
ForceContentType("application/json").
Post(path)
err = c.checkResponse(res, path, err)

View File

@ -3,11 +3,12 @@ package panel
import (
"fmt"
"github.com/go-resty/resty/v2"
path2 "path"
)
// Describe return a description of the client
func (c *Client) Describe() ClientInfo {
return ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType}
return ClientInfo{APIHost: c.APIHost, NodeID: c.NodeId, Key: c.Key, NodeType: c.NodeType}
}
// Debug set the client debug for client
@ -16,13 +17,12 @@ func (c *Client) Debug() {
}
func (c *Client) assembleURL(path string) string {
return c.APIHost + path
return path2.Join(c.APIHost + path)
}
func (c *Client) checkResponse(res *resty.Response, path string, err error) error {
if err != nil {
return fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
}
if res.StatusCode() > 400 {
body := res.Body()
return fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err)

View File

@ -41,16 +41,15 @@ type IpReportConfig struct {
}
type DynamicSpeedLimitConfig struct {
Periodic int `yaml:"Periodic"`
Traffic int64 `yaml:"Traffic"`
SpeedLimit uint64 `yaml:"SpeedLimit"`
ExpireTime int `yaml:"ExpireTime"`
Periodic int `yaml:"Periodic"`
Traffic int64 `yaml:"Traffic"`
SpeedLimit int `yaml:"SpeedLimit"`
ExpireTime int `yaml:"ExpireTime"`
}
type ControllerConfig struct {
ListenIP string `yaml:"ListenIP"`
SendIP string `yaml:"SendIP"`
UpdatePeriodic int `yaml:"UpdatePeriodic"`
EnableDNS bool `yaml:"EnableDNS"`
DNSType string `yaml:"DNSType"`
DisableUploadTraffic bool `yaml:"DisableUploadTraffic"`
@ -68,18 +67,17 @@ type ControllerConfig struct {
}
type ApiConfig struct {
APIHost string `yaml:"ApiHost"`
NodeID int `yaml:"NodeID"`
Key string `yaml:"ApiKey"`
NodeType string `yaml:"NodeType"`
EnableVless bool `yaml:"EnableVless"`
EnableXTLS bool `yaml:"EnableXTLS"`
//EnableSS2022 bool `yaml:"EnableSS2022"`
Timeout int `yaml:"Timeout"`
SpeedLimit float64 `yaml:"SpeedLimit"`
DeviceLimit int `yaml:"DeviceLimit"`
RuleListPath string `yaml:"RuleListPath"`
DisableCustomConfig bool `yaml:"DisableCustomConfig"`
APIHost string `yaml:"ApiHost"`
NodeID int `yaml:"NodeID"`
Key string `yaml:"ApiKey"`
NodeType string `yaml:"NodeType"`
EnableVless bool `yaml:"EnableVless"`
EnableXTLS bool `yaml:"EnableXTLS"`
Timeout int `yaml:"Timeout"`
SpeedLimit int `yaml:"SpeedLimit"`
DeviceLimit int `yaml:"DeviceLimit"`
RuleListPath string `yaml:"RuleListPath"`
DisableCustomConfig bool `yaml:"DisableCustomConfig"`
}
type NodeConfig struct {

View File

@ -12,15 +12,15 @@ import (
)
type UserLimitInfo struct {
UID int
SpeedLimit uint64
ExpireTime int64
//DeviceLimit int
UID int
SpeedLimit int
DynamicSpeedLimit int
ExpireTime int64
}
type InboundInfo struct {
Tag string
NodeSpeedLimit uint64
NodeSpeedLimit int
NodeDeviceLimit int
UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo
SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket
@ -37,27 +37,47 @@ func NewLimiter() *Limiter {
}
}
func (l *Limiter) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo) error {
func (l *Limiter) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo, users []panel.UserInfo) error {
inboundInfo := &InboundInfo{
Tag: tag,
NodeSpeedLimit: nodeInfo.SpeedLimit,
NodeDeviceLimit: nodeInfo.DeviceLimit,
UserLimitInfo: new(sync.Map),
SpeedLimiter: new(sync.Map),
UserOnlineIP: new(sync.Map),
}
for i := range users {
if users[i].SpeedLimit != 0 {
userLimit := &UserLimitInfo{
UID: users[i].Id,
SpeedLimit: users[i].SpeedLimit,
ExpireTime: 0,
}
inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, users[i].Uuid, users[i].Id), userLimit)
}
}
inboundInfo.UserLimitInfo = new(sync.Map)
l.InboundInfo.Store(tag, inboundInfo) // Replace the old inbound info
return nil
}
func (l *Limiter) UpdateInboundLimiter(tag string, deleted []panel.UserInfo) error {
func (l *Limiter) UpdateInboundLimiter(tag string, added []panel.UserInfo, deleted []panel.UserInfo) error {
if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo)
for i := range deleted {
inboundInfo.UserLimitInfo.Delete(fmt.Sprintf("%s|%s|%d", tag,
(deleted)[i].GetUserEmail(), (deleted)[i].UID))
inboundInfo.SpeedLimiter.Delete(fmt.Sprintf("%s|%s|%d", tag,
(deleted)[i].GetUserEmail(), (deleted)[i].UID)) // Delete limiter bucket
(deleted)[i].Uuid, (deleted)[i].Id))
}
for i := range added {
if added[i].SpeedLimit != 0 {
userLimit := &UserLimitInfo{
UID: added[i].Id,
SpeedLimit: added[i].SpeedLimit,
ExpireTime: 0,
}
inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag,
(added)[i].Uuid, (added)[i].Id), userLimit)
}
}
} else {
return fmt.Errorf("no such inbound in limiter: %s", tag)
@ -70,14 +90,14 @@ func (l *Limiter) DeleteInboundLimiter(tag string) error {
return nil
}
func (l *Limiter) AddUserSpeedLimit(tag string, userInfo *panel.UserInfo, limit uint64, expire int64) error {
func (l *Limiter) AddDynamicSpeedLimit(tag string, userInfo *panel.UserInfo, limit int, expire int64) error {
if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo)
userLimit := &UserLimitInfo{
SpeedLimit: limit,
ExpireTime: time.Now().Add(time.Duration(expire) * time.Second).Unix(),
DynamicSpeedLimit: limit,
ExpireTime: time.Now().Add(time.Duration(expire) * time.Second).Unix(),
}
inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, userInfo.GetUserEmail(), userInfo.UID), userLimit)
inboundInfo.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, userInfo.Uuid, userInfo.Id), userLimit)
return nil
} else {
return fmt.Errorf("no such inbound in limiter: %s", tag)
@ -167,15 +187,17 @@ func (l *Limiter) CheckSpeedAndDeviceLimit(tag string, email string, ip string)
if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo)
nodeLimit := inboundInfo.NodeSpeedLimit
var userLimit uint64 = 0
userLimit := 0
expired := false
if v, ok := inboundInfo.UserLimitInfo.Load(email); ok {
u := v.(*UserLimitInfo)
if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 {
userLimit = 0
if u.SpeedLimit != 0 {
userLimit = u.SpeedLimit
}
expired = true
} else {
userLimit = u.SpeedLimit
userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
}
}
ipMap := new(sync.Map)
@ -200,9 +222,9 @@ func (l *Limiter) CheckSpeedAndDeviceLimit(tag string, email string, ip string)
}
}
}
limit := determineSpeedLimit(nodeLimit, userLimit) // If you need the Speed limit
limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit
if limit > 0 {
limiter := ratelimit.NewBucketWithQuantum(time.Second, int64(limit), int64(limit)) // Byte/s
limiter := ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s
if v, ok := inboundInfo.SpeedLimiter.LoadOrStore(email, limiter); ok {
if expired {
inboundInfo.SpeedLimiter.Store(email, limiter)
@ -246,22 +268,22 @@ func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
}
// determineSpeedLimit returns the minimum non-zero rate
func determineSpeedLimit(nodeLimit, userLimit uint64) (limit uint64) {
if nodeLimit == 0 || userLimit == 0 {
if nodeLimit > userLimit {
return nodeLimit
} else if nodeLimit < userLimit {
return userLimit
func determineSpeedLimit(limit1, limit2 int) (limit int) {
if limit1 == 0 || limit2 == 0 {
if limit1 > limit2 {
return limit1
} else if limit1 < limit2 {
return limit2
} else {
return 0
}
} else {
if nodeLimit > userLimit {
return userLimit
} else if nodeLimit < userLimit {
return nodeLimit
if limit1 > limit2 {
return limit2
} else if limit1 < limit2 {
return limit1
} else {
return nodeLimit
return limit1
}
}
}

View File

@ -16,7 +16,7 @@ func NewRule() *Rule {
}
}
func (r *Rule) UpdateRule(tag string, newRuleList *panel.DetectRule) error {
func (r *Rule) UpdateRule(tag string, newRuleList []panel.DestinationRule) error {
if value, ok := r.Rule.LoadOrStore(tag, newRuleList); ok {
oldRuleList := value.([]panel.DestinationRule)
if !reflect.DeepEqual(oldRuleList, newRuleList) {
@ -30,20 +30,13 @@ func (r *Rule) Detect(tag string, destination string, protocol string) (reject b
reject = false
// If we have some rule for this inbound
if value, ok := r.Rule.Load(tag); ok {
ruleList := value.(*panel.DetectRule)
for i, _ := range ruleList.DestinationRule {
if ruleList.DestinationRule[i].Pattern.Match([]byte(destination)) {
ruleList := value.([]panel.DestinationRule)
for i := range ruleList {
if ruleList[i].Pattern.Match([]byte(destination)) {
reject = true
break
}
}
if !reject {
for _, v := range ruleList.ProtocolRule {
if v == protocol {
return true
}
}
}
}
return reject
}

View File

@ -1,10 +1,10 @@
package core
import (
"encoding/json"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/core/app/dispatcher"
_ "github.com/Yuzuki616/V2bX/core/distro/all"
"github.com/goccy/go-json"
"github.com/xtls/xray-core/app/proxyman"
"github.com/xtls/xray-core/app/stats"
"github.com/xtls/xray-core/common/serial"

View File

@ -28,8 +28,8 @@ func (p *Core) AddInbound(config *core.InboundHandlerConfig) error {
return nil
}
func (p *Core) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo) error {
return p.dispatcher.Limiter.AddInboundLimiter(tag, nodeInfo)
func (p *Core) AddInboundLimiter(tag string, nodeInfo *panel.NodeInfo, users []panel.UserInfo) error {
return p.dispatcher.Limiter.AddInboundLimiter(tag, nodeInfo, users)
}
func (p *Core) GetInboundLimiter(tag string) (*dispatcher.InboundInfo, error) {
@ -40,14 +40,14 @@ func (p *Core) GetInboundLimiter(tag string) (*dispatcher.InboundInfo, error) {
return nil, fmt.Errorf("not found limiter")
}
func (p *Core) UpdateInboundLimiter(tag string, deleted []panel.UserInfo) error {
return p.dispatcher.Limiter.UpdateInboundLimiter(tag, deleted)
func (p *Core) UpdateInboundLimiter(tag string, added []panel.UserInfo, deleted []panel.UserInfo) error {
return p.dispatcher.Limiter.UpdateInboundLimiter(tag, added, deleted)
}
func (p *Core) DeleteInboundLimiter(tag string) error {
return p.dispatcher.Limiter.DeleteInboundLimiter(tag)
}
func (p *Core) UpdateRule(tag string, newRuleList *panel.DetectRule) error {
func (p *Core) UpdateRule(tag string, newRuleList []panel.DestinationRule) error {
return p.dispatcher.RuleManager.UpdateRule(tag, newRuleList)
}

View File

@ -80,8 +80,8 @@ func (p *Core) GetUserTraffic(email string, reset bool) (up int64, down int64) {
return up, down
}
func (p *Core) AddUserSpeedLimit(tag string, user *panel.UserInfo, speedLimit uint64, expire int64) error {
return p.dispatcher.Limiter.AddUserSpeedLimit(tag, user, speedLimit, expire)
func (p *Core) AddUserSpeedLimit(tag string, user *panel.UserInfo, speedLimit int, expire int64) error {
return p.dispatcher.Limiter.AddDynamicSpeedLimit(tag, user, speedLimit, expire)
}
func (p *Core) ListOnlineIp(tag string) ([]dispatcher.UserIpList, error) {

View File

@ -28,7 +28,6 @@ Nodes:
ControllerConfig:
ListenIP: 0.0.0.0 # IP address you want to listen
SendIP: 0.0.0.0 # IP address you want to send pacakage
UpdatePeriodic: 60 # Time to update the nodeinfo, how many sec.
EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
EnableProxyProtocol: false # Only works for WebSocket and TCP
@ -83,7 +82,6 @@ Nodes:
# DeviceLimit: 0 # Local settings will replace remote settings
# ControllerConfig:
# ListenIP: 0.0.0.0 # IP address you want to listen
# UpdatePeriodic: 10 # Time to update the nodeinfo, how many sec.
# EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
# CertConfig:
# CertMode: dns # Option about how to get certificate: none, file, http, dns

View File

@ -1,108 +1,47 @@
package controller
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/node/controller/legoCmd"
"github.com/goccy/go-json"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/core"
coreConf "github.com/xtls/xray-core/infra/conf"
)
// buildInbound build Inbound config for different protocol
func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag string) (*core.InboundHandlerConfig, error) {
var proxySetting interface{}
if nodeInfo.NodeType == "V2ray" {
defer func() {
//Clear v2ray config
nodeInfo.V2ray = nil
}()
if nodeInfo.EnableVless {
//Set vless
nodeInfo.V2ray.Inbounds[0].Protocol = "vless"
if config.EnableFallback {
// Set fallback
fallbackConfigs, err := buildVlessFallbacks(config.FallBackConfigs)
if err == nil {
proxySetting = &coreConf.VLessInboundConfig{
Decryption: "none",
Fallbacks: fallbackConfigs,
}
} else {
return nil, err
}
} else {
proxySetting = &coreConf.VLessInboundConfig{
Decryption: "none",
}
}
} else {
// Set vmess
nodeInfo.V2ray.Inbounds[0].Protocol = "vmess"
proxySetting = &coreConf.VMessInboundConfig{}
}
} else if nodeInfo.NodeType == "Trojan" {
defer func() {
//clear trojan and v2ray config
nodeInfo.V2ray = nil
nodeInfo.Trojan = nil
}()
nodeInfo.V2ray = &panel.V2rayConfig{}
nodeInfo.V2ray.Inbounds = make([]coreConf.InboundDetourConfig, 1)
nodeInfo.V2ray.Inbounds[0].Protocol = "trojan"
if config.EnableFallback {
// Set fallback
fallbackConfigs, err := buildTrojanFallbacks(config.FallBackConfigs)
if err == nil {
proxySetting = &coreConf.TrojanServerConfig{
Fallbacks: fallbackConfigs,
}
} else {
return nil, err
}
} else {
proxySetting = &coreConf.TrojanServerConfig{}
}
nodeInfo.V2ray.Inbounds[0].PortList = &coreConf.PortList{
Range: []coreConf.PortRange{{From: uint32(nodeInfo.Trojan.LocalPort), To: uint32(nodeInfo.Trojan.LocalPort)}},
}
t := coreConf.TransportProtocol(nodeInfo.Trojan.TransportProtocol)
nodeInfo.V2ray.Inbounds[0].StreamSetting = &coreConf.StreamConfig{Network: &t}
} else if nodeInfo.NodeType == "Shadowsocks" {
defer func() {
//Clear v2ray config
nodeInfo.V2ray = nil
}()
nodeInfo.V2ray = &panel.V2rayConfig{}
nodeInfo.V2ray.Inbounds = []coreConf.InboundDetourConfig{{Protocol: "shadowsocks"}}
proxySetting = &coreConf.ShadowsocksServerConfig{}
randomPasswd := uuid.New()
defaultSSuser := &coreConf.ShadowsocksUserConfig{
Cipher: "aes-128-gcm",
Password: randomPasswd.String(),
}
proxySetting, _ := proxySetting.(*coreConf.ShadowsocksServerConfig)
proxySetting.Users = append(proxySetting.Users, defaultSSuser)
proxySetting.NetworkList = &coreConf.NetworkList{"tcp", "udp"}
proxySetting.IVCheck = true
if config.DisableIVCheck {
proxySetting.IVCheck = false
}
nodeInfo.V2ray.Inbounds[0].PortList = &coreConf.PortList{
Range: []coreConf.PortRange{{From: uint32(nodeInfo.SS.Port), To: uint32(nodeInfo.SS.Port)}},
}
t := coreConf.TransportProtocol(nodeInfo.SS.TransportProtocol)
nodeInfo.V2ray.Inbounds[0].StreamSetting = &coreConf.StreamConfig{Network: &t}
} else {
inbound := &coreConf.InboundDetourConfig{}
// Set network protocol
t := coreConf.TransportProtocol(nodeInfo.Network)
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
var err error
switch nodeInfo.NodeType {
case "V2ray":
err = buildV2ray(config, nodeInfo, inbound)
case "Trojan":
err = buildTrojan(config, nodeInfo, inbound)
case "Shadowsocks":
err = buildShadowsocks(config, nodeInfo, inbound)
default:
return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks", nodeInfo.NodeType)
}
// Build Listen IP address
if err != nil {
return nil, err
}
// Set server port
inbound.PortList = &coreConf.PortList{
Range: []coreConf.PortRange{{From: uint32(nodeInfo.ServerPort), To: uint32(nodeInfo.ServerPort)}},
}
// Set Listen IP address
ipAddress := net.ParseAddress(config.ListenIP)
nodeInfo.V2ray.Inbounds[0].ListenOn = &coreConf.Address{Address: ipAddress}
// SniffingConfig
inbound.ListenOn = &coreConf.Address{Address: ipAddress}
// Set SniffingConfig
sniffingConfig := &coreConf.SniffingConfig{
Enabled: true,
DestOverride: &coreConf.StringList{"http", "tls"},
@ -110,31 +49,23 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
if config.DisableSniffing {
sniffingConfig.Enabled = false
}
nodeInfo.V2ray.Inbounds[0].SniffingConfig = sniffingConfig
var setting json.RawMessage
// Build Protocol and Protocol setting
setting, err := json.Marshal(proxySetting)
if err != nil {
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
}
if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network == "tcp" {
if nodeInfo.V2ray.Inbounds[0].StreamSetting.TCPSettings != nil {
nodeInfo.V2ray.Inbounds[0].StreamSetting.TCPSettings.AcceptProxyProtocol = config.EnableProxyProtocol
inbound.SniffingConfig = sniffingConfig
if nodeInfo.NodeType == "tcp" {
if inbound.StreamSetting.TCPSettings != nil {
inbound.StreamSetting.TCPSettings.AcceptProxyProtocol = config.EnableProxyProtocol
} else {
tcpSetting := &coreConf.TCPConfig{
AcceptProxyProtocol: config.EnableProxyProtocol,
} //Enable proxy protocol
nodeInfo.V2ray.Inbounds[0].StreamSetting.TCPSettings = tcpSetting
inbound.StreamSetting.TCPSettings = tcpSetting
}
} else if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network == "ws" {
nodeInfo.V2ray.Inbounds[0].StreamSetting.WSSettings = &coreConf.WebSocketConfig{
} else if nodeInfo.NodeType == "ws" {
inbound.StreamSetting.WSSettings = &coreConf.WebSocketConfig{
AcceptProxyProtocol: config.EnableProxyProtocol} //Enable proxy protocol
}
// Build TLS and XTLS settings
// Set TLS and XTLS settings
if nodeInfo.EnableTls && config.CertConfig.CertMode != "none" {
nodeInfo.V2ray.Inbounds[0].StreamSetting.Security = nodeInfo.TLSType
inbound.StreamSetting.Security = nodeInfo.TLSType
certFile, keyFile, err := getCertFile(config.CertConfig)
if err != nil {
return nil, err
@ -144,7 +75,7 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
}
tlsSettings.Certs = append(tlsSettings.Certs, &coreConf.TLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600})
nodeInfo.V2ray.Inbounds[0].StreamSetting.TLSSettings = tlsSettings
inbound.StreamSetting.TLSSettings = tlsSettings
} else if nodeInfo.TLSType == "xtls" {
xtlsSettings := &coreConf.XTLSConfig{
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
@ -153,23 +84,139 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s
CertFile: certFile,
KeyFile: keyFile,
OcspStapling: 3600})
nodeInfo.V2ray.Inbounds[0].StreamSetting.XTLSSettings = xtlsSettings
inbound.StreamSetting.XTLSSettings = xtlsSettings
}
} else if nodeInfo.NodeType == "V2ray" {
nodeInfo.V2ray.Inbounds[0].StreamSetting.Security = "none"
}
// Support ProxyProtocol for any transport protocol
if *nodeInfo.V2ray.Inbounds[0].StreamSetting.Network != "tcp" &&
*nodeInfo.V2ray.Inbounds[0].StreamSetting.Network != "ws" &&
if *inbound.StreamSetting.Network != "tcp" &&
*inbound.StreamSetting.Network != "ws" &&
config.EnableProxyProtocol {
sockoptConfig := &coreConf.SocketConfig{
AcceptProxyProtocol: config.EnableProxyProtocol,
} //Enable proxy protocol
nodeInfo.V2ray.Inbounds[0].StreamSetting.SocketSettings = sockoptConfig
inbound.StreamSetting.SocketSettings = sockoptConfig
}
nodeInfo.V2ray.Inbounds[0].Settings = &setting
nodeInfo.V2ray.Inbounds[0].Tag = tag
return nodeInfo.V2ray.Inbounds[0].Build()
inbound.Tag = tag
return inbound.Build()
}
func buildV2ray(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
if nodeInfo.EnableVless {
//Set vless
inbound.Protocol = "vless"
if config.EnableFallback {
// Set fallback
fallbackConfigs, err := buildVlessFallbacks(config.FallBackConfigs)
if err != nil {
return err
}
s, err := json.Marshal(&coreConf.VLessInboundConfig{
Decryption: "none",
Fallbacks: fallbackConfigs,
})
if err != nil {
return fmt.Errorf("marshal vless fallback config error: %s", err)
}
inbound.Settings = (*json.RawMessage)(&s)
} else {
var err error
s, err := json.Marshal(&coreConf.VLessInboundConfig{
Decryption: "none",
})
if err != nil {
return fmt.Errorf("marshal vless config error: %s", err)
}
inbound.Settings = (*json.RawMessage)(&s)
}
} else {
// Set vmess
inbound.Protocol = "vmess"
var err error
s, err := json.Marshal(&coreConf.VMessInboundConfig{})
if err != nil {
return fmt.Errorf("marshal vmess settings error: %s", err)
}
inbound.Settings = (*json.RawMessage)(&s)
}
switch nodeInfo.Network {
case "tcp":
err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.TCPSettings)
if err != nil {
return fmt.Errorf("unmarshal tcp settings error: %s", err)
}
case "ws":
err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.WSSettings)
if err != nil {
return fmt.Errorf("unmarshal ws settings error: %s", err)
}
case "grpc":
err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.GRPCConfig)
if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err)
}
}
return nil
}
func buildTrojan(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
inbound.Protocol = "trojan"
if config.EnableFallback {
// Set fallback
fallbackConfigs, err := buildTrojanFallbacks(config.FallBackConfigs)
if err != nil {
return err
}
s, err := json.Marshal(&coreConf.TrojanServerConfig{
Fallbacks: fallbackConfigs,
})
inbound.Settings = (*json.RawMessage)(&s)
if err != nil {
return fmt.Errorf("marshal trojan fallback config error: %s", err)
}
} else {
s := []byte("{}")
inbound.Settings = (*json.RawMessage)(&s)
}
t := coreConf.TransportProtocol(nodeInfo.Network)
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
return nil
}
func buildShadowsocks(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
inbound.Protocol = "shadowsocks"
settings := &coreConf.ShadowsocksServerConfig{
Cipher: nodeInfo.Cipher,
}
p := make([]byte, 32)
_, err := rand.Read(p)
if err != nil {
return fmt.Errorf("generate random password error: %s", err)
}
randomPasswd := hex.EncodeToString(p)
cipher := nodeInfo.Cipher
if nodeInfo.ServerKey != "" {
settings.Password = nodeInfo.ServerKey
randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd))
cipher = ""
}
defaultSSuser := &coreConf.ShadowsocksUserConfig{
Cipher: cipher,
Password: randomPasswd,
}
settings.Users = append(settings.Users, defaultSSuser)
settings.NetworkList = &coreConf.NetworkList{"tcp", "udp"}
settings.IVCheck = true
if config.DisableIVCheck {
settings.IVCheck = false
}
t := coreConf.TransportProtocol("tcp")
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
s, err := json.Marshal(settings)
inbound.Settings = (*json.RawMessage)(&s)
if err != nil {
return fmt.Errorf("marshal shadowsocks settings error: %s", err)
}
return nil
}
func getCertFile(certConfig *conf.CertConfig) (certFile string, keyFile string, err error) {
@ -199,7 +246,6 @@ func getCertFile(certConfig *conf.CertConfig) (certFile string, keyFile string,
}
return certPath, keyPath, err
}
return "", "", fmt.Errorf("unsupported certmode: %s", certConfig.CertMode)
}
@ -207,14 +253,11 @@ func buildVlessFallbacks(fallbackConfigs []*conf.FallBackConfig) ([]*coreConf.VL
if fallbackConfigs == nil {
return nil, fmt.Errorf("you must provide FallBackConfigs")
}
vlessFallBacks := make([]*coreConf.VLessInboundFallback, len(fallbackConfigs))
for i, c := range fallbackConfigs {
if c.Dest == "" {
return nil, fmt.Errorf("dest is required for fallback fialed")
}
var dest json.RawMessage
dest, err := json.Marshal(c.Dest)
if err != nil {

View File

@ -3,11 +3,11 @@ package cmd
import (
"crypto"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
"github.com/goccy/go-json"
"io/ioutil"
"net/url"
"os"

View File

@ -3,8 +3,8 @@ package cmd
import (
"bytes"
"crypto/x509"
"encoding/json"
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
"github.com/goccy/go-json"
"io/ioutil"
"os"
"path/filepath"

View File

@ -1,8 +1,8 @@
package cmd
import (
"encoding/json"
"fmt"
"github.com/goccy/go-json"
"io/ioutil"
"net/url"
"path/filepath"

View File

@ -14,7 +14,6 @@ import (
type Node struct {
server *core.Core
config *conf.ControllerConfig
clientInfo panel.ClientInfo
apiClient panel.Panel
nodeInfo *panel.NodeInfo
@ -25,14 +24,15 @@ type Node struct {
userReportPeriodic *task.Periodic
onlineIpReportPeriodic *task.Periodic
DynamicSpeedLimitPeriodic *task.Periodic
*conf.ControllerConfig
}
// New return a Node service with default parameters.
func New(server *core.Core, api panel.Panel, config *conf.ControllerConfig) *Node {
controller := &Node{
server: server,
config: config,
apiClient: api,
server: server,
ControllerConfig: config,
apiClient: api,
}
return controller
}
@ -64,71 +64,67 @@ func (c *Node) Start() error {
if err != nil {
return err
}
if err := c.server.AddInboundLimiter(c.Tag, c.nodeInfo); err != nil {
if err := c.server.AddInboundLimiter(c.Tag, c.nodeInfo, c.userList); err != nil {
return fmt.Errorf("add inbound limiter failed: %s", err)
}
// Add Rule Manager
if !c.config.DisableGetRule {
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
log.Printf("Get rule list filed: %s", err)
} else if ruleList != nil {
if err := c.server.UpdateRule(c.Tag, ruleList); err != nil {
log.Printf("Update rule filed: %s", err)
}
if !c.DisableGetRule {
if err := c.server.UpdateRule(c.Tag, newNodeInfo.Rules); err != nil {
log.Printf("Update rule filed: %s", err)
}
}
// fetch node info task
c.nodeInfoMonitorPeriodic = &task.Periodic{
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
Interval: time.Duration(c.nodeInfo.BaseConfig.PullInterval.(int)) * time.Second,
Execute: c.nodeInfoMonitor,
}
// fetch user list task
c.userReportPeriodic = &task.Periodic{
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
Interval: time.Duration(c.nodeInfo.BaseConfig.PushInterval.(int)) * time.Second,
Execute: c.reportUserTraffic,
}
log.Printf("[%s: %d] Start monitor node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
// delay to start nodeInfoMonitor
go func() {
time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second)
time.Sleep(time.Duration(c.nodeInfo.BaseConfig.PullInterval.(int)) * time.Second)
_ = c.nodeInfoMonitorPeriodic.Start()
}()
log.Printf("[%s: %d] Start report node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
// delay to start userReport
go func() {
time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second)
time.Sleep(time.Duration(c.nodeInfo.BaseConfig.PushInterval.(int)) * time.Second)
_ = c.userReportPeriodic.Start()
}()
if c.config.EnableDynamicSpeedLimit {
if c.EnableDynamicSpeedLimit {
// Check dynamic speed limit task
c.DynamicSpeedLimitPeriodic = &task.Periodic{
Interval: time.Duration(c.config.DynamicSpeedLimitConfig.Periodic) * time.Second,
Interval: time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second,
Execute: c.dynamicSpeedLimit,
}
go func() {
time.Sleep(time.Duration(c.config.DynamicSpeedLimitConfig.Periodic) * time.Second)
time.Sleep(time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second)
_ = c.DynamicSpeedLimitPeriodic.Start()
}()
log.Printf("[%s: %d] Start dynamic speed limit", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
}
if c.config.EnableIpRecorder {
switch c.config.IpRecorderConfig.Type {
if c.EnableIpRecorder {
switch c.IpRecorderConfig.Type {
case "Recorder":
c.ipRecorder = iprecoder.NewRecorder(c.config.IpRecorderConfig.RecorderConfig)
c.ipRecorder = iprecoder.NewRecorder(c.IpRecorderConfig.RecorderConfig)
case "Redis":
c.ipRecorder = iprecoder.NewRedis(c.config.IpRecorderConfig.RedisConfig)
c.ipRecorder = iprecoder.NewRedis(c.IpRecorderConfig.RedisConfig)
default:
log.Printf("recorder type: %s is not vail, disable recorder", c.config.IpRecorderConfig.Type)
log.Printf("recorder type: %s is not vail, disable recorder", c.IpRecorderConfig.Type)
return nil
}
// report and fetch online ip list task
c.onlineIpReportPeriodic = &task.Periodic{
Interval: time.Duration(c.config.IpRecorderConfig.Periodic) * time.Second,
Interval: time.Duration(c.IpRecorderConfig.Periodic) * time.Second,
Execute: c.reportOnlineIp,
}
go func() {
time.Sleep(time.Duration(c.config.IpRecorderConfig.Periodic) * time.Second)
time.Sleep(time.Duration(c.IpRecorderConfig.Periodic) * time.Second)
_ = c.onlineIpReportPeriodic.Start()
}()
log.Printf("[%s: %d] Start report online ip", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
@ -144,7 +140,6 @@ func (c *Node) Close() error {
log.Panicf("node info periodic close failed: %s", err)
}
}
if c.nodeInfoMonitorPeriodic != nil {
err := c.userReportPeriodic.Close()
if err != nil {
@ -167,5 +162,5 @@ func (c *Node) Close() error {
}
func (c *Node) buildNodeTag() string {
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.NodeId)
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.ListenIP, c.nodeInfo.NodeId)
}

View File

@ -1,10 +1,10 @@
package controller
import (
"encoding/json"
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
conf2 "github.com/Yuzuki616/V2bX/conf"
"github.com/goccy/go-json"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/core"

View File

@ -6,8 +6,8 @@ import (
"github.com/Yuzuki616/V2bX/node/controller/legoCmd"
"github.com/xtls/xray-core/common/protocol"
"log"
"reflect"
"runtime"
"strconv"
"time"
)
@ -21,53 +21,41 @@ func (c *Node) nodeInfoMonitor() (err error) {
var nodeInfoChanged = false
// If nodeInfo changed
if newNodeInfo != nil {
if c.nodeInfo.SS == nil || !reflect.DeepEqual(c.nodeInfo.SS, newNodeInfo.SS) {
// Remove old tag
oldTag := c.Tag
err := c.removeOldTag(oldTag)
if err != nil {
log.Print(err)
return nil
}
// Add new tag
c.nodeInfo = newNodeInfo
c.Tag = c.buildNodeTag()
err = c.addNewTag(newNodeInfo)
if err != nil {
log.Print(err)
return nil
}
nodeInfoChanged = true
// Remove Old limiter
if err = c.server.DeleteInboundLimiter(oldTag); err != nil {
log.Print(err)
return nil
}
// Remove old tag
oldTag := c.Tag
err := c.removeOldTag(oldTag)
if err != nil {
log.Print(err)
return nil
}
// Add new tag
c.nodeInfo = newNodeInfo
c.Tag = c.buildNodeTag()
err = c.addNewTag(newNodeInfo)
if err != nil {
log.Print(err)
return nil
}
nodeInfoChanged = true
// Remove Old limiter
if err = c.server.DeleteInboundLimiter(oldTag); err != nil {
log.Print(err)
return nil
}
if err := c.server.UpdateRule(c.Tag, newNodeInfo.Rules); err != nil {
log.Print(err)
}
}
// Check Rule
if !c.config.DisableGetRule {
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
log.Printf("Get rule list filed: %s", err)
} else if ruleList != nil {
if err := c.server.UpdateRule(c.Tag, ruleList); err != nil {
log.Print(err)
}
}
}
// Check Cert
if c.nodeInfo.EnableTls && c.config.CertConfig.CertMode != "none" &&
(c.config.CertConfig.CertMode == "dns" || c.config.CertConfig.CertMode == "http") {
if c.nodeInfo.EnableTls && c.CertConfig.CertMode != "none" &&
(c.CertConfig.CertMode == "dns" || c.CertConfig.CertMode == "http") {
lego, err := legoCmd.New()
if err != nil {
log.Print(err)
}
// Core-core supports the OcspStapling certification hot renew
_, _, err = lego.RenewCert(c.config.CertConfig.CertDomain, c.config.CertConfig.Email,
c.config.CertConfig.CertMode, c.config.CertConfig.Provider, c.config.CertConfig.DNSEnv)
_, _, err = lego.RenewCert(c.CertConfig.CertDomain, c.CertConfig.Email,
c.CertConfig.CertMode, c.CertConfig.Provider, c.CertConfig.DNSEnv)
if err != nil {
log.Print(err)
}
@ -80,27 +68,42 @@ func (c *Node) nodeInfoMonitor() (err error) {
}
if nodeInfoChanged {
c.userList = newUserInfo
newUserInfo = nil
err = c.addNewUser(c.userList, newNodeInfo)
if err != nil {
log.Print(err)
return nil
}
newNodeInfo = nil
// Add Limiter
if err := c.server.AddInboundLimiter(c.Tag, c.nodeInfo); err != nil {
if err := c.server.AddInboundLimiter(c.Tag, newNodeInfo, newUserInfo); err != nil {
log.Print(err)
return nil
}
runtime.GC()
// Check interval
if c.nodeInfoMonitorPeriodic.Interval != time.Duration(newNodeInfo.BaseConfig.PullInterval.(int))*time.Second {
c.nodeInfoMonitorPeriodic.Interval = time.Duration(newNodeInfo.BaseConfig.PullInterval.(int)) * time.Second
_ = c.nodeInfoMonitorPeriodic.Close()
go func() {
time.Sleep(c.nodeInfoMonitorPeriodic.Interval)
_ = c.nodeInfoMonitorPeriodic.Start()
}()
}
if c.userReportPeriodic.Interval != time.Duration(newNodeInfo.BaseConfig.PushInterval.(int))*time.Second {
c.userReportPeriodic.Interval = time.Duration(newNodeInfo.BaseConfig.PushInterval.(int)) * time.Second
_ = c.userReportPeriodic.Close()
go func() {
time.Sleep(c.userReportPeriodic.Interval)
_ = c.userReportPeriodic.Start()
}()
}
} else {
deleted, added := compareUserList(c.userList, newUserInfo)
if len(deleted) > 0 {
deletedEmail := make([]string, len(deleted))
for i := range deleted {
deletedEmail[i] = fmt.Sprintf("%s|%s|%d", c.Tag,
(deleted)[i].GetUserEmail(),
(deleted)[i].UID)
deletedEmail[i] = fmt.Sprintf("%s|%s|%d",
c.Tag,
(deleted)[i].Uuid,
(deleted)[i].Id)
}
err := c.server.RemoveUsers(deletedEmail, c.Tag)
if err != nil {
@ -114,16 +117,14 @@ func (c *Node) nodeInfoMonitor() (err error) {
}
}
if len(added) > 0 || len(deleted) > 0 {
defer runtime.GC()
// Update Limiter
if err := c.server.UpdateInboundLimiter(c.Tag, deleted); err != nil {
if err := c.server.UpdateInboundLimiter(c.Tag, added, deleted); err != nil {
log.Print(err)
}
}
log.Printf("[%s: %d] %d user deleted, %d user added", c.nodeInfo.NodeType, c.nodeInfo.NodeId,
len(deleted), len(added))
c.userList = newUserInfo
newUserInfo = nil
}
return nil
}
@ -141,24 +142,21 @@ func (c *Node) removeOldTag(oldTag string) (err error) {
}
func (c *Node) addNewTag(newNodeInfo *panel.NodeInfo) (err error) {
inboundConfig, err := buildInbound(c.config, newNodeInfo, c.Tag)
inboundConfig, err := buildInbound(c.ControllerConfig, newNodeInfo, c.Tag)
if err != nil {
return err
return fmt.Errorf("build inbound error: %s", err)
}
err = c.server.AddInbound(inboundConfig)
if err != nil {
return err
return fmt.Errorf("add inbound error: %s", err)
}
outBoundConfig, err := buildOutbound(c.config, newNodeInfo, c.Tag)
outBoundConfig, err := buildOutbound(c.ControllerConfig, newNodeInfo, c.Tag)
if err != nil {
return err
return fmt.Errorf("build outbound error: %s", err)
}
err = c.server.AddOutbound(outBoundConfig)
if err != nil {
return err
return fmt.Errorf("add outbound error: %s", err)
}
return nil
}
@ -174,13 +172,13 @@ func (c *Node) addNewUser(userInfo []panel.UserInfo, nodeInfo *panel.NodeInfo) (
} else if nodeInfo.NodeType == "Trojan" {
users = c.buildTrojanUsers(userInfo)
} else if nodeInfo.NodeType == "Shadowsocks" {
users = c.buildSSUsers(userInfo, getCipherFromString(nodeInfo.SS.CypherMethod))
users = c.buildSSUsers(userInfo, getCipherFromString(nodeInfo.Cipher))
} else {
return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType)
}
err = c.server.AddUsers(users, c.Tag)
if err != nil {
return err
return fmt.Errorf("add users error: %s", err)
}
log.Printf("[%s: %d] Added %d new users", c.nodeInfo.NodeType, c.nodeInfo.NodeId, len(userInfo))
return nil
@ -190,24 +188,24 @@ func compareUserList(old, new []panel.UserInfo) (deleted, added []panel.UserInfo
tmp := map[string]struct{}{}
tmp2 := map[string]struct{}{}
for i := range old {
tmp[(old)[i].GetUserEmail()] = struct{}{}
tmp[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{}
}
l := len(tmp)
for i := range new {
e := (new)[i].GetUserEmail()
e := new[i].Uuid + strconv.Itoa(new[i].SpeedLimit)
tmp[e] = struct{}{}
tmp2[e] = struct{}{}
if l != len(tmp) {
added = append(added, (new)[i])
added = append(added, new[i])
l++
}
}
tmp = nil
l = len(tmp2)
for i := range old {
tmp2[(old)[i].GetUserEmail()] = struct{}{}
tmp2[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{}
if l != len(tmp2) {
deleted = append(deleted, (old)[i])
deleted = append(deleted, old[i])
l++
}
}
@ -220,16 +218,16 @@ func (c *Node) reportUserTraffic() (err error) {
for i := range c.userList {
up, down := c.server.GetUserTraffic(c.buildUserTag(&(c.userList)[i]), true)
if up > 0 || down > 0 {
if c.config.EnableDynamicSpeedLimit {
if c.EnableDynamicSpeedLimit {
c.userList[i].Traffic += up + down
}
userTraffic = append(userTraffic, panel.UserTraffic{
UID: (c.userList)[i].UID,
UID: (c.userList)[i].Id,
Upload: up,
Download: down})
}
}
if len(userTraffic) > 0 && !c.config.DisableUploadTraffic {
if len(userTraffic) > 0 && !c.DisableUploadTraffic {
err = c.apiClient.ReportUserTraffic(userTraffic)
if err != nil {
log.Printf("Report user traffic faild: %s", err)
@ -238,7 +236,7 @@ func (c *Node) reportUserTraffic() (err error) {
}
}
userTraffic = nil
if !c.config.EnableIpRecorder {
if !c.EnableIpRecorder {
c.server.ClearOnlineIp(c.Tag)
}
runtime.GC()
@ -256,7 +254,7 @@ func (c *Node) reportOnlineIp() (err error) {
log.Print("Report online ip error: ", err)
c.server.ClearOnlineIp(c.Tag)
}
if c.config.IpRecorderConfig.EnableIpSync {
if c.IpRecorderConfig.EnableIpSync {
c.server.UpdateOnlineIp(c.Tag, onlineIp)
log.Printf("[Node: %d] Updated %d online ip", c.nodeInfo.NodeId, len(onlineIp))
}
@ -265,14 +263,14 @@ func (c *Node) reportOnlineIp() (err error) {
}
func (c *Node) dynamicSpeedLimit() error {
if c.config.EnableDynamicSpeedLimit {
if c.EnableDynamicSpeedLimit {
for i := range c.userList {
up, down := c.server.GetUserTraffic(c.buildUserTag(&(c.userList)[i]), false)
if c.userList[i].Traffic+down+up/1024/1024 > c.config.DynamicSpeedLimitConfig.Traffic {
if c.userList[i].Traffic+down+up/1024/1024 > c.DynamicSpeedLimitConfig.Traffic {
err := c.server.AddUserSpeedLimit(c.Tag,
&c.userList[i],
c.config.DynamicSpeedLimitConfig.SpeedLimit,
time.Now().Add(time.Second*time.Duration(c.config.DynamicSpeedLimitConfig.ExpireTime)).Unix())
c.DynamicSpeedLimitConfig.SpeedLimit,
time.Now().Add(time.Second*time.Duration(c.DynamicSpeedLimitConfig.ExpireTime)).Unix())
if err != nil {
log.Print(err)
}

View File

@ -3,14 +3,14 @@ package controller
import (
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
"strings"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/proxy/shadowsocks"
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
"github.com/xtls/xray-core/proxy/trojan"
"github.com/xtls/xray-core/proxy/vless"
"strings"
)
func (c *Node) buildVmessUsers(userInfo []panel.UserInfo) (users []*protocol.User) {
@ -23,7 +23,7 @@ func (c *Node) buildVmessUsers(userInfo []panel.UserInfo) (users []*protocol.Use
func (c *Node) buildVmessUser(userInfo *panel.UserInfo, serverAlterID uint16) (user *protocol.User) {
vmessAccount := &conf.VMessAccount{
ID: userInfo.V2rayUser.Uuid,
ID: userInfo.Uuid,
AlterIds: serverAlterID,
Security: "auto",
}
@ -44,7 +44,7 @@ func (c *Node) buildVlessUsers(userInfo []panel.UserInfo) (users []*protocol.Use
func (c *Node) buildVlessUser(userInfo *panel.UserInfo) (user *protocol.User) {
vlessAccount := &vless.Account{
Id: userInfo.V2rayUser.Uuid,
Id: userInfo.Uuid,
Flow: "xtls-rprx-direct",
}
return &protocol.User{
@ -64,7 +64,7 @@ func (c *Node) buildTrojanUsers(userInfo []panel.UserInfo) (users []*protocol.Us
func (c *Node) buildTrojanUser(userInfo *panel.UserInfo) (user *protocol.User) {
trojanAccount := &trojan.Account{
Password: userInfo.TrojanUser.Password,
Password: userInfo.Uuid,
Flow: "xtls-rprx-direct",
}
return &protocol.User{
@ -98,17 +98,28 @@ func (c *Node) buildSSUsers(userInfo []panel.UserInfo, cypher shadowsocks.Cipher
}
func (c *Node) buildSSUser(userInfo *panel.UserInfo, cypher shadowsocks.CipherType) (user *protocol.User) {
ssAccount := &shadowsocks.Account{
Password: userInfo.Secret,
CipherType: cypher,
}
return &protocol.User{
Level: 0,
Email: c.buildUserTag(userInfo),
Account: serial.ToTypedMessage(ssAccount),
if c.nodeInfo.ServerKey == "" {
ssAccount := &shadowsocks.Account{
Password: userInfo.Uuid,
CipherType: cypher,
}
return &protocol.User{
Level: 0,
Email: c.buildUserTag(userInfo),
Account: serial.ToTypedMessage(ssAccount),
}
} else {
ssAccount := &shadowsocks_2022.User{
Key: userInfo.Uuid,
}
return &protocol.User{
Level: 0,
Email: c.buildUserTag(userInfo),
Account: serial.ToTypedMessage(ssAccount),
}
}
}
func (c *Node) buildUserTag(user *panel.UserInfo) string {
return fmt.Sprintf("%s|%s|%d", c.Tag, user.GetUserEmail(), user.UID)
return fmt.Sprintf("%s|%s|%d", c.Tag, user.Uuid, user.Id)
}

View File

@ -18,9 +18,13 @@ func New() *Node {
func (n *Node) Start(nodes []*conf.NodeConfig, core *core.Core) error {
n.controllers = make([]*controller.Node, len(nodes))
for i, c := range nodes {
p, err := panel.New(c.ApiConfig)
if err != nil {
return err
}
// Register controller service
n.controllers[i] = controller.New(core, panel.New(c.ApiConfig), c.ControllerConfig)
err := n.controllers[i].Start()
n.controllers[i] = controller.New(core, p, c.ControllerConfig)
err = n.controllers[i].Start()
if err != nil {
return err
}