-
Notifications
You must be signed in to change notification settings - Fork 4.5k
internal/delegatingresolver: avoid proxy if networktype of target address is not tcp #8215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #8215 +/- ##
==========================================
- Coverage 82.17% 82.12% -0.05%
==========================================
Files 410 417 +7
Lines 40236 41412 +1176
==========================================
+ Hits 33065 34011 +946
- Misses 5822 5970 +148
- Partials 1349 1431 +82
🚀 New features to boost your workflow:
|
if len(curState.Endpoints) != 0 { | ||
networkType, ok = networktype.Get(curState.Endpoints[0].Addresses[0]) | ||
} else { | ||
networkType, ok = networktype.Get(curState.Addresses[0]) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we're making an assumption that the network type of the first address of the endpoint is the same the address type of all remaining addresses. This may be true for most real-world usecases, but it isn't a requirement specified in the resolver API. It should not be too difficult to avoid proxying on a per-address basis. There are two places below where we do the following, one for resolver.State.Addresses
and once for resolver.State.Endpoints
:
proxyattributes.Set(proxyAddr, proxyattributes.Options{
User: r.proxyURL.User,
ConnectAddr: targetAddr.Addr,
})
We can refactor this into a method that takes in the target address and proxy address and returns the combined address.
fn (r *delegatingResolver) combineAddresses(targetAddr, proxyAddr resolver.Address) resolver.Address {
if networktype.Get(targetAddr) != "tcp" {
return targetAddr
}
return proxyattributes.Set(proxyAddr, proxyattributes.Options{
User: r.proxyURL.User,
ConnectAddr: targetAddr.Addr,
})
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above comment end up adding duplicate addresses in the loop on targetResolverState.Endpoints
. We will probably need to invert the loop on r.proxyAddrs
and endpt.Addresses
and break early:
for _, endpt := range (*r.targetResolverState).Endpoints {
var addrs []resolver.Address
for _, targetAddr := range endpt.Addresses {
if networktype.Get(targetAddr) != "tcp" {
addrs = append(addrs, targetAddr)
continue
}
for _, proxyAddr := range r.proxyAddrs {
addrs = append(addrs, proxyattributes.Set(proxyAddr, proxyattributes.Options{
User: r.proxyURL.User,
ConnectAddr: targetAddr.Addr,
}))
}
}
endpoints = append(endpoints, resolver.Endpoint{Addresses: addrs})
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, done ! Also added a check in beginning , if there is not address with network type TCP , we do not wait for proxy update and just update the cc state.
Please also fix the format of the release notes. |
if networkType, ok := networktype.Get(addr); !ok || networkType == "tcp" { | ||
isTCP = true | ||
break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out here we're assuming !ok
to be the same of tcp
. However, the old code in the dialer used to parse the address string when !ok
to determine the network type, we should probably maintain the same behaviour.
grpc-go/internal/transport/http2_client.go
Lines 177 to 182 in 172fc5b
if !ok { | |
networkType, address = parseDialTarget(address) | |
} | |
if networkType == "tcp" && useProxy { | |
return proxyDial(ctx, address, grpcUA) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
if networkType, ok := networktype.Get(addr); !ok || networkType == "tcp" || isTCP { | ||
isTCP = true | ||
break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the code will be simpler if we refactor finding a TCP address in a helper function. The helper function can return early instead of keeping track of a isTCP
boolean and breaking out of nested loops.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right! Done!
} else { | ||
addresses = append(addresses, proxyattributes.Set(proxyAddr, proxyattributes.Options{ | ||
User: r.proxyURL.User, | ||
ConnectAddr: targetAddr.Addr, | ||
})) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: You can use continue
in the if
block to avoid indenting the else
block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
} else { | ||
for _, proxyAddr := range r.proxyAddrs { | ||
addrs = append(addrs, proxyattributes.Set(proxyAddr, proxyattributes.Options{ | ||
User: r.proxyURL.User, | ||
ConnectAddr: targetAddr.Addr, | ||
})) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: You can use continue
in the if
block to avoid indenting the else
block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
internal/resolver/delegatingresolver/delegatingresolver_ext_test.go
Outdated
Show resolved
Hide resolved
internal/resolver/delegatingresolver/delegatingresolver_ext_test.go
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with minor comments.
go func() { | ||
r.childMu.Lock() | ||
defer r.childMu.Unlock() | ||
if _, ok := r.proxyResolver.(nopResolver); ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: We can invert the check to return early, reducing one level of indentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right! Done!
r.childMu.Lock() | ||
defer r.childMu.Unlock() | ||
if _, ok := r.proxyResolver.(nopResolver); ok { | ||
proxyResolver, err := r.proxyURIResolver(resolver.BuildOptions{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case of failures, we will still be retrying creation of the proxy resolver every time the target resolver produces an update. Not sure if this is a problem, maybe other reviewers can chime in.
// ResolveNow of manual proxy resolver will not be called since proxy | ||
// resolver is only built when we get the first update from target resolver | ||
// and so , in the first resolveNow, proxy resolver will be a no-op resolver | ||
// and only target resolver's ResolveNow will be called. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can you please break this long sentence up? There's also an extra space before the ,
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// Wait for proxy resolver to be built. | ||
select { | ||
case <-proxyResolverBuilt: | ||
case <-time.After(defaultTestTimeout): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use the existing context here to ensure the test runs for a max of defaultTestTimeout
and not defaultTestTimeout
per assertion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// resolver, since we want to avoid proxy for any network type apart from | ||
// tcp. | ||
if diff := cmp.Diff(gotState, wantState); diff != "" { | ||
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Since diff
is a string, we should use %s
to format it. This allows linters to catch data type mismatches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
|
||
// Tests the scenario where a proxy is configured, and the resolver returns | ||
// addresses with varied network type. The test verifies that the delegating |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think you should be more precise and say "tcp and non-tcp addresses" instead of "varied".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
select { | ||
case <-stateCh: | ||
t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.") | ||
case <-time.After(defaultTestShortTimeout): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, create a single context and use it to wait for the remaining assertions also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaultTestShortTimeout
is used here because we do not want a state update , I dont think waiting for ctx.Done()
will make sense here , and we wait at just one other place for defaultTestTimeout
, do let me know if we should do ctx.Done()
there?
// Create a list of combined endpoints by pairing all proxy endpoints with | ||
// every target endpoint. Each time, it constructs a new [resolver.Endpoint] | ||
// using the all addresses from all the proxy endpoint and the target | ||
// addresses from one endpoint. The target address and user information from | ||
// the proxy URL are added as attributes to the proxy address.The resulting | ||
// list of addresses is then grouped into endpoints, covering all | ||
// combinations of proxy and target endpoints. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a lot of repetition between this comment and the one at the top of this method. Can you make this one more concise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// triggering a state update if both resolvers are ready. If the ClientConn | ||
// returns a non-nil error, it calls `ResolveNow()` on the proxy resolver. It | ||
// is a StateListener function of wrappingClientConn passed to the target resolver. | ||
// updateTargetResolverState is the StateListener function provided to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this StateListener
being mentioned in a couple of places. There is no such method on the resolver.ClientConn
interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its a method on the wrappingClientConn , used to call different update state functions for proxy and target resolver. Should that not be mentioned in the comments? or is the use not clear ?
Fixes: #8207
As mentioned in gRFC A1 , we support TCP-level proxy through the
HTTPS_PROXY
or theHTTP_PROXY
environment variables. So if the resolved addresses for target have addresses that do not have network type oftcp
, proxy needs to be avoided for such addresses.Changes made in delegating resolver:
tcp
. If it does not , do not wait for proxy resolver update since we do not want to connect to proxy for any address.tcp
network type to indicate use of proxy and leave the other addresses as is in the new state update.RELEASE NOTES: