@@ -63,6 +63,11 @@ type ServerInfo struct {
6363 Proto stamps.StampProtoType
6464 useGet bool
6565 odohTargetConfigs []ODoHTargetConfig
66+
67+ // WP2 strategy fields
68+ totalQueries uint64 // Total queries sent to this server
69+ failedQueries uint64 // Failed queries count
70+ lastUpdateTime time.Time // Last time metrics were updated
6671}
6772
6873type LBStrategy interface {
@@ -120,7 +125,33 @@ func (LBStrategyRandom) getActiveCount(serversCount int) int {
120125 return serversCount
121126}
122127
123- var DefaultLBStrategy = LBStrategyP2 {}
128+ type LBStrategyWP2 struct {}
129+
130+ func (LBStrategyWP2 ) getCandidate (serversCount int ) int {
131+ if serversCount <= 1 {
132+ return 0
133+ }
134+ if serversCount == 2 {
135+ return rand .Intn (2 )
136+ }
137+
138+ // Select two random servers
139+ first := rand .Intn (serversCount )
140+ second := rand .Intn (serversCount )
141+
142+ // Ensure we have two different servers
143+ for second == first {
144+ second = rand .Intn (serversCount )
145+ }
146+
147+ return first // Will be refined in getWeightedCandidate
148+ }
149+
150+ func (LBStrategyWP2 ) getActiveCount (serversCount int ) int {
151+ return serversCount // All servers are considered active for WP2
152+ }
153+
154+ var DefaultLBStrategy = LBStrategyWP2 {}
124155
125156type DNSCryptRelay struct {
126157 RelayUDPAddr * net.UDPAddr
@@ -324,17 +355,136 @@ func (serversInfo *ServersInfo) getOne() *ServerInfo {
324355 serversInfo .Unlock ()
325356 return nil
326357 }
327- candidate := serversInfo .lbStrategy .getCandidate (serversCount )
328- if serversInfo .lbEstimator {
329- serversInfo .estimatorUpdate (candidate )
358+
359+ var candidate int
360+
361+ // Check if using WP2 strategy
362+ if _ , isWP2 := serversInfo .lbStrategy .(LBStrategyWP2 ); isWP2 {
363+ candidate = serversInfo .getWeightedCandidate (serversCount )
364+ } else {
365+ candidate = serversInfo .lbStrategy .getCandidate (serversCount )
366+ if serversInfo .lbEstimator {
367+ serversInfo .estimatorUpdate (candidate )
368+ }
330369 }
370+
331371 serverInfo := serversInfo .inner [candidate ]
332- dlog .Debugf ("Using candidate [%s] RTT: %d" , serverInfo .Name , int (serverInfo .rtt .Value ()))
372+ dlog .Debugf ("Using candidate [%s] RTT: %d Score: %.3f" ,
373+ serverInfo .Name ,
374+ int (serverInfo .rtt .Value ()),
375+ serversInfo .calculateServerScore (serverInfo ))
333376 serversInfo .Unlock ()
334377
335378 return serverInfo
336379}
337380
381+ // getWeightedCandidate implements the WP2 algorithm
382+ func (serversInfo * ServersInfo ) getWeightedCandidate (serversCount int ) int {
383+ if serversCount <= 1 {
384+ return 0
385+ }
386+
387+ // Select two random servers
388+ first := rand .Intn (serversCount )
389+ second := rand .Intn (serversCount )
390+
391+ // Ensure we have two different servers
392+ for second == first {
393+ second = rand .Intn (serversCount )
394+ }
395+
396+ server1 := serversInfo .inner [first ]
397+ server2 := serversInfo .inner [second ]
398+
399+ // Calculate weighted scores
400+ score1 := serversInfo .calculateServerScore (server1 )
401+ score2 := serversInfo .calculateServerScore (server2 )
402+
403+ // Select the better performing server with small randomization
404+ if score1 > score2 {
405+ return first
406+ } else if score2 > score1 {
407+ return second
408+ } else {
409+ // Tie-breaker: random selection
410+ if rand .Float64 () < 0.5 {
411+ return first
412+ }
413+ return second
414+ }
415+ }
416+
417+ // calculateServerScore computes a performance score for server selection
418+ func (serversInfo * ServersInfo ) calculateServerScore (server * ServerInfo ) float64 {
419+ // Base score from RTT (lower RTT = higher score)
420+ rtt := server .rtt .Value ()
421+ if rtt <= 0 {
422+ rtt = 1000 // Default high RTT for servers without data
423+ }
424+
425+ // Normalize RTT to a 0-1 scale (1000ms max)
426+ rttScore := 1.0 - (rtt / 1000.0 )
427+ if rttScore < 0.0 {
428+ rttScore = 0.0
429+ }
430+
431+ // Success rate score
432+ successRate := 1.0 // Default to perfect success rate
433+ if server .totalQueries > 0 {
434+ successRate = float64 (server .totalQueries - server .failedQueries ) / float64 (server .totalQueries )
435+ }
436+
437+ // Combine scores (RTT weighted 70%, success rate 30%)
438+ finalScore := (rttScore * 0.7 ) + (successRate * 0.3 )
439+
440+ return finalScore
441+ }
442+
443+ // updateServerStats updates server statistics after each query
444+ func (serversInfo * ServersInfo ) updateServerStats (serverName string , success bool ) {
445+ serversInfo .Lock ()
446+ defer serversInfo .Unlock ()
447+
448+ for _ , server := range serversInfo .inner {
449+ if server .Name == serverName {
450+ server .totalQueries ++
451+ if ! success {
452+ server .failedQueries ++
453+ }
454+ server .lastUpdateTime = time .Now ()
455+
456+ // Reset counters periodically to prevent overflow and adapt to changes
457+ if server .totalQueries > 10000 {
458+ server .totalQueries = server .totalQueries / 2
459+ server .failedQueries = server .failedQueries / 2
460+ }
461+ break
462+ }
463+ }
464+ }
465+
466+ // logWP2Stats logs WP2 performance statistics for debugging
467+ func (serversInfo * ServersInfo ) logWP2Stats () {
468+ if _ , isWP2 := serversInfo .lbStrategy .(LBStrategyWP2 ); ! isWP2 {
469+ return
470+ }
471+
472+ serversInfo .RLock ()
473+ defer serversInfo .RUnlock ()
474+
475+ dlog .Debug ("WP2 Strategy Server Statistics:" )
476+ for i , server := range serversInfo .inner {
477+ score := serversInfo .calculateServerScore (server )
478+ successRate := 1.0
479+ if server .totalQueries > 0 {
480+ successRate = float64 (server .totalQueries - server .failedQueries ) / float64 (server .totalQueries )
481+ }
482+
483+ dlog .Debugf ("[%d] %s: RTT=%dms, Score=%.3f, Success=%.2f%%, Queries=%d" ,
484+ i , server .Name , int (server .rtt .Value ()), score , successRate * 100 , server .totalQueries )
485+ }
486+ }
487+
338488func fetchServerInfo (proxy * Proxy , name string , stamp stamps.ServerStamp , isNew bool ) (ServerInfo , error ) {
339489 if stamp .Proto == stamps .StampProtoTypeDNSCrypt {
340490 return fetchDNSCryptServerInfo (proxy , name , stamp , isNew )
0 commit comments