Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions dnscrypt-proxy/config_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,23 +201,38 @@ func configureServerParams(proxy *Proxy, config *Config) {
func configureLoadBalancing(proxy *Proxy, config *Config) {
lbStrategy := LBStrategy(DefaultLBStrategy)
switch lbStrategyStr := strings.ToLower(config.LBStrategy); lbStrategyStr {

case "":
// default - WP2 is now the default strategy
dlog.Noticef("Using default Weighted Power of Two (WP2) load balancing strategy")

case "p2":
lbStrategy = LBStrategyP2{}
case "ph":
lbStrategy = LBStrategyPH{}
case "fastest":
case "fastest", "first":
// "fastest" kept for backward compatibility with older configs
fallthrough
case "first":
lbStrategy = LBStrategyFirst{}
case "random":
lbStrategy = LBStrategyRandom{}
case "wp2":
lbStrategy = LBStrategyWP2{}
case "wph":
lbStrategy = LBStrategyWPH{}

default:
// Support wpN (e.g. wp3) -> LBStrategyWPN{n}
if after, ok := strings.CutPrefix(lbStrategyStr, "wp"); ok {
n, err := strconv.ParseInt(after, 10, 32)
if err != nil || n <= 0 {
dlog.Warnf("Invalid load balancing strategy: [%s]", config.LBStrategy)
} else {
lbStrategy = LBStrategyWPN{n: int(n)}
}
break
}

// Support pN (e.g. p3) -> LBStrategyPN{n}
if after, ok := strings.CutPrefix(lbStrategyStr, "p"); ok {
n, err := strconv.ParseInt(after, 10, 32)
if err != nil || n <= 0 {
Expand All @@ -229,10 +244,12 @@ func configureLoadBalancing(proxy *Proxy, config *Config) {
dlog.Warnf("Unknown load balancing strategy: [%s]", config.LBStrategy)
}
}

proxy.serversInfo.lbStrategy = lbStrategy
proxy.serversInfo.lbEstimator = config.LBEstimator
}


// configurePlugins - Configures DNS plugins
func configurePlugins(proxy *Proxy, config *Config) {
// Configure listen addresses and paths
Expand Down
2 changes: 2 additions & 0 deletions dnscrypt-proxy/example-dnscrypt-proxy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ keepalive = 30
## Load-balancing strategy: 'wp2' (default), 'p2', 'ph', 'p<n>', 'first', or 'random'
## 'wp2' (default): Weighted Power of Two - selects the better performing server
## from two random candidates based on real-time RTT and success rates.
## 'wph': randomly selects the better performing server from fastest half of servers.
## 'wp<n>': randomly selects the better performing server from fastest n servers.
## 'p2': Randomly choose 1 of the fastest 2 servers by latency.
## 'ph': Randomly choose from fastest half of servers.
## 'p<n>': Randomly choose from fastest n servers (e.g., 'p3' for fastest 3).
Expand Down
45 changes: 38 additions & 7 deletions dnscrypt-proxy/serversInfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ func (LBStrategyRandom) getActiveCount(serversCount int) int {
type LBStrategyWP2 struct{}

func (LBStrategyWP2) getCandidate(serversCount int) int {
// This function is not used for WP2 - getWeightedCandidate is used instead
// But we need to implement it to satisfy the LBStrategy interface
// This function is not used for WP2 - getWeightedCandidate is used instead.
// But we need to implement it to satisfy the LBStrategy interface.
if serversCount <= 1 {
return 0
}
Expand All @@ -145,6 +145,30 @@ func (LBStrategyWP2) getActiveCount(serversCount int) int {
return serversCount // All servers are considered active for WP2
}

// LBStrategyWPH is the weighted version of PH: it limits the candidate pool
// to the first half of the servers, then applies the WP2 two-choice scoring.
type LBStrategyWPH struct{}

func (LBStrategyWPH) getCandidate(serversCount int) int {
return rand.Intn((serversCount + 1) / 2)
}

func (LBStrategyWPH) getActiveCount(serversCount int) int {
return (serversCount + 1) / 2
}

// LBStrategyWPN is the weighted version of PN: it limits the candidate pool
// to the first N servers, then applies the WP2 two-choice scoring.
type LBStrategyWPN struct{ n int }

func (s LBStrategyWPN) getCandidate(serversCount int) int {
return rand.Intn(Min(serversCount, s.n))
}

func (s LBStrategyWPN) getActiveCount(serversCount int) int {
return Min(serversCount, s.n)
}

var DefaultLBStrategy = LBStrategyWP2{}

type DNSCryptRelay struct {
Expand Down Expand Up @@ -460,10 +484,16 @@ func (serversInfo *ServersInfo) getOne() *ServerInfo {

var candidate int

// Check if using WP2 strategy
if _, isWP2 := serversInfo.lbStrategy.(LBStrategyWP2); isWP2 {
candidate = serversInfo.getWeightedCandidate(serversCount)
} else {
// WP2, WPH, and WPN all use the weighted two-choice algorithm.
// WPH/WPN restrict the pool using their PH/PN active-count semantics.
switch strategy := serversInfo.lbStrategy.(type) {
case LBStrategyWP2:
candidate = serversInfo.getWeightedCandidate(strategy.getActiveCount(serversCount))
case LBStrategyWPH:
candidate = serversInfo.getWeightedCandidate(strategy.getActiveCount(serversCount))
case LBStrategyWPN:
candidate = serversInfo.getWeightedCandidate(strategy.getActiveCount(serversCount))
default:
candidate = serversInfo.lbStrategy.getCandidate(serversCount)
if serversInfo.lbEstimator {
serversInfo.estimatorUpdate(candidate)
Expand All @@ -480,7 +510,8 @@ func (serversInfo *ServersInfo) getOne() *ServerInfo {
return serverInfo
}

// getWeightedCandidate implements the WP2 algorithm
// getWeightedCandidate applies the WP2 two-choice algorithm within the
// specified active server pool.
func (serversInfo *ServersInfo) getWeightedCandidate(serversCount int) int {
if serversCount <= 1 {
return 0
Expand Down