From c40fc44a34d35f851319c06a7ca2b03784be4e77 Mon Sep 17 00:00:00 2001 From: ll11l1lIllIl1lll <88377095+ll11l1lIllIl1lll@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:10:48 +0000 Subject: [PATCH] SplitHTTP: Client supports HTTP/3 (#3543) Closes https://github.com/XTLS/Xray-core/issues/3456 Co-authored-by: Fangliding Co-authored-by: mmmray <142015632+mmmray@users.noreply.github.com> --- go.mod | 1 + go.sum | 2 ++ transport/internet/splithttp/client.go | 3 +- transport/internet/splithttp/dialer.go | 38 +++++++++++++++++++++----- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index b88896ad..9dee55e5 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/onsi/ginkgo/v2 v2.19.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/vishvananda/netns v0.0.4 // indirect go.uber.org/mock v0.4.0 // indirect diff --git a/go.sum b/go.sum index c8f3b597..f30c211a 100644 --- a/go.sum +++ b/go.sum @@ -110,6 +110,8 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjCA= github.com/quic-go/quic-go v0.45.1/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= diff --git a/transport/internet/splithttp/client.go b/transport/internet/splithttp/client.go index dd48ee6d..2a467d7d 100644 --- a/transport/internet/splithttp/client.go +++ b/transport/internet/splithttp/client.go @@ -32,6 +32,7 @@ type DefaultDialerClient struct { download *http.Client upload *http.Client isH2 bool + isH3 bool // pool of net.Conn, created using dialUploadConn uploadRawPool *sync.Pool dialUploadConn func(ctxInner context.Context) (net.Conn, error) @@ -118,7 +119,7 @@ func (c *DefaultDialerClient) SendUploadRequest(ctx context.Context, url string, } req.Header = c.transportConfig.GetRequestHeader() - if c.isH2 { + if c.isH2 || c.isH3 { resp, err := c.upload.Do(req) if err != nil { return err diff --git a/transport/internet/splithttp/dialer.go b/transport/internet/splithttp/dialer.go index 3a7ce641..9f98da3d 100644 --- a/transport/internet/splithttp/dialer.go +++ b/transport/internet/splithttp/dialer.go @@ -10,6 +10,8 @@ import ( "sync" "time" + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/errors" @@ -50,12 +52,9 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in return client } - if browser_dialer.HasBrowserDialer() { - return &BrowserDialerClient{} - } - tlsConfig := tls.ConfigFromStreamSettings(streamSettings) isH2 := tlsConfig != nil && !(len(tlsConfig.NextProtocol) == 1 && tlsConfig.NextProtocol[0] == "http/1.1") + isH3 := tlsConfig != nil && (len(tlsConfig.NextProtocol) == 1 && tlsConfig.NextProtocol[0] == "h3") var gotlsConfig *gotls.Config @@ -83,10 +82,35 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in return conn, nil } - var uploadTransport http.RoundTripper var downloadTransport http.RoundTripper + var uploadTransport http.RoundTripper - if isH2 { + if isH3 { + dest.Network = net.Network_UDP + quicConfig := &quic.Config{ + HandshakeIdleTimeout: 10 * time.Second, + MaxIdleTimeout: 90 * time.Second, + KeepAlivePeriod: 3 * time.Second, + Allow0RTT: true, + } + roundTripper := &http3.RoundTripper{ + TLSClientConfig: gotlsConfig, + QUICConfig: quicConfig, + Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings) + if err != nil { + return nil, err + } + udpAddr, err := net.ResolveUDPAddr("udp", conn.RemoteAddr().String()) + if err != nil { + return nil, err + } + return quic.DialEarly(ctx, conn.(*internet.PacketConnWrapper).Conn.(*net.UDPConn), udpAddr, tlsCfg, cfg) + }, + } + downloadTransport = roundTripper + uploadTransport = roundTripper + } else if isH2 { downloadTransport = &http2.Transport{ DialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) { return dialContext(ctxInner) @@ -107,7 +131,6 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in // http.Client and our custom dial context. DisableKeepAlives: true, } - // we use uploadRawPool for that uploadTransport = nil } @@ -121,6 +144,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in Transport: uploadTransport, }, isH2: isH2, + isH3: isH3, uploadRawPool: &sync.Pool{}, dialUploadConn: dialContext, }