mirror of
https://github.com/wyx2685/V2bX.git
synced 2026-02-04 04:30:08 +00:00
update
refactor limiter fix getLink bug add connection limit move limit config to ControllerConfig del dynamic speed limit (next version will be re add) del online ip sync (next version will be re add)
This commit is contained in:
96
limiter/conn.go
Normal file
96
limiter/conn.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ConnLimiter struct {
|
||||
ipLimit int
|
||||
connLimit int
|
||||
count sync.Map //map[string]int
|
||||
ip sync.Map //map[string]map[string]*sync.Map
|
||||
}
|
||||
|
||||
func NewConnLimiter(conn int, ip int) *ConnLimiter {
|
||||
return &ConnLimiter{
|
||||
connLimit: conn,
|
||||
ipLimit: ip,
|
||||
count: sync.Map{},
|
||||
ip: sync.Map{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ConnLimiter) AddConnCount(user string, ip string) (limit bool) {
|
||||
if c.connLimit != 0 {
|
||||
if v, ok := c.count.Load(user); ok {
|
||||
if v.(int) >= c.connLimit {
|
||||
return true
|
||||
} else {
|
||||
c.count.Store(user, v.(int)+1)
|
||||
}
|
||||
} else {
|
||||
c.count.Store(user, 1)
|
||||
}
|
||||
}
|
||||
if c.ipLimit == 0 {
|
||||
return false
|
||||
}
|
||||
ipMap := new(sync.Map)
|
||||
ipMap.Store(ip, 1)
|
||||
if v, ok := c.ip.LoadOrStore(user, ipMap); ok {
|
||||
// have online ip
|
||||
ips := v.(*sync.Map)
|
||||
cn := 0
|
||||
if online, ok := ips.Load(ip); !ok {
|
||||
ips.Range(func(key, value interface{}) bool {
|
||||
cn++
|
||||
if cn >= c.ipLimit {
|
||||
limit = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if limit {
|
||||
return
|
||||
}
|
||||
ips.Store(ip, 1)
|
||||
} else {
|
||||
// have this ip
|
||||
ips.Store(ip, online.(int)+1)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *ConnLimiter) DelConnCount(user string, ip string) {
|
||||
if c.connLimit != 0 {
|
||||
if v, ok := c.count.Load(user); ok {
|
||||
if v.(int) == 1 {
|
||||
c.count.Delete(user)
|
||||
} else {
|
||||
c.count.Store(user, v.(int)-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.ipLimit == 0 {
|
||||
return
|
||||
}
|
||||
if i, ok := c.ip.Load(user); ok {
|
||||
is := i.(*sync.Map)
|
||||
if i, ok := is.Load(ip); ok {
|
||||
if i.(int) == 1 {
|
||||
is.Delete(ip)
|
||||
} else {
|
||||
is.Store(user, i.(int)-1)
|
||||
}
|
||||
notDel := false
|
||||
c.ip.Range(func(_, _ any) bool {
|
||||
notDel = true
|
||||
return true
|
||||
})
|
||||
if !notDel {
|
||||
c.ip.Delete(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
limiter/conn_test.go
Normal file
38
limiter/conn_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var c *ConnLimiter
|
||||
|
||||
func init() {
|
||||
c = NewConnLimiter(1, 1)
|
||||
}
|
||||
|
||||
func TestConnLimiter_AddConnCount(t *testing.T) {
|
||||
t.Log(c.AddConnCount("1", "1"))
|
||||
t.Log(c.AddConnCount("1", "2"))
|
||||
}
|
||||
|
||||
func TestConnLimiter_DelConnCount(t *testing.T) {
|
||||
t.Log(c.AddConnCount("1", "1"))
|
||||
t.Log(c.AddConnCount("1", "2"))
|
||||
c.DelConnCount("1", "1")
|
||||
t.Log(c.AddConnCount("1", "2"))
|
||||
}
|
||||
|
||||
func BenchmarkConnLimiter(b *testing.B) {
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
c.AddConnCount("1", "2")
|
||||
c.DelConnCount("1", "2")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
}
|
||||
37
limiter/dynamic.go
Normal file
37
limiter/dynamic.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (l *Limiter) AddDynamicSpeedLimit(tag string, userInfo *panel.UserInfo, limitNum int, expire int64) error {
|
||||
userLimit := &UserLimitInfo{
|
||||
DynamicSpeedLimit: limitNum,
|
||||
ExpireTime: time.Now().Add(time.Duration(expire) * time.Second).Unix(),
|
||||
}
|
||||
l.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, userInfo.Uuid, userInfo.Id), userLimit)
|
||||
return nil
|
||||
}
|
||||
|
||||
// determineSpeedLimit returns the minimum non-zero rate
|
||||
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 limit1 > limit2 {
|
||||
return limit2
|
||||
} else if limit1 < limit2 {
|
||||
return limit1
|
||||
} else {
|
||||
return limit1
|
||||
}
|
||||
}
|
||||
}
|
||||
166
limiter/limiter.go
Normal file
166
limiter/limiter.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/juju/ratelimit"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var limitLock sync.RWMutex
|
||||
var limiter map[string]*Limiter
|
||||
|
||||
func Init() {
|
||||
limiter = map[string]*Limiter{}
|
||||
}
|
||||
|
||||
type Limiter struct {
|
||||
Rules []panel.DestinationRule
|
||||
ProtocolRules []string
|
||||
SpeedLimit int
|
||||
UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo
|
||||
ConnLimiter *ConnLimiter // Key: Uid value: ConnLimiter
|
||||
SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket
|
||||
}
|
||||
|
||||
type UserLimitInfo struct {
|
||||
UID int
|
||||
SpeedLimit int
|
||||
DynamicSpeedLimit int
|
||||
ExpireTime int64
|
||||
}
|
||||
|
||||
type LimitConfig struct {
|
||||
SpeedLimit int
|
||||
IpLimit int
|
||||
ConnLimit int
|
||||
}
|
||||
|
||||
func AddLimiter(tag string, l *LimitConfig, users []panel.UserInfo) *Limiter {
|
||||
info := &Limiter{
|
||||
SpeedLimit: l.SpeedLimit,
|
||||
UserLimitInfo: new(sync.Map),
|
||||
ConnLimiter: NewConnLimiter(l.ConnLimit, l.IpLimit),
|
||||
SpeedLimiter: new(sync.Map),
|
||||
}
|
||||
for i := range users {
|
||||
if users[i].SpeedLimit != 0 {
|
||||
userLimit := &UserLimitInfo{
|
||||
UID: users[i].Id,
|
||||
SpeedLimit: users[i].SpeedLimit,
|
||||
ExpireTime: 0,
|
||||
}
|
||||
info.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d", tag, users[i].Uuid, users[i].Id), userLimit)
|
||||
}
|
||||
}
|
||||
limitLock.Lock()
|
||||
limiter[tag] = info
|
||||
limitLock.Unlock()
|
||||
return info
|
||||
}
|
||||
|
||||
func GetLimiter(tag string) (info *Limiter, err error) {
|
||||
limitLock.RLock()
|
||||
info, ok := limiter[tag]
|
||||
limitLock.RUnlock()
|
||||
if !ok {
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func UpdateLimiter(tag string, added []panel.UserInfo, deleted []panel.UserInfo) error {
|
||||
l, err := GetLimiter(tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get limit error: %s", err)
|
||||
}
|
||||
for i := range deleted {
|
||||
l.UserLimitInfo.Delete(fmt.Sprintf("%s|%s|%d",
|
||||
tag,
|
||||
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,
|
||||
}
|
||||
l.UserLimitInfo.Store(fmt.Sprintf("%s|%s|%d",
|
||||
tag,
|
||||
added[i].Uuid,
|
||||
added[i].Id), userLimit)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteLimiter(tag string) {
|
||||
limitLock.Lock()
|
||||
delete(limiter, tag)
|
||||
limitLock.Unlock()
|
||||
}
|
||||
|
||||
func (l *Limiter) CheckLimit(email string, ip string) (Bucket *ratelimit.Bucket, Reject bool) {
|
||||
// ip and conn limiter
|
||||
if l.ConnLimiter.AddConnCount(email, ip) {
|
||||
return nil, true
|
||||
}
|
||||
// check and gen speed limit Bucket
|
||||
nodeLimit := l.SpeedLimit
|
||||
userLimit := 0
|
||||
if v, ok := l.UserLimitInfo.Load(email); ok {
|
||||
u := v.(*UserLimitInfo)
|
||||
if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 {
|
||||
if u.SpeedLimit != 0 {
|
||||
userLimit = u.SpeedLimit
|
||||
u.DynamicSpeedLimit = 0
|
||||
u.ExpireTime = 0
|
||||
} else {
|
||||
l.UserLimitInfo.Delete(email)
|
||||
}
|
||||
} else {
|
||||
userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
|
||||
}
|
||||
}
|
||||
limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit
|
||||
if limit > 0 {
|
||||
Bucket = ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s
|
||||
if v, ok := l.SpeedLimiter.LoadOrStore(email, Bucket); ok {
|
||||
return v.(*ratelimit.Bucket), false
|
||||
} else {
|
||||
l.SpeedLimiter.Store(email, Bucket)
|
||||
return Bucket, false
|
||||
}
|
||||
} else {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
type UserIpList struct {
|
||||
Uid int `json:"Uid"`
|
||||
IpList []string `json:"Ips"`
|
||||
}
|
||||
|
||||
func determineDeviceLimit(nodeLimit, userLimit int) (limit int) {
|
||||
if nodeLimit == 0 || userLimit == 0 {
|
||||
if nodeLimit > userLimit {
|
||||
return nodeLimit
|
||||
} else if nodeLimit < userLimit {
|
||||
return userLimit
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
if nodeLimit > userLimit {
|
||||
return userLimit
|
||||
} else if nodeLimit < userLimit {
|
||||
return nodeLimit
|
||||
} else {
|
||||
return nodeLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
34
limiter/rule.go
Normal file
34
limiter/rule.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func (l *Limiter) CheckDomainRule(destination string) (reject bool) {
|
||||
// have rule
|
||||
for i := range l.Rules {
|
||||
if l.Rules[i].Pattern.MatchString(destination) {
|
||||
reject = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Limiter) CheckProtocolRule(protocol string) (reject bool) {
|
||||
for i := range l.ProtocolRules {
|
||||
if l.ProtocolRules[i] == protocol {
|
||||
reject = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Limiter) UpdateRule(newRuleList []panel.DestinationRule) error {
|
||||
if !reflect.DeepEqual(l.Rules, newRuleList) {
|
||||
l.Rules = newRuleList
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1
limiter/task.go
Normal file
1
limiter/task.go
Normal file
@@ -0,0 +1 @@
|
||||
package limiter
|
||||
Reference in New Issue
Block a user