mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
feat: add bandwidth limiter
This commit is contained in:
parent
ae8187ed15
commit
ab1412a0c9
@ -70,6 +70,7 @@ type Rule interface {
|
|||||||
Match(metadata *InboundContext) bool
|
Match(metadata *InboundContext) bool
|
||||||
Outbound() string
|
Outbound() string
|
||||||
String() string
|
String() string
|
||||||
|
Limiters() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSRule interface {
|
type DNSRule interface {
|
||||||
|
4
box.go
4
box.go
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/inbound"
|
"github.com/sagernet/sing-box/inbound"
|
||||||
|
"github.com/sagernet/sing-box/limiter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
@ -76,6 +77,9 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
|
if len(options.Limiters) > 0 {
|
||||||
|
ctx = limiter.WithDefault(ctx, logFactory.NewLogger("limiter"), options.Limiters)
|
||||||
|
}
|
||||||
router, err := route.NewRouter(
|
router, err := route.NewRouter(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory,
|
logFactory,
|
||||||
|
@ -11,6 +11,7 @@ sing-box uses JSON for configuration files.
|
|||||||
"ntp": {},
|
"ntp": {},
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
|
"limiters": [],
|
||||||
"route": {},
|
"route": {},
|
||||||
"experimental": {}
|
"experimental": {}
|
||||||
}
|
}
|
||||||
@ -25,6 +26,7 @@ sing-box uses JSON for configuration files.
|
|||||||
| `ntp` | [NTP](./ntp) |
|
| `ntp` | [NTP](./ntp) |
|
||||||
| `inbounds` | [Inbound](./inbound) |
|
| `inbounds` | [Inbound](./inbound) |
|
||||||
| `outbounds` | [Outbound](./outbound) |
|
| `outbounds` | [Outbound](./outbound) |
|
||||||
|
| `limiters` | [Limiter](./limiter) |
|
||||||
| `route` | [Route](./route) |
|
| `route` | [Route](./route) |
|
||||||
| `experimental` | [Experimental](./experimental) |
|
| `experimental` | [Experimental](./experimental) |
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
"dns": {},
|
"dns": {},
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
|
"limiters": [],
|
||||||
"route": {},
|
"route": {},
|
||||||
"experimental": {}
|
"experimental": {}
|
||||||
}
|
}
|
||||||
@ -23,6 +24,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
| `dns` | [DNS](./dns) |
|
| `dns` | [DNS](./dns) |
|
||||||
| `inbounds` | [入站](./inbound) |
|
| `inbounds` | [入站](./inbound) |
|
||||||
| `outbounds` | [出站](./outbound) |
|
| `outbounds` | [出站](./outbound) |
|
||||||
|
| `limiters` | [限速](./limiter) |
|
||||||
| `route` | [路由](./route) |
|
| `route` | [路由](./route) |
|
||||||
| `experimental` | [实验性](./experimental) |
|
| `experimental` | [实验性](./experimental) |
|
||||||
|
|
||||||
|
50
docs/configuration/limiter/index.md
Normal file
50
docs/configuration/limiter/index.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Limiter
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"limiters": [
|
||||||
|
{
|
||||||
|
"tag": "limiter-a",
|
||||||
|
"download": "1M",
|
||||||
|
"upload": "10M",
|
||||||
|
"auth_user": [
|
||||||
|
"user-a",
|
||||||
|
"user-b"
|
||||||
|
],
|
||||||
|
"inbound": [
|
||||||
|
"in-a",
|
||||||
|
"in-b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### download upload
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Format: `[Integer][Unit]` e.g. `100M, 100m, 1G, 1g`.
|
||||||
|
|
||||||
|
Supported units (case insensitive): `B, K, M, G, T, P, E`.
|
||||||
|
|
||||||
|
#### tag
|
||||||
|
|
||||||
|
The tag of the limiter, used in route rule.
|
||||||
|
|
||||||
|
#### auth_user
|
||||||
|
|
||||||
|
Global limiter for a group of usernames, see each inbound for details.
|
||||||
|
|
||||||
|
#### inbound
|
||||||
|
|
||||||
|
Global limiter for a group of inbounds.
|
||||||
|
|
||||||
|
!!! info ""
|
||||||
|
|
||||||
|
All the auth_users, inbounds and route rule with limiter tag share the same limiter. To take effect independently, configure limiters seperately.
|
50
docs/configuration/limiter/index.zh.md
Normal file
50
docs/configuration/limiter/index.zh.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# 限速
|
||||||
|
|
||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"limiters": [
|
||||||
|
{
|
||||||
|
"tag": "limiter-a",
|
||||||
|
"download": "1M",
|
||||||
|
"upload": "10M",
|
||||||
|
"auth_user": [
|
||||||
|
"user-a",
|
||||||
|
"user-b"
|
||||||
|
],
|
||||||
|
"inbound": [
|
||||||
|
"in-a",
|
||||||
|
"in-b"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### download upload
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
格式: `[Integer][Unit]` 例如: `100M, 100m, 1G, 1g`.
|
||||||
|
|
||||||
|
支持的单位 (大小写不敏感): `B, K, M, G, T, P, E`.
|
||||||
|
|
||||||
|
#### tag
|
||||||
|
|
||||||
|
限速标签,在路由规则中使用。
|
||||||
|
|
||||||
|
#### auth_user
|
||||||
|
|
||||||
|
用户组全局限速,参阅入站设置。
|
||||||
|
|
||||||
|
#### inbound
|
||||||
|
|
||||||
|
入站组全局限速。
|
||||||
|
|
||||||
|
!!! info ""
|
||||||
|
|
||||||
|
所有用户、入站和有限速标签的路由规则共享同一个限速。为了独立生效,请分别配置限速器。
|
@ -84,14 +84,22 @@
|
|||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
"clash_mode": "direct",
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": "direct"
|
"outbound": "direct",
|
||||||
|
"limiter": [
|
||||||
|
"limiter-a",
|
||||||
|
"limiter-b"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": "direct"
|
"outbound": "direct",
|
||||||
|
"limiter": [
|
||||||
|
"limiter-a",
|
||||||
|
"limiter-b"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -238,6 +246,10 @@ Invert match result.
|
|||||||
|
|
||||||
Tag of the target outbound.
|
Tag of the target outbound.
|
||||||
|
|
||||||
|
#### limiter
|
||||||
|
|
||||||
|
Tags of [Limiter](/configuration/limiter). Take effect for all connections matching this rule.
|
||||||
|
|
||||||
### Logical Fields
|
### Logical Fields
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
@ -82,14 +82,22 @@
|
|||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
"clash_mode": "direct",
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": "direct"
|
"outbound": "direct",
|
||||||
|
"limiter": [
|
||||||
|
"limiter-a",
|
||||||
|
"limiter-b"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": "direct"
|
"outbound": "direct",
|
||||||
|
"limiter": [
|
||||||
|
"limiter-a",
|
||||||
|
"limiter-b"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -236,6 +244,10 @@
|
|||||||
|
|
||||||
目标出站的标签。
|
目标出站的标签。
|
||||||
|
|
||||||
|
#### limiter
|
||||||
|
|
||||||
|
[限速](/zh/configuration/inbound) 标签。对所有匹配该规则的连接生效。
|
||||||
|
|
||||||
### 逻辑字段
|
### 逻辑字段
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
106
limiter/builder.go
Normal file
106
limiter/builder.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/humanize"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
limiterTag = "tag"
|
||||||
|
limiterUser = "user"
|
||||||
|
limiterInbound = "inbound"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Manager = (*defaultManager)(nil)
|
||||||
|
|
||||||
|
type defaultManager struct {
|
||||||
|
mp *sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithDefault(ctx context.Context, logger log.ContextLogger, options []option.Limiter) context.Context {
|
||||||
|
m := &defaultManager{mp: &sync.Map{}}
|
||||||
|
for i, option := range options {
|
||||||
|
if err := m.createLimiter(ctx, option); err != nil {
|
||||||
|
logger.ErrorContext(ctx, fmt.Sprintf("id=%d, %s", i, err))
|
||||||
|
} else {
|
||||||
|
logger.InfoContext(ctx, fmt.Sprintf("id=%d, tag=%s, users=%v, inbounds=%v, download=%s, upload=%s",
|
||||||
|
i, option.Tag, option.AuthUser, option.Inbound, option.Download, option.Upload))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return service.ContextWith[Manager](ctx, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildKey(prefix string, tag string) string {
|
||||||
|
return fmt.Sprintf("%s|%s", prefix, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultManager) createLimiter(ctx context.Context, option option.Limiter) (err error) {
|
||||||
|
var download, upload uint64
|
||||||
|
if len(option.Download) > 0 {
|
||||||
|
download, err = humanize.ParseBytes(option.Download)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(option.Upload) > 0 {
|
||||||
|
upload, err = humanize.ParseBytes(option.Upload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if download == 0 && upload == 0 {
|
||||||
|
return E.New("download/upload, at least one must be set")
|
||||||
|
}
|
||||||
|
l := newLimiter(download, upload)
|
||||||
|
valid := false
|
||||||
|
if len(option.Tag) > 0 {
|
||||||
|
valid = true
|
||||||
|
m.mp.Store(buildKey(limiterTag, option.Tag), l)
|
||||||
|
}
|
||||||
|
if len(option.AuthUser) > 0 {
|
||||||
|
valid = true
|
||||||
|
for _, user := range option.AuthUser {
|
||||||
|
m.mp.Store(buildKey(limiterUser, user), l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(option.Inbound) > 0 {
|
||||||
|
valid = true
|
||||||
|
for _, inbound := range option.Inbound {
|
||||||
|
m.mp.Store(buildKey(limiterInbound, inbound), l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
return E.New("tag/user/inbound, at least one must be set")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultManager) LoadLimiters(tags []string, user, inbound string) (limiters []*limiter) {
|
||||||
|
for _, t := range tags {
|
||||||
|
if v, ok := m.mp.Load(buildKey(limiterTag, t)); ok {
|
||||||
|
limiters = append(limiters, v.(*limiter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := m.mp.Load(buildKey(limiterUser, user)); ok {
|
||||||
|
limiters = append(limiters, v.(*limiter))
|
||||||
|
}
|
||||||
|
if v, ok := m.mp.Load(buildKey(limiterInbound, inbound)); ok {
|
||||||
|
limiters = append(limiters, v.(*limiter))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultManager) NewConnWithLimiters(ctx context.Context, conn net.Conn, limiters []*limiter) net.Conn {
|
||||||
|
for _, limiter := range limiters {
|
||||||
|
conn = &connWithLimiter{Conn: conn, limiter: limiter, ctx: ctx}
|
||||||
|
}
|
||||||
|
return conn
|
||||||
|
}
|
77
limiter/limiter.go
Normal file
77
limiter/limiter.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type limiter struct {
|
||||||
|
downloadLimiter *rate.Limiter
|
||||||
|
uploadLimiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLimiter(download, upload uint64) *limiter {
|
||||||
|
var downloadLimiter, uploadLimiter *rate.Limiter
|
||||||
|
if download > 0 {
|
||||||
|
downloadLimiter = rate.NewLimiter(rate.Limit(float64(download)), int(download))
|
||||||
|
}
|
||||||
|
if upload > 0 {
|
||||||
|
uploadLimiter = rate.NewLimiter(rate.Limit(float64(upload)), int(upload))
|
||||||
|
}
|
||||||
|
return &limiter{downloadLimiter: downloadLimiter, uploadLimiter: uploadLimiter}
|
||||||
|
}
|
||||||
|
|
||||||
|
type connWithLimiter struct {
|
||||||
|
net.Conn
|
||||||
|
limiter *limiter
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *connWithLimiter) Read(p []byte) (n int, err error) {
|
||||||
|
if conn.limiter == nil || conn.limiter.downloadLimiter == nil {
|
||||||
|
return conn.Conn.Read(p)
|
||||||
|
}
|
||||||
|
b := conn.limiter.downloadLimiter.Burst()
|
||||||
|
if b < len(p) {
|
||||||
|
p = p[:b]
|
||||||
|
}
|
||||||
|
n, err = conn.Conn.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.limiter.downloadLimiter.WaitN(conn.ctx, n)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *connWithLimiter) Write(p []byte) (n int, err error) {
|
||||||
|
if conn.limiter == nil || conn.limiter.uploadLimiter == nil {
|
||||||
|
return conn.Conn.Write(p)
|
||||||
|
}
|
||||||
|
var nn int
|
||||||
|
b := conn.limiter.uploadLimiter.Burst()
|
||||||
|
for {
|
||||||
|
end := len(p)
|
||||||
|
if end == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if b < len(p) {
|
||||||
|
end = b
|
||||||
|
}
|
||||||
|
err = conn.limiter.uploadLimiter.WaitN(conn.ctx, end)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nn, err = conn.Conn.Write(p[:end])
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p = p[end:]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
11
limiter/manager.go
Normal file
11
limiter/manager.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
LoadLimiters(tags []string, user, inbound string) []*limiter
|
||||||
|
NewConnWithLimiters(ctx context.Context, conn net.Conn, limiters []*limiter) net.Conn
|
||||||
|
}
|
@ -16,6 +16,7 @@ type _Options struct {
|
|||||||
Inbounds []Inbound `json:"inbounds,omitempty"`
|
Inbounds []Inbound `json:"inbounds,omitempty"`
|
||||||
Outbounds []Outbound `json:"outbounds,omitempty"`
|
Outbounds []Outbound `json:"outbounds,omitempty"`
|
||||||
Route *RouteOptions `json:"route,omitempty"`
|
Route *RouteOptions `json:"route,omitempty"`
|
||||||
|
Limiters []Limiter `json:"limiters,omitempty"`
|
||||||
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
option/limiter.go
Normal file
9
option/limiter.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
type Limiter struct {
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Download string `json:"download,omitempty"`
|
||||||
|
Upload string `json:"upload,omitempty"`
|
||||||
|
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
}
|
@ -80,6 +80,7 @@ type DefaultRule struct {
|
|||||||
ClashMode string `json:"clash_mode,omitempty"`
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
Invert bool `json:"invert,omitempty"`
|
||||||
Outbound string `json:"outbound,omitempty"`
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
Limiter Listable[string] `json:"limiter,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r DefaultRule) IsValid() bool {
|
func (r DefaultRule) IsValid() bool {
|
||||||
@ -90,10 +91,11 @@ func (r DefaultRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LogicalRule struct {
|
type LogicalRule struct {
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
Rules []DefaultRule `json:"rules,omitempty"`
|
Rules []DefaultRule `json:"rules,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
Invert bool `json:"invert,omitempty"`
|
||||||
Outbound string `json:"outbound,omitempty"`
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
Limiter Listable[string] `json:"limiter,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r LogicalRule) IsValid() bool {
|
func (r LogicalRule) IsValid() bool {
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/sniff"
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
|
"github.com/sagernet/sing-box/limiter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/ntp"
|
"github.com/sagernet/sing-box/ntp"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@ -85,6 +86,7 @@ type Router struct {
|
|||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
v2rayServer adapter.V2RayServer
|
v2rayServer adapter.V2RayServer
|
||||||
|
limiterManager limiter.Manager
|
||||||
platformInterface platform.Interface
|
platformInterface platform.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,6 +500,9 @@ func (r *Router) Start() error {
|
|||||||
return E.Cause(err, "initialize time service")
|
return E.Cause(err, "initialize time service")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if limiterManger := service.FromContext[limiter.Manager](r.ctx); limiterManger != nil {
|
||||||
|
r.limiterManager = limiterManger
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -701,6 +706,18 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
if !common.Contains(detour.Network(), N.NetworkTCP) {
|
if !common.Contains(detour.Network(), N.NetworkTCP) {
|
||||||
return E.New("missing supported outbound, closing connection")
|
return E.New("missing supported outbound, closing connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.limiterManager != nil {
|
||||||
|
var limiterTags []string
|
||||||
|
if matchedRule != nil {
|
||||||
|
limiterTags = matchedRule.Limiters()
|
||||||
|
}
|
||||||
|
limiters := r.limiterManager.LoadLimiters(limiterTags, metadata.User, metadata.Inbound)
|
||||||
|
if len(limiters) > 0 {
|
||||||
|
conn = r.limiterManager.NewConnWithLimiters(ctx, conn, limiters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if r.clashServer != nil {
|
if r.clashServer != nil {
|
||||||
trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, matchedRule)
|
trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, matchedRule)
|
||||||
defer tracker.Leave()
|
defer tracker.Leave()
|
||||||
|
@ -18,6 +18,7 @@ type abstractDefaultRule struct {
|
|||||||
allItems []RuleItem
|
allItems []RuleItem
|
||||||
invert bool
|
invert bool
|
||||||
outbound string
|
outbound string
|
||||||
|
limiters []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *abstractDefaultRule) Type() string {
|
func (r *abstractDefaultRule) Type() string {
|
||||||
@ -126,6 +127,10 @@ func (r *abstractDefaultRule) Outbound() string {
|
|||||||
return r.outbound
|
return r.outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Limiters() []string {
|
||||||
|
return r.limiters
|
||||||
|
}
|
||||||
|
|
||||||
func (r *abstractDefaultRule) String() string {
|
func (r *abstractDefaultRule) String() string {
|
||||||
if !r.invert {
|
if !r.invert {
|
||||||
return strings.Join(F.MapToString(r.allItems), " ")
|
return strings.Join(F.MapToString(r.allItems), " ")
|
||||||
@ -139,6 +144,7 @@ type abstractLogicalRule struct {
|
|||||||
mode string
|
mode string
|
||||||
invert bool
|
invert bool
|
||||||
outbound string
|
outbound string
|
||||||
|
limiters []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *abstractLogicalRule) Type() string {
|
func (r *abstractLogicalRule) Type() string {
|
||||||
@ -191,6 +197,10 @@ func (r *abstractLogicalRule) Outbound() string {
|
|||||||
return r.outbound
|
return r.outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Limiters() []string {
|
||||||
|
return r.limiters
|
||||||
|
}
|
||||||
|
|
||||||
func (r *abstractLogicalRule) String() string {
|
func (r *abstractLogicalRule) String() string {
|
||||||
var op string
|
var op string
|
||||||
switch r.mode {
|
switch r.mode {
|
||||||
|
@ -184,6 +184,9 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
|||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
|
if len(options.Limiter) > 0 {
|
||||||
|
rule.limiters = append(rule.limiters, options.Limiter...)
|
||||||
|
}
|
||||||
return rule, nil
|
return rule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,5 +219,8 @@ func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options opt
|
|||||||
}
|
}
|
||||||
r.rules[i] = rule
|
r.rules[i] = rule
|
||||||
}
|
}
|
||||||
|
if len(options.Limiter) > 0 {
|
||||||
|
r.limiters = append(r.limiters, options.Limiter...)
|
||||||
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user