Files
upstream-grpc-go/xds/googledirectpath/googlec2p.go
upstream-mirror e241b33f26
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 20:40:15 +05:00

251 lines
7.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
*
* Copyright 2021 gRPC authors.
*
* 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 googledirectpath implements a resolver that configures xds to make
// cloud to prod directpath connection.
//
// It's a combo of DNS and xDS resolvers. It delegates to DNS if
// - not on GCE, or
// - xDS bootstrap env var is set (so this client needs to do normal xDS, not
// direct path, and clients with this scheme is not part of the xDS mesh).
package googledirectpath
import (
"encoding/json"
"fmt"
rand "math/rand/v2"
"net/url"
"sync"
"time"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/googlecloud"
internalgrpclog "google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/internal/xds/xdsclient"
"google.golang.org/grpc/resolver"
_ "google.golang.org/grpc/xds" // To register xds resolvers and balancers.
)
const (
c2pScheme = "google-c2p"
c2pAuthority = "traffic-director-c2p.xds.googleapis.com"
defaultUniverseDomain = "googleapis.com"
zoneURL = "http://metadata.google.internal/computeMetadata/v1/instance/zone"
ipv6URL = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ipv6s"
ipv6CapableMetadataName = "TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE"
httpReqTimeout = 10 * time.Second
logPrefix = "[google-c2p-resolver]"
dnsName, xdsName = "dns", "xds"
)
var (
logger = internalgrpclog.NewPrefixLogger(grpclog.Component("directpath"), logPrefix)
universeDomainMu sync.Mutex
universeDomain = ""
// For overriding in unittests.
onGCE = googlecloud.OnGCE
randInt = rand.Int
xdsClientPool = xdsclient.DefaultPool
)
func init() {
resolver.Register(c2pResolverBuilder{})
}
// SetUniverseDomain informs the gRPC library of the universe domain
// in which the process is running (for example, "googleapis.com").
// It is the caller's responsibility to ensure that the domain is correct.
//
// This setting is used by the "google-c2p" resolver (the resolver used
// for URIs with the "google-c2p" scheme) to configure its dependencies.
//
// If a gRPC channel is created with the "google-c2p" URI scheme and this
// function has NOT been called, then gRPC configures the universe domain as
// "googleapis.com".
//
// Returns nil if either:
//
// a) The universe domain has not yet been configured.
// b) The universe domain has been configured and matches the provided value.
//
// Otherwise, returns an error.
func SetUniverseDomain(domain string) error {
universeDomainMu.Lock()
defer universeDomainMu.Unlock()
if domain == "" {
return fmt.Errorf("universe domain cannot be empty")
}
if universeDomain == "" {
universeDomain = domain
return nil
}
if universeDomain != domain {
return fmt.Errorf("universe domain cannot be set to %s, already set to different value: %s", domain, universeDomain)
}
return nil
}
func getXdsServerURI() string {
universeDomainMu.Lock()
defer universeDomainMu.Unlock()
if universeDomain == "" {
universeDomain = defaultUniverseDomain
}
// Put env var override logic after default value logic so
// that tests still run the default value logic.
if envconfig.C2PResolverTestOnlyTrafficDirectorURI != "" {
return envconfig.C2PResolverTestOnlyTrafficDirectorURI
}
return fmt.Sprintf("dns:///directpath-pa.%s", universeDomain)
}
type c2pResolverWrapper struct {
resolver.Resolver
cancel func() // Release the reference to the xDS client that was created in Build().
}
func (r *c2pResolverWrapper) Close() {
r.Resolver.Close()
r.cancel()
}
type c2pResolverBuilder struct{}
func (c2pResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
if t.URL.Host != "" {
return nil, fmt.Errorf("google-c2p URI scheme does not support authorities")
}
isGCE := onGCE()
// We check only for the presence of the "force-xds" query parameter key,
// and do not evaluate its value.
_, forceXDS := t.URL.Query()["force-xds"]
if !forceXDS && !isGCE {
// If not xDS, fallback to DNS.
t.URL.Scheme = dnsName
return resolver.Get(dnsName).Build(t, cc, opts)
}
var zone string
var ipv6Capable bool
if forceXDS {
// If the force-xds query parameter is present, GCE metadata server
// queries are bypassed.
zone = ""
ipv6Capable = true
} else {
// Note that the following calls to getZone() and getIPv6Capable() does I/O,
// and has 10 seconds timeout each.
//
// This should be fine in most of the cases. In certain error cases, this
// could block Dial() for up to 10 seconds (each blocking call has its own
// goroutine).
var wg sync.WaitGroup
wg.Go(func() { zone = getZone(httpReqTimeout) })
wg.Go(func() { ipv6Capable = getIPv6Capable(httpReqTimeout) })
wg.Wait()
}
xdsServerURI := getXdsServerURI()
nodeCfg := newNodeConfig(zone, ipv6Capable, forceXDS)
xdsServerCfg := newXdsServerConfig(xdsServerURI)
authoritiesCfg := newAuthoritiesConfig(xdsServerCfg)
cfg := map[string]any{
"xds_servers": []any{xdsServerCfg},
"client_default_listener_resource_name_template": "%s",
"authorities": authoritiesCfg,
"node": nodeCfg,
}
cfgJSON, err := json.Marshal(cfg)
if err != nil {
return nil, fmt.Errorf("failed to marshal bootstrap configuration: %v", err)
}
config, err := bootstrap.NewConfigFromContents(cfgJSON)
if err != nil {
return nil, fmt.Errorf("failed to parse bootstrap contents: %s, %v", string(cfgJSON), err)
}
t = resolver.Target{
URL: url.URL{
Scheme: xdsName,
Host: c2pAuthority,
Path: t.URL.Path,
},
}
// Create a new xDS client for this target using the provided bootstrap
// configuration. This client is stored in the xdsclient pool’s internal
// cache, keeping it alive and associated with this resolver until Closed().
// While the c2p resolver itself does not directly use the client, creating
// it ensures that when the xDS resolver later requests a client for the
// same target, the existing instance will be reused.
_, cancel, err := xdsClientPool.NewClientWithConfig(t.String(), opts.MetricsRecorder, config)
if err != nil {
return nil, fmt.Errorf("failed to create xds client: %v", err)
}
r, err := resolver.Get(xdsName).Build(t, cc, opts)
if err != nil {
cancel()
return nil, err
}
return &c2pResolverWrapper{Resolver: r, cancel: cancel}, nil
}
func (b c2pResolverBuilder) Scheme() string {
return c2pScheme
}
func newNodeConfig(zone string, ipv6Capable bool, forceXDS bool) map[string]any {
prefix := "C2P"
if forceXDS {
prefix = "C2P-non-gcp"
}
node := map[string]any{
"id": fmt.Sprintf("%s-%d", prefix, randInt()),
}
if zone != "" {
node["locality"] = map[string]any{"zone": zone}
}
// Enable dualstack endpoints in TD.
if ipv6Capable {
node["metadata"] = map[string]any{ipv6CapableMetadataName: true}
}
return node
}
func newAuthoritiesConfig(serverCfg map[string]any) map[string]any {
return map[string]any{
c2pAuthority: map[string]any{"xds_servers": []any{serverCfg}},
}
}
func newXdsServerConfig(uri string) map[string]any {
return map[string]any{
"server_uri": uri,
"channel_creds": []map[string]any{{"type": "google_default"}},
"server_features": []any{"ignore_resource_deletion"},
}
}