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:
yuzuki999
2023-05-16 09:15:29 +08:00
parent 2d7aaef066
commit 15c36a9580
35 changed files with 564 additions and 617 deletions

96
limiter/conn.go Normal file
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
package limiter