diff --git a/api/panel/node.go b/api/panel/node.go index 92fd83d..3e8d119 100644 --- a/api/panel/node.go +++ b/api/panel/node.go @@ -10,18 +10,19 @@ import ( ) 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:"-"` + NodeId int `json:"-"` + NodeType string `json:"-"` + Rules []*regexp.Regexp `json:"-"` + 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"` } type Route struct { Id int `json:"id"` @@ -33,14 +34,6 @@ type BaseConfig struct { PushInterval any `json:"push_interval"` PullInterval any `json:"pull_interval"` } -type DestinationRule struct { - ID int - Pattern *regexp.Regexp -} -type localNodeConfig struct { - NodeId int - NodeType string -} func (c *Client) GetNodeInfo() (nodeInfo *NodeInfo, err error) { const path = "/api/v1/server/UniProxy/config" @@ -66,10 +59,7 @@ func (c *Client) GetNodeInfo() (nodeInfo *NodeInfo, err error) { matchs = nodeInfo.Routes[i].Match.([]string) } for _, v := range matchs { - nodeInfo.Rules = append(nodeInfo.Rules, DestinationRule{ - ID: nodeInfo.Routes[i].Id, - Pattern: regexp.MustCompile(v), - }) + nodeInfo.Rules = append(nodeInfo.Rules, regexp.MustCompile(v)) } } } diff --git a/api/panel/panel.go b/api/panel/panel.go index 3594052..1659ad7 100644 --- a/api/panel/panel.go +++ b/api/panel/panel.go @@ -15,20 +15,13 @@ import ( // Panel is the interface for different panel's api. -type ClientInfo struct { - APIHost string - NodeID int - Key string - NodeType string -} - type Client struct { client *resty.Client APIHost string Key string NodeType string NodeId int - LocalRuleList []DestinationRule + LocalRuleList []*regexp.Regexp etag string } @@ -74,8 +67,8 @@ func New(c *conf.ApiConfig) (*Client, error) { } // readLocalRuleList reads the local rule list file -func readLocalRuleList(path string) (LocalRuleList []DestinationRule) { - LocalRuleList = make([]DestinationRule, 0) +func readLocalRuleList(path string) (LocalRuleList []*regexp.Regexp) { + LocalRuleList = make([]*regexp.Regexp, 0) if path != "" { // open the file file, err := os.Open(path) @@ -87,10 +80,7 @@ func readLocalRuleList(path string) (LocalRuleList []DestinationRule) { fileScanner := bufio.NewScanner(file) // read line by line for fileScanner.Scan() { - LocalRuleList = append(LocalRuleList, DestinationRule{ - ID: -1, - Pattern: regexp.MustCompile(fileScanner.Text()), - }) + LocalRuleList = append(LocalRuleList, regexp.MustCompile(fileScanner.Text())) } // handle first encountered error while reading if err := fileScanner.Err(); err != nil { diff --git a/api/panel/utils.go b/api/panel/utils.go index fa9a30f..56aeb0f 100644 --- a/api/panel/utils.go +++ b/api/panel/utils.go @@ -6,11 +6,6 @@ import ( 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} -} - // Debug set the client debug for client func (c *Client) Debug() { c.client.SetDebug(true) diff --git a/conf/node.go b/conf/node.go index 354c860..40b2da9 100644 --- a/conf/node.go +++ b/conf/node.go @@ -1,5 +1,7 @@ package conf +import "github.com/goccy/go-json" + type NodeConfig struct { ApiConfig *ApiConfig `yaml:"ApiConfig"` ControllerConfig *ControllerConfig `yaml:"ControllerConfig"` @@ -20,13 +22,16 @@ type ControllerConfig struct { EnableDNS bool `yaml:"EnableDNS"` DNSType string `yaml:"DNSType"` EnableVless bool `yaml:"EnableVless"` + EnableXtls bool `json:"EnableXtls"` LimitConfig LimitConfig `yaml:"LimitConfig"` DisableUploadTraffic bool `yaml:"DisableUploadTraffic"` DisableGetRule bool `yaml:"DisableGetRule"` EnableProxyProtocol bool `yaml:"EnableProxyProtocol"` - EnableFallback bool `yaml:"EnableFallback"` DisableIVCheck bool `yaml:"DisableIVCheck"` DisableSniffing bool `yaml:"DisableSniffing"` + EnableReality bool `yaml:"EnableReality"` + RealityConfig RealityConfig `yaml:"RealityConfig"` + EnableFallback bool `yaml:"EnableFallback"` FallBackConfigs []FallBackConfig `yaml:"FallBackConfigs"` CertConfig *CertConfig `yaml:"CertConfig"` } @@ -88,3 +93,14 @@ type CertConfig struct { Email string `yaml:"Email"` DNSEnv map[string]string `yaml:"DNSEnv"` } + +type RealityConfig struct { + Dest json.RawMessage `yaml:"Dest"` + Xver uint64 `yaml:"Xver"` + ServerNames []string `yaml:"ServerNames"` + PrivateKey string `yaml:"PrivateKey"` + MinClientVer string `yaml:"MinClientVer"` + MaxClientVer string `yaml:"MaxClientVer"` + MaxTimeDiff uint64 `yaml:"MaxTimeDiff"` + ShortIds []string `yaml:"ShortIds"` +} diff --git a/example/config.yml.example b/example/config.yml.example index a752d0a..c26a087 100644 --- a/example/config.yml.example +++ b/example/config.yml.example @@ -27,6 +27,21 @@ Nodes: DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy EnableVless: false # Enable Vless for V2ray Type EnableProxyProtocol: false # Only works for WebSocket and TCP + EnableXtls: false # Enable xtls-rprx-vision, only vless + EnableReality: false # Enable reality + RealityConfig: + Dest: 80 # Same fallback dest + Xver: "example.com:443" # Same fallback xver + ServerNames: + - "example.com" + - "www.example.com" + PrivateKey: "" # Private key for server + MinClientVer: "" # Min client version + MaxClientVer: "" # Max client version + MaxTimeDiff: 0 + ShortIds: + - "" + - "0123456789abcdef" EnableFallback: false # Only support for Trojan and Vless FallBackConfigs: # Support multiple fallbacks - SNI: # TLS SNI(Server Name Indication), Empty for any @@ -34,6 +49,7 @@ Nodes: Path: # HTTP PATH, Empty for any Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details. ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for dsable + LimitConfig: EnableRealtime: false # Check device limit on real time SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable diff --git a/limiter/limiter.go b/limiter/limiter.go index baa38f0..905aa74 100644 --- a/limiter/limiter.go +++ b/limiter/limiter.go @@ -8,6 +8,7 @@ import ( "github.com/juju/ratelimit" "github.com/xtls/xray-core/common/task" "log" + "regexp" "sync" "time" ) @@ -29,7 +30,7 @@ func Init() { } type Limiter struct { - Rules []panel.DestinationRule + Rules []*regexp.Regexp ProtocolRules []string SpeedLimit int UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo diff --git a/limiter/rule.go b/limiter/rule.go index 1e95343..ce4dcf5 100644 --- a/limiter/rule.go +++ b/limiter/rule.go @@ -1,14 +1,14 @@ package limiter import ( - "github.com/Yuzuki616/V2bX/api/panel" "reflect" + "regexp" ) func (l *Limiter) CheckDomainRule(destination string) (reject bool) { // have rule for i := range l.Rules { - if l.Rules[i].Pattern.MatchString(destination) { + if l.Rules[i].MatchString(destination) { reject = true break } @@ -26,7 +26,7 @@ func (l *Limiter) CheckProtocolRule(protocol string) (reject bool) { return } -func (l *Limiter) UpdateRule(newRuleList []panel.DestinationRule) error { +func (l *Limiter) UpdateRule(newRuleList []*regexp.Regexp) error { if !reflect.DeepEqual(l.Rules, newRuleList) { l.Rules = newRuleList } diff --git a/node/controller.go b/node/controller.go index c4b24d3..40fd06c 100644 --- a/node/controller.go +++ b/node/controller.go @@ -14,7 +14,6 @@ import ( type Controller struct { server *core.Core - clientInfo panel.ClientInfo apiClient *panel.Client nodeInfo *panel.NodeInfo Tag string @@ -40,7 +39,6 @@ func NewController(server *core.Core, api *panel.Client, config *conf.Controller // Start implement the Start() function of the service interface func (c *Controller) Start() error { - c.clientInfo = c.apiClient.Describe() // First fetch Node Info var err error c.nodeInfo, err = c.apiClient.GetNodeInfo() diff --git a/node/controller_test.go b/node/controller_test.go deleted file mode 100644 index a7a711a..0000000 --- a/node/controller_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package node_test - -import ( - "fmt" - "github.com/Yuzuki616/V2bX/api/panel" - "github.com/Yuzuki616/V2bX/conf" - "github.com/Yuzuki616/V2bX/core" - _ "github.com/Yuzuki616/V2bX/core/distro/all" - . "github.com/Yuzuki616/V2bX/node" - xCore "github.com/xtls/xray-core/core" - coreConf "github.com/xtls/xray-core/infra/conf" - "os" - "os/signal" - "runtime" - "syscall" - "testing" -) - -func TestController(t *testing.T) { - serverConfig := &coreConf.Config{ - Stats: &coreConf.StatsConfig{}, - LogConfig: &coreConf.LogConfig{LogLevel: "debug"}, - } - policyConfig := &coreConf.PolicyConfig{} - policyConfig.Levels = map[uint32]*coreConf.Policy{0: &coreConf.Policy{ - StatsUserUplink: true, - StatsUserDownlink: true, - }} - serverConfig.Policy = policyConfig - config, _ := serverConfig.Build() - - // config := &core.Config{ - // App: []*serial.TypedMessage{ - // serial.ToTypedMessage(&dispatcher.Config{}), - // serial.ToTypedMessage(&proxyman.InboundConfig{}), - // serial.ToTypedMessage(&proxyman.OutboundConfig{}), - // serial.ToTypedMessage(&stats.Config{}), - // }} - - server, err := xCore.New(config) - defer server.Close() - if err != nil { - t.Errorf("failed to create instance: %s", err) - } - if err = server.Start(); err != nil { - t.Errorf("Failed to start instance: %s", err) - } - certConfig := &conf.CertConfig{ - CertMode: "http", - CertDomain: "test.ss.tk", - Provider: "alidns", - Email: "ss@ss.com", - } - controlerconfig := &conf.ControllerConfig{ - UpdatePeriodic: 5, - CertConfig: certConfig, - } - apiConfig := &conf.ApiConfig{ - APIHost: "http://127.0.0.1:667", - Key: "123", - NodeID: 41, - NodeType: "V2ray", - } - apiclient := panel.New(apiConfig) - c := &core.Core{Server: server} - c.Start() - node := New(c, apiclient, controlerconfig) - fmt.Println("Sleep 1s") - err = node.Start() - if err != nil { - t.Error(err) - } - //Explicitly triggering GC to remove garbage from config loading. - runtime.GC() - { - osSignals := make(chan os.Signal, 1) - signal.Notify(osSignals, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM) - <-osSignals - } -} diff --git a/node/inbound.go b/node/inbound.go index 75d5943..b919bfd 100644 --- a/node/inbound.go +++ b/node/inbound.go @@ -15,8 +15,8 @@ import ( 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) { +// BuildInbound build Inbound config for different protocol +func BuildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag string) (*core.InboundHandlerConfig, error) { inbound := &coreConf.InboundDetourConfig{} // Set network protocol t := coreConf.TransportProtocol(nodeInfo.Network) @@ -65,17 +65,39 @@ func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag s AcceptProxyProtocol: config.EnableProxyProtocol} //Enable proxy protocol } // Set TLS and XTLS settings - if nodeInfo.Tls != 0 && config.CertConfig.CertMode != "none" { - inbound.StreamSetting.Security = "tls" - certFile, keyFile, err := getCertFile(config.CertConfig) - if err != nil { - return nil, err + if nodeInfo.Tls != 0 { + if config.CertConfig.CertMode != "none" { + // Normal tls + inbound.StreamSetting.Security = "tls" + certFile, keyFile, err := getCertFile(config.CertConfig) + if err != nil { + return nil, err + } + inbound.StreamSetting.TLSSettings = &coreConf.TLSConfig{ + Certs: []*coreConf.TLSCertConfig{ + { + CertFile: certFile, + KeyFile: keyFile, + OcspStapling: 3600, + }, + }, + RejectUnknownSNI: config.CertConfig.RejectUnknownSni, + } } - tlsSettings := &coreConf.TLSConfig{ - RejectUnknownSNI: config.CertConfig.RejectUnknownSni, + + } else if config.EnableReality { + // Reality + inbound.StreamSetting.Security = "reality" + inbound.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{ + Dest: config.RealityConfig.Dest, + Xver: config.RealityConfig.Xver, + ServerNames: config.RealityConfig.ServerNames, + PrivateKey: config.RealityConfig.PrivateKey, + MinClientVer: config.RealityConfig.MinClientVer, + MaxClientVer: config.RealityConfig.MaxClientVer, + MaxTimeDiff: config.RealityConfig.MaxTimeDiff, + ShortIds: config.RealityConfig.ShortIds, } - tlsSettings.Certs = append(tlsSettings.Certs, &coreConf.TLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600}) - inbound.StreamSetting.TLSSettings = tlsSettings } // Support ProxyProtocol for any transport protocol if *inbound.StreamSetting.Network != "tcp" && @@ -128,6 +150,9 @@ func buildV2ray(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound } inbound.Settings = (*json.RawMessage)(&s) } + if len(nodeInfo.NetworkSettings) == 0 { + return nil + } switch nodeInfo.Network { case "tcp": err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.TCPSettings) diff --git a/node/inbound_test.go b/node/inbound_test.go index c80dd0a..abc567d 100644 --- a/node/inbound_test.go +++ b/node/inbound_test.go @@ -2,97 +2,32 @@ package node_test import ( "github.com/Yuzuki616/V2bX/api/panel" + "github.com/Yuzuki616/V2bX/conf" . "github.com/Yuzuki616/V2bX/node" "testing" ) func TestBuildV2ray(t *testing.T) { nodeInfo := &panel.NodeInfo{ - NodeType: "V2ray", - NodeID: 1, - Port: 1145, - SpeedLimit: 0, - AlterID: 2, - TransportProtocol: "ws", - Host: "test.test.tk", - Path: "v2ray", - EnableTLS: false, - TLSType: "tls", + NodeType: "v2ray", + NodeId: 1, + ServerPort: 1145, + Network: "ws", + NetworkSettings: nil, + Host: "test.test.tk", + ServerName: "test.test.tk", } - certConfig := &CertConfig{ - CertMode: "http", + certConfig := &conf.CertConfig{ + CertMode: "none", CertDomain: "test.test.tk", Provider: "alidns", Email: "test@gmail.com", } - config := &Config{ + config := &conf.ControllerConfig{ + ListenIP: "0.0.0.0", CertConfig: certConfig, } - _, err := buildInbound(config, nodeInfo) - if err != nil { - t.Error(err) - } -} - -func TestBuildTrojan(t *testing.T) { - nodeInfo := &panel.NodeInfo{ - NodeType: "Trojan", - NodeID: 1, - Port: 1145, - SpeedLimit: 0, - AlterID: 2, - TransportProtocol: "tcp", - Host: "trojan.test.tk", - Path: "v2ray", - EnableTLS: false, - TLSType: "tls", - } - DNSEnv := make(map[string]string) - DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa" - DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb" - certConfig := &CertConfig{ - CertMode: "dns", - CertDomain: "trojan.test.tk", - Provider: "alidns", - Email: "test@gmail.com", - DNSEnv: DNSEnv, - } - config := &Config{ - CertConfig: certConfig, - } - _, err := buildInbound(config, nodeInfo) - if err != nil { - t.Error(err) - } -} - -func TestBuildSS(t *testing.T) { - nodeInfo := &panel.NodeInfo{ - NodeType: "Shadowsocks", - NodeID: 1, - Port: 1145, - SpeedLimit: 0, - AlterID: 2, - TransportProtocol: "tcp", - Host: "test.test.tk", - Path: "v2ray", - EnableTLS: false, - TLSType: "tls", - } - DNSEnv := make(map[string]string) - DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa" - DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb" - certConfig := &CertConfig{ - CertMode: "dns", - CertDomain: "trojan.test.tk", - Provider: "alidns", - Email: "test@me.com", - DNSEnv: DNSEnv, - } - config := &Config{ - CertConfig: certConfig, - } - _, err := buildInbound(config, nodeInfo) + _, err := BuildInbound(config, nodeInfo, "11") if err != nil { t.Error(err) } diff --git a/node/task.go b/node/task.go index df92255..c39fd5a 100644 --- a/node/task.go +++ b/node/task.go @@ -164,7 +164,7 @@ func (c *Controller) removeOldNode(oldTag string) (err error) { } func (c *Controller) addNewNode(newNodeInfo *panel.NodeInfo) (err error) { - inboundConfig, err := buildInbound(c.ControllerConfig, newNodeInfo, c.Tag) + inboundConfig, err := BuildInbound(c.ControllerConfig, newNodeInfo, c.Tag) if err != nil { return fmt.Errorf("build inbound error: %s", err) } diff --git a/node/user.go b/node/user.go index 85abeb5..f4e729e 100644 --- a/node/user.go +++ b/node/user.go @@ -15,6 +15,8 @@ import ( "strings" ) +const xtlsFLow = "xtls-rprx-vision" + func (c *Controller) addNewUser(userInfo []panel.UserInfo, nodeInfo *panel.NodeInfo) (err error) { users := make([]*protocol.User, 0, len(userInfo)) switch nodeInfo.NodeType { @@ -70,8 +72,10 @@ func (c *Controller) buildVlessUsers(userInfo []panel.UserInfo) (users []*protoc func (c *Controller) buildVlessUser(userInfo *panel.UserInfo) (user *protocol.User) { vlessAccount := &vless.Account{ - Id: userInfo.Uuid, - Flow: "xtls-rprx-direct", + Id: userInfo.Uuid, + } + if c.EnableXtls { + vlessAccount.Flow = xtlsFLow } return &protocol.User{ Level: 0, @@ -91,7 +95,6 @@ func (c *Controller) buildTrojanUsers(userInfo []panel.UserInfo) (users []*proto func (c *Controller) buildTrojanUser(userInfo *panel.UserInfo) (user *protocol.User) { trojanAccount := &trojan.Account{ Password: userInfo.Uuid, - Flow: "xtls-rprx-direct", } return &protocol.User{ Level: 0,