11package http
22
33import (
4+ "context"
45 "crypto/tls"
6+ "fmt"
7+ "net"
58 "net/http"
69 "time"
10+
11+ "k8s.io/klog/v2"
712)
813
14+ // ImmediateFallbackDialContext creates a DialContext function that tries connection
15+ // attempts sequentially in the order returned by DNS, without the 300ms Happy Eyeballs
16+ // delay. This respects DNS server ordering while eliminating the racing delay.
17+ //
18+ // Go's standard Happy Eyeballs implementation (RFC 6555/8305) is in the net package:
19+ // https://cs.opensource.google/go/go/+/refs/tags/go1.25.3:src/net/dial.go;l=525 (DialContext)
20+ // https://cs.opensource.google/go/go/+/refs/tags/go1.25.3:src/net/dial.go;l=585 (dialParallel)
21+ func ImmediateFallbackDialContext (ctx context.Context , network , address string ) (net.Conn , error ) {
22+ // Split the address into host and port
23+ host , port , err := net .SplitHostPort (address )
24+ if err != nil {
25+ return nil , err
26+ }
27+
28+ klog .InfoS ("Resolving DNS for connection" , "host" , host , "port" , port , "network" , network )
29+
30+ // Resolve all IP addresses for the host
31+ ips , err := net .DefaultResolver .LookupIP (ctx , "ip" , host )
32+ if err != nil {
33+ klog .ErrorS (err , "DNS resolution failed" , "host" , host )
34+ return nil , err
35+ }
36+
37+ if len (ips ) == 0 {
38+ err := fmt .Errorf ("no IP addresses found for host %s" , host )
39+ klog .ErrorS (err , "DNS resolution returned no addresses" , "host" , host )
40+ return nil , err
41+ }
42+
43+ // Convert IPs to strings for logging
44+ ipStrings := make ([]string , 0 , len (ips ))
45+ for _ , ip := range ips {
46+ ipStrings = append (ipStrings , ip .String ())
47+ }
48+ klog .InfoS ("DNS resolution complete" , "host" , host , "addressCount" , len (ips ), "addresses" , ipStrings )
49+
50+ dialer := & net.Dialer {
51+ Timeout : 30 * time .Second ,
52+ KeepAlive : 30 * time .Second ,
53+ }
54+
55+ // Try each address sequentially in the order DNS returned them
56+ var lastErr error
57+ for i , ip := range ips {
58+ // Determine address type and dial network
59+ var addrType , dialNetwork string
60+ if ip .To4 () != nil {
61+ addrType = "IPv4"
62+ dialNetwork = network
63+ if network == "tcp" {
64+ dialNetwork = "tcp4"
65+ }
66+ } else {
67+ addrType = "IPv6"
68+ dialNetwork = network
69+ if network == "tcp" {
70+ dialNetwork = "tcp6"
71+ }
72+ }
73+
74+ target := net .JoinHostPort (ip .String (), port )
75+ klog .InfoS ("Attempting connection" , "host" , host , "type" , addrType ,
76+ "address" , ip .String (), "port" , port , "attempt" , i + 1 , "of" , len (ips ))
77+
78+ conn , err := dialer .DialContext (ctx , dialNetwork , target )
79+ if err == nil {
80+ klog .InfoS ("Successfully connected" , "host" , host , "type" , addrType ,
81+ "address" , ip .String (), "port" , port )
82+ return conn , nil
83+ }
84+ klog .ErrorS (err , "Connection failed" , "host" , host , "type" , addrType ,
85+ "address" , ip .String (), "port" , port , "attempt" , i + 1 , "of" , len (ips ))
86+ lastErr = err
87+ }
88+
89+ klog .ErrorS (lastErr , "All connection attempts failed" , "host" , host , "totalAttempts" , len (ips ))
90+ return nil , lastErr
91+ }
92+
93+ // ConfigureDefaultTransport configures http.DefaultTransport to use ImmediateFallbackDialContext.
94+ // This affects all HTTP clients that use the default transport, including the containers/image
95+ // library used for pulling from registries. Returns an error if DefaultTransport is not *http.Transport.
96+ func ConfigureDefaultTransport () error {
97+ transport , ok := http .DefaultTransport .(* http.Transport )
98+ if ! ok {
99+ return fmt .Errorf ("http.DefaultTransport is not *http.Transport, cannot configure custom dialer" )
100+ }
101+ transport .DialContext = ImmediateFallbackDialContext
102+ return nil
103+ }
104+
9105func BuildHTTPClient (cpw * CertPoolWatcher ) (* http.Client , error ) {
10106 httpClient := & http.Client {Timeout : 10 * time .Second }
11107
@@ -14,13 +110,16 @@ func BuildHTTPClient(cpw *CertPoolWatcher) (*http.Client, error) {
14110 return nil , err
15111 }
16112
17- tlsConfig := & tls.Config {
113+ // Clone the default transport to inherit custom dialer and other defaults
114+ transport , ok := http .DefaultTransport .(* http.Transport )
115+ if ! ok {
116+ return nil , fmt .Errorf ("http.DefaultTransport is not *http.Transport, cannot build HTTP client" )
117+ }
118+ tlsTransport := transport .Clone ()
119+ tlsTransport .TLSClientConfig = & tls.Config {
18120 RootCAs : pool ,
19121 MinVersion : tls .VersionTLS12 ,
20122 }
21- tlsTransport := & http.Transport {
22- TLSClientConfig : tlsConfig ,
23- }
24123 httpClient .Transport = tlsTransport
25124
26125 return httpClient , nil
0 commit comments