From b7a22c729be98e613d90ab8e1140b72ea009696b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 01:33:30 +0000 Subject: [PATCH] Xray-core: Dynamic Chrome User-Agent for all HTTP requests by default (overwriteable through config) (#5658) https://github.com/XTLS/Xray-core/issues/4996#issuecomment-3855274627 https://github.com/XTLS/Xray-core/pull/5658#issuecomment-3857332687 --------- Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> Co-authored-by: Fangliding <63339210+Fangliding@users.noreply.github.com> --- app/dns/nameserver_doh.go | 1 + app/observatory/burst/ping.go | 2 ++ app/observatory/observer.go | 5 ++++- common/utils/browser.go | 28 ++++++++++++++++++++++++ go.mod | 2 +- infra/conf/transport_authenticators.go | 8 +++---- proxy/http/client.go | 4 ++++ transport/internet/grpc/dial.go | 7 ++++-- transport/internet/httpupgrade/dialer.go | 4 ++++ transport/internet/reality/reality.go | 3 ++- transport/internet/splithttp/config.go | 4 ++++ transport/internet/tls/ech.go | 1 + transport/internet/websocket/config.go | 4 ++++ 13 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 common/utils/browser.go diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go index c0c1af26..2126bcd8 100644 --- a/app/dns/nameserver_doh.go +++ b/app/dns/nameserver_doh.go @@ -214,6 +214,7 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, req.Header.Add("Accept", "application/dns-message") req.Header.Add("Content-Type", "application/dns-message") + req.Header.Set("User-Agent", utils.ChromeUA) req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) hc := s.httpClient diff --git a/app/observatory/burst/ping.go b/app/observatory/burst/ping.go index fcb40f1a..f08adb57 100644 --- a/app/observatory/burst/ping.go +++ b/app/observatory/burst/ping.go @@ -7,6 +7,7 @@ import ( "time" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet/tagged" ) @@ -61,6 +62,7 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) { if err != nil { return rttFailed, err } + req.Header.Set("User-Agent", utils.ChromeUA) start := time.Now() resp, err := s.httpClient.Do(req) diff --git a/app/observatory/observer.go b/app/observatory/observer.go index a6fba3ad..0ff9ba64 100644 --- a/app/observatory/observer.go +++ b/app/observatory/observer.go @@ -15,6 +15,7 @@ import ( "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/task" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/extension" "github.com/xtls/xray-core/features/outbound" @@ -162,7 +163,9 @@ func (o *Observer) probe(outbound string) ProbeResult { if o.config.ProbeUrl != "" { probeURL = o.config.ProbeUrl } - response, err := httpClient.Get(probeURL) + req, _ := http.NewRequest(http.MethodGet, probeURL, nil) + req.Header.Set("User-Agent", utils.ChromeUA) + response, err := httpClient.Do(req) if err != nil { return errors.New("outbound failed to relay connection").Base(err) } diff --git a/common/utils/browser.go b/common/utils/browser.go new file mode 100644 index 00000000..91209f4b --- /dev/null +++ b/common/utils/browser.go @@ -0,0 +1,28 @@ +package utils + +import ( + "math/rand" + "strconv" + "time" + + "github.com/klauspost/cpuid/v2" +) + +func ChromeVersion() int { + // Use only CPU info as seed for PRNG + seed := int64(cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine) + rng := rand.New(rand.NewSource(seed)) + // Start from Chrome 144 released on 2026.1.13 + releaseDate := time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC) + version := 144 + now := time.Now() + // Each version has random 25-45 day interval + for releaseDate.Before(now) { + releaseDate = releaseDate.AddDate(0, 0, rng.Intn(21)+25) + version++ + } + return version - 1 +} + +// ChromeUA provides default browser User-Agent based on CPU-seeded PRNG. +var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(ChromeVersion()) + ".0.0.0 Safari/537.36" diff --git a/go.mod b/go.mod index 77d8780c..4a97fead 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/golang/mock v1.7.0-rc.1 github.com/google/go-cmp v0.7.0 github.com/gorilla/websocket v1.5.3 + github.com/klauspost/cpuid/v2 v2.0.12 github.com/miekg/dns v1.1.72 github.com/pelletier/go-toml v1.9.5 github.com/pires/go-proxyproto v0.9.2 @@ -39,7 +40,6 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/juju/ratelimit v1.0.2 // indirect github.com/klauspost/compress v1.17.4 // indirect - github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect diff --git a/infra/conf/transport_authenticators.go b/infra/conf/transport_authenticators.go index 2274c5ad..ea286f41 100644 --- a/infra/conf/transport_authenticators.go +++ b/infra/conf/transport_authenticators.go @@ -4,6 +4,7 @@ import ( "sort" "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet/headers/http" "github.com/xtls/xray-core/transport/internet/headers/noop" "google.golang.org/protobuf/proto" @@ -40,11 +41,8 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) { Value: []string{"www.baidu.com", "www.bing.com"}, }, { - Name: "User-Agent", - Value: []string{ - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", - "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46", - }, + Name: "User-Agent", + Value: []string{utils.ChromeUA}, }, { Name: "Accept-Encoding", diff --git a/proxy/http/client.go b/proxy/http/client.go index 1f804d0b..f79ce547 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -21,6 +21,7 @@ import ( "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/task" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/transport" @@ -219,6 +220,9 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u for _, h := range header { req.Header.Set(h.Key, h.Value) } + if req.Header.Get("User-Agent") == "" { + req.Header.Set("User-Agent", utils.ChromeUA) + } connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { req.Header.Set("Proxy-Connection", "Keep-Alive") diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 454b3280..e0b0aa03 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -10,6 +10,7 @@ import ( "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/grpc/encoding" "github.com/xtls/xray-core/transport/internet/reality" @@ -167,9 +168,11 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in dialOptions = append(dialOptions, grpc.WithInitialWindowSize(grpcSettings.InitialWindowsSize)) } - if grpcSettings.UserAgent != "" { - dialOptions = append(dialOptions, grpc.WithUserAgent(grpcSettings.UserAgent)) + userAgent := grpcSettings.UserAgent + if userAgent == "" { + userAgent = utils.ChromeUA } + dialOptions = append(dialOptions, grpc.WithUserAgent(userAgent)) var grpcDestHost string if dest.Address.Family().IsDomain() { diff --git a/transport/internet/httpupgrade/dialer.go b/transport/internet/httpupgrade/dialer.go index c10bd97e..4d718eb8 100644 --- a/transport/internet/httpupgrade/dialer.go +++ b/transport/internet/httpupgrade/dialer.go @@ -10,6 +10,7 @@ import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" @@ -86,6 +87,9 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings * for key, value := range transportConfiguration.Header { AddHeader(req.Header, key, value) } + if req.Header.Get("User-Agent") == "" { + req.Header.Set("User-Agent", utils.ChromeUA) + } req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "websocket") diff --git a/transport/internet/reality/reality.go b/transport/internet/reality/reality.go index 21b185ea..1f6de2b5 100644 --- a/transport/internet/reality/reality.go +++ b/transport/internet/reality/reality.go @@ -27,6 +27,7 @@ import ( "github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/transport/internet/tls" "golang.org/x/crypto/hkdf" @@ -222,7 +223,7 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati if req == nil { return } - req.Header.Set("User-Agent", fingerprint.Client) // TODO: User-Agent map + req.Header.Set("User-Agent", utils.ChromeUA) if first && config.Show { fmt.Printf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent()) } diff --git a/transport/internet/splithttp/config.go b/transport/internet/splithttp/config.go index 9ecd5a43..1819f0e9 100644 --- a/transport/internet/splithttp/config.go +++ b/transport/internet/splithttp/config.go @@ -6,6 +6,7 @@ import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/crypto" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet" ) @@ -47,6 +48,9 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Headers { header.Add(k, v) } + if header.Get("User-Agent") == "" { + header.Set("User-Agent", utils.ChromeUA) + } return header } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 389d0699..6ba38aea 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -257,6 +257,7 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b } req.Header.Set("Accept", "application/dns-message") req.Header.Set("Content-Type", "application/dns-message") + req.Header.Set("User-Agent", utils.ChromeUA) req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) resp, err := client.Do(req) diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 4f2c0a14..bd38cd4f 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/transport/internet" ) @@ -23,6 +24,9 @@ func (c *Config) GetRequestHeader() http.Header { for k, v := range c.Header { header.Add(k, v) } + if header.Get("User-Agent") == "" { + header.Set("User-Agent", utils.ChromeUA) + } return header }