support xtls-rprx-vision, support reality

This commit is contained in:
yuzuki999 2023-05-22 10:36:10 +08:00
parent 0ebc45e2d2
commit 40edaedb22
13 changed files with 112 additions and 223 deletions

View File

@ -10,18 +10,19 @@ import (
) )
type NodeInfo struct { type NodeInfo struct {
Host string `json:"host"` NodeId int `json:"-"`
ServerPort int `json:"server_port"` NodeType string `json:"-"`
ServerName string `json:"server_name"` Rules []*regexp.Regexp `json:"-"`
Network string `json:"network"` Host string `json:"host"`
NetworkSettings json.RawMessage `json:"networkSettings"` ServerPort int `json:"server_port"`
Cipher string `json:"cipher"` ServerName string `json:"server_name"`
ServerKey string `json:"server_key"` Network string `json:"network"`
Tls int `json:"tls"` NetworkSettings json.RawMessage `json:"networkSettings"`
Routes []Route `json:"routes"` Cipher string `json:"cipher"`
BaseConfig *BaseConfig `json:"base_config"` ServerKey string `json:"server_key"`
Rules []DestinationRule `json:"-"` Tls int `json:"tls"`
localNodeConfig `json:"-"` Routes []Route `json:"routes"`
BaseConfig *BaseConfig `json:"base_config"`
} }
type Route struct { type Route struct {
Id int `json:"id"` Id int `json:"id"`
@ -33,14 +34,6 @@ type BaseConfig struct {
PushInterval any `json:"push_interval"` PushInterval any `json:"push_interval"`
PullInterval any `json:"pull_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) { func (c *Client) GetNodeInfo() (nodeInfo *NodeInfo, err error) {
const path = "/api/v1/server/UniProxy/config" 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) matchs = nodeInfo.Routes[i].Match.([]string)
} }
for _, v := range matchs { for _, v := range matchs {
nodeInfo.Rules = append(nodeInfo.Rules, DestinationRule{ nodeInfo.Rules = append(nodeInfo.Rules, regexp.MustCompile(v))
ID: nodeInfo.Routes[i].Id,
Pattern: regexp.MustCompile(v),
})
} }
} }
} }

View File

@ -15,20 +15,13 @@ import (
// Panel is the interface for different panel's api. // Panel is the interface for different panel's api.
type ClientInfo struct {
APIHost string
NodeID int
Key string
NodeType string
}
type Client struct { type Client struct {
client *resty.Client client *resty.Client
APIHost string APIHost string
Key string Key string
NodeType string NodeType string
NodeId int NodeId int
LocalRuleList []DestinationRule LocalRuleList []*regexp.Regexp
etag string etag string
} }
@ -74,8 +67,8 @@ func New(c *conf.ApiConfig) (*Client, error) {
} }
// readLocalRuleList reads the local rule list file // readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList []DestinationRule) { func readLocalRuleList(path string) (LocalRuleList []*regexp.Regexp) {
LocalRuleList = make([]DestinationRule, 0) LocalRuleList = make([]*regexp.Regexp, 0)
if path != "" { if path != "" {
// open the file // open the file
file, err := os.Open(path) file, err := os.Open(path)
@ -87,10 +80,7 @@ func readLocalRuleList(path string) (LocalRuleList []DestinationRule) {
fileScanner := bufio.NewScanner(file) fileScanner := bufio.NewScanner(file)
// read line by line // read line by line
for fileScanner.Scan() { for fileScanner.Scan() {
LocalRuleList = append(LocalRuleList, DestinationRule{ LocalRuleList = append(LocalRuleList, regexp.MustCompile(fileScanner.Text()))
ID: -1,
Pattern: regexp.MustCompile(fileScanner.Text()),
})
} }
// handle first encountered error while reading // handle first encountered error while reading
if err := fileScanner.Err(); err != nil { if err := fileScanner.Err(); err != nil {

View File

@ -6,11 +6,6 @@ import (
path2 "path" 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 // Debug set the client debug for client
func (c *Client) Debug() { func (c *Client) Debug() {
c.client.SetDebug(true) c.client.SetDebug(true)

View File

@ -1,5 +1,7 @@
package conf package conf
import "github.com/goccy/go-json"
type NodeConfig struct { type NodeConfig struct {
ApiConfig *ApiConfig `yaml:"ApiConfig"` ApiConfig *ApiConfig `yaml:"ApiConfig"`
ControllerConfig *ControllerConfig `yaml:"ControllerConfig"` ControllerConfig *ControllerConfig `yaml:"ControllerConfig"`
@ -20,13 +22,16 @@ type ControllerConfig struct {
EnableDNS bool `yaml:"EnableDNS"` EnableDNS bool `yaml:"EnableDNS"`
DNSType string `yaml:"DNSType"` DNSType string `yaml:"DNSType"`
EnableVless bool `yaml:"EnableVless"` EnableVless bool `yaml:"EnableVless"`
EnableXtls bool `json:"EnableXtls"`
LimitConfig LimitConfig `yaml:"LimitConfig"` LimitConfig LimitConfig `yaml:"LimitConfig"`
DisableUploadTraffic bool `yaml:"DisableUploadTraffic"` DisableUploadTraffic bool `yaml:"DisableUploadTraffic"`
DisableGetRule bool `yaml:"DisableGetRule"` DisableGetRule bool `yaml:"DisableGetRule"`
EnableProxyProtocol bool `yaml:"EnableProxyProtocol"` EnableProxyProtocol bool `yaml:"EnableProxyProtocol"`
EnableFallback bool `yaml:"EnableFallback"`
DisableIVCheck bool `yaml:"DisableIVCheck"` DisableIVCheck bool `yaml:"DisableIVCheck"`
DisableSniffing bool `yaml:"DisableSniffing"` DisableSniffing bool `yaml:"DisableSniffing"`
EnableReality bool `yaml:"EnableReality"`
RealityConfig RealityConfig `yaml:"RealityConfig"`
EnableFallback bool `yaml:"EnableFallback"`
FallBackConfigs []FallBackConfig `yaml:"FallBackConfigs"` FallBackConfigs []FallBackConfig `yaml:"FallBackConfigs"`
CertConfig *CertConfig `yaml:"CertConfig"` CertConfig *CertConfig `yaml:"CertConfig"`
} }
@ -88,3 +93,14 @@ type CertConfig struct {
Email string `yaml:"Email"` Email string `yaml:"Email"`
DNSEnv map[string]string `yaml:"DNSEnv"` 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"`
}

View File

@ -27,6 +27,21 @@ Nodes:
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
EnableVless: false # Enable Vless for V2ray Type EnableVless: false # Enable Vless for V2ray Type
EnableProxyProtocol: false # Only works for WebSocket and TCP 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 EnableFallback: false # Only support for Trojan and Vless
FallBackConfigs: # Support multiple fallbacks FallBackConfigs: # Support multiple fallbacks
- SNI: # TLS SNI(Server Name Indication), Empty for any - SNI: # TLS SNI(Server Name Indication), Empty for any
@ -34,6 +49,7 @@ Nodes:
Path: # HTTP PATH, Empty for any Path: # HTTP PATH, Empty for any
Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details. 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 ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for dsable
LimitConfig: LimitConfig:
EnableRealtime: false # Check device limit on real time EnableRealtime: false # Check device limit on real time
SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable

View File

@ -8,6 +8,7 @@ import (
"github.com/juju/ratelimit" "github.com/juju/ratelimit"
"github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/common/task"
"log" "log"
"regexp"
"sync" "sync"
"time" "time"
) )
@ -29,7 +30,7 @@ func Init() {
} }
type Limiter struct { type Limiter struct {
Rules []panel.DestinationRule Rules []*regexp.Regexp
ProtocolRules []string ProtocolRules []string
SpeedLimit int SpeedLimit int
UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo

View File

@ -1,14 +1,14 @@
package limiter package limiter
import ( import (
"github.com/Yuzuki616/V2bX/api/panel"
"reflect" "reflect"
"regexp"
) )
func (l *Limiter) CheckDomainRule(destination string) (reject bool) { func (l *Limiter) CheckDomainRule(destination string) (reject bool) {
// have rule // have rule
for i := range l.Rules { for i := range l.Rules {
if l.Rules[i].Pattern.MatchString(destination) { if l.Rules[i].MatchString(destination) {
reject = true reject = true
break break
} }
@ -26,7 +26,7 @@ func (l *Limiter) CheckProtocolRule(protocol string) (reject bool) {
return return
} }
func (l *Limiter) UpdateRule(newRuleList []panel.DestinationRule) error { func (l *Limiter) UpdateRule(newRuleList []*regexp.Regexp) error {
if !reflect.DeepEqual(l.Rules, newRuleList) { if !reflect.DeepEqual(l.Rules, newRuleList) {
l.Rules = newRuleList l.Rules = newRuleList
} }

View File

@ -14,7 +14,6 @@ import (
type Controller struct { type Controller struct {
server *core.Core server *core.Core
clientInfo panel.ClientInfo
apiClient *panel.Client apiClient *panel.Client
nodeInfo *panel.NodeInfo nodeInfo *panel.NodeInfo
Tag string 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 // Start implement the Start() function of the service interface
func (c *Controller) Start() error { func (c *Controller) Start() error {
c.clientInfo = c.apiClient.Describe()
// First fetch Node Info // First fetch Node Info
var err error var err error
c.nodeInfo, err = c.apiClient.GetNodeInfo() c.nodeInfo, err = c.apiClient.GetNodeInfo()

View File

@ -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
}
}

View File

@ -15,8 +15,8 @@ import (
coreConf "github.com/xtls/xray-core/infra/conf" coreConf "github.com/xtls/xray-core/infra/conf"
) )
// buildInbound build Inbound config for different protocol // BuildInbound build Inbound config for different protocol
func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag string) (*core.InboundHandlerConfig, error) { func BuildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag string) (*core.InboundHandlerConfig, error) {
inbound := &coreConf.InboundDetourConfig{} inbound := &coreConf.InboundDetourConfig{}
// Set network protocol // Set network protocol
t := coreConf.TransportProtocol(nodeInfo.Network) 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 AcceptProxyProtocol: config.EnableProxyProtocol} //Enable proxy protocol
} }
// Set TLS and XTLS settings // Set TLS and XTLS settings
if nodeInfo.Tls != 0 && config.CertConfig.CertMode != "none" { if nodeInfo.Tls != 0 {
inbound.StreamSetting.Security = "tls" if config.CertConfig.CertMode != "none" {
certFile, keyFile, err := getCertFile(config.CertConfig) // Normal tls
if err != nil { inbound.StreamSetting.Security = "tls"
return nil, err 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 // Support ProxyProtocol for any transport protocol
if *inbound.StreamSetting.Network != "tcp" && if *inbound.StreamSetting.Network != "tcp" &&
@ -128,6 +150,9 @@ func buildV2ray(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound
} }
inbound.Settings = (*json.RawMessage)(&s) inbound.Settings = (*json.RawMessage)(&s)
} }
if len(nodeInfo.NetworkSettings) == 0 {
return nil
}
switch nodeInfo.Network { switch nodeInfo.Network {
case "tcp": case "tcp":
err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.TCPSettings) err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.TCPSettings)

View File

@ -2,97 +2,32 @@ package node_test
import ( import (
"github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
. "github.com/Yuzuki616/V2bX/node" . "github.com/Yuzuki616/V2bX/node"
"testing" "testing"
) )
func TestBuildV2ray(t *testing.T) { func TestBuildV2ray(t *testing.T) {
nodeInfo := &panel.NodeInfo{ nodeInfo := &panel.NodeInfo{
NodeType: "V2ray", NodeType: "v2ray",
NodeID: 1, NodeId: 1,
Port: 1145, ServerPort: 1145,
SpeedLimit: 0, Network: "ws",
AlterID: 2, NetworkSettings: nil,
TransportProtocol: "ws", Host: "test.test.tk",
Host: "test.test.tk", ServerName: "test.test.tk",
Path: "v2ray",
EnableTLS: false,
TLSType: "tls",
} }
certConfig := &CertConfig{ certConfig := &conf.CertConfig{
CertMode: "http", CertMode: "none",
CertDomain: "test.test.tk", CertDomain: "test.test.tk",
Provider: "alidns", Provider: "alidns",
Email: "test@gmail.com", Email: "test@gmail.com",
} }
config := &Config{ config := &conf.ControllerConfig{
ListenIP: "0.0.0.0",
CertConfig: certConfig, CertConfig: certConfig,
} }
_, err := buildInbound(config, nodeInfo) _, err := BuildInbound(config, nodeInfo, "11")
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)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@ -164,7 +164,7 @@ func (c *Controller) removeOldNode(oldTag string) (err error) {
} }
func (c *Controller) addNewNode(newNodeInfo *panel.NodeInfo) (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 { if err != nil {
return fmt.Errorf("build inbound error: %s", err) return fmt.Errorf("build inbound error: %s", err)
} }

View File

@ -15,6 +15,8 @@ import (
"strings" "strings"
) )
const xtlsFLow = "xtls-rprx-vision"
func (c *Controller) addNewUser(userInfo []panel.UserInfo, nodeInfo *panel.NodeInfo) (err error) { func (c *Controller) addNewUser(userInfo []panel.UserInfo, nodeInfo *panel.NodeInfo) (err error) {
users := make([]*protocol.User, 0, len(userInfo)) users := make([]*protocol.User, 0, len(userInfo))
switch nodeInfo.NodeType { 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) { func (c *Controller) buildVlessUser(userInfo *panel.UserInfo) (user *protocol.User) {
vlessAccount := &vless.Account{ vlessAccount := &vless.Account{
Id: userInfo.Uuid, Id: userInfo.Uuid,
Flow: "xtls-rprx-direct", }
if c.EnableXtls {
vlessAccount.Flow = xtlsFLow
} }
return &protocol.User{ return &protocol.User{
Level: 0, 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) { func (c *Controller) buildTrojanUser(userInfo *panel.UserInfo) (user *protocol.User) {
trojanAccount := &trojan.Account{ trojanAccount := &trojan.Account{
Password: userInfo.Uuid, Password: userInfo.Uuid,
Flow: "xtls-rprx-direct",
} }
return &protocol.User{ return &protocol.User{
Level: 0, Level: 0,