Geodata: Apply uTLS Chrome fingerprint when downloading (#6371)

Closes https://github.com/XTLS/Xray-core/issues/6369
This commit is contained in:
风扇滑翔翼
2026-06-24 18:49:05 +08:00
committed by RPRX
parent f9eb1597ad
commit 7e7e820763
+52 -10
View File
@@ -2,6 +2,7 @@ package geodata
import ( import (
"context" "context"
"crypto/tls"
go_errors "errors" go_errors "errors"
"io" "io"
"net/http" "net/http"
@@ -9,6 +10,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
utls "github.com/refraction-networking/utls"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/platform/filesystem" "github.com/xtls/xray-core/common/platform/filesystem"
@@ -16,6 +18,7 @@ import (
"github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport/internet/tagged" "github.com/xtls/xray-core/transport/internet/tagged"
"golang.org/x/net/http2"
) )
const idleTimeout = 30 * time.Second const idleTimeout = 30 * time.Second
@@ -27,7 +30,8 @@ type stage struct {
type downloader struct { type downloader struct {
ctx context.Context ctx context.Context
client *http.Client httpClient *http.Client
httpsClient *http.Client
} }
type idleConn struct { type idleConn struct {
@@ -54,16 +58,13 @@ func (c *idleConn) Write(b []byte) (int, error) {
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader { func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
return &downloader{ return &downloader{
ctx: ctx, ctx: ctx,
client: newClient(ctx, dispatcher, outbound), httpClient: newClient(ctx, dispatcher, outbound, false),
httpsClient: newClient(ctx, dispatcher, outbound, true),
} }
} }
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string) *http.Client { func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client {
return &http.Client{ dial := func(ctx context.Context, network, address string) (net.Conn, error) {
Transport: &http.Transport{
Proxy: nil,
DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
var conn net.Conn var conn net.Conn
err := task.Run(ctx, func() error { err := task.Run(ctx, func() error {
if tagged.Dialer == nil { if tagged.Dialer == nil {
@@ -86,8 +87,42 @@ func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound
return &idleConn{ return &idleConn{
Conn: conn, Conn: conn,
}, nil }, nil
}
if isHTTPS {
return &http.Client{
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network string, address string, cfg *tls.Config) (net.Conn, error) {
conn, err := dial(ctx, network, address)
if err != nil {
return nil, err
}
host, _, _ := net.SplitHostPort(address)
tlsConn := utls.UClient(conn, &utls.Config{ServerName: host}, utls.HelloChrome_Auto)
handshakeCtx, cancel := context.WithTimeout(ctx, idleTimeout)
defer cancel()
if err := tlsConn.HandshakeContext(handshakeCtx); err != nil {
conn.Close()
return nil, err
}
return tlsConn, nil
}, },
TLSHandshakeTimeout: idleTimeout, },
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if req.URL.Scheme != "https" {
return errors.New("redirected to non-https URL: ", req.URL.String())
}
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
},
}
} else {
return &http.Client{
Transport: &http.Transport{
Proxy: nil,
DisableKeepAlives: true,
DialContext: dial,
ResponseHeaderTimeout: idleTimeout, ResponseHeaderTimeout: idleTimeout,
}, },
CheckRedirect: func(req *http.Request, via []*http.Request) error { CheckRedirect: func(req *http.Request, via []*http.Request) error {
@@ -100,6 +135,7 @@ func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound
return nil return nil
}, },
} }
}
} }
func (d *downloader) download(assets []*Asset) ([]stage, error) { func (d *downloader) download(assets []*Asset) ([]stage, error) {
@@ -160,7 +196,13 @@ func (d *downloader) fetch(rawURL string, writer io.Writer) error {
} }
utils.TryDefaultHeadersWith(req.Header, "nav") utils.TryDefaultHeadersWith(req.Header, "nav")
resp, err := d.client.Do(req) var client *http.Client
if req.URL.Scheme == "https" {
client = d.httpsClient
} else {
client = d.httpClient
}
resp, err := client.Do(req)
if err != nil { if err != nil {
return err return err
} }