-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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
internal/delegatingresolver: avoid proxy if networktype of target address is not tcp #8215
base: master
Are you sure you want to change the base?
Changes from all commits
751dd5e
4476246
39d3ded
fcbc507
618877a
d150d8e
325e8cb
f2a6133
8742ea7
607ec91
d254b04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,10 +24,12 @@ | |
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
"sync" | ||
|
||
"google.golang.org/grpc/grpclog" | ||
"google.golang.org/grpc/internal/proxyattributes" | ||
"google.golang.org/grpc/internal/transport/networktype" | ||
"google.golang.org/grpc/resolver" | ||
"google.golang.org/grpc/serviceconfig" | ||
) | ||
|
@@ -189,18 +191,80 @@ | |
r.proxyResolver = nil | ||
} | ||
|
||
// parseDialTarget returns the network and address to pass to dialer. | ||
func parseDialTarget(target string) (string, string) { | ||
net := "tcp" | ||
m1 := strings.Index(target, ":") | ||
m2 := strings.Index(target, ":/") | ||
// handle unix:addr which will fail with url.Parse | ||
if m1 >= 0 && m2 < 0 { | ||
if n := target[0:m1]; n == "unix" { | ||
return n, target[m1+1:] | ||
} | ||
} | ||
if m2 >= 0 { | ||
t, err := url.Parse(target) | ||
if err != nil { | ||
return net, target | ||
} | ||
scheme := t.Scheme | ||
addr := t.Path | ||
if scheme == "unix" { | ||
if addr == "" { | ||
addr = t.Host | ||
} | ||
return scheme, addr | ||
} | ||
} | ||
return net, target | ||
} | ||
|
||
func networkTypeFromAddr(addr resolver.Address) (string, resolver.Address) { | ||
networkType, ok := networktype.Get(addr) | ||
if !ok { | ||
networkType, addr.Addr = parseDialTarget(addr.Addr) | ||
} | ||
return networkType, addr | ||
} | ||
|
||
func tcpAddressPresent(state *resolver.State) bool { | ||
for _, addr := range state.Addresses { | ||
if networkType, _ := networkTypeFromAddr(addr); networkType == "tcp" { | ||
return true | ||
} | ||
} | ||
for _, endpoint := range state.Endpoints { | ||
for _, addr := range endpoint.Addresses { | ||
if networktype, _ := networkTypeFromAddr(addr); networktype == "tcp" { | ||
return true | ||
} | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// updateClientConnStateLocked creates a list of combined addresses by | ||
// pairing each proxy address with every target address. For each pair, it | ||
// generates a new [resolver.Address] using the proxy address, and adding the | ||
// target address as the attribute along with user info. It returns nil if | ||
// either resolver has not sent update even once and returns the error from | ||
// ClientConn update once both resolvers have sent update atleast once. | ||
func (r *delegatingResolver) updateClientConnStateLocked() error { | ||
if r.targetResolverState == nil || r.proxyAddrs == nil { | ||
if r.targetResolverState == nil { | ||
return nil | ||
} | ||
|
||
curState := *r.targetResolverState | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even though we have this local variable |
||
|
||
// If no addresses returned by resolver have network type as tcp , do not wait for proxy update. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Please wrap comment lines at 80-cols. See: go/go-style/decisions#comment-line-length |
||
if !tcpAddressPresent(r.targetResolverState) { | ||
return r.cc.UpdateState(curState) | ||
} | ||
|
||
if r.proxyAddrs == nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see that the previous check is moved here. |
||
return nil | ||
} | ||
|
||
// If multiple resolved proxy addresses are present, we send only the | ||
// unresolved proxy host and let net.Dial handle the proxy host name | ||
// resolution when creating the transport. Sending all resolved addresses | ||
|
@@ -218,6 +282,11 @@ | |
} | ||
var addresses []resolver.Address | ||
for _, targetAddr := range (*r.targetResolverState).Addresses { | ||
// Avoid proxy when network is not tcp. | ||
if networkType, targetAddr := networkTypeFromAddr(targetAddr); networkType != "tcp" { | ||
addresses = append(addresses, targetAddr) | ||
continue | ||
} | ||
addresses = append(addresses, proxyattributes.Set(proxyAddr, proxyattributes.Options{ | ||
User: r.proxyURL.User, | ||
ConnectAddr: targetAddr.Addr, | ||
|
@@ -234,8 +303,13 @@ | |
var endpoints []resolver.Endpoint | ||
for _, endpt := range (*r.targetResolverState).Endpoints { | ||
var addrs []resolver.Address | ||
for _, proxyAddr := range r.proxyAddrs { | ||
for _, targetAddr := range endpt.Addresses { | ||
for _, targetAddr := range endpt.Addresses { | ||
// Avoid proxy when network is not tcp. | ||
if networkType, targetAddr := networkTypeFromAddr(targetAddr); networkType != "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, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ import ( | |
"google.golang.org/grpc/internal/proxyattributes" | ||
"google.golang.org/grpc/internal/resolver/delegatingresolver" | ||
"google.golang.org/grpc/internal/testutils" | ||
"google.golang.org/grpc/internal/transport/networktype" | ||
"google.golang.org/grpc/resolver" | ||
"google.golang.org/grpc/resolver/manual" | ||
"google.golang.org/grpc/serviceconfig" | ||
|
@@ -441,16 +442,16 @@ func (s) TestDelegatingResolverForEndpointsWithProxy(t *testing.T) { | |
{ | ||
Addresses: []resolver.Address{ | ||
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr1), | ||
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr2), | ||
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr1), | ||
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr2), | ||
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr2), | ||
}, | ||
}, | ||
{ | ||
Addresses: []resolver.Address{ | ||
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr3), | ||
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr4), | ||
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr3), | ||
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr4), | ||
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr4), | ||
}, | ||
}, | ||
|
@@ -760,3 +761,154 @@ func (s) TestDelegatingResolverResolveNow(t *testing.T) { | |
t.Fatalf("context timed out waiting for proxyResolver.ResolveNow() to be called.") | ||
} | ||
} | ||
|
||
// Tests the scenario where a proxy is configured, and the resolver returns a | ||
// network type other than tcp for all addresses. The test verifies that the | ||
// delegating resolver avoids the proxy update and directly sends the update | ||
// from target resolver to clientconn. | ||
func (s) TestDelegatingResolverForNonTCPTarget(t *testing.T) { | ||
const ( | ||
targetTestAddr = "test.target" | ||
resolvedTargetTestAddr1 = "1.1.1.1:8080" | ||
resolvedTargetTestAddr2 = "2.2.2.2:8080" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not used. Please delete. |
||
envProxyAddr = "proxytest.com" | ||
) | ||
hpfe := func(req *http.Request) (*url.URL, error) { | ||
if req.URL.Host == targetTestAddr { | ||
return &url.URL{ | ||
Scheme: "https", | ||
Host: envProxyAddr, | ||
}, nil | ||
} | ||
t.Errorf("Unexpected request host to proxy: %s want %s", req.URL.Host, targetTestAddr) | ||
return nil, nil | ||
} | ||
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment | ||
delegatingresolver.HTTPSProxyFromEnvironment = hpfe | ||
defer func() { | ||
delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe | ||
}() | ||
|
||
// Manual resolver to control the target resolution. | ||
targetResolver := manual.NewBuilderWithScheme("test") | ||
target := targetResolver.Scheme() + ":///" + targetTestAddr | ||
// Set up a manual DNS resolver to control the proxy address resolution. | ||
proxyResolver := setupDNS(t) | ||
|
||
tcc, stateCh, _ := createTestResolverClientConn(t) | ||
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil { | ||
t.Fatalf("Failed to create delegating resolver: %v", err) | ||
} | ||
|
||
// Set network to anything other than tcp. | ||
nonTCPAddr := networktype.Set(resolver.Address{Addr: resolvedTargetTestAddr1}, "unix") | ||
targetResolver.UpdateState(resolver.State{ | ||
Addresses: []resolver.Address{nonTCPAddr}, | ||
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr}}}, | ||
ServiceConfig: &serviceconfig.ParseResult{}, | ||
}) | ||
|
||
select { | ||
case <-stateCh: | ||
case <-time.After(defaultTestShortTimeout): | ||
t.Fatalf("Delegating resolver did not call update state") | ||
} | ||
Comment on lines
+813
to
+815
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be racy, especially on some slow GitHub action machines? We generally use |
||
|
||
proxyResolver.UpdateState(resolver.State{ | ||
Addresses: []resolver.Address{{Addr: envProxyAddr}}, | ||
ServiceConfig: &serviceconfig.ParseResult{}, | ||
}) | ||
|
||
var gotState resolver.State | ||
select { | ||
case gotState = <-stateCh: | ||
case <-time.After(defaultTestTimeout): | ||
t.Fatal("Timeout when waiting for a state update from the delegating resolver") | ||
} | ||
wantState := resolver.State{ | ||
Addresses: []resolver.Address{nonTCPAddr}, | ||
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr}}}, | ||
ServiceConfig: &serviceconfig.ParseResult{}, | ||
} | ||
|
||
// Verify that the state clientconn receives is same as updated by target resolver, | ||
// since we want to avoid proxy for any network type aprt from tcp. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: typo "aprt" |
||
if diff := cmp.Diff(gotState, wantState); diff != "" { | ||
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff) | ||
} | ||
} | ||
|
||
// Tests the scenario where a proxy is configured, and the resolver returns addresses with varied | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Same here. Please wrap comments at 80-cols. Please figure out a way to configure your editor to do this for you. |
||
// network type. The test verifies that the delegating resolver doesnt add | ||
// proxyatrribute to adresses with network type other than tcp , but adds the | ||
// proxyattribute to addresses with network type tcp. | ||
func (s) TestDelegatingResolverForMixNetworkType(t *testing.T) { | ||
const ( | ||
targetTestAddr = "test.target" | ||
resolvedTargetTestAddr1 = "1.1.1.1:8080" | ||
resolvedTargetTestAddr2 = "2.2.2.2:8080" | ||
envProxyAddr = "proxytest.com" | ||
resolvedProxyTestAddr1 = "11.11.11.11:7687" | ||
resolvedProxyTestAddr2 = "22.22.22.22:7687" | ||
Comment on lines
+851
to
+852
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two are not used. Please delete. |
||
) | ||
hpfe := func(req *http.Request) (*url.URL, error) { | ||
if req.URL.Host == targetTestAddr { | ||
return &url.URL{ | ||
Scheme: "https", | ||
Host: envProxyAddr, | ||
}, nil | ||
} | ||
t.Errorf("Unexpected request host to proxy: %s want %s", req.URL.Host, targetTestAddr) | ||
return nil, nil | ||
} | ||
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment | ||
delegatingresolver.HTTPSProxyFromEnvironment = hpfe | ||
defer func() { | ||
delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe | ||
}() | ||
|
||
// Manual resolver to control the target resolution. | ||
targetResolver := manual.NewBuilderWithScheme("test") | ||
target := targetResolver.Scheme() + ":///" + targetTestAddr | ||
// Set up a manual DNS resolver to control the proxy address resolution. | ||
proxyResolver := setupDNS(t) | ||
|
||
tcc, stateCh, _ := createTestResolverClientConn(t) | ||
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil { | ||
t.Fatalf("Failed to create delegating resolver: %v", err) | ||
} | ||
// Set network to anything other than tcp. | ||
nonTCPAddr := networktype.Set(resolver.Address{Addr: resolvedTargetTestAddr1}, "unix") | ||
targetResolver.UpdateState(resolver.State{ | ||
Addresses: []resolver.Address{nonTCPAddr, {Addr: resolvedTargetTestAddr2}}, | ||
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr, {Addr: resolvedTargetTestAddr2}}}}, | ||
ServiceConfig: &serviceconfig.ParseResult{}, | ||
}) | ||
|
||
select { | ||
case <-stateCh: | ||
t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.") | ||
case <-time.After(defaultTestShortTimeout): | ||
} | ||
|
||
proxyResolver.UpdateState(resolver.State{ | ||
Addresses: []resolver.Address{{Addr: envProxyAddr}}, | ||
ServiceConfig: &serviceconfig.ParseResult{}, | ||
}) | ||
|
||
var gotState resolver.State | ||
select { | ||
case gotState = <-stateCh: | ||
case <-time.After(defaultTestTimeout): | ||
t.Fatal("Timeout when waiting for a state update from the delegating resolver") | ||
} | ||
wantState := resolver.State{ | ||
Addresses: []resolver.Address{nonTCPAddr, proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr2)}, | ||
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr, proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr2)}}}, | ||
ServiceConfig: &serviceconfig.ParseResult{}, | ||
} | ||
|
||
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.
If
r.proxyAddrs == nil
is no longer in this conditional, does it mean that we can have a case where the proxy is configured, but we don't end up waiting for the resolved proxy addresses here?