github.com/metacubex/mihomo@v1.18.5/dns/doh.go (about) 1 package dns 2 3 import ( 4 "context" 5 "crypto/tls" 6 "encoding/base64" 7 "errors" 8 "fmt" 9 "io" 10 "net" 11 "net/http" 12 "net/url" 13 "runtime" 14 "strconv" 15 "sync" 16 "time" 17 18 "github.com/metacubex/mihomo/component/ca" 19 C "github.com/metacubex/mihomo/constant" 20 "github.com/metacubex/mihomo/log" 21 "github.com/metacubex/quic-go" 22 "github.com/metacubex/quic-go/http3" 23 D "github.com/miekg/dns" 24 "golang.org/x/exp/slices" 25 "golang.org/x/net/http2" 26 ) 27 28 // Values to configure HTTP and HTTP/2 transport. 29 const ( 30 // transportDefaultReadIdleTimeout is the default timeout for pinging 31 // idle connections in HTTP/2 transport. 32 transportDefaultReadIdleTimeout = 30 * time.Second 33 34 // transportDefaultIdleConnTimeout is the default timeout for idle 35 // connections in HTTP transport. 36 transportDefaultIdleConnTimeout = 5 * time.Minute 37 38 // dohMaxConnsPerHost controls the maximum number of connections for 39 // each host. 40 dohMaxConnsPerHost = 1 41 dialTimeout = 10 * time.Second 42 43 // dohMaxIdleConns controls the maximum number of connections being idle 44 // at the same time. 45 dohMaxIdleConns = 1 46 maxElapsedTime = time.Second * 30 47 ) 48 49 var DefaultHTTPVersions = []C.HTTPVersion{C.HTTPVersion11, C.HTTPVersion2} 50 51 // dnsOverHTTPS is a struct that implements the Upstream interface for the 52 // DNS-over-HTTPS protocol. 53 type dnsOverHTTPS struct { 54 // The Client's Transport typically has internal state (cached TCP 55 // connections), so Clients should be reused instead of created as 56 // needed. Clients are safe for concurrent use by multiple goroutines. 57 client *http.Client 58 clientMu sync.Mutex 59 60 // quicConfig is the QUIC configuration that is used if HTTP/3 is enabled 61 // for this upstream. 62 quicConfig *quic.Config 63 quicConfigGuard sync.Mutex 64 url *url.URL 65 r *Resolver 66 httpVersions []C.HTTPVersion 67 proxyAdapter C.ProxyAdapter 68 proxyName string 69 addr string 70 } 71 72 // type check 73 var _ dnsClient = (*dnsOverHTTPS)(nil) 74 75 // newDoH returns the DNS-over-HTTPS Upstream. 76 func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) dnsClient { 77 u, _ := url.Parse(urlString) 78 httpVersions := DefaultHTTPVersions 79 if preferH3 { 80 httpVersions = append(httpVersions, C.HTTPVersion3) 81 } 82 83 if params["h3"] == "true" { 84 httpVersions = []C.HTTPVersion{C.HTTPVersion3} 85 } 86 87 doh := &dnsOverHTTPS{ 88 url: u, 89 addr: u.String(), 90 r: r, 91 proxyAdapter: proxyAdapter, 92 proxyName: proxyName, 93 quicConfig: &quic.Config{ 94 KeepAlivePeriod: QUICKeepAlivePeriod, 95 TokenStore: newQUICTokenStore(), 96 }, 97 httpVersions: httpVersions, 98 } 99 100 runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close) 101 102 return doh 103 } 104 105 // Address implements the Upstream interface for *dnsOverHTTPS. 106 func (doh *dnsOverHTTPS) Address() string { 107 return doh.addr 108 } 109 func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { 110 // Quote from https://www.rfc-editor.org/rfc/rfc8484.html: 111 // In order to maximize HTTP cache friendliness, DoH clients using media 112 // formats that include the ID field from the DNS message header, such 113 // as "application/dns-message", SHOULD use a DNS ID of 0 in every DNS 114 // request. 115 m = m.Copy() 116 id := m.Id 117 m.Id = 0 118 defer func() { 119 // Restore the original ID to not break compatibility with proxies. 120 m.Id = id 121 if msg != nil { 122 msg.Id = id 123 } 124 }() 125 126 // Check if there was already an active client before sending the request. 127 // We'll only attempt to re-connect if there was one. 128 client, isCached, err := doh.getClient(ctx) 129 if err != nil { 130 return nil, fmt.Errorf("failed to init http client: %w", err) 131 } 132 133 // Make the first attempt to send the DNS query. 134 msg, err = doh.exchangeHTTPS(ctx, client, m) 135 136 // Make up to 2 attempts to re-create the HTTP client and send the request 137 // again. There are several cases (mostly, with QUIC) where this workaround 138 // is necessary to make HTTP client usable. We need to make 2 attempts in 139 // the case when the connection was closed (due to inactivity for example) 140 // AND the server refuses to open a 0-RTT connection. 141 for i := 0; isCached && doh.shouldRetry(err) && i < 2; i++ { 142 client, err = doh.resetClient(ctx, err) 143 if err != nil { 144 return nil, fmt.Errorf("failed to reset http client: %w", err) 145 } 146 147 msg, err = doh.exchangeHTTPS(ctx, client, m) 148 } 149 150 if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { 151 // If the request failed anyway, make sure we don't use this client. 152 _, resErr := doh.resetClient(ctx, err) 153 154 return nil, fmt.Errorf("%w (resErr:%v)", err, resErr) 155 } 156 157 return msg, err 158 } 159 160 // Close implements the Upstream interface for *dnsOverHTTPS. 161 func (doh *dnsOverHTTPS) Close() (err error) { 162 doh.clientMu.Lock() 163 defer doh.clientMu.Unlock() 164 165 runtime.SetFinalizer(doh, nil) 166 167 if doh.client == nil { 168 return nil 169 } 170 171 return doh.closeClient(doh.client) 172 } 173 174 // closeClient cleans up resources used by client if necessary. Note, that at 175 // this point it should only be done for HTTP/3 as it may leak due to keep-alive 176 // connections. 177 func (doh *dnsOverHTTPS) closeClient(client *http.Client) (err error) { 178 if isHTTP3(client) { 179 return client.Transport.(io.Closer).Close() 180 } 181 182 return nil 183 } 184 185 // exchangeHTTPS logs the request and its result and calls exchangeHTTPSClient. 186 func (doh *dnsOverHTTPS) exchangeHTTPS(ctx context.Context, client *http.Client, req *D.Msg) (resp *D.Msg, err error) { 187 resp, err = doh.exchangeHTTPSClient(ctx, client, req) 188 return resp, err 189 } 190 191 // exchangeHTTPSClient sends the DNS query to a DoH resolver using the specified 192 // http.Client instance. 193 func (doh *dnsOverHTTPS) exchangeHTTPSClient( 194 ctx context.Context, 195 client *http.Client, 196 req *D.Msg, 197 ) (resp *D.Msg, err error) { 198 buf, err := req.Pack() 199 if err != nil { 200 return nil, fmt.Errorf("packing message: %w", err) 201 } 202 203 // It appears, that GET requests are more memory-efficient with Golang 204 // implementation of HTTP/2. 205 method := http.MethodGet 206 if isHTTP3(client) { 207 // If we're using HTTP/3, use http3.MethodGet0RTT to force using 0-RTT. 208 method = http3.MethodGet0RTT 209 } 210 211 url := doh.url 212 url.RawQuery = fmt.Sprintf("dns=%s", base64.RawURLEncoding.EncodeToString(buf)) 213 httpReq, err := http.NewRequestWithContext(ctx, method, url.String(), nil) 214 if err != nil { 215 return nil, fmt.Errorf("creating http request to %s: %w", url, err) 216 } 217 218 httpReq.Header.Set("Accept", "application/dns-message") 219 httpReq.Header.Set("User-Agent", "") 220 httpResp, err := client.Do(httpReq) 221 if err != nil { 222 return nil, fmt.Errorf("requesting %s: %w", url, err) 223 } 224 defer httpResp.Body.Close() 225 226 body, err := io.ReadAll(httpResp.Body) 227 if err != nil { 228 return nil, fmt.Errorf("reading %s: %w", url, err) 229 } 230 231 if httpResp.StatusCode != http.StatusOK { 232 return nil, 233 fmt.Errorf( 234 "expected status %d, got %d from %s", 235 http.StatusOK, 236 httpResp.StatusCode, 237 url, 238 ) 239 } 240 241 resp = &D.Msg{} 242 err = resp.Unpack(body) 243 if err != nil { 244 return nil, fmt.Errorf( 245 "unpacking response from %s: body is %s: %w", 246 url, 247 body, 248 err, 249 ) 250 } 251 252 if resp.Id != req.Id { 253 err = D.ErrId 254 } 255 256 return resp, err 257 } 258 259 // shouldRetry checks what error we have received and returns true if we should 260 // re-create the HTTP client and retry the request. 261 func (doh *dnsOverHTTPS) shouldRetry(err error) (ok bool) { 262 if err == nil { 263 return false 264 } 265 266 var netErr net.Error 267 if errors.As(err, &netErr) && netErr.Timeout() { 268 // If this is a timeout error, trying to forcibly re-create the HTTP 269 // client instance. This is an attempt to fix an issue with DoH client 270 // stalling after a network change. 271 // 272 // See https://github.com/AdguardTeam/AdGuardHome/issues/3217. 273 return true 274 } 275 276 if isQUICRetryError(err) { 277 return true 278 } 279 280 return false 281 } 282 283 // resetClient triggers re-creation of the *http.Client that is used by this 284 // upstream. This method accepts the error that caused resetting client as 285 // depending on the error we may also reset the QUIC config. 286 func (doh *dnsOverHTTPS) resetClient(ctx context.Context, resetErr error) (client *http.Client, err error) { 287 doh.clientMu.Lock() 288 defer doh.clientMu.Unlock() 289 290 if errors.Is(resetErr, quic.Err0RTTRejected) { 291 // Reset the TokenStore only if 0-RTT was rejected. 292 doh.resetQUICConfig() 293 } 294 295 oldClient := doh.client 296 if oldClient != nil { 297 closeErr := doh.closeClient(oldClient) 298 if closeErr != nil { 299 log.Warnln("warning: failed to close the old http client: %v", closeErr) 300 } 301 } 302 303 log.Debugln("re-creating the http client due to %v", resetErr) 304 doh.client, err = doh.createClient(ctx) 305 306 return doh.client, err 307 } 308 309 // getQUICConfig returns the QUIC config in a thread-safe manner. Note, that 310 // this method returns a pointer, it is forbidden to change its properties. 311 func (doh *dnsOverHTTPS) getQUICConfig() (c *quic.Config) { 312 doh.quicConfigGuard.Lock() 313 defer doh.quicConfigGuard.Unlock() 314 315 return doh.quicConfig 316 } 317 318 // resetQUICConfig Re-create the token store to make sure we're not trying to 319 // use invalid for 0-RTT. 320 func (doh *dnsOverHTTPS) resetQUICConfig() { 321 doh.quicConfigGuard.Lock() 322 defer doh.quicConfigGuard.Unlock() 323 324 doh.quicConfig = doh.quicConfig.Clone() 325 doh.quicConfig.TokenStore = newQUICTokenStore() 326 } 327 328 // getClient gets or lazily initializes an HTTP client (and transport) that will 329 // be used for this DoH resolver. 330 func (doh *dnsOverHTTPS) getClient(ctx context.Context) (c *http.Client, isCached bool, err error) { 331 startTime := time.Now() 332 333 doh.clientMu.Lock() 334 defer doh.clientMu.Unlock() 335 if doh.client != nil { 336 return doh.client, true, nil 337 } 338 339 // Timeout can be exceeded while waiting for the lock. This happens quite 340 // often on mobile devices. 341 elapsed := time.Since(startTime) 342 if elapsed > maxElapsedTime { 343 return nil, false, fmt.Errorf("timeout exceeded: %s", elapsed) 344 } 345 346 log.Debugln("creating a new http client") 347 doh.client, err = doh.createClient(ctx) 348 349 return doh.client, false, err 350 } 351 352 // createClient creates a new *http.Client instance. The HTTP protocol version 353 // will depend on whether HTTP3 is allowed and provided by this upstream. Note, 354 // that we'll attempt to establish a QUIC connection when creating the client in 355 // order to check whether HTTP3 is supported. 356 func (doh *dnsOverHTTPS) createClient(ctx context.Context) (*http.Client, error) { 357 transport, err := doh.createTransport(ctx) 358 if err != nil { 359 return nil, fmt.Errorf("[%s] initializing http transport: %w", doh.url.String(), err) 360 } 361 362 client := &http.Client{ 363 Transport: transport, 364 Timeout: DefaultTimeout, 365 Jar: nil, 366 } 367 368 doh.client = client 369 370 return doh.client, nil 371 } 372 373 // createTransport initializes an HTTP transport that will be used specifically 374 // for this DoH resolver. This HTTP transport ensures that the HTTP requests 375 // will be sent exactly to the IP address got from the bootstrap resolver. Note, 376 // that this function will first attempt to establish a QUIC connection (if 377 // HTTP3 is enabled in the upstream options). If this attempt is successful, 378 // it returns an HTTP3 transport, otherwise it returns the H1/H2 transport. 379 func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) { 380 tlsConfig := ca.GetGlobalTLSConfig( 381 &tls.Config{ 382 InsecureSkipVerify: false, 383 MinVersion: tls.VersionTLS12, 384 SessionTicketsDisabled: false, 385 }) 386 var nextProtos []string 387 for _, v := range doh.httpVersions { 388 nextProtos = append(nextProtos, string(v)) 389 } 390 tlsConfig.NextProtos = nextProtos 391 dialContext := getDialHandler(doh.r, doh.proxyAdapter, doh.proxyName) 392 393 if slices.Contains(doh.httpVersions, C.HTTPVersion3) { 394 // First, we attempt to create an HTTP3 transport. If the probe QUIC 395 // connection is established successfully, we'll be using HTTP3 for this 396 // upstream. 397 transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext) 398 if err == nil { 399 log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String()) 400 return transportH3, nil 401 } 402 } 403 404 log.Debugln("[%s] using HTTP/2 for this upstream: %v", doh.url.String(), err) 405 406 if !doh.supportsHTTP() { 407 return nil, errors.New("HTTP1/1 and HTTP2 are not supported by this upstream") 408 } 409 410 transport := &http.Transport{ 411 TLSClientConfig: tlsConfig, 412 DisableCompression: true, 413 DialContext: dialContext, 414 IdleConnTimeout: transportDefaultIdleConnTimeout, 415 MaxConnsPerHost: dohMaxConnsPerHost, 416 MaxIdleConns: dohMaxIdleConns, 417 // Since we have a custom DialContext, we need to use this field to 418 // make golang http.Client attempt to use HTTP/2. Otherwise, it would 419 // only be used when negotiated on the TLS level. 420 ForceAttemptHTTP2: true, 421 } 422 423 // Explicitly configure transport to use HTTP/2. 424 // 425 // See https://github.com/AdguardTeam/dnsproxy/issues/11. 426 var transportH2 *http2.Transport 427 transportH2, err = http2.ConfigureTransports(transport) 428 if err != nil { 429 return nil, err 430 } 431 432 // Enable HTTP/2 pings on idle connections. 433 transportH2.ReadIdleTimeout = transportDefaultReadIdleTimeout 434 435 return transport, nil 436 } 437 438 // http3Transport is a wrapper over *http3.RoundTripper that tries to optimize 439 // its behavior. The main thing that it does is trying to force use a single 440 // connection to a host instead of creating a new one all the time. It also 441 // helps mitigate race issues with quic-go. 442 type http3Transport struct { 443 baseTransport *http3.RoundTripper 444 445 closed bool 446 mu sync.RWMutex 447 } 448 449 // type check 450 var _ http.RoundTripper = (*http3Transport)(nil) 451 452 // RoundTrip implements the http.RoundTripper interface for *http3Transport. 453 func (h *http3Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { 454 h.mu.RLock() 455 defer h.mu.RUnlock() 456 457 if h.closed { 458 return nil, net.ErrClosed 459 } 460 461 // Try to use cached connection to the target host if it's available. 462 resp, err = h.baseTransport.RoundTripOpt(req, http3.RoundTripOpt{OnlyCachedConn: true}) 463 464 if errors.Is(err, http3.ErrNoCachedConn) { 465 // If there are no cached connection, trigger creating a new one. 466 resp, err = h.baseTransport.RoundTrip(req) 467 } 468 469 return resp, err 470 } 471 472 // type check 473 var _ io.Closer = (*http3Transport)(nil) 474 475 // Close implements the io.Closer interface for *http3Transport. 476 func (h *http3Transport) Close() (err error) { 477 h.mu.Lock() 478 defer h.mu.Unlock() 479 480 h.closed = true 481 482 return h.baseTransport.Close() 483 } 484 485 // createTransportH3 tries to create an HTTP/3 transport for this upstream. 486 // We should be able to fall back to H1/H2 in case if HTTP/3 is unavailable or 487 // if it is too slow. In order to do that, this method will run two probes 488 // in parallel (one for TLS, the other one for QUIC) and if QUIC is faster it 489 // will create the *http3.RoundTripper instance. 490 func (doh *dnsOverHTTPS) createTransportH3( 491 ctx context.Context, 492 tlsConfig *tls.Config, 493 dialContext dialHandler, 494 ) (roundTripper http.RoundTripper, err error) { 495 if !doh.supportsH3() { 496 return nil, errors.New("HTTP3 support is not enabled") 497 } 498 499 addr, err := doh.probeH3(ctx, tlsConfig, dialContext) 500 if err != nil { 501 return nil, err 502 } 503 504 rt := &http3.RoundTripper{ 505 Dial: func( 506 ctx context.Context, 507 508 // Ignore the address and always connect to the one that we got 509 // from the bootstrapper. 510 _ string, 511 tlsCfg *tls.Config, 512 cfg *quic.Config, 513 ) (c quic.EarlyConnection, err error) { 514 return doh.dialQuic(ctx, addr, tlsCfg, cfg) 515 }, 516 DisableCompression: true, 517 TLSClientConfig: tlsConfig, 518 QUICConfig: doh.getQUICConfig(), 519 } 520 521 return &http3Transport{baseTransport: rt}, nil 522 } 523 524 func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { 525 ip, port, err := net.SplitHostPort(addr) 526 if err != nil { 527 return nil, err 528 } 529 portInt, err := strconv.Atoi(port) 530 if err != nil { 531 return nil, err 532 } 533 udpAddr := net.UDPAddr{ 534 IP: net.ParseIP(ip), 535 Port: portInt, 536 } 537 conn, err := listenPacket(ctx, doh.proxyAdapter, doh.proxyName, "udp", addr, doh.r) 538 if err != nil { 539 return nil, err 540 } 541 transport := quic.Transport{Conn: conn} 542 transport.SetCreatedConn(true) // auto close conn 543 transport.SetSingleUse(true) // auto close transport 544 tlsCfg = tlsCfg.Clone() 545 if host, _, err := net.SplitHostPort(doh.url.Host); err == nil { 546 tlsCfg.ServerName = host 547 } else { 548 // It's ok if net.SplitHostPort returns an error - it could be a hostname/IP address without a port. 549 tlsCfg.ServerName = doh.url.Host 550 } 551 return transport.DialEarly(ctx, &udpAddr, tlsCfg, cfg) 552 } 553 554 // probeH3 runs a test to check whether QUIC is faster than TLS for this 555 // upstream. If the test is successful it will return the address that we 556 // should use to establish the QUIC connections. 557 func (doh *dnsOverHTTPS) probeH3( 558 ctx context.Context, 559 tlsConfig *tls.Config, 560 dialContext dialHandler, 561 ) (addr string, err error) { 562 // We're using bootstrapped address instead of what's passed to the function 563 // it does not create an actual connection, but it helps us determine 564 // what IP is actually reachable (when there are v4/v6 addresses). 565 rawConn, err := dialContext(ctx, "udp", doh.url.Host) 566 if err != nil { 567 return "", fmt.Errorf("failed to dial: %w", err) 568 } 569 addr = rawConn.RemoteAddr().String() 570 // It's never actually used. 571 _ = rawConn.Close() 572 573 // Avoid spending time on probing if this upstream only supports HTTP/3. 574 if doh.supportsH3() && !doh.supportsHTTP() { 575 return addr, nil 576 } 577 578 // Use a new *tls.Config with empty session cache for probe connections. 579 // Surprisingly, this is really important since otherwise it invalidates 580 // the existing cache. 581 // TODO(ameshkov): figure out why the sessions cache invalidates here. 582 probeTLSCfg := tlsConfig.Clone() 583 probeTLSCfg.ClientSessionCache = nil 584 585 // Do not expose probe connections to the callbacks that are passed to 586 // the bootstrap options to avoid side-effects. 587 // TODO(ameshkov): consider exposing, somehow mark that this is a probe. 588 probeTLSCfg.VerifyPeerCertificate = nil 589 probeTLSCfg.VerifyConnection = nil 590 591 // Run probeQUIC and probeTLS in parallel and see which one is faster. 592 chQuic := make(chan error, 1) 593 chTLS := make(chan error, 1) 594 go doh.probeQUIC(ctx, addr, probeTLSCfg, chQuic) 595 go doh.probeTLS(ctx, dialContext, probeTLSCfg, chTLS) 596 597 select { 598 case quicErr := <-chQuic: 599 if quicErr != nil { 600 // QUIC failed, return error since HTTP3 was not preferred. 601 return "", quicErr 602 } 603 604 // Return immediately, QUIC was faster. 605 return addr, quicErr 606 case tlsErr := <-chTLS: 607 if tlsErr != nil { 608 // Return immediately, TLS failed. 609 log.Debugln("probing TLS: %v", tlsErr) 610 return addr, nil 611 } 612 613 return "", errors.New("TLS was faster than QUIC, prefer it") 614 } 615 } 616 617 // probeQUIC attempts to establish a QUIC connection to the specified address. 618 // We run probeQUIC and probeTLS in parallel and see which one is faster. 619 func (doh *dnsOverHTTPS) probeQUIC(ctx context.Context, addr string, tlsConfig *tls.Config, ch chan error) { 620 startTime := time.Now() 621 conn, err := doh.dialQuic(ctx, addr, tlsConfig, doh.getQUICConfig()) 622 if err != nil { 623 ch <- fmt.Errorf("opening QUIC connection to %s: %w", doh.Address(), err) 624 return 625 } 626 627 // Ignore the error since there's no way we can use it for anything useful. 628 _ = conn.CloseWithError(QUICCodeNoError, "") 629 630 ch <- nil 631 632 elapsed := time.Now().Sub(startTime) 633 log.Debugln("elapsed on establishing a QUIC connection: %s", elapsed) 634 } 635 636 // probeTLS attempts to establish a TLS connection to the specified address. We 637 // run probeQUIC and probeTLS in parallel and see which one is faster. 638 func (doh *dnsOverHTTPS) probeTLS(ctx context.Context, dialContext dialHandler, tlsConfig *tls.Config, ch chan error) { 639 startTime := time.Now() 640 641 conn, err := doh.tlsDial(ctx, dialContext, "tcp", tlsConfig) 642 if err != nil { 643 ch <- fmt.Errorf("opening TLS connection: %w", err) 644 return 645 } 646 647 // Ignore the error since there's no way we can use it for anything useful. 648 _ = conn.Close() 649 650 ch <- nil 651 652 elapsed := time.Now().Sub(startTime) 653 log.Debugln("elapsed on establishing a TLS connection: %s", elapsed) 654 } 655 656 // supportsH3 returns true if HTTP/3 is supported by this upstream. 657 func (doh *dnsOverHTTPS) supportsH3() (ok bool) { 658 for _, v := range doh.supportedHTTPVersions() { 659 if v == C.HTTPVersion3 { 660 return true 661 } 662 } 663 664 return false 665 } 666 667 // supportsHTTP returns true if HTTP/1.1 or HTTP2 is supported by this upstream. 668 func (doh *dnsOverHTTPS) supportsHTTP() (ok bool) { 669 for _, v := range doh.supportedHTTPVersions() { 670 if v == C.HTTPVersion11 || v == C.HTTPVersion2 { 671 return true 672 } 673 } 674 675 return false 676 } 677 678 // supportedHTTPVersions returns the list of supported HTTP versions. 679 func (doh *dnsOverHTTPS) supportedHTTPVersions() (v []C.HTTPVersion) { 680 v = doh.httpVersions 681 if v == nil { 682 v = DefaultHTTPVersions 683 } 684 685 return v 686 } 687 688 // isHTTP3 checks if the *http.Client is an HTTP/3 client. 689 func isHTTP3(client *http.Client) (ok bool) { 690 _, ok = client.Transport.(*http3Transport) 691 692 return ok 693 } 694 695 // tlsDial is basically the same as tls.DialWithDialer, but we will call our own 696 // dialContext function to get connection. 697 func (doh *dnsOverHTTPS) tlsDial(ctx context.Context, dialContext dialHandler, network string, config *tls.Config) (*tls.Conn, error) { 698 // We're using bootstrapped address instead of what's passed 699 // to the function. 700 rawConn, err := dialContext(ctx, network, doh.url.Host) 701 if err != nil { 702 return nil, err 703 } 704 705 // We want the timeout to cover the whole process: TCP connection and 706 // TLS handshake dialTimeout will be used as connection deadLine. 707 conn := tls.Client(rawConn, config) 708 709 err = conn.SetDeadline(time.Now().Add(dialTimeout)) 710 if err != nil { 711 // Must not happen in normal circumstances. 712 log.Errorln("cannot set deadline: %v", err) 713 return nil, err 714 } 715 716 err = conn.Handshake() 717 if err != nil { 718 defer conn.Close() 719 return nil, err 720 } 721 722 return conn, nil 723 }