| // Copyright 2023 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package grpctransport |
| |
| import ( |
| "context" |
| "net" |
| "os" |
| "strconv" |
| "strings" |
| |
| "cloud.google.com/go/auth" |
| "cloud.google.com/go/compute/metadata" |
| "google.golang.org/grpc" |
| grpcgoogle "google.golang.org/grpc/credentials/google" |
| ) |
| |
| func isDirectPathEnabled(endpoint string, opts *Options) bool { |
| if opts.InternalOptions != nil && !opts.InternalOptions.EnableDirectPath { |
| return false |
| } |
| if !checkDirectPathEndPoint(endpoint) { |
| return false |
| } |
| if b, _ := strconv.ParseBool(os.Getenv(disableDirectPathEnvVar)); b { |
| return false |
| } |
| return true |
| } |
| |
| func checkDirectPathEndPoint(endpoint string) bool { |
| // Only [dns:///]host[:port] is supported, not other schemes (e.g., "tcp://" or "unix://"). |
| // Also don't try direct path if the user has chosen an alternate name resolver |
| // (i.e., via ":///" prefix). |
| if strings.Contains(endpoint, "://") && !strings.HasPrefix(endpoint, "dns:///") { |
| return false |
| } |
| |
| if endpoint == "" { |
| return false |
| } |
| |
| return true |
| } |
| |
| func isTokenProviderDirectPathCompatible(tp auth.TokenProvider, _ *Options) bool { |
| if tp == nil { |
| return false |
| } |
| tok, err := tp.Token(context.Background()) |
| if err != nil { |
| return false |
| } |
| if tok == nil { |
| return false |
| } |
| if source, _ := tok.Metadata["auth.google.tokenSource"].(string); source != "compute-metadata" { |
| return false |
| } |
| if acct, _ := tok.Metadata["auth.google.serviceAccount"].(string); acct != "default" { |
| return false |
| } |
| return true |
| } |
| |
| func isDirectPathXdsUsed(o *Options) bool { |
| // Method 1: Enable DirectPath xDS by env; |
| if b, _ := strconv.ParseBool(os.Getenv(enableDirectPathXdsEnvVar)); b { |
| return true |
| } |
| // Method 2: Enable DirectPath xDS by option; |
| if o.InternalOptions != nil && o.InternalOptions.EnableDirectPathXds { |
| return true |
| } |
| return false |
| } |
| |
| // configureDirectPath returns some dial options and an endpoint to use if the |
| // configuration allows the use of direct path. If it does not the provided |
| // grpcOpts and endpoint are returned. |
| func configureDirectPath(grpcOpts []grpc.DialOption, opts *Options, endpoint string, creds *auth.Credentials) ([]grpc.DialOption, string) { |
| if isDirectPathEnabled(endpoint, opts) && metadata.OnGCE() && isTokenProviderDirectPathCompatible(creds, opts) { |
| // Overwrite all of the previously specific DialOptions, DirectPath uses its own set of credentials and certificates. |
| grpcOpts = []grpc.DialOption{ |
| grpc.WithCredentialsBundle(grpcgoogle.NewDefaultCredentialsWithOptions(grpcgoogle.DefaultCredentialsOptions{PerRPCCreds: &grpcCredentialsProvider{creds: creds}}))} |
| if timeoutDialerOption != nil { |
| grpcOpts = append(grpcOpts, timeoutDialerOption) |
| } |
| // Check if google-c2p resolver is enabled for DirectPath |
| if isDirectPathXdsUsed(opts) { |
| // google-c2p resolver target must not have a port number |
| if addr, _, err := net.SplitHostPort(endpoint); err == nil { |
| endpoint = "google-c2p:///" + addr |
| } else { |
| endpoint = "google-c2p:///" + endpoint |
| } |
| } else { |
| if !strings.HasPrefix(endpoint, "dns:///") { |
| endpoint = "dns:///" + endpoint |
| } |
| grpcOpts = append(grpcOpts, |
| // For now all DirectPath go clients will be using the following lb config, but in future |
| // when different services need different configs, then we should change this to a |
| // per-service config. |
| grpc.WithDisableServiceConfig(), |
| grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}}]}}]}`)) |
| } |
| // TODO: add support for system parameters (quota project, request reason) via chained interceptor. |
| } |
| return grpcOpts, endpoint |
| } |