11package http
22
33import (
4+ "context"
45 "crypto/tls"
6+ "net"
57 "net/http"
68 "time"
9+
10+ "k8s.io/klog/v2"
711)
812
13+ // IPv4PreferringDialContext creates a DialContext function that prefers IPv4 addresses
14+ // when both IPv4 and IPv6 addresses are available. It tries all IPv4 addresses first,
15+ // and only falls back to IPv6 if all IPv4 connection attempts fail. If only one type
16+ // is available, it uses whichever is present.
17+ func IPv4PreferringDialContext (ctx context.Context , network , address string ) (net.Conn , error ) {
18+ // Split the address into host and port
19+ host , port , err := net .SplitHostPort (address )
20+ if err != nil {
21+ return nil , err
22+ }
23+
24+ klog .V (4 ).InfoS ("Resolving DNS for connection" , "host" , host , "port" , port , "network" , network )
25+
26+ // Resolve all IP addresses for the host
27+ ips , err := net .DefaultResolver .LookupIP (ctx , "ip" , host )
28+ if err != nil {
29+ klog .V (2 ).ErrorS (err , "DNS resolution failed" , "host" , host )
30+ return nil , err
31+ }
32+
33+ // Separate IPv4 and IPv6 addresses
34+ var ipv4Addrs , ipv6Addrs []net.IP
35+ for _ , ip := range ips {
36+ if ip .To4 () != nil {
37+ ipv4Addrs = append (ipv4Addrs , ip )
38+ } else {
39+ ipv6Addrs = append (ipv6Addrs , ip )
40+ }
41+ }
42+
43+ klog .V (4 ).InfoS ("DNS resolution complete" , "host" , host , "ipv4Count" , len (ipv4Addrs ), "ipv6Count" , len (ipv6Addrs ))
44+ if len (ipv4Addrs ) > 0 {
45+ klog .V (4 ).InfoS ("IPv4 addresses found" , "host" , host , "addresses" , ipv4Addrs )
46+ }
47+ if len (ipv6Addrs ) > 0 {
48+ klog .V (4 ).InfoS ("IPv6 addresses found" , "host" , host , "addresses" , ipv6Addrs )
49+ }
50+
51+ // Try to connect to each address, preferring IPv4 first
52+ dialer := & net.Dialer {
53+ Timeout : 30 * time .Second ,
54+ KeepAlive : 30 * time .Second ,
55+ }
56+
57+ // Try all IPv4 addresses first
58+ var lastErr error
59+ for i , ip := range ipv4Addrs {
60+ dialNetwork := network
61+ if network == "tcp" {
62+ dialNetwork = "tcp4"
63+ }
64+
65+ target := net .JoinHostPort (ip .String (), port )
66+ klog .V (2 ).InfoS ("Attempting IPv4 connection" , "host" , host , "address" , ip .String (), "port" , port , "attempt" , i + 1 , "of" , len (ipv4Addrs ))
67+
68+ conn , err := dialer .DialContext (ctx , dialNetwork , target )
69+ if err == nil {
70+ klog .InfoS ("Successfully connected via IPv4" , "host" , host , "address" , ip .String (), "port" , port )
71+ return conn , nil
72+ }
73+ klog .V (2 ).ErrorS (err , "IPv4 connection failed" , "host" , host , "address" , ip .String (), "port" , port , "attempt" , i + 1 , "of" , len (ipv4Addrs ))
74+ lastErr = err
75+ }
76+
77+ // If all IPv4 attempts failed, try IPv6 addresses
78+ if len (ipv4Addrs ) > 0 && len (ipv6Addrs ) > 0 {
79+ klog .V (2 ).InfoS ("All IPv4 attempts failed, falling back to IPv6" , "host" , host , "ipv4Tried" , len (ipv4Addrs ))
80+ }
81+
82+ for i , ip := range ipv6Addrs {
83+ dialNetwork := network
84+ if network == "tcp" {
85+ dialNetwork = "tcp6"
86+ }
87+
88+ target := net .JoinHostPort (ip .String (), port )
89+ klog .V (2 ).InfoS ("Attempting IPv6 connection" , "host" , host , "address" , ip .String (), "port" , port , "attempt" , i + 1 , "of" , len (ipv6Addrs ))
90+
91+ conn , err := dialer .DialContext (ctx , dialNetwork , target )
92+ if err == nil {
93+ klog .InfoS ("Successfully connected via IPv6" , "host" , host , "address" , ip .String (), "port" , port )
94+ return conn , nil
95+ }
96+ klog .V (2 ).ErrorS (err , "IPv6 connection failed" , "host" , host , "address" , ip .String (), "port" , port , "attempt" , i + 1 , "of" , len (ipv6Addrs ))
97+ lastErr = err
98+ }
99+
100+ klog .ErrorS (lastErr , "All connection attempts failed" , "host" , host , "ipv4Tried" , len (ipv4Addrs ), "ipv6Tried" , len (ipv6Addrs ))
101+ return nil , lastErr
102+ }
103+
9104func BuildHTTPClient (cpw * CertPoolWatcher ) (* http.Client , error ) {
10105 httpClient := & http.Client {Timeout : 10 * time .Second }
11106
@@ -14,13 +109,12 @@ func BuildHTTPClient(cpw *CertPoolWatcher) (*http.Client, error) {
14109 return nil , err
15110 }
16111
17- tlsConfig := & tls.Config {
112+ // Clone the default transport to inherit IPv4 preference and other defaults
113+ tlsTransport := http .DefaultTransport .(* http.Transport ).Clone ()
114+ tlsTransport .TLSClientConfig = & tls.Config {
18115 RootCAs : pool ,
19116 MinVersion : tls .VersionTLS12 ,
20117 }
21- tlsTransport := & http.Transport {
22- TLSClientConfig : tlsConfig ,
23- }
24118 httpClient .Transport = tlsTransport
25119
26120 return httpClient , nil
0 commit comments