From 48da83fc3de2828ff0aedf64befc963b5fce44e9 Mon Sep 17 00:00:00 2001 From: yuzuki999 Date: Wed, 17 May 2023 09:46:52 +0800 Subject: [PATCH] update example, add realtime option, fix ip limit bug for packet protocol --- conf/conf.go | 1 + conf/node.go | 95 ++++++++++++++++++++------------------ example/config.yml.example | 33 ++++++------- limiter/clear.go | 8 ++-- limiter/conn.go | 46 ++++++++++++------ limiter/conn_test.go | 2 +- limiter/limiter.go | 15 ++---- node/controller.go | 6 +-- node/task.go | 8 +--- 9 files changed, 114 insertions(+), 100 deletions(-) diff --git a/conf/conf.go b/conf/conf.go index ceab89f..37def33 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -58,6 +58,7 @@ func (p *Conf) Watch(filePath string, reload func()) error { case event := <-watcher.Events: if event.Has(fsnotify.Write) { log.Println("config dir changed, reloading...") + *p = *New() err := p.LoadFromPath(filePath) if err != nil { log.Printf("reload config error: %s", err) diff --git a/conf/node.go b/conf/node.go index 941c574..276b297 100644 --- a/conf/node.go +++ b/conf/node.go @@ -1,14 +1,46 @@ package conf -type CertConfig struct { - CertMode string `yaml:"CertMode"` // none, file, http, dns - RejectUnknownSni bool `yaml:"RejectUnknownSni"` - CertDomain string `yaml:"CertDomain"` - CertFile string `yaml:"CertFile"` - KeyFile string `yaml:"KeyFile"` - Provider string `yaml:"Provider"` // alidns, cloudflare, gandi, godaddy.... - Email string `yaml:"Email"` - DNSEnv map[string]string `yaml:"DNSEnv"` +type NodeConfig struct { + ApiConfig *ApiConfig `yaml:"ApiConfig"` + ControllerConfig *ControllerConfig `yaml:"ControllerConfig"` +} + +type ApiConfig struct { + APIHost string `yaml:"ApiHost"` + NodeID int `yaml:"NodeID"` + Key string `yaml:"ApiKey"` + NodeType string `yaml:"NodeType"` + Timeout int `yaml:"Timeout"` + RuleListPath string `yaml:"RuleListPath"` +} + +type ControllerConfig struct { + ListenIP string `yaml:"ListenIP"` + SendIP string `yaml:"SendIP"` + EnableDNS bool `yaml:"EnableDNS"` + DNSType string `yaml:"DNSType"` + EnableVless bool `yaml:"EnableVless"` + EnableTls bool `yaml:"EnableTls"` + 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"` + FallBackConfigs []*FallBackConfig `yaml:"FallBackConfigs"` + CertConfig *CertConfig `yaml:"CertConfig"` +} + +type LimitConfig struct { + EnableRealtime bool `yaml:"EnableRealtime"` + SpeedLimit int `yaml:"SpeedLimit"` + IPLimit int `yaml:"DeviceLimit"` + ConnLimit int `yaml:"ConnLimit"` + EnableIpRecorder bool `yaml:"EnableIpRecorder"` + IpRecorderConfig *IpReportConfig `yaml:"IpRecorderConfig"` + EnableDynamicSpeedLimit bool `yaml:"EnableDynamicSpeedLimit"` + DynamicSpeedLimitConfig *DynamicSpeedLimitConfig `yaml:"DynamicSpeedLimitConfig"` } type FallBackConfig struct { @@ -47,40 +79,13 @@ type DynamicSpeedLimitConfig struct { ExpireTime int `yaml:"ExpireTime"` } -type ControllerConfig struct { - ListenIP string `yaml:"ListenIP"` - SendIP string `yaml:"SendIP"` - EnableDNS bool `yaml:"EnableDNS"` - DNSType string `yaml:"DNSType"` - EnableVless bool `yaml:"EnableVless"` - EnableTls bool `yaml:"EnableTls"` - SpeedLimit int `yaml:"SpeedLimit"` - IPLimit int `yaml:"DeviceLimit"` - ConnLimit int `yaml:"ConnLimit"` - DisableUploadTraffic bool `yaml:"DisableUploadTraffic"` - DisableGetRule bool `yaml:"DisableGetRule"` - EnableProxyProtocol bool `yaml:"EnableProxyProtocol"` - EnableFallback bool `yaml:"EnableFallback"` - DisableIVCheck bool `yaml:"DisableIVCheck"` - DisableSniffing bool `yaml:"DisableSniffing"` - FallBackConfigs []*FallBackConfig `yaml:"FallBackConfigs"` - EnableIpRecorder bool `yaml:"EnableIpRecorder"` - IpRecorderConfig *IpReportConfig `yaml:"IpRecorderConfig"` - EnableDynamicSpeedLimit bool `yaml:"EnableDynamicSpeedLimit"` - DynamicSpeedLimitConfig *DynamicSpeedLimitConfig `yaml:"DynamicSpeedLimitConfig"` - CertConfig *CertConfig `yaml:"CertConfig"` -} - -type ApiConfig struct { - APIHost string `yaml:"ApiHost"` - NodeID int `yaml:"NodeID"` - Key string `yaml:"ApiKey"` - NodeType string `yaml:"NodeType"` - Timeout int `yaml:"Timeout"` - RuleListPath string `yaml:"RuleListPath"` -} - -type NodeConfig struct { - ApiConfig *ApiConfig `yaml:"ApiConfig"` - ControllerConfig *ControllerConfig `yaml:"ControllerConfig"` +type CertConfig struct { + CertMode string `yaml:"CertMode"` // none, file, http, dns + RejectUnknownSni bool `yaml:"RejectUnknownSni"` + CertDomain string `yaml:"CertDomain"` + CertFile string `yaml:"CertFile"` + KeyFile string `yaml:"KeyFile"` + Provider string `yaml:"Provider"` // alidns, cloudflare, gandi, godaddy.... + Email string `yaml:"Email"` + DNSEnv map[string]string `yaml:"DNSEnv"` } diff --git a/example/config.yml.example b/example/config.yml.example index c5ebd08..a752d0a 100644 --- a/example/config.yml.example +++ b/example/config.yml.example @@ -13,33 +13,34 @@ ConnetionConfig: DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second BufferSize: 64 # The internal cache size of each connection, kB Nodes: - - - ApiConfig: + - ApiConfig: ApiHost: "http://127.0.0.1:667" ApiKey: "123" NodeID: 41 NodeType: V2ray # Node type: V2ray, Shadowsocks, Trojan Timeout: 30 # Timeout for the api request - EnableVless: false # Enable Vless for V2ray Type - SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable - DeviceLimit: 0 # Local settings will replace remote settings, 0 means disable RuleListPath: # /etc/XrayR/rulelist Path to local rulelist file ControllerConfig: ListenIP: 0.0.0.0 # IP address you want to listen SendIP: 0.0.0.0 # IP address you want to send pacakage EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy + EnableVless: false # Enable Vless for V2ray Type EnableProxyProtocol: false # Only works for WebSocket and TCP EnableFallback: false # Only support for Trojan and Vless - FallBackConfigs: # Support multiple fallbacks - - - SNI: # TLS SNI(Server Name Indication), Empty for any + FallBackConfigs: # Support multiple fallbacks + - SNI: # TLS SNI(Server Name Indication), Empty for any Alpn: # Alpn, 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. ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for dsable - EnableIpRecorder: false # Enable online ip report - IpRecorderConfig: + LimitConfig: + EnableRealtime: false # Check device limit on real time + SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable + DeviceLimit: 0 # Local settings will replace remote settings, 0 means disable + ConnLimit: 0 # Connecting limit, only working for TCP, 0mean + EnableIpRecorder: false # Enable online ip report + IpRecorderConfig: Type: "Recorder" # Recorder type: Recorder, Redis RecorderConfig: Url: "http://127.0.0.1:123" # Report url @@ -52,12 +53,12 @@ Nodes: Expiry: 60 # redis expiry time, sec. Periodic: 60 # Report interval, sec. EnableIpSync: false # Enable online ip sync - EnableDynamicSpeedLimit: false # Enable dynamic speed limit - DynamicSpeedLimitConfig: - Periodic: 60 # Time to check the user traffic , sec. - Traffic: 0 # Traffic limit, MB - SpeedLimit: 0 # Speed limit, Mbps - ExpireTime: 0 # Time limit, sec. + EnableDynamicSpeedLimit: false # Enable dynamic speed limit + DynamicSpeedLimitConfig: + Periodic: 60 # Time to check the user traffic , sec. + Traffic: 0 # Traffic limit, MB + SpeedLimit: 0 # Speed limit, Mbps + ExpireTime: 0 # Time limit, sec. CertConfig: CertMode: dns # Option about how to get certificate: none, file, http, dns. Choose "none" will forcedly disable the tls config. CertDomain: "node1.test.com" # Domain to cert diff --git a/limiter/clear.go b/limiter/clear.go index 14a49c7..47db76f 100644 --- a/limiter/clear.go +++ b/limiter/clear.go @@ -2,13 +2,13 @@ package limiter import "log" -func ClearPacketOnlineIP() error { - log.Println("Limiter: Clear packet online ip...") +func ClearOnlineIP() error { + log.Println("Limiter: Clear online ip...") limitLock.RLock() for _, l := range limiter { - l.ConnLimiter.ClearPacketOnlineIP() + l.ConnLimiter.ClearOnlineIP() } limitLock.RUnlock() - log.Println("Limiter: Clear packet online ip done") + log.Println("Limiter: Clear online ip done") return nil } diff --git a/limiter/conn.go b/limiter/conn.go index 4e88fd1..290e410 100644 --- a/limiter/conn.go +++ b/limiter/conn.go @@ -5,15 +5,16 @@ import ( ) type ConnLimiter struct { - realTime bool + realtime bool ipLimit int connLimit int count sync.Map // map[string]int ip sync.Map // map[string]map[string]int } -func NewConnLimiter(conn int, ip int) *ConnLimiter { +func NewConnLimiter(conn int, ip int, realtime bool) *ConnLimiter { return &ConnLimiter{ + realtime: realtime, connLimit: conn, ipLimit: ip, count: sync.Map{}, @@ -38,10 +39,14 @@ func (c *ConnLimiter) AddConnCount(user string, ip string, isTcp bool) (limit bo } // default user map ipMap := new(sync.Map) - if isTcp { - ipMap.Store(ip, 2) + if c.realtime { + if isTcp { + ipMap.Store(ip, 2) + } else { + ipMap.Store(ip, 1) + } } else { - ipMap.Store(ip, 1) + ipMap.Store(ip, struct{}{}) } // check user online ip if v, ok := c.ip.LoadOrStore(user, ipMap); ok { @@ -50,9 +55,11 @@ func (c *ConnLimiter) AddConnCount(user string, ip string, isTcp bool) (limit bo cn := 0 if online, ok := ips.Load(ip); ok { // online ip - if isTcp { - // count add - ips.Store(ip, online.(int)+2) + if c.realtime { + if online.(int)%2 == 0 && isTcp { + // count add + ips.Store(ip, online.(int)+2) + } } } else { // not online ip @@ -67,10 +74,14 @@ func (c *ConnLimiter) AddConnCount(user string, ip string, isTcp bool) (limit bo if limit { return } - if isTcp { - ips.Store(ip, 2) + if c.realtime { + if isTcp { + ips.Store(ip, 2) + } else { + ips.Store(ip, 1) + } } else { - ips.Store(ip, 1) + ips.Store(ip, struct{}{}) } } } @@ -79,6 +90,9 @@ func (c *ConnLimiter) AddConnCount(user string, ip string, isTcp bool) (limit bo // DelConnCount Delete tcp connection count, no tcp do not use func (c *ConnLimiter) DelConnCount(user string, ip string) { + if !c.realtime { + return + } if c.connLimit != 0 { if v, ok := c.count.Load(user); ok { if v.(int) == 1 { @@ -111,12 +125,18 @@ func (c *ConnLimiter) DelConnCount(user string, ip string) { } } -// ClearPacketOnlineIP Clear udp,icmp and other packet protocol online ip -func (c *ConnLimiter) ClearPacketOnlineIP() { +// ClearOnlineIP Clear udp,icmp and other packet protocol online ip +func (c *ConnLimiter) ClearOnlineIP() { c.ip.Range(func(_, v any) bool { userIp := v.(*sync.Map) userIp.Range(func(ip, v any) bool { + if c.realtime { + // clear not realtime ip + userIp.Delete(ip) + return true + } if v.(int) == 1 { + // clear packet ip for realtime userIp.Delete(ip) } return true diff --git a/limiter/conn_test.go b/limiter/conn_test.go index ad3bacd..cd739bb 100644 --- a/limiter/conn_test.go +++ b/limiter/conn_test.go @@ -26,7 +26,7 @@ func TestConnLimiter_DelConnCount(t *testing.T) { func TestConnLimiter_ClearPacketOnlineIP(t *testing.T) { t.Log(c.AddConnCount("1", "1", false)) t.Log(c.AddConnCount("1", "2", false)) - c.ClearPacketOnlineIP() + c.ClearOnlineIP() t.Log(c.AddConnCount("1", "2", true)) c.DelConnCount("1", "2") t.Log(c.AddConnCount("1", "1", false)) diff --git a/limiter/limiter.go b/limiter/limiter.go index 5dd55a4..baa38f0 100644 --- a/limiter/limiter.go +++ b/limiter/limiter.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "github.com/Yuzuki616/V2bX/api/panel" + "github.com/Yuzuki616/V2bX/conf" "github.com/juju/ratelimit" "github.com/xtls/xray-core/common/task" "log" @@ -18,10 +19,10 @@ func Init() { limiter = map[string]*Limiter{} c := task.Periodic{ Interval: time.Minute * 2, - Execute: ClearPacketOnlineIP, + Execute: ClearOnlineIP, } go func() { - log.Println("Limiter: ClearPacketOnlineIP started") + log.Println("Limiter: ClearOnlineIP started") time.Sleep(time.Minute * 2) c.Start() }() @@ -43,17 +44,11 @@ type UserLimitInfo struct { ExpireTime int64 } -type LimitConfig struct { - SpeedLimit int - IpLimit int - ConnLimit int -} - -func AddLimiter(tag string, l *LimitConfig, users []panel.UserInfo) *Limiter { +func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo) *Limiter { info := &Limiter{ SpeedLimit: l.SpeedLimit, UserLimitInfo: new(sync.Map), - ConnLimiter: NewConnLimiter(l.ConnLimit, l.IpLimit), + ConnLimiter: NewConnLimiter(l.ConnLimit, l.IPLimit, l.EnableRealtime), SpeedLimiter: new(sync.Map), } for i := range users { diff --git a/node/controller.go b/node/controller.go index d5d68a9..785d412 100644 --- a/node/controller.go +++ b/node/controller.go @@ -58,11 +58,7 @@ func (c *Controller) Start() error { c.Tag = c.buildNodeTag() // add limiter - l := limiter.AddLimiter(c.Tag, &limiter.LimitConfig{ - SpeedLimit: c.SpeedLimit, - IpLimit: c.IPLimit, - ConnLimit: c.ConnLimit, - }, c.userList) + l := limiter.AddLimiter(c.Tag, &c.LimitConfig, c.userList) // add rule limiter if !c.DisableGetRule { if err = l.UpdateRule(c.nodeInfo.Rules); err != nil { diff --git a/node/task.go b/node/task.go index 3267f44..e6ed333 100644 --- a/node/task.go +++ b/node/task.go @@ -88,11 +88,7 @@ func (c *Controller) nodeInfoMonitor() (err error) { if nodeInfoChanged { c.userList = newUserInfo // Add new Limiter - l := limiter.AddLimiter(c.Tag, &limiter.LimitConfig{ - SpeedLimit: c.SpeedLimit, - IpLimit: c.IPLimit, - ConnLimit: c.ConnLimit, - }, newUserInfo) + l := limiter.AddLimiter(c.Tag, &c.LimitConfig, newUserInfo) err = c.addNewUser(newUserInfo, newNodeInfo) if err != nil { log.Print(err) @@ -243,7 +239,7 @@ func (c *Controller) 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.EnableDynamicSpeedLimit { + if c.LimitConfig.EnableDynamicSpeedLimit { c.userList[i].Traffic += up + down } userTraffic = append(userTraffic, panel.UserTraffic{