Skip to content

delegatingresolver: avoid proxy for resolved addresses in NO_PROXY env #8329

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

Merged
merged 11 commits into from
May 23, 2025

Conversation

eshitachandwani
Copy link
Member

@eshitachandwani eshitachandwani commented May 15, 2025

Fixes: #8327

Add a check for resolved addresses to avoid proxy for addresses mentioned in NO_PROXY environment variable. We only had a check for unresolved addresses.

RELEASE NOTES:

  • client: restore support for NO_PROXY environment variable when connecting to locally-resolved addresses. This fixes a regression and restores historical grpc-go behavior (use case 2 from gRFC A1 ).

@eshitachandwani eshitachandwani requested review from dfawley and removed request for purnesh42H and dfawley May 15, 2025 11:51
@eshitachandwani eshitachandwani added Type: Bug Area: Resolvers/Balancers Includes LB policy & NR APIs, resolver/balancer/picker wrappers, LB policy impls and utilities. labels May 15, 2025
Copy link

codecov bot commented May 15, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 82.29%. Comparing base (1ecde18) to head (11903ba).
Report is 6 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8329      +/-   ##
==========================================
+ Coverage   82.19%   82.29%   +0.09%     
==========================================
  Files         419      419              
  Lines       42025    42048      +23     
==========================================
+ Hits        34543    34603      +60     
+ Misses       6012     5986      -26     
+ Partials     1470     1459      -11     
Files with missing lines Coverage Δ
.../resolver/delegatingresolver/delegatingresolver.go 86.42% <100.00%> (+0.91%) ⬆️

... and 27 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@purnesh42H
Copy link
Contributor

I think release notes should mention the bug fix. How about something like client: fixed a bug that was attempting to use a proxy for addresses matching NO_PROXY environment variable?

@@ -241,7 +253,7 @@ func (r *delegatingResolver) updateClientConnStateLocked() error {
var addresses []resolver.Address
for _, targetAddr := range (*r.targetResolverState).Addresses {
// Avoid proxy when network is not tcp.
if networkType := networkTypeFromAddr(targetAddr); networkType != "tcp" {
if networkType := networkTypeFromAddr(targetAddr); networkType != "tcp" || !isProxyNeeded(targetAddr.Addr) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if networkType := networkTypeFromAddr(targetAddr); networkType != "tcp" || !isProxyNeeded(targetAddr.Addr) {
!isProxyNeeded(targetAddr.Addr) {
addresses = append(addresses, targetAddr)
continue
}
if networkType := networkTypeFromAddr(targetAddr); networkType != "tcp" {
addresses = append(addresses, targetAddr)
continue
}

to avoid checking network type if proxy is anyway not needed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also combine both the network type check and NO_PROXY check in the same helper function

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -259,7 +271,7 @@ func (r *delegatingResolver) updateClientConnStateLocked() error {
var addrs []resolver.Address
for _, targetAddr := range endpt.Addresses {
// Avoid proxy when network is not tcp.
if networkType := networkTypeFromAddr(targetAddr); networkType != "tcp" {
if networkType := networkTypeFromAddr(targetAddr); networkType != "tcp" || !isProxyNeeded(targetAddr.Addr) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same suggestion as above

@@ -210,6 +210,18 @@ func isTCPAddressPresent(state *resolver.State) bool {
return false
}

func isProxyNeeded(address string) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about skipProxy?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Host: envProxyAddr,
}, nil
}
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
Copy link
Contributor

@purnesh42H purnesh42H May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't have much context but is it possible to set the NO_PROXY env var for this test instead of just overriding from here? That would be a more reliable test.

I found one example here https://github.com/grpc/grpc-go/blob/master/stats/opentelemetry/csm/pluginoption_test.go#L437

Copy link
Contributor

@purnesh42H purnesh42H May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also might be good to verify putting in the IP range as mention in the bug?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember having some problem with using the proxy environment variable and so we switched to replacing the function.
SOmething on the lines that the function used to retrieve proxy address uses Do.Once function and that gets done the first time we run the test suite and so every time we do not get the value set in proxy environments and so we decided to replace the function in tests.

@purnesh42H
Copy link
Contributor

I don't have much knowledge about the recent changes here. So I will defer to @easwars and @arjan-bal

return true
}
// Avoid proxy when network is not tcp.
if networkType := networkTypeFromAddr(address); networkType != "tcp" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make isTCPAddressPresent use skipProxy instead and inline networkTypeFromAddr . isTCPAddressPresent would also need to be renamed to something more appropriate then. This would unify the checks in both places.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we inline networkTypeFromAddr since there is only one caller now? This would also prevent people from accidentally bypassing the "ignore proxy" check in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right! Missed that. Done!

@arjan-bal arjan-bal removed their assignment May 16, 2025
Copy link
Contributor

@arjan-bal arjan-bal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@eshitachandwani eshitachandwani removed their assignment May 19, 2025
@arjan-bal
Copy link
Contributor

Please open a flake for the failing advancedtls test and assign it to someone from the security team.

https://github.com/grpc/grpc-go/actions/runs/15105499577/job/42453426886?pr=8329

@nicholasngai
Copy link

FWIW, I think it is worth mentioning that this behavior is probably non-idiomatic and is really only being put in for backwards compatibility. For example, it would not make sense for dns:///localhost:1234 to match NO_PROXY=127.0.0.1—browsers and anyone else checking NO_PROXY traditionally only match against the target URL, not the underlying resolved addresses.

There is potential for inconsistent behavior here: For example, this would mean that dns:///example.com:1234 with NO_PROXY=1.2.3.4 would route to the proxy if the DNS for example.com pointed to 5.6.7.8 one day but then not route to the proxy if the DNS pointed to 1.2.3.4 the next day—applications generally would only match against NO_PROXY=example.com instead.

@eshitachandwani
Copy link
Member Author

FWIW, I think it is worth mentioning that this behavior is probably non-idiomatic and is really only being put in for backwards compatibility. For example, it would not make sense for dns:///localhost:1234 to match NO_PROXY=127.0.0.1—browsers and anyone else checking NO_PROXY traditionally only match against the target URL, not the underlying resolved addresses.

There is potential for inconsistent behavior here: For example, this would mean that dns:///example.com:1234 with NO_PROXY=1.2.3.4 would route to the proxy if the DNS for example.com pointed to 5.6.7.8 one day but then not route to the proxy if the DNS pointed to 1.2.3.4 the next day—applications generally would only match against NO_PROXY=example.com instead.

IMO including IP addresses in the NO_PROXY environment variable should not be considered inconsistent behavior. The Go proxy configuration explicitly supports IP addresses in NO_PROXY, and it is common for users to intentionally set this environment variable to exclude certain internal or reserved IP ranges from proxying. This is a practical and expected use case—particularly in environments where internal services are hosted on private network ranges. This behavior aligns with Go’s documented proxy semantics and is consistent with standard practices in private and enterprise deployments.

@dfawley
Copy link
Member

dfawley commented May 21, 2025

I agree we should support IP addresses, but it seems we should also support domain names here, too. If the primary resolver is DNS and NO_PROXY includes the hostname we are looking up, we should not use a proxy.

@eshitachandwani
Copy link
Member Author

I agree we should support IP addresses, but it seems we should also support domain names here, too. If the primary resolver is DNS and NO_PROXY includes the hostname we are looking up, we should not use a proxy.

Right! That is there, I have not changed that! Just added another check for IP address also after the addresses are resolved.

@dfawley
Copy link
Member

dfawley commented May 21, 2025

I see. So this is happening later in the process, after addresses are resolved, when we are doing local resolution. And since our behavior for many years had been to respect http_proxy and no_proxy for locally-resolved addresses, this change is restoring that behavior.

This is basically use case 2 from gRFC A1. And I think as long as we are doing local name resolution, then it is pretty reasonable to check no_proxy for these resolved addresses. And since it's also restoring backward compatibility, then there's no reason to not do this. Note that Java doesn't support use case 2, and C++ doesn't use the environment variables for that use case, but custom logic instead.

Comment on lines 357 to 358
// If no addresses returned by resolver have network type as tcp , do not
// wait for proxy update.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -177,14 +177,10 @@ func (s) TestDelegatingResolverwithDNSAndProxyWithTargetResolution(t *testing.T)
resolvedProxyTestAddr1 = "11.11.11.11:7687"
)
hpfe := func(req *http.Request) (*url.URL, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a lot of repetition of this, which required you to change it in many many places. Can you factor this out and share it between the test cases since it seems to be identical every time?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 959 to 960
// the delegating resolver doesn't add proxyatrribute to adresses excluded , but
// adds the proxyattribute to all other addresses.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure you don't have spaces before commas. (I've noticed this elsewhere, too.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -97,14 +97,10 @@ func (s) TestGRPCDialWithProxy(t *testing.T) {

// Overwrite the function in the test and restore them in defer.
hpfe := func(req *http.Request) (*url.URL, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments for this file re: re-use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@dfawley dfawley assigned eshitachandwani and unassigned easwars May 22, 2025
@eshitachandwani eshitachandwani merged commit 443caad into grpc:master May 23, 2025
22 of 25 checks passed
eshitachandwani added a commit to eshitachandwani/grpc-go that referenced this pull request May 23, 2025
eshitachandwani added a commit to eshitachandwani/grpc-go that referenced this pull request May 23, 2025
eshitachandwani added a commit to eshitachandwani/grpc-go that referenced this pull request May 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Resolvers/Balancers Includes LB policy & NR APIs, resolver/balancer/picker wrappers, LB policy impls and utilities. Type: Bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

NO_PROXY environment variable no longer respected on custom resolver results
6 participants