Add anti-buffering relay headers and optional randomized query suffixes

This commit is contained in:
Amin.MasterkinG
2026-04-21 15:28:09 +03:30
parent bf5c0ef06e
commit 74ff27ac78
7 changed files with 172 additions and 1 deletions
+62
View File
@@ -65,6 +65,26 @@ func (b *relayHeaderBuilder) Apply(req *http.Request) {
}
}
func (b *relayHeaderBuilder) BuildRelayURL(rawURL string) string {
if !b.cfg.HTTPRandomizeQuerySuffix {
return rawURL
}
parsed, err := url.Parse(rawURL)
if err != nil {
return rawURL
}
query := parsed.Query()
key, value := b.randomQuerySuffix()
if key == "" || value == "" {
return rawURL
}
query.Set(key, value)
parsed.RawQuery = query.Encode()
return parsed.String()
}
func (b *relayHeaderBuilder) applyBrowserProfile(req *http.Request) {
req.Header.Set("Accept", pickRandomString(
"*/*",
@@ -282,3 +302,45 @@ func randomHex(byteCount int) string {
return hex.EncodeToString(raw)
}
func (b *relayHeaderBuilder) randomQuerySuffix() (string, string) {
patterns := []struct {
key string
value func() string
}{
{key: "webhe", value: func() string { return randomTokenPattern(6, 8, 10) }},
{key: "r", value: func() string { return randomHex(12) }},
{key: "_", value: func() string { return randomAlphaNumeric(18) }},
{key: "cache_bust", value: func() string { return randomTokenPattern(8, 6, 8) }},
{key: "v", value: func() string { return randomTokenPattern(4, 4, 6) }},
}
pattern := patterns[randomIndex(len(patterns))]
return pattern.key, pattern.value()
}
func randomTokenPattern(parts ...int) string {
if len(parts) == 0 {
return ""
}
values := make([]string, 0, len(parts))
for _, partLength := range parts {
values = append(values, randomAlphaNumeric(partLength))
}
return strings.Join(values, "-")
}
func randomAlphaNumeric(length int) string {
if length <= 0 {
return ""
}
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
var builder strings.Builder
builder.Grow(length)
for i := 0; i < length; i++ {
builder.WriteByte(alphabet[randomIndex(len(alphabet))])
}
return builder.String()
}
+53
View File
@@ -0,0 +1,53 @@
package client
import (
"net/url"
"strings"
"testing"
"masterhttprelayvpn/internal/config"
)
func TestBuildRelayURLLeavesURLUntouchedWhenSuffixRandomizationDisabled(t *testing.T) {
builder := newRelayHeaderBuilder(config.Config{
RelayURL: "https://example.com/relay",
HTTPRandomizeQuerySuffix: false,
}, nil)
got := builder.BuildRelayURL("https://example.com/relay")
if got != "https://example.com/relay" {
t.Fatalf("expected relay URL to stay unchanged, got %q", got)
}
}
func TestBuildRelayURLAddsRandomQuerySuffixWhenEnabled(t *testing.T) {
builder := newRelayHeaderBuilder(config.Config{
RelayURL: "https://example.com/relay?existing=1",
HTTPRandomizeQuerySuffix: true,
}, nil)
got := builder.BuildRelayURL("https://example.com/relay?existing=1")
parsed, err := url.Parse(got)
if err != nil {
t.Fatalf("parse randomized relay URL: %v", err)
}
query := parsed.Query()
if query.Get("existing") != "1" {
t.Fatalf("expected existing query parameter to be preserved, got %q", query.Get("existing"))
}
randomKeys := []string{"webhe", "r", "_", "cache_bust", "v"}
found := false
for _, key := range randomKeys {
if value := query.Get(key); value != "" {
found = true
if strings.TrimSpace(value) == "" {
t.Fatalf("expected randomized query value for key %q to be non-empty", key)
}
}
}
if !found {
t.Fatalf("expected one randomized query suffix key, got query %q", parsed.RawQuery)
}
}
+6 -1
View File
@@ -430,7 +430,12 @@ func (c *Client) reclaimExpiredReorder() {
func (w *sendWorker) postBatch(ctx context.Context, c *Client, batch protocol.Batch, body []byte) error {
pingOnly := isPingOnlyBatch(batch)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.cfg.RelayURL, bytes.NewReader(body))
relayURL := c.cfg.RelayURL
if c.headerBuilder != nil {
relayURL = c.headerBuilder.BuildRelayURL(relayURL)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, relayURL, bytes.NewReader(body))
if err != nil {
if pingOnly {
c.failPing()