Skip to content

Conversation

eshitachandwani
Copy link
Member

@eshitachandwani eshitachandwani commented Sep 27, 2025

Fixes: #8607

RELEASE NOTES:

  • Fixes a bug where default port 443 was not being added to addresses without port being sent to proxy.
  • Adds a new environment variable GRPC_EXPERIMENTAL_RESOLVER_ADD_DEFAULT_PORT for adding a default port to addresses being sent to proxy which is set by default.

Copy link

codecov bot commented Sep 28, 2025

Codecov Report

❌ Patch coverage is 83.87097% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.98%. Comparing base (bb71072) to head (c19e88d).
⚠️ Report is 14 commits behind head on master.

Files with missing lines Patch % Lines
.../resolver/delegatingresolver/delegatingresolver.go 83.87% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8613      +/-   ##
==========================================
- Coverage   82.00%   81.98%   -0.02%     
==========================================
  Files         415      415              
  Lines       40697    40736      +39     
==========================================
+ Hits        33372    33398      +26     
- Misses       5937     5950      +13     
  Partials     1388     1388              
Files with missing lines Coverage Δ
internal/envconfig/envconfig.go 100.00% <ø> (ø)
.../resolver/delegatingresolver/delegatingresolver.go 86.29% <83.87%> (-0.14%) ⬇️

... and 38 files with indirect coverage changes

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

Comment on lines 114 to 117
addr, err := parseTarget(target.Endpoint())
if err != nil {
return nil, fmt.Errorf("delegating_resolver: invalid target address %q: %v", target.Endpoint(), err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should guard this call to parseTarget behind the feature flag since it can potentially lead to new/different failures after this PR is merged.

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 have only one block guarded by the feature flag if we instead do the following:

addr := target.Endpoint()
if target.URL.Scheme == "dns" && !targetResolutionEnabled && envconfig.AddDefaultPort {
 addr, err = parseTarget(target.Endpoint(), defaultPort)
  if err != nil {
    // return some error
  }
}

This way we can use the parseTarget function from the dns resolver without any modifications.

Copy link
Member Author

Choose a reason for hiding this comment

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

My thinking was we should do the basic check and add localhost for all addresses if no host is present irrespective of the resolver being used, but adding default port 443 is only for DNS resolver with target resolution disabled. And the first part should not be behind the flag , but only 2nd part should. That is my understanding, let me know if I am missing something.

Copy link
Contributor

Choose a reason for hiding this comment

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

From my understanding, only the DNS resolver was defaulting the hostname to localhost. Applying this logic to all resolvers would be a behaviour change which could potentially break some custom resolver that isn't expecting this. I would recommend avoiding such a change to be safe.


Also, since we're providing a env variable flag, we should ensure the flag can revert all (if not most) changes in this PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed.

name string
target string
wantConnectAddress string
wantErrorSubstring string
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add another test param for the env variable and tests cases for the env variable being disabled?

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 already have a seperate test for it : TestDelegatingResolverEnvVarForDefaultPortDisable

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 combine the tests using an additional param? That should help reduce duplicate code.

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 +218 to +220
if target == "" {
return "", fmt.Errorf("missing address")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this even possible since we already parse the target URI specified by the user in clientconn.go before we get here?

Ideally, it would be nicer to avoid checking conditions that are not possible, since it would simplify the code.

Comment on lines +223 to +224
// If the port field is empty (target ends with colon), e.g. "[::1]:",
// this is an error.
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: I think it would be better to group these inline comments into a docstring for the function. That way, one can just read that docstring and get to know what the function is doing instead of reading it one piece at a time.

return "", fmt.Errorf("missing address")
}
if host, port, err := net.SplitHostPort(target); err == nil {
if port == "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on the documentation of net.SplitHostPort, the port seems to be mandatory and it will return a missing port in address error in that case. So, I don't think we could have err == nil and port == "".

// SplitHostPort splits a network address of the form "host:port",
// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or
// host%zone and port.

Please correct me if I'm wrong.

disableDefaultPortEnvVar bool
}{
{
name: "no port in target",
Copy link
Contributor

Choose a reason for hiding this comment

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

t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.disableDefaultPortEnvVar {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you have to check this field? Why can't you set the value of the env var to the one specified by the test unconditionally? That way, the code will be immune to changes in the default value of the env var.

}
overrideTestHTTPSProxy(t, envProxyAddr)

targetResolver := manual.NewBuilderWithScheme("dns")
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't you have to unregister the original dns resolver and re-register it at the end of the test, for this test to not affect other tests?

targetResolver := manual.NewBuilderWithScheme("dns")
target := targetResolver.Scheme() + ":///" + test.target
// Set up a manual DNS resolver to control the proxy address resolution.
proxyResolver, proxyResolverBuilt := setupDNS(t)
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I see that this helper does re-register the original dns resolver. But maybe this section can still be a little more explicit/readable etc. I'm not able to think of the exact way to make this better right now.

Comment on lines +307 to +323
if test.wantErrorSubstring != "" {
// Case 1: We expected an error.
if err == nil {
t.Fatalf("Delegating resolver created, want error containing %q", test.wantErrorSubstring)
}
if !strings.Contains(err.Error(), test.wantErrorSubstring) {
t.Fatalf("Delegating resolver failed with error %v, want error containing %v", err.Error(), test.wantErrorSubstring)
}
return
}

// Case 2: We did NOT expect an error.
if err != nil {
t.Fatalf("Delegating resolver creation failed unexpectedly with error: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
Copy link
Contributor

Choose a reason for hiding this comment

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

This test logic seems complicated enough to me that you can actually split the tests into two: go/gotip/episodes/50

@easwars easwars assigned eshitachandwani and unassigned arjan-bal Oct 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

HTTPS_PROXY support issues non-compliant CONNECT requests
3 participants