mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Merge 630cdb075709496fddfecadf1cc537d761be6096 into c5ba03a2f7bf53c9e3bd555d292be21f47b0e932
This commit is contained in:
commit
c89a66edbf
@ -67,6 +67,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"
|
||||||
@ -77,6 +78,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) |
|
||||||
|
|
||||||
|
56
docs/configuration/limiter/index.md
Normal file
56
docs/configuration/limiter/index.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Limiter
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"limiters": [
|
||||||
|
{
|
||||||
|
"tag": "limiter-a",
|
||||||
|
"download": "10M",
|
||||||
|
"upload": "1M",
|
||||||
|
"auth_user": [
|
||||||
|
"user-a",
|
||||||
|
"user-b"
|
||||||
|
],
|
||||||
|
"auth_user_independent": false,
|
||||||
|
"inbound": [
|
||||||
|
"in-a",
|
||||||
|
"in-b"
|
||||||
|
],
|
||||||
|
"inbound_independent": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
Apply limiter for a group of usernames, see each inbound for details.
|
||||||
|
|
||||||
|
#### auth_user_independent
|
||||||
|
|
||||||
|
Make each auth_user's limiter independent. If disabled, the same limiter will be shared.
|
||||||
|
|
||||||
|
#### inbound
|
||||||
|
|
||||||
|
Apply limiter for a group of inbounds.
|
||||||
|
|
||||||
|
#### inbound_independent
|
||||||
|
|
||||||
|
Make each inbound's limiter independent. If disabled, the same limiter will be shared.
|
56
docs/configuration/limiter/index.zh.md
Normal file
56
docs/configuration/limiter/index.zh.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# 限速
|
||||||
|
|
||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"limiters": [
|
||||||
|
{
|
||||||
|
"tag": "limiter-a",
|
||||||
|
"download": "10M",
|
||||||
|
"upload": "1M",
|
||||||
|
"auth_user": [
|
||||||
|
"user-a",
|
||||||
|
"user-b"
|
||||||
|
],
|
||||||
|
"auth_user_independent": false,
|
||||||
|
"inbound": [
|
||||||
|
"in-a",
|
||||||
|
"in-b"
|
||||||
|
],
|
||||||
|
"inbound_independent": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### download upload
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
格式: `[Integer][Unit]` 例如: `100M, 100m, 1G, 1g`.
|
||||||
|
|
||||||
|
支持的单位 (大小写不敏感): `B, K, M, G, T, P, E`.
|
||||||
|
|
||||||
|
#### tag
|
||||||
|
|
||||||
|
限速标签,在路由规则中使用。
|
||||||
|
|
||||||
|
#### auth_user
|
||||||
|
|
||||||
|
用户组限速,参阅入站设置。
|
||||||
|
|
||||||
|
#### auth_user_independent
|
||||||
|
|
||||||
|
使每个用户有单独的限速。关闭时将共享限速。
|
||||||
|
|
||||||
|
#### inbound
|
||||||
|
|
||||||
|
入站组限速。
|
||||||
|
|
||||||
|
#### inbound_independent
|
||||||
|
|
||||||
|
使每个入站有单独的限速。关闭时将共享限速。
|
@ -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
|
||||||
|
111
limiter/builder.go
Normal file
111
limiter/builder.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"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 (
|
||||||
|
prefixTag = "tag"
|
||||||
|
prefixUser = "user"
|
||||||
|
prefixInbound = "inbound"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Manager = (*defaultManager)(nil)
|
||||||
|
|
||||||
|
type limiterKey struct {
|
||||||
|
Prefix string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultManager struct {
|
||||||
|
mp map[limiterKey]*limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithDefault(ctx context.Context, logger log.ContextLogger, options []option.Limiter) context.Context {
|
||||||
|
m := &defaultManager{mp: make(map[limiterKey]*limiter)}
|
||||||
|
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 (m *defaultManager) createLimiter(ctx context.Context, option option.Limiter) (err error) {
|
||||||
|
var download, upload uint64
|
||||||
|
if option.Download != "" {
|
||||||
|
download, err = humanize.ParseBytes(option.Download)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if option.Upload != "" {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
if option.Tag == "" && len(option.AuthUser) == 0 && len(option.Inbound) == 0 {
|
||||||
|
return E.New("tag/user/inbound, at least one must be set")
|
||||||
|
}
|
||||||
|
var sharedLimiter *limiter
|
||||||
|
if option.Tag != "" || !option.AuthUserIndependent || !option.InboundIndependent {
|
||||||
|
sharedLimiter = newLimiter(download, upload)
|
||||||
|
}
|
||||||
|
if option.Tag != "" {
|
||||||
|
m.mp[limiterKey{prefixTag, option.Tag}] = sharedLimiter
|
||||||
|
}
|
||||||
|
for _, user := range option.AuthUser {
|
||||||
|
if option.AuthUserIndependent {
|
||||||
|
m.mp[limiterKey{prefixUser, user}] = newLimiter(download, upload)
|
||||||
|
} else {
|
||||||
|
m.mp[limiterKey{prefixUser, user}] = sharedLimiter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, inbound := range option.Inbound {
|
||||||
|
if option.InboundIndependent {
|
||||||
|
m.mp[limiterKey{prefixInbound, inbound}] = newLimiter(download, upload)
|
||||||
|
} else {
|
||||||
|
m.mp[limiterKey{prefixInbound, inbound}] = sharedLimiter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultManager) NewConnWithLimiters(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, rule adapter.Rule) net.Conn {
|
||||||
|
var limiters []*limiter
|
||||||
|
if rule != nil {
|
||||||
|
for _, tag := range rule.Limiters() {
|
||||||
|
if v, ok := m.mp[limiterKey{prefixTag, tag}]; ok {
|
||||||
|
limiters = append(limiters, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if metadata != nil {
|
||||||
|
if v, ok := m.mp[limiterKey{prefixUser, metadata.User}]; ok {
|
||||||
|
limiters = append(limiters, v)
|
||||||
|
}
|
||||||
|
if v, ok := m.mp[limiterKey{prefixInbound, metadata.Inbound}]; ok {
|
||||||
|
limiters = append(limiters, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.uploadLimiter == nil {
|
||||||
|
return conn.Conn.Read(p)
|
||||||
|
}
|
||||||
|
b := conn.limiter.uploadLimiter.Burst()
|
||||||
|
if b < len(p) {
|
||||||
|
p = p[:b]
|
||||||
|
}
|
||||||
|
n, err = conn.Conn.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.limiter.uploadLimiter.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.downloadLimiter == nil {
|
||||||
|
return conn.Conn.Write(p)
|
||||||
|
}
|
||||||
|
var nn int
|
||||||
|
b := conn.limiter.downloadLimiter.Burst()
|
||||||
|
for {
|
||||||
|
end := len(p)
|
||||||
|
if end == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if b < len(p) {
|
||||||
|
end = b
|
||||||
|
}
|
||||||
|
err = conn.limiter.downloadLimiter.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
|
||||||
|
}
|
12
limiter/manager.go
Normal file
12
limiter/manager.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
NewConnWithLimiters(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, rule adapter.Rule) net.Conn
|
||||||
|
}
|
@ -59,6 +59,8 @@ nav:
|
|||||||
- FakeIP: configuration/dns/fakeip.md
|
- FakeIP: configuration/dns/fakeip.md
|
||||||
- NTP:
|
- NTP:
|
||||||
- configuration/ntp/index.md
|
- configuration/ntp/index.md
|
||||||
|
- Limiter:
|
||||||
|
- configuration/limiter/index.md
|
||||||
- Route:
|
- Route:
|
||||||
- configuration/route/index.md
|
- configuration/route/index.md
|
||||||
- GeoIP: configuration/route/geoip.md
|
- GeoIP: configuration/route/geoip.md
|
||||||
@ -186,6 +188,8 @@ plugins:
|
|||||||
DNS Server: DNS 服务器
|
DNS Server: DNS 服务器
|
||||||
DNS Rule: DNS 规则
|
DNS Rule: DNS 规则
|
||||||
|
|
||||||
|
Limiter: 限速
|
||||||
|
|
||||||
Route: 路由
|
Route: 路由
|
||||||
Route Rule: 路由规则
|
Route Rule: 路由规则
|
||||||
Protocol Sniff: 协议探测
|
Protocol Sniff: 协议探测
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
option/limiter.go
Normal file
11
option/limiter.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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"`
|
||||||
|
AuthUserIndependent bool `json:"auth_user_independent,omitempty"`
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
InboundIndependent bool `json:"inbound_independent,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 {
|
||||||
@ -94,6 +95,7 @@ type LogicalRule struct {
|
|||||||
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 {
|
||||||
|
@ -20,6 +20,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,6 +694,11 @@ 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 {
|
||||||
|
conn = r.limiterManager.NewConnWithLimiters(ctx, conn, &metadata, matchedRule)
|
||||||
|
}
|
||||||
|
|
||||||
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