Merge 81e7bd5adb304b69f0f4e74effbb9cd77038e054 into 8ce44b765f5438e01b3bdd46e8acff2a66877df4

This commit is contained in:
ashly-right 2024-05-13 22:19:47 +02:00 committed by GitHub
commit 1841a4bc98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 128 additions and 25 deletions

View File

@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.8.0" !!! question "Since sing-box 1.8.0"
!!! quote "Changes in sing-box 1.9.0" !!! quote "Changes in sing-box 1.9.0"

View File

@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.8.0 起" !!! question "自 sing-box 1.8.0 起"
!!! quote "sing-box 1.9.0 中的更改" !!! quote "sing-box 1.9.0 中的更改"

View File

@ -14,7 +14,8 @@
"interval": "", "interval": "",
"tolerance": 0, "tolerance": 0,
"idle_timeout": "", "idle_timeout": "",
"interrupt_exist_connections": false "interrupt_exist_connections": false,
"randomize": false
} }
``` ```
@ -47,3 +48,9 @@ The idle timeout. `30m` will be used if empty.
Interrupt existing connections when the selected outbound has changed. Interrupt existing connections when the selected outbound has changed.
Only inbound connections are affected by this setting, internal connections will always be interrupted. Only inbound connections are affected by this setting, internal connections will always be interrupted.
#### randomize
Outbound would be selected randomly within the best latency in the tolerance range. It's deactivated by default.
The interrupt_exist_connections will be ignored if the randomize is activated.

View File

@ -14,7 +14,8 @@
"interval": "", "interval": "",
"tolerance": 50, "tolerance": 50,
"idle_timeout": "", "idle_timeout": "",
"interrupt_exist_connections": false "interrupt_exist_connections": false,
"randomize": false
} }
``` ```
@ -47,3 +48,10 @@
当选定的出站发生更改时,中断现有连接。 当选定的出站发生更改时,中断现有连接。
仅入站连接受此设置影响,内部连接将始终被中断。 仅入站连接受此设置影响,内部连接将始终被中断。
#### randomize
出站将在容忍范围内的最佳延迟内随机选择。 默认情况下它处于禁用状态。
如果激活了随机化则interrupt_exist_connections将被忽略。

View File

@ -13,4 +13,5 @@ type URLTestOutboundOptions struct {
Tolerance uint16 `json:"tolerance,omitempty"` Tolerance uint16 `json:"tolerance,omitempty"`
IdleTimeout Duration `json:"idle_timeout,omitempty"` IdleTimeout Duration `json:"idle_timeout,omitempty"`
InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"`
Randomize bool `json:"randomize,omitempty"`
} }

View File

@ -2,6 +2,7 @@ package outbound
import ( import (
"context" "context"
"math/rand"
"net" "net"
"sync" "sync"
"time" "time"
@ -38,6 +39,7 @@ type URLTest struct {
idleTimeout time.Duration idleTimeout time.Duration
group *URLTestGroup group *URLTestGroup
interruptExternalConnections bool interruptExternalConnections bool
randomize bool
} }
func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) { func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) {
@ -57,6 +59,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
tolerance: options.Tolerance, tolerance: options.Tolerance,
idleTimeout: time.Duration(options.IdleTimeout), idleTimeout: time.Duration(options.IdleTimeout),
interruptExternalConnections: options.InterruptExistConnections, interruptExternalConnections: options.InterruptExistConnections,
randomize: options.Randomize,
} }
if len(outbound.tags) == 0 { if len(outbound.tags) == 0 {
return nil, E.New("missing tags") return nil, E.New("missing tags")
@ -83,6 +86,7 @@ func (s *URLTest) Start() error {
s.tolerance, s.tolerance,
s.idleTimeout, s.idleTimeout,
s.interruptExternalConnections, s.interruptExternalConnections,
s.randomize,
) )
if err != nil { if err != nil {
return err return err
@ -126,6 +130,9 @@ func (s *URLTest) CheckOutbounds() {
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
s.group.Touch() s.group.Touch()
var outbound adapter.Outbound var outbound adapter.Outbound
if s.randomize {
outbound = s.group.selectRandomOutbound(network)
} else {
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkTCP: case N.NetworkTCP:
outbound = s.group.selectedOutboundTCP outbound = s.group.selectedOutboundTCP
@ -137,6 +144,7 @@ func (s *URLTest) DialContext(ctx context.Context, network string, destination M
if outbound == nil { if outbound == nil {
outbound, _ = s.group.Select(network) outbound, _ = s.group.Select(network)
} }
}
if outbound == nil { if outbound == nil {
return nil, E.New("missing supported outbound") return nil, E.New("missing supported outbound")
} }
@ -151,10 +159,15 @@ func (s *URLTest) DialContext(ctx context.Context, network string, destination M
func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
s.group.Touch() s.group.Touch()
outbound := s.group.selectedOutboundUDP var outbound adapter.Outbound
if s.randomize {
outbound = s.group.selectRandomOutbound(N.NetworkUDP) // Since ListenPacket is for UDP, we pass "N.NetworkUDP" as the network type
} else {
outbound = s.group.selectedOutboundUDP
if outbound == nil { if outbound == nil {
outbound, _ = s.group.Select(N.NetworkUDP) outbound, _ = s.group.Select(N.NetworkUDP)
} }
}
if outbound == nil { if outbound == nil {
return nil, E.New("missing supported outbound") return nil, E.New("missing supported outbound")
} }
@ -196,6 +209,9 @@ type URLTestGroup struct {
pauseManager pause.Manager pauseManager pause.Manager
selectedOutboundTCP adapter.Outbound selectedOutboundTCP adapter.Outbound
selectedOutboundUDP adapter.Outbound selectedOutboundUDP adapter.Outbound
randomize bool
bestTCPLatencyOutbounds []adapter.Outbound
bestUDPLatencyOutbounds []adapter.Outbound
interruptGroup *interrupt.Group interruptGroup *interrupt.Group
interruptExternalConnections bool interruptExternalConnections bool
@ -216,6 +232,7 @@ func NewURLTestGroup(
tolerance uint16, tolerance uint16,
idleTimeout time.Duration, idleTimeout time.Duration,
interruptExternalConnections bool, interruptExternalConnections bool,
randomize bool,
) (*URLTestGroup, error) { ) (*URLTestGroup, error) {
if interval == 0 { if interval == 0 {
interval = C.DefaultURLTestInterval interval = C.DefaultURLTestInterval
@ -250,6 +267,7 @@ func NewURLTestGroup(
pauseManager: service.FromContext[pause.Manager](ctx), pauseManager: service.FromContext[pause.Manager](ctx),
interruptGroup: interrupt.NewGroup(), interruptGroup: interrupt.NewGroup(),
interruptExternalConnections: interruptExternalConnections, interruptExternalConnections: interruptExternalConnections,
randomize: randomize,
}, nil }, nil
} }
@ -349,6 +367,9 @@ func (g *URLTestGroup) loopCheck() {
} }
g.pauseManager.WaitActive() g.pauseManager.WaitActive()
g.CheckOutbounds(false) g.CheckOutbounds(false)
if g.randomize {
g.selectBestLatencyOutbounds()
}
} }
} }
@ -357,7 +378,15 @@ func (g *URLTestGroup) CheckOutbounds(force bool) {
} }
func (g *URLTestGroup) URLTest(ctx context.Context) (map[string]uint16, error) { func (g *URLTestGroup) URLTest(ctx context.Context) (map[string]uint16, error) {
return g.urlTest(ctx, false) result, err := g.urlTest(ctx, false)
if err != nil {
return nil, err
}
if g.randomize {
g.selectBestLatencyOutbounds()
}
return result, nil
} }
func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint16, error) { func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint16, error) {
@ -423,3 +452,69 @@ func (g *URLTestGroup) performUpdateCheck() {
g.interruptGroup.Interrupt(g.interruptExternalConnections) g.interruptGroup.Interrupt(g.interruptExternalConnections)
} }
} }
func (g *URLTestGroup) selectBestLatencyOutbounds() {
var bestTCPLatency uint16
var bestUDPLatency uint16
var bestTCPOutbounds []adapter.Outbound
var bestUDPOutbounds []adapter.Outbound
for _, detour := range g.outbounds {
history := g.history.LoadURLTestHistory(RealTag(detour))
if history == nil {
continue
}
if common.Contains(detour.Network(), N.NetworkTCP) {
if bestTCPLatency == 0 || history.Delay < bestTCPLatency {
bestTCPLatency = history.Delay
}
}
if common.Contains(detour.Network(), N.NetworkUDP) {
if bestUDPLatency == 0 || history.Delay < bestUDPLatency {
bestUDPLatency = history.Delay
}
}
}
for _, detour := range g.outbounds {
history := g.history.LoadURLTestHistory(RealTag(detour))
if history == nil {
continue
}
if common.Contains(detour.Network(), N.NetworkTCP) && history.Delay <= bestTCPLatency+g.tolerance {
bestTCPOutbounds = append(bestTCPOutbounds, detour)
}
if common.Contains(detour.Network(), N.NetworkUDP) && history.Delay <= bestUDPLatency+g.tolerance {
bestUDPOutbounds = append(bestUDPOutbounds, detour)
}
}
g.bestTCPLatencyOutbounds = bestTCPOutbounds
g.bestUDPLatencyOutbounds = bestUDPOutbounds
}
// selectRandomOutbound selects an outbound randomly among the outbounds with the best latency
func (g *URLTestGroup) selectRandomOutbound(network string) adapter.Outbound {
var bestOutbounds []adapter.Outbound
switch network {
case N.NetworkTCP:
bestOutbounds = g.bestTCPLatencyOutbounds
case N.NetworkUDP:
bestOutbounds = g.bestUDPLatencyOutbounds
default:
return nil
}
if len(bestOutbounds) == 0 {
return nil
}
randIndex := rand.Intn(len(bestOutbounds))
g.logger.Debug("Random outbound selection: ", bestOutbounds[randIndex].Tag())
return bestOutbounds[randIndex]
}