mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-03 02:08:45 +00:00
Geodata: Apply uTLS Chrome fingerprint when downloading (#6371)
Closes https://github.com/XTLS/Xray-core/issues/6369
This commit is contained in:
+85
-43
@@ -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
|
||||||
@@ -26,8 +29,9 @@ 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 {
|
||||||
@@ -53,52 +57,84 @@ 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{
|
var conn net.Conn
|
||||||
Proxy: nil,
|
err := task.Run(ctx, func() error {
|
||||||
DisableKeepAlives: true,
|
if tagged.Dialer == nil {
|
||||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
return errors.New("tagged dialer is not initialized")
|
||||||
var conn net.Conn
|
|
||||||
err := task.Run(ctx, func() error {
|
|
||||||
if tagged.Dialer == nil {
|
|
||||||
return errors.New("tagged dialer is not initialized")
|
|
||||||
}
|
|
||||||
dest, err := net.ParseDestination(network + ":" + address)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("cannot understand address").Base(err)
|
|
||||||
}
|
|
||||||
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("cannot dial remote address ", dest).Base(err)
|
|
||||||
}
|
|
||||||
conn = c
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("cannot finish connection").Base(err)
|
|
||||||
}
|
|
||||||
return &idleConn{
|
|
||||||
Conn: conn,
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
TLSHandshakeTimeout: idleTimeout,
|
|
||||||
ResponseHeaderTimeout: 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 {
|
dest, err := net.ParseDestination(network + ":" + address)
|
||||||
return errors.New("stopped after 10 redirects")
|
if err != nil {
|
||||||
|
return errors.New("cannot understand address").Base(err)
|
||||||
}
|
}
|
||||||
|
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("cannot dial remote address ", dest).Base(err)
|
||||||
|
}
|
||||||
|
conn = c
|
||||||
return nil
|
return nil
|
||||||
},
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("cannot finish connection").Base(err)
|
||||||
|
}
|
||||||
|
return &idleConn{
|
||||||
|
Conn: conn,
|
||||||
|
}, 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
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user