feat: add check_tolerance to make node selection stable

This commit is contained in:
mzz2017
2023-02-09 21:16:51 +08:00
parent e3a71d0ee2
commit 158dfb2a27
6 changed files with 33 additions and 19 deletions

View File

@ -26,6 +26,7 @@ type AliveDialerSet struct {
dialerGroupName string
l4proto consts.L4ProtoStr
ipversion consts.IpVersionStr
tolerance time.Duration
aliveChangeCallback func(alive bool)
@ -43,6 +44,7 @@ func NewAliveDialerSet(
dialerGroupName string,
l4proto consts.L4ProtoStr,
ipversion consts.IpVersionStr,
tolerance time.Duration,
selectionPolicy consts.DialerSelectionPolicy,
dialers []*Dialer,
aliveChangeCallback func(alive bool),
@ -53,6 +55,7 @@ func NewAliveDialerSet(
dialerGroupName: dialerGroupName,
l4proto: l4proto,
ipversion: ipversion,
tolerance: tolerance,
aliveChangeCallback: aliveChangeCallback,
dialerToIndex: make(map[*Dialer]int),
dialerToLatency: make(map[*Dialer]time.Duration),
@ -146,14 +149,19 @@ func (a *AliveDialerSet) NotifyLatencyChange(dialer *Dialer, alive bool) {
bakOldBestDialer := a.minLatency.dialer
// Calc minLatency.
a.dialerToLatency[dialer] = latency
if alive && latency < a.minLatency.latency {
if alive && latency <= a.minLatency.latency-a.tolerance {
a.minLatency.latency = latency
a.minLatency.dialer = dialer
} else if a.minLatency.dialer == dialer {
a.minLatency.latency = time.Hour
a.minLatency.dialer = nil
a.calcMinLatency()
// Now `a.minLatency.dialer` will be nil if there is no alive dialer.
if latency > a.minLatency.latency {
// Latency increases.
a.minLatency.latency = time.Hour
a.minLatency.dialer = nil
a.calcMinLatency()
// Now `a.minLatency.dialer` will be nil if there is no alive dialer.
} else {
a.minLatency.latency = latency
}
}
currentAlive := a.minLatency.dialer != nil
// If best dialer changed.
@ -169,7 +177,8 @@ func (a *AliveDialerSet) NotifyLatencyChange(dialer *Dialer, alive bool) {
string(a.selectionPolicy): a.minLatency.latency,
"group": a.dialerGroupName,
"network": string(a.l4proto) + string(a.ipversion),
"dialer": a.minLatency.dialer.Name(),
"new dialer": a.minLatency.dialer.Name(),
"old dialer": bakOldBestDialer.Name(),
}).Infof("Group %vselects dialer", re)
} else {
// Alive -> not alive
@ -199,7 +208,7 @@ func (a *AliveDialerSet) calcMinLatency() {
if !ok {
continue
}
if latency < a.minLatency.latency {
if latency <= a.minLatency.latency-a.tolerance {
a.minLatency.latency = latency
a.minLatency.dialer = d
}

View File

@ -37,6 +37,7 @@ type GlobalOption struct {
TcpCheckOptionRaw TcpCheckOptionRaw // Lazy parse
UdpCheckOptionRaw UdpCheckOptionRaw // Lazy parse
CheckInterval time.Duration
CheckTolerance time.Duration
}
type InstanceOption struct {

View File

@ -39,10 +39,10 @@ type DialerGroup struct {
func NewDialerGroup(option *dialer.GlobalOption, name string, dialers []*dialer.Dialer, p DialerSelectionPolicy, aliveChangeCallback func(alive bool, l4proto uint8, ipversion uint8)) *DialerGroup {
log := option.Log
var registeredAliveDialerSet bool
aliveTcp4DialerSet := dialer.NewAliveDialerSet(log, name, consts.L4ProtoStr_TCP, consts.IpVersionStr_4, p.Policy, dialers, func(alive bool) { aliveChangeCallback(alive, unix.IPPROTO_TCP, 4) }, true)
aliveTcp6DialerSet := dialer.NewAliveDialerSet(log, name, consts.L4ProtoStr_TCP, consts.IpVersionStr_6, p.Policy, dialers, func(alive bool) { aliveChangeCallback(alive, unix.IPPROTO_TCP, 6) }, true)
aliveUdp4DialerSet := dialer.NewAliveDialerSet(log, name, consts.L4ProtoStr_UDP, consts.IpVersionStr_4, p.Policy, dialers, func(alive bool) { aliveChangeCallback(alive, unix.IPPROTO_UDP, 4) }, true)
aliveUdp6DialerSet := dialer.NewAliveDialerSet(log, name, consts.L4ProtoStr_UDP, consts.IpVersionStr_6, p.Policy, dialers, func(alive bool) { aliveChangeCallback(alive, unix.IPPROTO_UDP, 6) }, true)
aliveTcp4DialerSet := dialer.NewAliveDialerSet(log, name, consts.L4ProtoStr_TCP, consts.IpVersionStr_4, option.CheckTolerance, p.Policy, dialers, func(alive bool) { aliveChangeCallback(alive, unix.IPPROTO_TCP, 4) }, true)
aliveTcp6DialerSet := dialer.NewAliveDialerSet(log, name, consts.L4ProtoStr_TCP, consts.IpVersionStr_6, option.CheckTolerance, p.Policy, dialers, func(alive bool) { aliveChangeCallback(alive, unix.IPPROTO_TCP, 6) }, true)
aliveUdp4DialerSet := dialer.NewAliveDialerSet(log, name, consts.L4ProtoStr_UDP, consts.IpVersionStr_4, option.CheckTolerance, p.Policy, dialers, func(alive bool) { aliveChangeCallback(alive, unix.IPPROTO_UDP, 4) }, true)
aliveUdp6DialerSet := dialer.NewAliveDialerSet(log, name, consts.L4ProtoStr_UDP, consts.IpVersionStr_6, option.CheckTolerance, p.Policy, dialers, func(alive bool) { aliveChangeCallback(alive, unix.IPPROTO_UDP, 6) }, true)
switch p.Policy {
case consts.DialerSelectionPolicy_Random,

View File

@ -14,14 +14,15 @@ import (
)
type Global struct {
TproxyPort uint16 `mapstructure:"tproxy_port" default:"12345"`
LogLevel string `mapstructure:"log_level" default:"info"`
TcpCheckUrl string `mapstructure:"tcp_check_url" default:"http://cp.cloudflare.com"`
UdpCheckDns string `mapstructure:"udp_check_dns" default:"cloudflare-dns.com:53"`
CheckInterval time.Duration `mapstructure:"check_interval" default:"30s"`
DnsUpstream common.UrlOrEmpty `mapstructure:"dns_upstream" require:""`
LanInterface []string `mapstructure:"lan_interface"`
WanInterface []string `mapstructure:"wan_interface"`
TproxyPort uint16 `mapstructure:"tproxy_port" default:"12345"`
LogLevel string `mapstructure:"log_level" default:"info"`
TcpCheckUrl string `mapstructure:"tcp_check_url" default:"http://cp.cloudflare.com"`
UdpCheckDns string `mapstructure:"udp_check_dns" default:"cloudflare-dns.com:53"`
CheckInterval time.Duration `mapstructure:"check_interval" default:"30s"`
CheckTolerance time.Duration `mapstructure:"check_tolerance" default:"0"`
DnsUpstream common.UrlOrEmpty `mapstructure:"dns_upstream" require:""`
LanInterface []string `mapstructure:"lan_interface"`
WanInterface []string `mapstructure:"wan_interface"`
}
type Group struct {

View File

@ -196,6 +196,7 @@ func NewControlPlane(
TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{Raw: global.TcpCheckUrl},
UdpCheckOptionRaw: dialer.UdpCheckOptionRaw{Raw: global.UdpCheckDns},
CheckInterval: global.CheckInterval,
CheckTolerance: global.CheckTolerance,
}
outbounds := []*outbound.DialerGroup{
outbound.NewDialerGroup(option, consts.OutboundDirect.String(),

View File

@ -10,6 +10,8 @@ global {
tcp_check_url: 'http://cp.cloudflare.com'
udp_check_dns: 'dns.google:53'
check_interval: 30s
# Group will switch node only when new_latency <= old_latency - tolerance
check_tolerance: 50ms
# Value can be scheme://host:port or empty string ''.
# The scheme can be tcp/udp/tcp+udp. Empty string '' indicates as-is.