google.golang.org/grpc@v1.72.2/xds/googledirectpath/googlec2p.go (about) 1 /* 2 * 3 * Copyright 2021 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Package googledirectpath implements a resolver that configures xds to make 20 // cloud to prod directpath connection. 21 // 22 // It's a combo of DNS and xDS resolvers. It delegates to DNS if 23 // - not on GCE, or 24 // - xDS bootstrap env var is set (so this client needs to do normal xDS, not 25 // direct path, and clients with this scheme is not part of the xDS mesh). 26 package googledirectpath 27 28 import ( 29 "encoding/json" 30 "fmt" 31 rand "math/rand/v2" 32 "net/url" 33 "sync" 34 "time" 35 36 "google.golang.org/grpc/grpclog" 37 "google.golang.org/grpc/internal/envconfig" 38 "google.golang.org/grpc/internal/googlecloud" 39 internalgrpclog "google.golang.org/grpc/internal/grpclog" 40 "google.golang.org/grpc/internal/xds/bootstrap" 41 "google.golang.org/grpc/resolver" 42 "google.golang.org/grpc/xds/internal/xdsclient" 43 44 _ "google.golang.org/grpc/xds" // To register xds resolvers and balancers. 45 ) 46 47 const ( 48 c2pScheme = "google-c2p" 49 c2pAuthority = "traffic-director-c2p.xds.googleapis.com" 50 51 defaultUniverseDomain = "googleapis.com" 52 zoneURL = "http://metadata.google.internal/computeMetadata/v1/instance/zone" 53 ipv6URL = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ipv6s" 54 ipv6CapableMetadataName = "TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE" 55 httpReqTimeout = 10 * time.Second 56 57 logPrefix = "[google-c2p-resolver]" 58 dnsName, xdsName = "dns", "xds" 59 ) 60 61 var ( 62 logger = internalgrpclog.NewPrefixLogger(grpclog.Component("directpath"), logPrefix) 63 universeDomainMu sync.Mutex 64 universeDomain = "" 65 // For overriding in unittests. 66 onGCE = googlecloud.OnGCE 67 randInt = rand.Int 68 xdsClientPool = xdsclient.DefaultPool 69 ) 70 71 func init() { 72 resolver.Register(c2pResolverBuilder{}) 73 } 74 75 // SetUniverseDomain informs the gRPC library of the universe domain 76 // in which the process is running (for example, "googleapis.com"). 77 // It is the caller's responsibility to ensure that the domain is correct. 78 // 79 // This setting is used by the "google-c2p" resolver (the resolver used 80 // for URIs with the "google-c2p" scheme) to configure its dependencies. 81 // 82 // If a gRPC channel is created with the "google-c2p" URI scheme and this 83 // function has NOT been called, then gRPC configures the universe domain as 84 // "googleapis.com". 85 // 86 // Returns nil if either: 87 // 88 // a) The universe domain has not yet been configured. 89 // b) The universe domain has been configured and matches the provided value. 90 // 91 // Otherwise, returns an error. 92 func SetUniverseDomain(domain string) error { 93 universeDomainMu.Lock() 94 defer universeDomainMu.Unlock() 95 if domain == "" { 96 return fmt.Errorf("universe domain cannot be empty") 97 } 98 if universeDomain == "" { 99 universeDomain = domain 100 return nil 101 } 102 if universeDomain != domain { 103 return fmt.Errorf("universe domain cannot be set to %s, already set to different value: %s", domain, universeDomain) 104 } 105 return nil 106 } 107 108 func getXdsServerURI() string { 109 universeDomainMu.Lock() 110 defer universeDomainMu.Unlock() 111 if universeDomain == "" { 112 universeDomain = defaultUniverseDomain 113 } 114 // Put env var override logic after default value logic so 115 // that tests still run the default value logic. 116 if envconfig.C2PResolverTestOnlyTrafficDirectorURI != "" { 117 return envconfig.C2PResolverTestOnlyTrafficDirectorURI 118 } 119 return fmt.Sprintf("dns:///directpath-pa.%s", universeDomain) 120 } 121 122 type c2pResolverBuilder struct{} 123 124 func (c2pResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { 125 if t.URL.Host != "" { 126 return nil, fmt.Errorf("google-c2p URI scheme does not support authorities") 127 } 128 129 if !runDirectPath() { 130 // If not xDS, fallback to DNS. 131 t.URL.Scheme = dnsName 132 return resolver.Get(dnsName).Build(t, cc, opts) 133 } 134 135 // Note that the following calls to getZone() and getIPv6Capable() does I/O, 136 // and has 10 seconds timeout each. 137 // 138 // This should be fine in most of the cases. In certain error cases, this 139 // could block Dial() for up to 10 seconds (each blocking call has its own 140 // goroutine). 141 zoneCh, ipv6CapableCh := make(chan string), make(chan bool) 142 go func() { zoneCh <- getZone(httpReqTimeout) }() 143 go func() { ipv6CapableCh <- getIPv6Capable(httpReqTimeout) }() 144 145 xdsServerURI := getXdsServerURI() 146 nodeCfg := newNodeConfig(<-zoneCh, <-ipv6CapableCh) 147 xdsServerCfg := newXdsServerConfig(xdsServerURI) 148 authoritiesCfg := newAuthoritiesConfig(xdsServerCfg) 149 150 cfg := map[string]any{ 151 "xds_servers": []any{xdsServerCfg}, 152 "client_default_listener_resource_name_template": "%s", 153 "authorities": authoritiesCfg, 154 "node": nodeCfg, 155 } 156 cfgJSON, err := json.Marshal(cfg) 157 if err != nil { 158 return nil, fmt.Errorf("failed to marshal bootstrap configuration: %v", err) 159 } 160 config, err := bootstrap.NewConfigFromContents(cfgJSON) 161 if err != nil { 162 return nil, fmt.Errorf("failed to parse bootstrap contents: %s, %v", string(cfgJSON), err) 163 } 164 xdsClientPool.SetFallbackBootstrapConfig(config) 165 166 t = resolver.Target{ 167 URL: url.URL{ 168 Scheme: xdsName, 169 Host: c2pAuthority, 170 Path: t.URL.Path, 171 }, 172 } 173 return resolver.Get(xdsName).Build(t, cc, opts) 174 } 175 176 func (b c2pResolverBuilder) Scheme() string { 177 return c2pScheme 178 } 179 180 func newNodeConfig(zone string, ipv6Capable bool) map[string]any { 181 node := map[string]any{ 182 "id": fmt.Sprintf("C2P-%d", randInt()), 183 "locality": map[string]any{"zone": zone}, 184 } 185 if ipv6Capable { 186 node["metadata"] = map[string]any{ipv6CapableMetadataName: true} 187 } 188 return node 189 } 190 191 func newAuthoritiesConfig(serverCfg map[string]any) map[string]any { 192 return map[string]any{ 193 c2pAuthority: map[string]any{"xds_servers": []any{serverCfg}}, 194 } 195 } 196 197 func newXdsServerConfig(uri string) map[string]any { 198 return map[string]any{ 199 "server_uri": uri, 200 "channel_creds": []map[string]any{{"type": "google_default"}}, 201 "server_features": []any{"ignore_resource_deletion"}, 202 } 203 } 204 205 // runDirectPath returns whether this resolver should use direct path. 206 // 207 // direct path is enabled if this client is running on GCE. 208 func runDirectPath() bool { 209 return onGCE() 210 }