mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-14 00:51:12 +03:00
197 lines
4.9 KiB
Go
197 lines
4.9 KiB
Go
package rate
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/AliRizaAynaci/gorl/v2"
|
|
"github.com/AliRizaAynaci/gorl/v2/core"
|
|
"github.com/sagernet/sing-box/adapter"
|
|
"github.com/sagernet/sing-box/log"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
"github.com/shtorm-7/go-cache/v2"
|
|
)
|
|
|
|
type (
|
|
ConnIDGetter = func(context.Context, *adapter.InboundContext) (string, bool)
|
|
RateGetter = func(id string) error
|
|
|
|
RateStrategy interface {
|
|
request(ctx context.Context, metadata *adapter.InboundContext) error
|
|
}
|
|
)
|
|
|
|
type DefaultRateStrategy struct {
|
|
limiter core.Limiter
|
|
queue chan struct{}
|
|
}
|
|
|
|
func NewDefaultRateStrategy(strategy string, count int, interval time.Duration) (*DefaultRateStrategy, error) {
|
|
limiter, err := gorl.New(core.Config{
|
|
Strategy: core.StrategyType(strings.ReplaceAll(strategy, "-", "_")),
|
|
Limit: count,
|
|
Window: interval,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &DefaultRateStrategy{limiter: limiter, queue: make(chan struct{}, 1)}, nil
|
|
}
|
|
|
|
func (s *DefaultRateStrategy) request(ctx context.Context, metadata *adapter.InboundContext) error {
|
|
select {
|
|
case s.queue <- struct{}{}:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
defer func() {
|
|
<-s.queue
|
|
}()
|
|
r, err := s.limiter.Allow(ctx, metadata.Destination.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !r.Allowed {
|
|
select {
|
|
case <-time.After(r.RetryAfter):
|
|
_, err = s.limiter.Allow(ctx, metadata.Destination.String())
|
|
return err
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *DefaultRateStrategy) close() error {
|
|
return s.limiter.Close()
|
|
}
|
|
|
|
type ConnectionRateStrategy struct {
|
|
connIDGetter ConnIDGetter
|
|
createStrategy func() (*DefaultRateStrategy, error)
|
|
limiters *cache.Cache[string, *DefaultRateStrategy]
|
|
|
|
mtx sync.Mutex
|
|
}
|
|
|
|
func NewConnectionRateStrategy(connIDGetter ConnIDGetter, strategy string, count int, interval time.Duration) (*ConnectionRateStrategy, error) {
|
|
limiters := cache.New[string, *DefaultRateStrategy](interval, time.Second)
|
|
limiters.OnEvicted(func(s string, strategy *DefaultRateStrategy) {
|
|
strategy.close()
|
|
})
|
|
return &ConnectionRateStrategy{
|
|
connIDGetter: connIDGetter,
|
|
createStrategy: func() (*DefaultRateStrategy, error) {
|
|
return NewDefaultRateStrategy(strategy, count, interval)
|
|
},
|
|
limiters: limiters,
|
|
}, nil
|
|
}
|
|
|
|
func (s *ConnectionRateStrategy) request(ctx context.Context, metadata *adapter.InboundContext) error {
|
|
id, ok := s.connIDGetter(ctx, metadata)
|
|
if !ok {
|
|
return E.New("id not found")
|
|
}
|
|
s.mtx.Lock()
|
|
strategy, ok := s.limiters.Get(id)
|
|
if !ok {
|
|
newStrategy, err := s.createStrategy()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.limiters.SetDefault(id, newStrategy)
|
|
strategy = newStrategy
|
|
} else {
|
|
s.limiters.UpdateExpirationDefault(id)
|
|
}
|
|
s.mtx.Unlock()
|
|
return strategy.request(ctx, metadata)
|
|
}
|
|
|
|
type UsersRateStrategy struct {
|
|
strategies map[string]RateStrategy
|
|
mtx sync.Mutex
|
|
}
|
|
|
|
func NewUsersRateStrategy(strategies map[string]RateStrategy) *UsersRateStrategy {
|
|
return &UsersRateStrategy{
|
|
strategies: strategies,
|
|
}
|
|
}
|
|
|
|
func (s *UsersRateStrategy) request(ctx context.Context, metadata *adapter.InboundContext) error {
|
|
s.mtx.Lock()
|
|
defer s.mtx.Unlock()
|
|
var user string
|
|
if metadata != nil {
|
|
user = metadata.User
|
|
}
|
|
strategy, ok := s.strategies[user]
|
|
if ok {
|
|
return strategy.request(ctx, metadata)
|
|
}
|
|
return E.New("user strategy not found: ", user)
|
|
}
|
|
|
|
type ManagerRateStrategy struct {
|
|
*UsersRateStrategy
|
|
}
|
|
|
|
func NewManagerRateStrategy() *ManagerRateStrategy {
|
|
return &ManagerRateStrategy{
|
|
UsersRateStrategy: NewUsersRateStrategy(map[string]RateStrategy{}),
|
|
}
|
|
}
|
|
|
|
func (s *ManagerRateStrategy) UpdateStrategies(strategies map[string]RateStrategy) {
|
|
s.mtx.Lock()
|
|
defer s.mtx.Unlock()
|
|
s.strategies = strategies
|
|
}
|
|
|
|
type BypassRateStrategy struct{}
|
|
|
|
func NewBypassRateStrategy() *BypassRateStrategy {
|
|
return &BypassRateStrategy{}
|
|
}
|
|
|
|
func (s *BypassRateStrategy) request(ctx context.Context, metadata *adapter.InboundContext) error {
|
|
return nil
|
|
}
|
|
|
|
func CreateStrategy(strategy string, connectionType string, count int, interval time.Duration) (RateStrategy, error) {
|
|
if strategy == "bypass" {
|
|
return NewBypassRateStrategy(), nil
|
|
}
|
|
var connIDGetter ConnIDGetter
|
|
switch connectionType {
|
|
case "hwid":
|
|
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
|
id, ok := ctx.Value("hwid").(string)
|
|
return id, ok
|
|
}
|
|
case "mux":
|
|
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
|
id, ok := log.MuxIDFromContext(ctx)
|
|
if !ok {
|
|
return "", ok
|
|
}
|
|
return strconv.FormatUint(uint64(id.ID), 10), ok
|
|
}
|
|
case "source_ip":
|
|
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
|
return metadata.Source.IPAddr().String(), true
|
|
}
|
|
case "default", "":
|
|
return NewDefaultRateStrategy(strategy, count, interval)
|
|
default:
|
|
return nil, E.New("connection type not found: ", connectionType)
|
|
}
|
|
return NewConnectionRateStrategy(connIDGetter, strategy, count, interval)
|
|
}
|