Просмотр исходного кода

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 <[email protected]>
Co-authored-by: Fangliding <[email protected]>
Copilot 1 месяц назад
Родитель
Сommit
b7a22c729b

+ 1 - 0
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

+ 2 - 0
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)

+ 4 - 1
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)
 		}

+ 28 - 0
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"

+ 1 - 1
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

+ 3 - 5
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",

+ 4 - 0
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")

+ 5 - 2
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() {

+ 4 - 0
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")
 

+ 2 - 1
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())
 				}

+ 4 - 0
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
 }
 

+ 1 - 0
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)

+ 4 - 0
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
 }