Skip to content

Commit 86c45df

Browse files
committed
Balancer System
1 parent bd6dbf1 commit 86c45df

8 files changed

Lines changed: 563 additions & 17 deletions

File tree

client_config.toml.simple

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ PROTOCOL_TYPE = "SOCKS5"
1313
# Must match server DOMAIN values.
1414
DOMAINS = ["v.domain.com"]
1515

16+
# Resolver balancing strategy:
17+
# 0=Round Robin (default), 1=Random, 2=Round Robin, 3=Least Loss, 4=Lowest Latency
18+
RESOLVER_BALANCING_STRATEGY = 0
19+
1620
# Encryption method:
1721
# 0=None, 1=XOR, 2=ChaCha20, 3=AES-128-GCM, 4=AES-192-GCM, 5=AES-256-GCM
1822
# Must match server DATA_ENCRYPTION_METHOD.

cmd/client/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ func main() {
2929
cfg.ProtocolType,
3030
cfg.DataEncryptionMethod,
3131
)
32+
log.Infof(
33+
"[*] <green>Resolver Balancing Strategy</green>: <cyan>%d</cyan>",
34+
cfg.ResolverBalancingStrategy,
35+
)
3236
log.Infof(
3337
"[*] <green>Configured Domains</green>: <magenta>%d</magenta>",
3438
len(cfg.Domains),
@@ -41,5 +45,9 @@ func main() {
4145
"[*] <green>Connection Catalog</green>: <magenta>%d</magenta> domain-resolver pairs",
4246
len(app.Connections()),
4347
)
48+
log.Infof(
49+
"[*] <green>Active Connections</green>: <magenta>%d</magenta>",
50+
app.Balancer().ValidCount(),
51+
)
4452
log.Infof("[*] <green>Client Bootstrap Ready</green>")
4553
}

internal/client/balancer.go

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
// ==============================================================================
2+
// MasterDnsVPN
3+
// Author: MasterkinG32
4+
// Github: https://github.com/masterking32
5+
// Year: 2026
6+
// ==============================================================================
7+
8+
package client
9+
10+
import (
11+
"sync"
12+
"sync/atomic"
13+
"time"
14+
)
15+
16+
const (
17+
BalancingRoundRobinDefault = 0
18+
BalancingRandom = 1
19+
BalancingRoundRobin = 2
20+
BalancingLeastLoss = 3
21+
BalancingLowestLatency = 4
22+
)
23+
24+
type Balancer struct {
25+
strategy int
26+
rrCounter atomic.Uint64
27+
rngState atomic.Uint64
28+
29+
mu sync.RWMutex
30+
connections []*Connection
31+
valid []int
32+
indexByKey map[string]int
33+
stats []connectionStats
34+
}
35+
36+
type connectionStats struct {
37+
sent atomic.Uint64
38+
acked atomic.Uint64
39+
rttMicrosSum atomic.Uint64
40+
rttCount atomic.Uint64
41+
}
42+
43+
func NewBalancer(strategy int) *Balancer {
44+
b := &Balancer{
45+
strategy: strategy,
46+
indexByKey: make(map[string]int),
47+
}
48+
b.rngState.Store(seedRNG())
49+
return b
50+
}
51+
52+
func (b *Balancer) SetConnections(connections []*Connection) {
53+
b.mu.Lock()
54+
defer b.mu.Unlock()
55+
56+
b.connections = connections
57+
b.indexByKey = make(map[string]int, len(connections))
58+
b.valid = make([]int, 0, len(connections))
59+
b.stats = make([]connectionStats, len(connections))
60+
61+
for idx, conn := range connections {
62+
if conn == nil {
63+
continue
64+
}
65+
b.indexByKey[conn.Key] = idx
66+
if conn.IsValid {
67+
b.valid = append(b.valid, idx)
68+
}
69+
}
70+
}
71+
72+
func (b *Balancer) ValidCount() int {
73+
b.mu.RLock()
74+
defer b.mu.RUnlock()
75+
return len(b.valid)
76+
}
77+
78+
func (b *Balancer) SetConnectionValidity(key string, valid bool) bool {
79+
b.mu.Lock()
80+
defer b.mu.Unlock()
81+
82+
idx, ok := b.indexByKey[key]
83+
if !ok {
84+
return false
85+
}
86+
87+
conn := b.connections[idx]
88+
if conn == nil || conn.IsValid == valid {
89+
return ok
90+
}
91+
92+
conn.IsValid = valid
93+
b.refreshValidLocked()
94+
return true
95+
}
96+
97+
func (b *Balancer) RefreshValidConnections() {
98+
b.mu.Lock()
99+
defer b.mu.Unlock()
100+
b.refreshValidLocked()
101+
}
102+
103+
func (b *Balancer) ReportSend(serverKey string) {
104+
if stats := b.statsForKey(serverKey); stats != nil {
105+
stats.sent.Add(1)
106+
}
107+
}
108+
109+
func (b *Balancer) ReportSuccess(serverKey string, rtt time.Duration) {
110+
stats := b.statsForKey(serverKey)
111+
if stats == nil {
112+
return
113+
}
114+
115+
stats.acked.Add(1)
116+
if rtt > 0 {
117+
stats.rttMicrosSum.Add(uint64(rtt / time.Microsecond))
118+
stats.rttCount.Add(1)
119+
}
120+
121+
sent := stats.sent.Load()
122+
if sent <= 1000 {
123+
return
124+
}
125+
126+
stats.sent.Store(sent / 2)
127+
stats.acked.Store(stats.acked.Load() / 2)
128+
stats.rttMicrosSum.Store(stats.rttMicrosSum.Load() / 2)
129+
stats.rttCount.Store(stats.rttCount.Load() / 2)
130+
}
131+
132+
func (b *Balancer) ResetServerStats(serverKey string) {
133+
stats := b.statsForKey(serverKey)
134+
if stats == nil {
135+
return
136+
}
137+
138+
stats.sent.Store(0)
139+
stats.acked.Store(0)
140+
stats.rttMicrosSum.Store(0)
141+
stats.rttCount.Store(0)
142+
}
143+
144+
func (b *Balancer) GetBestConnection() (Connection, bool) {
145+
selected := b.GetUniqueConnections(1)
146+
if len(selected) == 0 {
147+
return Connection{}, false
148+
}
149+
return selected[0], true
150+
}
151+
152+
func (b *Balancer) GetUniqueConnections(requiredCount int) []Connection {
153+
valid := b.snapshotValid()
154+
count := normalizeRequiredCount(len(valid), requiredCount, 1)
155+
if count == 0 {
156+
return nil
157+
}
158+
159+
switch b.strategy {
160+
case BalancingRandom:
161+
return b.selectRandom(valid, count)
162+
case BalancingLeastLoss:
163+
return b.selectLowestScore(valid, count, b.lossScore)
164+
case BalancingLowestLatency:
165+
return b.selectLowestScore(valid, count, b.latencyScore)
166+
default:
167+
return b.selectRoundRobin(valid, count)
168+
}
169+
}
170+
171+
func (b *Balancer) refreshValidLocked() {
172+
valid := make([]int, 0, len(b.connections))
173+
for idx, conn := range b.connections {
174+
if conn != nil && conn.IsValid {
175+
valid = append(valid, idx)
176+
}
177+
}
178+
b.valid = valid
179+
}
180+
181+
func (b *Balancer) snapshotValid() []int {
182+
b.mu.RLock()
183+
defer b.mu.RUnlock()
184+
185+
if len(b.valid) == 0 {
186+
return nil
187+
}
188+
189+
snapshot := make([]int, len(b.valid))
190+
copy(snapshot, b.valid)
191+
return snapshot
192+
}
193+
194+
func (b *Balancer) statsForKey(serverKey string) *connectionStats {
195+
b.mu.RLock()
196+
idx, ok := b.indexByKey[serverKey]
197+
if !ok || idx < 0 || idx >= len(b.stats) {
198+
b.mu.RUnlock()
199+
return nil
200+
}
201+
stats := &b.stats[idx]
202+
b.mu.RUnlock()
203+
return stats
204+
}
205+
206+
func normalizeRequiredCount(validCount, requiredCount, defaultIfInvalid int) int {
207+
if validCount <= 0 {
208+
return 0
209+
}
210+
if requiredCount <= 0 {
211+
requiredCount = defaultIfInvalid
212+
}
213+
if requiredCount > validCount {
214+
return validCount
215+
}
216+
return requiredCount
217+
}
218+
219+
func (b *Balancer) selectRoundRobin(valid []int, count int) []Connection {
220+
start := int(b.rrCounter.Add(uint64(count)) - uint64(count))
221+
selected := make([]Connection, 0, count)
222+
for i := 0; i < count; i++ {
223+
selected = append(selected, b.connectionAt(valid[(start+i)%len(valid)]))
224+
}
225+
return selected
226+
}
227+
228+
func (b *Balancer) selectRandom(valid []int, count int) []Connection {
229+
order := make([]int, len(valid))
230+
copy(order, valid)
231+
232+
for i := 0; i < count; i++ {
233+
j := i + int(b.nextRandom()%uint64(len(order)-i))
234+
order[i], order[j] = order[j], order[i]
235+
}
236+
237+
selected := make([]Connection, 0, count)
238+
for i := 0; i < count; i++ {
239+
selected = append(selected, b.connectionAt(order[i]))
240+
}
241+
return selected
242+
}
243+
244+
func (b *Balancer) selectLowestScore(valid []int, count int, scorer func(int) float64) []Connection {
245+
bestIdx := make([]int, 0, count)
246+
bestScores := make([]float64, 0, count)
247+
248+
for _, idx := range valid {
249+
score := scorer(idx)
250+
insertPos := len(bestScores)
251+
252+
for i := 0; i < len(bestScores); i++ {
253+
if score < bestScores[i] {
254+
insertPos = i
255+
break
256+
}
257+
}
258+
259+
if len(bestScores) < count {
260+
bestScores = append(bestScores, 0)
261+
bestIdx = append(bestIdx, 0)
262+
} else if insertPos == len(bestScores) {
263+
continue
264+
}
265+
266+
copy(bestScores[insertPos+1:], bestScores[insertPos:])
267+
copy(bestIdx[insertPos+1:], bestIdx[insertPos:])
268+
bestScores[insertPos] = score
269+
bestIdx[insertPos] = idx
270+
271+
if len(bestScores) > count {
272+
bestScores = bestScores[:count]
273+
bestIdx = bestIdx[:count]
274+
}
275+
}
276+
277+
selected := make([]Connection, 0, len(bestIdx))
278+
for _, idx := range bestIdx {
279+
selected = append(selected, b.connectionAt(idx))
280+
}
281+
return selected
282+
}
283+
284+
func (b *Balancer) connectionAt(idx int) Connection {
285+
b.mu.RLock()
286+
defer b.mu.RUnlock()
287+
if idx < 0 || idx >= len(b.connections) || b.connections[idx] == nil {
288+
return Connection{}
289+
}
290+
return *b.connections[idx]
291+
}
292+
293+
func (b *Balancer) lossScore(idx int) float64 {
294+
stats := &b.stats[idx]
295+
sent := stats.sent.Load()
296+
if sent < 5 {
297+
return 0.5
298+
}
299+
300+
acked := stats.acked.Load()
301+
loss := 1.0 - (float64(acked) / float64(sent))
302+
if loss < 0 {
303+
return 0
304+
}
305+
if loss > 1 {
306+
return 1
307+
}
308+
return loss
309+
}
310+
311+
func (b *Balancer) latencyScore(idx int) float64 {
312+
stats := &b.stats[idx]
313+
count := stats.rttCount.Load()
314+
if count < 5 {
315+
return 999000.0
316+
}
317+
return float64(stats.rttMicrosSum.Load()) / float64(count)
318+
}
319+
320+
func (b *Balancer) nextRandom() uint64 {
321+
for {
322+
current := b.rngState.Load()
323+
next := xorshift64(current)
324+
if b.rngState.CompareAndSwap(current, next) {
325+
return next
326+
}
327+
}
328+
}
329+
330+
func seedRNG() uint64 {
331+
seed := uint64(time.Now().UnixNano())
332+
if seed == 0 {
333+
return 0x9e3779b97f4a7c15
334+
}
335+
return seed
336+
}
337+
338+
func xorshift64(v uint64) uint64 {
339+
if v == 0 {
340+
v = 0x9e3779b97f4a7c15
341+
}
342+
v ^= v << 13
343+
v ^= v >> 7
344+
v ^= v << 17
345+
return v
346+
}

0 commit comments

Comments
 (0)