mirror of
https://github.com/masterking32/MasterHttpRelayVPN.git
synced 2026-05-19 08:04:38 +03:00
Add multi-relay URL support with round-robin and random endpoint selection
This commit is contained in:
@@ -33,6 +33,7 @@ type Client struct {
|
||||
socksConnections *SOCKSConnectionStore
|
||||
chunkPolicy ChunkPolicy
|
||||
headerBuilder *relayHeaderBuilder
|
||||
relayURLs []string
|
||||
|
||||
connMu sync.Mutex
|
||||
conns map[net.Conn]struct{}
|
||||
@@ -46,6 +47,7 @@ type Client struct {
|
||||
idlePongStreak atomic.Int64
|
||||
pingState atomic.Int32
|
||||
batchCursor atomic.Uint64
|
||||
relayURLCursor atomic.Uint64
|
||||
}
|
||||
|
||||
func New(cfg config.Config, lg *logger.Logger) *Client {
|
||||
@@ -58,6 +60,7 @@ func New(cfg config.Config, lg *logger.Logger) *Client {
|
||||
socksConnections: NewSOCKSConnectionStore(),
|
||||
chunkPolicy: newChunkPolicy(cfg),
|
||||
headerBuilder: newRelayHeaderBuilder(cfg, lg),
|
||||
relayURLs: cfg.RelayEndpointURLs(),
|
||||
conns: make(map[net.Conn]struct{}),
|
||||
workCh: make(chan struct{}, 1),
|
||||
}
|
||||
@@ -216,3 +219,22 @@ func generateClientSessionKey() string {
|
||||
|
||||
return fmt.Sprintf("%s_%s", now, hex.EncodeToString(random))
|
||||
}
|
||||
|
||||
func (c *Client) nextRelayURL() string {
|
||||
if len(c.relayURLs) == 0 {
|
||||
return c.cfg.RelayURL
|
||||
}
|
||||
if len(c.relayURLs) == 1 {
|
||||
return c.relayURLs[0]
|
||||
}
|
||||
|
||||
switch c.cfg.RelayURLSelection {
|
||||
case "random":
|
||||
return c.relayURLs[randomIndex(len(c.relayURLs))]
|
||||
case "round_robin":
|
||||
fallthrough
|
||||
default:
|
||||
index := c.relayURLCursor.Add(1) - 1
|
||||
return c.relayURLs[index%uint64(len(c.relayURLs))]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,18 +224,29 @@ func buildRefererCandidates(cfg config.Config) []string {
|
||||
return []string{cfg.HTTPReferer}
|
||||
}
|
||||
|
||||
relayURL, err := url.Parse(cfg.RelayURL)
|
||||
if err != nil || relayURL.Scheme == "" || relayURL.Host == "" {
|
||||
return nil
|
||||
}
|
||||
candidates := make([]string, 0)
|
||||
seen := make(map[string]struct{})
|
||||
for _, rawRelayURL := range cfg.RelayEndpointURLs() {
|
||||
relayURL, err := url.Parse(rawRelayURL)
|
||||
if err != nil || relayURL.Scheme == "" || relayURL.Host == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
base := relayURL.Scheme + "://" + relayURL.Host
|
||||
return []string{
|
||||
base + "/",
|
||||
base + "/index.html",
|
||||
base + "/home",
|
||||
base + "/api/status",
|
||||
base := relayURL.Scheme + "://" + relayURL.Host
|
||||
for _, candidate := range []string{
|
||||
base + "/",
|
||||
base + "/index.html",
|
||||
base + "/home",
|
||||
base + "/api/status",
|
||||
} {
|
||||
if _, exists := seen[candidate]; exists {
|
||||
continue
|
||||
}
|
||||
seen[candidate] = struct{}{}
|
||||
candidates = append(candidates, candidate)
|
||||
}
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
func pickRandomString(values ...string) string {
|
||||
|
||||
@@ -51,3 +51,36 @@ func TestBuildRelayURLAddsRandomQuerySuffixWhenEnabled(t *testing.T) {
|
||||
t.Fatalf("expected one randomized query suffix key, got query %q", parsed.RawQuery)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRefererCandidatesIncludesAllRelayHosts(t *testing.T) {
|
||||
cfg := config.Config{
|
||||
RelayURLs: []string{
|
||||
"https://relay-a.example/relay",
|
||||
"https://relay-b.example/relay",
|
||||
},
|
||||
}
|
||||
|
||||
candidates := buildRefererCandidates(cfg)
|
||||
expected := map[string]bool{
|
||||
"https://relay-a.example/": false,
|
||||
"https://relay-a.example/index.html": false,
|
||||
"https://relay-a.example/home": false,
|
||||
"https://relay-a.example/api/status": false,
|
||||
"https://relay-b.example/": false,
|
||||
"https://relay-b.example/index.html": false,
|
||||
"https://relay-b.example/home": false,
|
||||
"https://relay-b.example/api/status": false,
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if _, ok := expected[candidate]; ok {
|
||||
expected[candidate] = true
|
||||
}
|
||||
}
|
||||
|
||||
for candidate, seen := range expected {
|
||||
if !seen {
|
||||
t.Fatalf("expected referer candidate %q to be present", candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ func (c *Client) reclaimExpiredReorder() {
|
||||
|
||||
func (w *sendWorker) postBatch(ctx context.Context, c *Client, batch protocol.Batch, body []byte) error {
|
||||
pingOnly := isPingOnlyBatch(batch)
|
||||
relayURL := c.cfg.RelayURL
|
||||
relayURL := c.nextRelayURL()
|
||||
if c.headerBuilder != nil {
|
||||
relayURL = c.headerBuilder.BuildRelayURL(relayURL)
|
||||
}
|
||||
|
||||
@@ -308,6 +308,53 @@ func TestSendWorkerTransportReuseLimitStaysWithinConfiguredRange(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextRelayURLUsesRoundRobinByDefault(t *testing.T) {
|
||||
cfg := testClientConfig()
|
||||
cfg.RelayURLs = []string{
|
||||
"https://relay-a.example/relay",
|
||||
"https://relay-b.example/relay",
|
||||
"https://relay-c.example/relay",
|
||||
}
|
||||
cfg.RelayURLSelection = "round_robin"
|
||||
|
||||
client := New(cfg, nil)
|
||||
expected := []string{
|
||||
"https://relay-a.example/relay",
|
||||
"https://relay-b.example/relay",
|
||||
"https://relay-c.example/relay",
|
||||
"https://relay-a.example/relay",
|
||||
}
|
||||
|
||||
for i, want := range expected {
|
||||
if got := client.nextRelayURL(); got != want {
|
||||
t.Fatalf("iteration %d: expected %q, got %q", i, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextRelayURLRandomChoosesConfiguredRelaySet(t *testing.T) {
|
||||
cfg := testClientConfig()
|
||||
cfg.RelayURLs = []string{
|
||||
"https://relay-a.example/relay",
|
||||
"https://relay-b.example/relay",
|
||||
"https://relay-c.example/relay",
|
||||
}
|
||||
cfg.RelayURLSelection = "random"
|
||||
|
||||
client := New(cfg, nil)
|
||||
allowed := map[string]bool{
|
||||
"https://relay-a.example/relay": true,
|
||||
"https://relay-b.example/relay": true,
|
||||
"https://relay-c.example/relay": true,
|
||||
}
|
||||
|
||||
for i := 0; i < 50; i++ {
|
||||
if got := client.nextRelayURL(); !allowed[got] {
|
||||
t.Fatalf("unexpected relay URL selected: %q", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPollBatchSkipsWhenTransportBusy(t *testing.T) {
|
||||
cfg := testClientConfig()
|
||||
client := New(cfg, nil)
|
||||
|
||||
Reference in New Issue
Block a user