88 "io"
99 "net"
1010 "net/http"
11+ "net/http/httptrace"
1112 "net/url"
1213 "os"
1314 "strings"
@@ -19,6 +20,8 @@ import (
1920
2021 "github.com/klauspost/compress/gzip"
2122 "github.com/klauspost/compress/zstd"
23+ "github.com/quic-go/quic-go"
24+ "github.com/quic-go/quic-go/http3"
2225 "golang.org/x/net/http2"
2326)
2427
@@ -92,42 +95,9 @@ func NewClient(cfg ClientConfig) *Client {
9295 var transport http.RoundTripper
9396 switch cfg .HTTP {
9497 case core .HTTP2 :
95- rt := & http2.Transport {
96- AllowHTTP : false , // Disable h2c, for now.
97- DialTLSContext : func (ctx context.Context , network string , addr string , cfg * tls.Config ) (net.Conn , error ) {
98- dial := baseDial
99- if dial == nil {
100- var dialer net.Dialer
101- dial = dialer .DialContext
102- }
103-
104- // Dial a connection and perform the TLS handshake.
105- conn , err := dial (ctx , network , addr )
106- if err != nil {
107- return nil , err
108- }
109-
110- if cfg .ServerName == "" {
111- c := cfg .Clone ()
112- host , _ , err := net .SplitHostPort (addr )
113- if err != nil {
114- host = addr
115- }
116- c .ServerName = host
117- cfg = c
118- }
119-
120- tlsConn := tls .Client (conn , cfg )
121- if err := tlsConn .HandshakeContext (ctx ); err != nil {
122- conn .Close ()
123- return nil , err
124- }
125- return tlsConn , nil
126- },
127- DisableCompression : true ,
128- TLSClientConfig : & tlsConfig ,
129- }
130- transport = rt
98+ transport = getHTTP2Transport (baseDial , & tlsConfig )
99+ case core .HTTP3 :
100+ transport = getHTTP3Transport (cfg .DNSServer , & tlsConfig )
131101 default :
132102 rt := & http.Transport {
133103 DialContext : baseDial ,
@@ -160,6 +130,114 @@ func NewClient(cfg ClientConfig) *Client {
160130 return & Client {c : client }
161131}
162132
133+ func getHTTP2Transport (baseDial func (context.Context , string , string ) (net.Conn , error ), tlsConfig * tls.Config ) http.RoundTripper {
134+ return & http2.Transport {
135+ AllowHTTP : false , // Disable h2c, for now.
136+ DialTLSContext : func (ctx context.Context , network string , addr string , cfg * tls.Config ) (net.Conn , error ) {
137+ dial := baseDial
138+ if dial == nil {
139+ var dialer net.Dialer
140+ dial = dialer .DialContext
141+ }
142+
143+ // Dial a connection and perform the TLS handshake.
144+ conn , err := dial (ctx , network , addr )
145+ if err != nil {
146+ return nil , err
147+ }
148+
149+ if cfg .ServerName == "" {
150+ c := cfg .Clone ()
151+ host , _ , err := net .SplitHostPort (addr )
152+ if err != nil {
153+ host = addr
154+ }
155+ c .ServerName = host
156+ cfg = c
157+ }
158+
159+ tlsConn := tls .Client (conn , cfg )
160+ if err := tlsConn .HandshakeContext (ctx ); err != nil {
161+ conn .Close ()
162+ return nil , err
163+ }
164+ return tlsConn , nil
165+ },
166+ DisableCompression : true ,
167+ TLSClientConfig : tlsConfig ,
168+ }
169+ }
170+
171+ func getHTTP3Transport (dnsServer * url.URL , tlsConfig * tls.Config ) http.RoundTripper {
172+ rt := & http3.Transport {
173+ DisableCompression : true ,
174+ TLSClientConfig : tlsConfig ,
175+ }
176+ if dnsServer != nil {
177+ rt .Dial = func (ctx context.Context , addr string , tlsCfg * tls.Config , qcfg * quic.Config ) (* quic.Conn , error ) {
178+ // Resolve the address to IPs.
179+ var ips []net.IPAddr
180+ var portStr string
181+ var err error
182+ if dnsServer .Scheme == "" {
183+ var host string
184+ host , portStr , err = net .SplitHostPort (addr )
185+ if err != nil {
186+ return nil , err
187+ }
188+ resolver := udpResolver (dnsServer .Host )
189+ ips , err = resolver .LookupIPAddr (ctx , host )
190+ } else {
191+ ips , portStr , err = resolveDOH (ctx , dnsServer , addr )
192+ }
193+ if err != nil {
194+ return nil , err
195+ }
196+ if len (ips ) == 0 {
197+ return nil , fmt .Errorf ("lookup %s: no addresses found" , addr )
198+ }
199+
200+ port , err := net .LookupPort ("udp" , portStr )
201+ if err != nil {
202+ return nil , err
203+ }
204+
205+ // Establish quic connection.
206+ trace := httptrace .ContextClientTrace (ctx )
207+ for _ , ip := range ips {
208+ udpAddr := & net.UDPAddr {IP : ip .IP , Port : port }
209+ var lc net.ListenConfig
210+ var packetConn net.PacketConn
211+ packetConn , err = lc .ListenPacket (ctx , "udp" , ":0" )
212+ if err != nil {
213+ continue
214+ }
215+
216+ if trace != nil && trace .TLSHandshakeStart != nil {
217+ trace .TLSHandshakeStart ()
218+ }
219+
220+ var conn * quic.Conn
221+ conn , err = quic .DialEarly (ctx , packetConn , udpAddr , tlsCfg , qcfg )
222+ if trace != nil && trace .TLSHandshakeDone != nil {
223+ var state tls.ConnectionState
224+ if conn != nil {
225+ state = conn .ConnectionState ().TLS
226+ }
227+ trace .TLSHandshakeDone (state , err )
228+ }
229+ if err != nil {
230+ continue
231+ }
232+ return conn , nil
233+ }
234+
235+ return nil , err
236+ }
237+ }
238+ return rt
239+ }
240+
163241// RequestConfig represents the configuration for creating an HTTP request.
164242type RequestConfig struct {
165243 AWSSigV4 * aws.Config
0 commit comments