Skip to content

Commit 0a492d9

Browse files
added support for round robin chains
1 parent c2e8ddf commit 0a492d9

File tree

3 files changed

+56
-15
lines changed

3 files changed

+56
-15
lines changed

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Specify http server in proxy configuration of Postman
3232
## Features
3333

3434
- **Proxy Chain functionality**
35-
Supports `strict`, `dynamic`, `random` chains of SOCKS5 proxy
35+
Supports `strict`, `dynamic`, `random`, `round_robin` chains of SOCKS5 proxy
3636

3737
- **DNS Leak Protection**
3838
DNS resolution occurs on SOCKS5 server side.
@@ -168,11 +168,20 @@ Config example:
168168
# (or proxy chain, see chain_len) from the list.
169169
# this option is good to test your IDS :)
170170

171-
# round_robin - Not supported
171+
# round_robin - Each connection will be done via chained proxies
172+
# of chain_len length
173+
# all proxies chained in the order as they appear in the list
174+
# at least one proxy must be online to play in chain
175+
# (dead proxies are skipped).
176+
# the start of the current proxy chain is the proxy after the last
177+
# proxy in the previously invoked proxy chain.
178+
# if the end of the proxy chain is reached while looking for proxies
179+
# start at the beginning again.
180+
# These semantics are not guaranteed in a multithreaded environment.
172181

173182
chain:
174-
type: strict # dynamic, strict, random
175-
length: 2 # maximum number of proxy in a chain (works only for random chain)
183+
type: strict # dynamic, strict, random, round_robin
184+
length: 2 # maximum number of proxy in a chain (works only for random chain and round_robin chain)
176185
proxy_list:
177186
- address: 127.0.0.1:1080
178187
username: username # username and password are optional

gohpts.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"slices"
1717
"strings"
1818
"sync"
19+
"sync/atomic"
1920
"time"
2021

2122
"github.com/goccy/go-yaml"
@@ -31,9 +32,10 @@ const (
3132
flushTimeout time.Duration = 10 * time.Millisecond
3233
availProxyUpdateInterval time.Duration = 30 * time.Second
3334
kbSize int64 = 1000
35+
rrIndexMax uint32 = 1_000_000
3436
)
3537

36-
var supportedChainTypes = []string{"strict", "dynamic", "random"} // TODO: round_robin chain
38+
var supportedChainTypes = []string{"strict", "dynamic", "random", "round_robin"}
3739

3840
// Hop-by-hop headers
3941
// https://datatracker.ietf.org/doc/html/rfc2616#section-13.5.1
@@ -105,6 +107,8 @@ type proxyApp struct {
105107
keyFile string
106108
httpServerAddr string
107109
proxychain *proxyChainConfig
110+
rrIndex uint32
111+
rrIndexReset uint32
108112

109113
mu sync.RWMutex
110114
availProxyList []proxyEntry
@@ -215,17 +219,35 @@ func (p *proxyApp) getSocks() (proxy.Dialer, *http.Client, error) {
215219
p.mu.RLock()
216220
defer p.mu.RUnlock()
217221
chainType := p.proxychain.Chain.Type
218-
copyProxyList := make([]proxyEntry, len(p.availProxyList))
222+
var chainLength int
223+
if p.proxychain.Chain.Length > len(p.availProxyList) || p.proxychain.Chain.Length <= 0 {
224+
chainLength = len(p.availProxyList)
225+
} else {
226+
chainLength = p.proxychain.Chain.Length
227+
}
228+
copyProxyList := make([]proxyEntry, 0, len(p.availProxyList))
219229
if chainType == "random" {
220230
copy(copyProxyList, p.availProxyList)
221231
shuffle(copyProxyList)
222-
var chainLength int
223-
if p.proxychain.Chain.Length > len(copyProxyList) || p.proxychain.Chain.Length <= 0 {
224-
chainLength = len(copyProxyList)
225-
} else {
226-
chainLength = p.proxychain.Chain.Length
227-
}
228232
copyProxyList = copyProxyList[:chainLength]
233+
} else if chainType == "round_robin" {
234+
var start uint32
235+
for {
236+
start = atomic.LoadUint32(&p.rrIndex)
237+
next := start + 1
238+
if start >= p.rrIndexReset {
239+
p.logger.Debug().Msg("Resetting round robin index")
240+
next = 0
241+
}
242+
if atomic.CompareAndSwapUint32(&p.rrIndex, start, next) {
243+
break
244+
}
245+
}
246+
startIdx := int(start % uint32(len(p.availProxyList)))
247+
for i := 0; i < chainLength; i++ {
248+
idx := (startIdx + i) % len(p.availProxyList)
249+
copyProxyList = append(copyProxyList, p.availProxyList[idx])
250+
}
229251
} else {
230252
copyProxyList = p.availProxyList
231253
}
@@ -639,6 +661,7 @@ func New(conf *Config) *proxyApp {
639661
if !slices.Contains(supportedChainTypes, chainType) {
640662
p.logger.Fatal().Msgf("[proxychain config] Chain type `%s` is not supported", chainType)
641663
}
664+
p.rrIndexReset = rrIndexMax
642665
} else {
643666
var dialer proxy.Dialer
644667
var err error

resources/proxychain_example.yaml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,20 @@
1313
# (or proxy chain, see chain_len) from the list.
1414
# this option is good to test your IDS :)
1515

16-
# round_robin - Not supported
16+
# round_robin - Each connection will be done via chained proxies
17+
# of chain_len length
18+
# all proxies chained in the order as they appear in the list
19+
# at least one proxy must be online to play in chain
20+
# (dead proxies are skipped).
21+
# the start of the current proxy chain is the proxy after the last
22+
# proxy in the previously invoked proxy chain.
23+
# if the end of the proxy chain is reached while looking for proxies
24+
# start at the beginning again.
25+
# These semantics are not guaranteed in a multithreaded environment.
1726

1827
chain:
19-
type: strict # dynamic, strict, random
20-
length: 2 # maximum number of proxy in a chain (works only for random chain)
28+
type: strict # dynamic, strict, random, round_robin
29+
length: 2 # maximum number of proxy in a chain (works only for random chain and round_robin chain)
2130
proxy_list:
2231
- address: 127.0.0.1:1080
2332
username: username # username and password are optional

0 commit comments

Comments
 (0)