From 82d8a8f0b484561cfff6c5507d21a7facc1c26aa Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Thu, 18 Apr 2024 16:12:44 -0700 Subject: [PATCH] Made base transport layer force ipv4 usage if ipv6 is not supported Fixes https://github.com/hashicorp/terraform-provider-google/issues/6782 Workaround for https://github.com/golang/go/issues/25321 --- .../terraform/transport/config.go.erb | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/mmv1/third_party/terraform/transport/config.go.erb b/mmv1/third_party/terraform/transport/config.go.erb index 3bace4f1d380..53264448d66c 100644 --- a/mmv1/third_party/terraform/transport/config.go.erb +++ b/mmv1/third_party/terraform/transport/config.go.erb @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "log" + "net" "net/http" "regexp" "strconv" @@ -28,6 +29,8 @@ import ( "github.com/hashicorp/terraform-provider-google/google/verify" + "golang.org/x/net/http2" + "golang.org/x/net/nettest" "golang.org/x/oauth2" "google.golang.org/grpc" googleoauth "golang.org/x/oauth2/google" @@ -82,6 +85,7 @@ import ( "google.golang.org/api/storage/v1" "google.golang.org/api/storagetransfer/v1" "google.golang.org/api/transport" + apihttp "google.golang.org/api/transport/http" ) type ProviderMeta struct { @@ -429,6 +433,39 @@ func SetEndpointDefaults(d *schema.ResourceData) error { return nil } +// baseTransport returns the base HTTP transport. It starts with the +// default transport and makes some tweaks to match best practices +// from google-api-go-client, as well as ensuring that IPv6 does +// not get used in environments that don't support it. +func baseTransport() (http.RoundTripper, error) { + trans := http.DefaultTransport.(*http.Transport).Clone() + // Increase MaxIdleConnsPerHost due to reported performance issues under load in the + // GCS client. + trans.MaxIdleConnsPerHost = 100 + + // Configure the ReadIdleTimeout HTTP/2 option for the + // transport. This allows broken idle connections to be pruned more quickly, + // preventing the client from attempting to re-use connections that will no + // longer work. http2Trans is discarded after configuration. + http2Trans, err := http2.ConfigureTransports(trans) + if err != nil { + return trans, err + } + http2Trans.ReadIdleTimeout = time.Second * 31 + + // Override dialer so that we don't try IPv6 if it's not supported. + // https://github.com/golang/go/issues/25321 + trans.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) { + d := &net.Dialer{} + if !nettest.SupportsIPv6() { + return d.DialContext(ctx, "tcp4", addr) + } + return d.DialContext(ctx, network, addr) + } + + return trans, nil +} + func (c *Config) LoadAndValidate(ctx context.Context) error { if len(c.Scopes) == 0 { c.Scopes = DefaultClientScopes @@ -445,8 +482,13 @@ func (c *Config) LoadAndValidate(ctx context.Context) error { cleanCtx := context.WithValue(ctx, oauth2.HTTPClient, cleanhttp.DefaultClient()) - // 1. MTLS TRANSPORT/CLIENT - sets up proper auth headers - client, _, err := transport.NewHTTPClient(cleanCtx, option.WithTokenSource(tokenSource)) + // 1. Set up base HTTP transport and configure token auth / API communication + base, err := baseTransport() + if err != nil { + return err + } + + transport, err := apihttp.NewTransport(cleanCtx, base, option.WithTokenSource(tokenSource)) if err != nil { return err } @@ -458,7 +500,7 @@ func (c *Config) LoadAndValidate(ctx context.Context) error { } // 2. Logging Transport - ensure we log HTTP requests to GCP APIs. - loggingTransport := logging.NewTransport("Google", client.Transport) + loggingTransport := logging.NewTransport("Google", transport) // 3. Retry Transport - retries common temporary errors // Keep order for wrapping logging so we log each retried request as well. @@ -479,8 +521,8 @@ func (c *Config) LoadAndValidate(ctx context.Context) error { headerTransport.Set("X-Goog-User-Project", c.BillingProject) } - // Set final transport value. - client.Transport = headerTransport + // Create http client + client := &http.Client{Transport: headerTransport} // This timeout is a timeout per HTTP request, not per logical operation. client.Timeout = c.synchronousTimeout()