google.golang.org/grpc@v1.72.2/internal/resolver/delegatingresolver/delegatingresolver.go (about)

     1  /*
     2   *
     3   * Copyright 2024 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 delegatingresolver implements a resolver capable of resolving both
    20  // target URIs and proxy addresses.
    21  package delegatingresolver
    22  
    23  import (
    24  	"fmt"
    25  	"net/http"
    26  	"net/url"
    27  	"sync"
    28  
    29  	"google.golang.org/grpc/grpclog"
    30  	"google.golang.org/grpc/internal/proxyattributes"
    31  	"google.golang.org/grpc/internal/transport"
    32  	"google.golang.org/grpc/internal/transport/networktype"
    33  	"google.golang.org/grpc/resolver"
    34  	"google.golang.org/grpc/serviceconfig"
    35  )
    36  
    37  var (
    38  	logger = grpclog.Component("delegating-resolver")
    39  	// HTTPSProxyFromEnvironment will be overwritten in the tests
    40  	HTTPSProxyFromEnvironment = http.ProxyFromEnvironment
    41  )
    42  
    43  // delegatingResolver manages both target URI and proxy address resolution by
    44  // delegating these tasks to separate child resolvers. Essentially, it acts as
    45  // an intermediary between the gRPC ClientConn and the child resolvers.
    46  //
    47  // It implements the [resolver.Resolver] interface.
    48  type delegatingResolver struct {
    49  	target   resolver.Target     // parsed target URI to be resolved
    50  	cc       resolver.ClientConn // gRPC ClientConn
    51  	proxyURL *url.URL            // proxy URL, derived from proxy environment and target
    52  
    53  	// We do not hold both mu and childMu in the same goroutine. Avoid holding
    54  	// both locks when calling into the child, as the child resolver may
    55  	// synchronously callback into the channel.
    56  	mu                  sync.Mutex         // protects all the fields below
    57  	targetResolverState *resolver.State    // state of the target resolver
    58  	proxyAddrs          []resolver.Address // resolved proxy addresses; empty if no proxy is configured
    59  
    60  	// childMu serializes calls into child resolvers. It also protects access to
    61  	// the following fields.
    62  	childMu        sync.Mutex
    63  	targetResolver resolver.Resolver // resolver for the target URI, based on its scheme
    64  	proxyResolver  resolver.Resolver // resolver for the proxy URI; nil if no proxy is configured
    65  }
    66  
    67  // nopResolver is a resolver that does nothing.
    68  type nopResolver struct{}
    69  
    70  func (nopResolver) ResolveNow(resolver.ResolveNowOptions) {}
    71  
    72  func (nopResolver) Close() {}
    73  
    74  // proxyURLForTarget determines the proxy URL for the given address based on the
    75  // environment. It can return the following:
    76  //   - nil URL, nil error: No proxy is configured or the address is excluded
    77  //     using the `NO_PROXY` environment variable or if req.URL.Host is
    78  //     "localhost" (with or without // a port number)
    79  //   - nil URL, non-nil error: An error occurred while retrieving the proxy URL.
    80  //   - non-nil URL, nil error: A proxy is configured, and the proxy URL was
    81  //     retrieved successfully without any errors.
    82  func proxyURLForTarget(address string) (*url.URL, error) {
    83  	req := &http.Request{URL: &url.URL{
    84  		Scheme: "https",
    85  		Host:   address,
    86  	}}
    87  	return HTTPSProxyFromEnvironment(req)
    88  }
    89  
    90  // New creates a new delegating resolver that can create up to two child
    91  // resolvers:
    92  //   - one to resolve the proxy address specified using the supported
    93  //     environment variables. This uses the registered resolver for the "dns"
    94  //     scheme. It is lazily built when a target resolver update contains at least
    95  //     one TCP address.
    96  //   - one to resolve the target URI using the resolver specified by the scheme
    97  //     in the target URI or specified by the user using the WithResolvers dial
    98  //     option. As a special case, if the target URI's scheme is "dns" and a
    99  //     proxy is specified using the supported environment variables, the target
   100  //     URI's path portion is used as the resolved address unless target
   101  //     resolution is enabled using the dial option.
   102  func New(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions, targetResolverBuilder resolver.Builder, targetResolutionEnabled bool) (resolver.Resolver, error) {
   103  	r := &delegatingResolver{
   104  		target:         target,
   105  		cc:             cc,
   106  		proxyResolver:  nopResolver{},
   107  		targetResolver: nopResolver{},
   108  	}
   109  
   110  	var err error
   111  	r.proxyURL, err = proxyURLForTarget(target.Endpoint())
   112  	if err != nil {
   113  		return nil, fmt.Errorf("delegating_resolver: failed to determine proxy URL for target %s: %v", target, err)
   114  	}
   115  
   116  	// proxy is not configured or proxy address excluded using `NO_PROXY` env
   117  	// var, so only target resolver is used.
   118  	if r.proxyURL == nil {
   119  		return targetResolverBuilder.Build(target, cc, opts)
   120  	}
   121  
   122  	if logger.V(2) {
   123  		logger.Infof("Proxy URL detected : %s", r.proxyURL)
   124  	}
   125  
   126  	// Resolver updates from one child may trigger calls into the other. Block
   127  	// updates until the children are initialized.
   128  	r.childMu.Lock()
   129  	defer r.childMu.Unlock()
   130  	// When the scheme is 'dns' and target resolution on client is not enabled,
   131  	// resolution should be handled by the proxy, not the client. Therefore, we
   132  	// bypass the target resolver and store the unresolved target address.
   133  	if target.URL.Scheme == "dns" && !targetResolutionEnabled {
   134  		r.targetResolverState = &resolver.State{
   135  			Addresses: []resolver.Address{{Addr: target.Endpoint()}},
   136  			Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: target.Endpoint()}}}},
   137  		}
   138  		r.updateTargetResolverState(*r.targetResolverState)
   139  		return r, nil
   140  	}
   141  	wcc := &wrappingClientConn{
   142  		stateListener: r.updateTargetResolverState,
   143  		parent:        r,
   144  	}
   145  	if r.targetResolver, err = targetResolverBuilder.Build(target, wcc, opts); err != nil {
   146  		return nil, fmt.Errorf("delegating_resolver: unable to build the resolver for target %s: %v", target, err)
   147  	}
   148  	return r, nil
   149  }
   150  
   151  // proxyURIResolver creates a resolver for resolving proxy URIs using the "dns"
   152  // scheme. It adjusts the proxyURL to conform to the "dns:///" format and builds
   153  // a resolver with a wrappingClientConn to capture resolved addresses.
   154  func (r *delegatingResolver) proxyURIResolver(opts resolver.BuildOptions) (resolver.Resolver, error) {
   155  	proxyBuilder := resolver.Get("dns")
   156  	if proxyBuilder == nil {
   157  		panic("delegating_resolver: resolver for proxy not found for scheme dns")
   158  	}
   159  	url := *r.proxyURL
   160  	url.Scheme = "dns"
   161  	url.Path = "/" + r.proxyURL.Host
   162  	url.Host = "" // Clear the Host field to conform to the "dns:///" format
   163  
   164  	proxyTarget := resolver.Target{URL: url}
   165  	wcc := &wrappingClientConn{
   166  		stateListener: r.updateProxyResolverState,
   167  		parent:        r,
   168  	}
   169  	return proxyBuilder.Build(proxyTarget, wcc, opts)
   170  }
   171  
   172  func (r *delegatingResolver) ResolveNow(o resolver.ResolveNowOptions) {
   173  	r.childMu.Lock()
   174  	defer r.childMu.Unlock()
   175  	r.targetResolver.ResolveNow(o)
   176  	r.proxyResolver.ResolveNow(o)
   177  }
   178  
   179  func (r *delegatingResolver) Close() {
   180  	r.childMu.Lock()
   181  	defer r.childMu.Unlock()
   182  	r.targetResolver.Close()
   183  	r.targetResolver = nil
   184  
   185  	r.proxyResolver.Close()
   186  	r.proxyResolver = nil
   187  }
   188  
   189  func needsProxyResolver(state *resolver.State) bool {
   190  	for _, addr := range state.Addresses {
   191  		if !skipProxy(addr) {
   192  			return true
   193  		}
   194  	}
   195  	for _, endpoint := range state.Endpoints {
   196  		for _, addr := range endpoint.Addresses {
   197  			if !skipProxy(addr) {
   198  				return true
   199  			}
   200  		}
   201  	}
   202  	return false
   203  }
   204  
   205  func skipProxy(address resolver.Address) bool {
   206  	// Avoid proxy when network is not tcp.
   207  	networkType, ok := networktype.Get(address)
   208  	if !ok {
   209  		networkType, _ = transport.ParseDialTarget(address.Addr)
   210  	}
   211  	if networkType != "tcp" {
   212  		return true
   213  	}
   214  
   215  	req := &http.Request{URL: &url.URL{
   216  		Scheme: "https",
   217  		Host:   address.Addr,
   218  	}}
   219  	// Avoid proxy when address included in `NO_PROXY` environment variable or
   220  	// fails to get the proxy address.
   221  	url, err := HTTPSProxyFromEnvironment(req)
   222  	if err != nil || url == nil {
   223  		return true
   224  	}
   225  	return false
   226  }
   227  
   228  // updateClientConnStateLocked constructs a combined list of addresses by
   229  // pairing each proxy address with every target address of type TCP. For each
   230  // pair, it creates a new [resolver.Address] using the proxy address and
   231  // attaches the corresponding target address and user info as attributes. Target
   232  // addresses that are not of type TCP are appended to the list as-is. The
   233  // function returns nil if either resolver has not yet provided an update, and
   234  // returns the result of ClientConn.UpdateState once both resolvers have
   235  // provided at least one update.
   236  func (r *delegatingResolver) updateClientConnStateLocked() error {
   237  	if r.targetResolverState == nil || r.proxyAddrs == nil {
   238  		return nil
   239  	}
   240  
   241  	// If multiple resolved proxy addresses are present, we send only the
   242  	// unresolved proxy host and let net.Dial handle the proxy host name
   243  	// resolution when creating the transport. Sending all resolved addresses
   244  	// would increase the number of addresses passed to the ClientConn and
   245  	// subsequently to load balancing (LB) policies like Round Robin, leading
   246  	// to additional TCP connections. However, if there's only one resolved
   247  	// proxy address, we send it directly, as it doesn't affect the address
   248  	// count returned by the target resolver and the address count sent to the
   249  	// ClientConn.
   250  	var proxyAddr resolver.Address
   251  	if len(r.proxyAddrs) == 1 {
   252  		proxyAddr = r.proxyAddrs[0]
   253  	} else {
   254  		proxyAddr = resolver.Address{Addr: r.proxyURL.Host}
   255  	}
   256  	var addresses []resolver.Address
   257  	for _, targetAddr := range (*r.targetResolverState).Addresses {
   258  		if skipProxy(targetAddr) {
   259  			addresses = append(addresses, targetAddr)
   260  			continue
   261  		}
   262  		addresses = append(addresses, proxyattributes.Set(proxyAddr, proxyattributes.Options{
   263  			User:        r.proxyURL.User,
   264  			ConnectAddr: targetAddr.Addr,
   265  		}))
   266  	}
   267  
   268  	// For each target endpoint, construct a new [resolver.Endpoint] that
   269  	// includes all addresses from all proxy endpoints and the addresses from
   270  	// that target endpoint, preserving the number of target endpoints.
   271  	var endpoints []resolver.Endpoint
   272  	for _, endpt := range (*r.targetResolverState).Endpoints {
   273  		var addrs []resolver.Address
   274  		for _, targetAddr := range endpt.Addresses {
   275  			// Avoid proxy when network is not tcp.
   276  			if skipProxy(targetAddr) {
   277  				addrs = append(addrs, targetAddr)
   278  				continue
   279  			}
   280  			for _, proxyAddr := range r.proxyAddrs {
   281  				addrs = append(addrs, proxyattributes.Set(proxyAddr, proxyattributes.Options{
   282  					User:        r.proxyURL.User,
   283  					ConnectAddr: targetAddr.Addr,
   284  				}))
   285  			}
   286  		}
   287  		endpoints = append(endpoints, resolver.Endpoint{Addresses: addrs})
   288  	}
   289  	// Use the targetResolverState for its service config and attributes
   290  	// contents. The state update is only sent after both the target and proxy
   291  	// resolvers have sent their updates, and curState has been updated with the
   292  	// combined addresses.
   293  	curState := *r.targetResolverState
   294  	curState.Addresses = addresses
   295  	curState.Endpoints = endpoints
   296  	return r.cc.UpdateState(curState)
   297  }
   298  
   299  // updateProxyResolverState updates the proxy resolver state by storing proxy
   300  // addresses and endpoints, marking the resolver as ready, and triggering a
   301  // state update if both proxy and target resolvers are ready. If the ClientConn
   302  // returns a non-nil error, it calls `ResolveNow()` on the target resolver.  It
   303  // is a StateListener function of wrappingClientConn passed to the proxy
   304  // resolver.
   305  func (r *delegatingResolver) updateProxyResolverState(state resolver.State) error {
   306  	r.mu.Lock()
   307  	defer r.mu.Unlock()
   308  	if logger.V(2) {
   309  		logger.Infof("Addresses received from proxy resolver: %s", state.Addresses)
   310  	}
   311  	if len(state.Endpoints) > 0 {
   312  		// We expect exactly one address per endpoint because the proxy resolver
   313  		// uses "dns" resolution.
   314  		r.proxyAddrs = make([]resolver.Address, 0, len(state.Endpoints))
   315  		for _, endpoint := range state.Endpoints {
   316  			r.proxyAddrs = append(r.proxyAddrs, endpoint.Addresses...)
   317  		}
   318  	} else if state.Addresses != nil {
   319  		r.proxyAddrs = state.Addresses
   320  	} else {
   321  		r.proxyAddrs = []resolver.Address{} // ensure proxyAddrs is non-nil to indicate an update has been received
   322  	}
   323  	err := r.updateClientConnStateLocked()
   324  	// Another possible approach was to block until updates are received from
   325  	// both resolvers. But this is not used because calling `New()` triggers
   326  	// `Build()` for the first resolver, which calls `UpdateState()`. And the
   327  	// second resolver hasn't sent an update yet, so it would cause `New()` to
   328  	// block indefinitely.
   329  	if err != nil {
   330  		go func() {
   331  			r.childMu.Lock()
   332  			defer r.childMu.Unlock()
   333  			if r.targetResolver != nil {
   334  				r.targetResolver.ResolveNow(resolver.ResolveNowOptions{})
   335  			}
   336  		}()
   337  	}
   338  	return err
   339  }
   340  
   341  // updateTargetResolverState is the StateListener function provided to the
   342  // target resolver via wrappingClientConn. It updates the resolver state and
   343  // marks the target resolver as ready. If the update includes at least one TCP
   344  // address and the proxy resolver has not yet been constructed, it initializes
   345  // the proxy resolver. A combined state update is triggered once both resolvers
   346  // are ready. If all addresses are non-TCP, it proceeds without waiting for the
   347  // proxy resolver. If ClientConn.UpdateState returns a non-nil error,
   348  // ResolveNow() is called on the proxy resolver.
   349  func (r *delegatingResolver) updateTargetResolverState(state resolver.State) error {
   350  	r.mu.Lock()
   351  	defer r.mu.Unlock()
   352  
   353  	if logger.V(2) {
   354  		logger.Infof("Addresses received from target resolver: %v", state.Addresses)
   355  	}
   356  	r.targetResolverState = &state
   357  	// If all addresses returned by the target resolver have a non-TCP network
   358  	// type, or are listed in the `NO_PROXY` environment variable, do not wait
   359  	// for proxy update.
   360  	if !needsProxyResolver(r.targetResolverState) {
   361  		return r.cc.UpdateState(*r.targetResolverState)
   362  	}
   363  
   364  	// The proxy resolver may be rebuilt multiple times, specifically each time
   365  	// the target resolver sends an update, even if the target resolver is built
   366  	// successfully but building the proxy resolver fails.
   367  	if len(r.proxyAddrs) == 0 {
   368  		go func() {
   369  			r.childMu.Lock()
   370  			defer r.childMu.Unlock()
   371  			if _, ok := r.proxyResolver.(nopResolver); !ok {
   372  				return
   373  			}
   374  			proxyResolver, err := r.proxyURIResolver(resolver.BuildOptions{})
   375  			if err != nil {
   376  				r.cc.ReportError(fmt.Errorf("delegating_resolver: unable to build the proxy resolver: %v", err))
   377  				return
   378  			}
   379  			r.proxyResolver = proxyResolver
   380  		}()
   381  	}
   382  
   383  	err := r.updateClientConnStateLocked()
   384  	if err != nil {
   385  		go func() {
   386  			r.childMu.Lock()
   387  			defer r.childMu.Unlock()
   388  			if r.proxyResolver != nil {
   389  				r.proxyResolver.ResolveNow(resolver.ResolveNowOptions{})
   390  			}
   391  		}()
   392  	}
   393  	return nil
   394  }
   395  
   396  // wrappingClientConn serves as an intermediary between the parent ClientConn
   397  // and the child resolvers created here. It implements the resolver.ClientConn
   398  // interface and is passed in that capacity to the child resolvers.
   399  type wrappingClientConn struct {
   400  	// Callback to deliver resolver state updates
   401  	stateListener func(state resolver.State) error
   402  	parent        *delegatingResolver
   403  }
   404  
   405  // UpdateState receives resolver state updates and forwards them to the
   406  // appropriate listener function (either for the proxy or target resolver).
   407  func (wcc *wrappingClientConn) UpdateState(state resolver.State) error {
   408  	return wcc.stateListener(state)
   409  }
   410  
   411  // ReportError intercepts errors from the child resolvers and passes them to
   412  // ClientConn.
   413  func (wcc *wrappingClientConn) ReportError(err error) {
   414  	wcc.parent.cc.ReportError(err)
   415  }
   416  
   417  // NewAddress intercepts the new resolved address from the child resolvers and
   418  // passes them to ClientConn.
   419  func (wcc *wrappingClientConn) NewAddress(addrs []resolver.Address) {
   420  	wcc.UpdateState(resolver.State{Addresses: addrs})
   421  }
   422  
   423  // ParseServiceConfig parses the provided service config and returns an object
   424  // that provides the parsed config.
   425  func (wcc *wrappingClientConn) ParseServiceConfig(serviceConfigJSON string) *serviceconfig.ParseResult {
   426  	return wcc.parent.cc.ParseServiceConfig(serviceConfigJSON)
   427  }