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 }