Skip to content

Commit 7fcfc87

Browse files
internal/delegatingresolver: avoid proxy if networktype of target address is not tcp (#8215) (#8296)
1 parent ad1e120 commit 7fcfc87

File tree

5 files changed

+334
-104
lines changed

5 files changed

+334
-104
lines changed

internal/resolver/delegatingresolver/delegatingresolver.go

Lines changed: 116 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828

2929
"google.golang.org/grpc/grpclog"
3030
"google.golang.org/grpc/internal/proxyattributes"
31+
"google.golang.org/grpc/internal/transport"
32+
"google.golang.org/grpc/internal/transport/networktype"
3133
"google.golang.org/grpc/resolver"
3234
"google.golang.org/grpc/serviceconfig"
3335
)
@@ -40,14 +42,17 @@ var (
4042

4143
// delegatingResolver manages both target URI and proxy address resolution by
4244
// delegating these tasks to separate child resolvers. Essentially, it acts as
43-
// a intermediary between the gRPC ClientConn and the child resolvers.
45+
// an intermediary between the gRPC ClientConn and the child resolvers.
4446
//
4547
// It implements the [resolver.Resolver] interface.
4648
type delegatingResolver struct {
4749
target resolver.Target // parsed target URI to be resolved
4850
cc resolver.ClientConn // gRPC ClientConn
4951
proxyURL *url.URL // proxy URL, derived from proxy environment and target
5052

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.
5156
mu sync.Mutex // protects all the fields below
5257
targetResolverState *resolver.State // state of the target resolver
5358
proxyAddrs []resolver.Address // resolved proxy addresses; empty if no proxy is configured
@@ -66,8 +71,8 @@ func (nopResolver) ResolveNow(resolver.ResolveNowOptions) {}
6671

6772
func (nopResolver) Close() {}
6873

69-
// proxyURLForTarget determines the proxy URL for the given address based on
70-
// the environment. It can return the following:
74+
// proxyURLForTarget determines the proxy URL for the given address based on the
75+
// environment. It can return the following:
7176
// - nil URL, nil error: No proxy is configured or the address is excluded
7277
// using the `NO_PROXY` environment variable or if req.URL.Host is
7378
// "localhost" (with or without // a port number)
@@ -86,7 +91,8 @@ func proxyURLForTarget(address string) (*url.URL, error) {
8691
// resolvers:
8792
// - one to resolve the proxy address specified using the supported
8893
// environment variables. This uses the registered resolver for the "dns"
89-
// scheme.
94+
// scheme. It is lazily built when a target resolver update contains at least
95+
// one TCP address.
9096
// - one to resolve the target URI using the resolver specified by the scheme
9197
// in the target URI or specified by the user using the WithResolvers dial
9298
// option. As a special case, if the target URI's scheme is "dns" and a
@@ -95,8 +101,10 @@ func proxyURLForTarget(address string) (*url.URL, error) {
95101
// resolution is enabled using the dial option.
96102
func New(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions, targetResolverBuilder resolver.Builder, targetResolutionEnabled bool) (resolver.Resolver, error) {
97103
r := &delegatingResolver{
98-
target: target,
99-
cc: cc,
104+
target: target,
105+
cc: cc,
106+
proxyResolver: nopResolver{},
107+
targetResolver: nopResolver{},
100108
}
101109

102110
var err error
@@ -123,37 +131,26 @@ func New(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOpti
123131
// resolution should be handled by the proxy, not the client. Therefore, we
124132
// bypass the target resolver and store the unresolved target address.
125133
if target.URL.Scheme == "dns" && !targetResolutionEnabled {
126-
state := resolver.State{
134+
r.targetResolverState = &resolver.State{
127135
Addresses: []resolver.Address{{Addr: target.Endpoint()}},
128136
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: target.Endpoint()}}}},
129137
}
130-
r.targetResolverState = &state
131-
} else {
132-
wcc := &wrappingClientConn{
133-
stateListener: r.updateTargetResolverState,
134-
parent: r,
135-
}
136-
if r.targetResolver, err = targetResolverBuilder.Build(target, wcc, opts); err != nil {
137-
return nil, fmt.Errorf("delegating_resolver: unable to build the resolver for target %s: %v", target, err)
138-
}
139-
}
140-
141-
if r.proxyResolver, err = r.proxyURIResolver(opts); err != nil {
142-
return nil, fmt.Errorf("delegating_resolver: failed to build resolver for proxy URL %q: %v", r.proxyURL, err)
138+
r.updateTargetResolverState(*r.targetResolverState)
139+
return r, nil
143140
}
144-
145-
if r.targetResolver == nil {
146-
r.targetResolver = nopResolver{}
141+
wcc := &wrappingClientConn{
142+
stateListener: r.updateTargetResolverState,
143+
parent: r,
147144
}
148-
if r.proxyResolver == nil {
149-
r.proxyResolver = nopResolver{}
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)
150147
}
151148
return r, nil
152149
}
153150

154-
// proxyURIResolver creates a resolver for resolving proxy URIs using the
155-
// "dns" scheme. It adjusts the proxyURL to conform to the "dns:///" format and
156-
// builds a resolver with a wrappingClientConn to capture resolved addresses.
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.
157154
func (r *delegatingResolver) proxyURIResolver(opts resolver.BuildOptions) (resolver.Resolver, error) {
158155
proxyBuilder := resolver.Get("dns")
159156
if proxyBuilder == nil {
@@ -189,18 +186,43 @@ func (r *delegatingResolver) Close() {
189186
r.proxyResolver = nil
190187
}
191188

192-
// updateClientConnStateLocked creates a list of combined addresses by
193-
// pairing each proxy address with every target address. For each pair, it
194-
// generates a new [resolver.Address] using the proxy address, and adding the
195-
// target address as the attribute along with user info. It returns nil if
196-
// either resolver has not sent update even once and returns the error from
197-
// ClientConn update once both resolvers have sent update atleast once.
189+
func networkTypeFromAddr(addr resolver.Address) string {
190+
networkType, ok := networktype.Get(addr)
191+
if !ok {
192+
networkType, _ = transport.ParseDialTarget(addr.Addr)
193+
}
194+
return networkType
195+
}
196+
197+
func isTCPAddressPresent(state *resolver.State) bool {
198+
for _, addr := range state.Addresses {
199+
if networkType := networkTypeFromAddr(addr); networkType == "tcp" {
200+
return true
201+
}
202+
}
203+
for _, endpoint := range state.Endpoints {
204+
for _, addr := range endpoint.Addresses {
205+
if networktype := networkTypeFromAddr(addr); networktype == "tcp" {
206+
return true
207+
}
208+
}
209+
}
210+
return false
211+
}
212+
213+
// updateClientConnStateLocked constructs a combined list of addresses by
214+
// pairing each proxy address with every target address of type TCP. For each
215+
// pair, it creates a new [resolver.Address] using the proxy address and
216+
// attaches the corresponding target address and user info as attributes. Target
217+
// addresses that are not of type TCP are appended to the list as-is. The
218+
// function returns nil if either resolver has not yet provided an update, and
219+
// returns the result of ClientConn.UpdateState once both resolvers have
220+
// provided at least one update.
198221
func (r *delegatingResolver) updateClientConnStateLocked() error {
199222
if r.targetResolverState == nil || r.proxyAddrs == nil {
200223
return nil
201224
}
202225

203-
curState := *r.targetResolverState
204226
// If multiple resolved proxy addresses are present, we send only the
205227
// unresolved proxy host and let net.Dial handle the proxy host name
206228
// resolution when creating the transport. Sending all resolved addresses
@@ -218,24 +240,30 @@ func (r *delegatingResolver) updateClientConnStateLocked() error {
218240
}
219241
var addresses []resolver.Address
220242
for _, targetAddr := range (*r.targetResolverState).Addresses {
243+
// Avoid proxy when network is not tcp.
244+
if networkType := networkTypeFromAddr(targetAddr); networkType != "tcp" {
245+
addresses = append(addresses, targetAddr)
246+
continue
247+
}
221248
addresses = append(addresses, proxyattributes.Set(proxyAddr, proxyattributes.Options{
222249
User: r.proxyURL.User,
223250
ConnectAddr: targetAddr.Addr,
224251
}))
225252
}
226253

227-
// Create a list of combined endpoints by pairing all proxy endpoints
228-
// with every target endpoint. Each time, it constructs a new
229-
// [resolver.Endpoint] using the all addresses from all the proxy endpoint
230-
// and the target addresses from one endpoint. The target address and user
231-
// information from the proxy URL are added as attributes to the proxy
232-
// address.The resulting list of addresses is then grouped into endpoints,
233-
// covering all combinations of proxy and target endpoints.
254+
// For each target endpoint, construct a new [resolver.Endpoint] that
255+
// includes all addresses from all proxy endpoints and the addresses from
256+
// that target endpoint, preserving the number of target endpoints.
234257
var endpoints []resolver.Endpoint
235258
for _, endpt := range (*r.targetResolverState).Endpoints {
236259
var addrs []resolver.Address
237-
for _, proxyAddr := range r.proxyAddrs {
238-
for _, targetAddr := range endpt.Addresses {
260+
for _, targetAddr := range endpt.Addresses {
261+
// Avoid proxy when network is not tcp.
262+
if networkType := networkTypeFromAddr(targetAddr); networkType != "tcp" {
263+
addrs = append(addrs, targetAddr)
264+
continue
265+
}
266+
for _, proxyAddr := range r.proxyAddrs {
239267
addrs = append(addrs, proxyattributes.Set(proxyAddr, proxyattributes.Options{
240268
User: r.proxyURL.User,
241269
ConnectAddr: targetAddr.Addr,
@@ -246,8 +274,9 @@ func (r *delegatingResolver) updateClientConnStateLocked() error {
246274
}
247275
// Use the targetResolverState for its service config and attributes
248276
// contents. The state update is only sent after both the target and proxy
249-
// resolvers have sent their updates, and curState has been updated with
250-
// the combined addresses.
277+
// resolvers have sent their updates, and curState has been updated with the
278+
// combined addresses.
279+
curState := *r.targetResolverState
251280
curState.Addresses = addresses
252281
curState.Endpoints = endpoints
253282
return r.cc.UpdateState(curState)
@@ -257,16 +286,17 @@ func (r *delegatingResolver) updateClientConnStateLocked() error {
257286
// addresses and endpoints, marking the resolver as ready, and triggering a
258287
// state update if both proxy and target resolvers are ready. If the ClientConn
259288
// returns a non-nil error, it calls `ResolveNow()` on the target resolver. It
260-
// is a StateListener function of wrappingClientConn passed to the proxy resolver.
289+
// is a StateListener function of wrappingClientConn passed to the proxy
290+
// resolver.
261291
func (r *delegatingResolver) updateProxyResolverState(state resolver.State) error {
262292
r.mu.Lock()
263293
defer r.mu.Unlock()
264294
if logger.V(2) {
265295
logger.Infof("Addresses received from proxy resolver: %s", state.Addresses)
266296
}
267297
if len(state.Endpoints) > 0 {
268-
// We expect exactly one address per endpoint because the proxy
269-
// resolver uses "dns" resolution.
298+
// We expect exactly one address per endpoint because the proxy resolver
299+
// uses "dns" resolution.
270300
r.proxyAddrs = make([]resolver.Address, 0, len(state.Endpoints))
271301
for _, endpoint := range state.Endpoints {
272302
r.proxyAddrs = append(r.proxyAddrs, endpoint.Addresses...)
@@ -294,11 +324,14 @@ func (r *delegatingResolver) updateProxyResolverState(state resolver.State) erro
294324
return err
295325
}
296326

297-
// updateTargetResolverState updates the target resolver state by storing target
298-
// addresses, endpoints, and service config, marking the resolver as ready, and
299-
// triggering a state update if both resolvers are ready. If the ClientConn
300-
// returns a non-nil error, it calls `ResolveNow()` on the proxy resolver. It
301-
// is a StateListener function of wrappingClientConn passed to the target resolver.
327+
// updateTargetResolverState is the StateListener function provided to the
328+
// target resolver via wrappingClientConn. It updates the resolver state and
329+
// marks the target resolver as ready. If the update includes at least one TCP
330+
// address and the proxy resolver has not yet been constructed, it initializes
331+
// the proxy resolver. A combined state update is triggered once both resolvers
332+
// are ready. If all addresses are non-TCP, it proceeds without waiting for the
333+
// proxy resolver. If ClientConn.UpdateState returns a non-nil error,
334+
// ResolveNow() is called on the proxy resolver.
302335
func (r *delegatingResolver) updateTargetResolverState(state resolver.State) error {
303336
r.mu.Lock()
304337
defer r.mu.Unlock()
@@ -307,6 +340,31 @@ func (r *delegatingResolver) updateTargetResolverState(state resolver.State) err
307340
logger.Infof("Addresses received from target resolver: %v", state.Addresses)
308341
}
309342
r.targetResolverState = &state
343+
// If no addresses returned by resolver have network type as tcp , do not
344+
// wait for proxy update.
345+
if !isTCPAddressPresent(r.targetResolverState) {
346+
return r.cc.UpdateState(*r.targetResolverState)
347+
}
348+
349+
// The proxy resolver may be rebuilt multiple times, specifically each time
350+
// the target resolver sends an update, even if the target resolver is built
351+
// successfully but building the proxy resolver fails.
352+
if len(r.proxyAddrs) == 0 {
353+
go func() {
354+
r.childMu.Lock()
355+
defer r.childMu.Unlock()
356+
if _, ok := r.proxyResolver.(nopResolver); !ok {
357+
return
358+
}
359+
proxyResolver, err := r.proxyURIResolver(resolver.BuildOptions{})
360+
if err != nil {
361+
r.cc.ReportError(fmt.Errorf("delegating_resolver: unable to build the proxy resolver: %v", err))
362+
return
363+
}
364+
r.proxyResolver = proxyResolver
365+
}()
366+
}
367+
310368
err := r.updateClientConnStateLocked()
311369
if err != nil {
312370
go func() {
@@ -335,7 +393,8 @@ func (wcc *wrappingClientConn) UpdateState(state resolver.State) error {
335393
return wcc.stateListener(state)
336394
}
337395

338-
// ReportError intercepts errors from the child resolvers and passes them to ClientConn.
396+
// ReportError intercepts errors from the child resolvers and passes them to
397+
// ClientConn.
339398
func (wcc *wrappingClientConn) ReportError(err error) {
340399
wcc.parent.cc.ReportError(err)
341400
}
@@ -346,8 +405,8 @@ func (wcc *wrappingClientConn) NewAddress(addrs []resolver.Address) {
346405
wcc.UpdateState(resolver.State{Addresses: addrs})
347406
}
348407

349-
// ParseServiceConfig parses the provided service config and returns an
350-
// object that provides the parsed config.
408+
// ParseServiceConfig parses the provided service config and returns an object
409+
// that provides the parsed config.
351410
func (wcc *wrappingClientConn) ParseServiceConfig(serviceConfigJSON string) *serviceconfig.ParseResult {
352411
return wcc.parent.cc.ParseServiceConfig(serviceConfigJSON)
353412
}

0 commit comments

Comments
 (0)