-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
transport,grpc: Integrate delegating resolver and introduce dial options for target host resolution #7881
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #7881 +/- ##
==========================================
+ Coverage 82.14% 82.34% +0.20%
==========================================
Files 382 383 +1
Lines 38711 38776 +65
==========================================
+ Hits 31798 31929 +131
+ Misses 5583 5531 -52
+ Partials 1330 1316 -14
|
7fa029c
to
619dcd4
Compare
internal/testutils/proxy.go
Outdated
} | ||
|
||
// NewProxyServer create and starts a proxy server. | ||
func NewProxyServer(lis net.Listener, requestCheck func(*http.Request) error, errCh chan error, doneCh chan struct{}, backendAddr string, resolutionOnClient bool, proxyServerStarted func()) *ProxyServer { |
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.
may be we can do something like this fake server in its own package
// Package fakeserver provides a fake implementation of the management server. |
New should only create the object and StartServer/Run should start the go routine to accept requests
internal/transport/http2_client.go
Outdated
address := addr.Addr | ||
|
||
//if the ProxyConnectAddr is set in the aattribute, do a proxy dial. |
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.
typo: attribute
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/transport/proxy.go
Outdated
func proxyDial(ctx context.Context, addr string, grpcUA string) (net.Conn, error) { | ||
newAddr := addr | ||
proxyURL, err := mapAddress(addr) | ||
// proxyDial dials, connecting to a proxy first if necessary. Dials, does 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.
we can remove the necessary part now? It will always dial proxy if 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.
Done
Addr: "test-address", | ||
Attributes: attributes.New(userAndConnectAddrKey, attr{user: nil, addr: "proxy-address"}), | ||
} | ||
|
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.
nix new line
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.
test/proxy_test.go
Outdated
|
||
// Tests grpc.NewClient with the default "dns" resolver and dial option | ||
// enabling target resolution on the client and verifies that the resolution | ||
// happens on client. |
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: mention the dial option WithTargetResolutionEnabled()
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.
test/proxy_test.go
Outdated
|
||
// Tests grpc.NewClient with grpc.WithNoProxy() set and verifies that it does | ||
// not dail to proxy, but directly to backend. | ||
func (s) TestGrpcNewClientWithNoProxy(t *testing.T) { |
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.
for the NoProxy and CustomDialer case, should we still override overrideHTTPSProxyFromEnvironment? That will make sure that even though proxy env is set we should skip it because of NoProxy and CustomDialer or is that not intended?
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 WithNoProxy
or WithContextDialer
are set, the HTTPSProxyFromEnviornment
function will never be called, but we can still override and add error if it is being called. Is it a good practice to do so? @purnesh42H
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.
yeah so right now if HTTPSProxyFromEnviornment is not present, it doesn't matter if WithNoProxy
or WithContextDialer
is set? The resolution will happen at client anyways? So having it present and not being called in these two cases is what we want to verify.
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.
test/proxy_test.go
Outdated
} | ||
wantProxyAuthStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+password)) | ||
if got := req.Header.Get("Proxy-Authorization"); got != wantProxyAuthStr { | ||
gotDecoded, _ := base64.StdEncoding.DecodeString(got) |
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 not ignore decoding errors. it should return error if decoding fails
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/testutils/proxy.go
Outdated
}() | ||
t.Logf("Started proxy at: %q", pLis.Addr()) | ||
t.Cleanup(p.stop) | ||
p.Addr = fmt.Sprintf("localhost:%d", ParsePort(t, pLis.Addr().String())) |
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.
Also why can't this just be pLis.Addr().String()
? There should be a comment explaining why it's doing all this extra stuff to seemingly just convert localhost:123
into localhost:123
.
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.
pLis..Addr()
will be a IP address, we convert it to localhost:port
to make sure the DNS resolver is working correctly for the proxy and connecting to the proxy server.
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.
Oh true it is converted to IP address. But why is that a problem?
If it's for testing the delegating resolver -> DNS resolver path, then it seems like this should just use the raw listener address, and the tests that care about that should convert it.
Either way, definitely needs a comment.
internal/transport/proxy.go
Outdated
u := t.Username() | ||
p, _ := t.Password() | ||
|
||
if user := opts.User; user != (url.Userinfo{}) { |
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.
This comparison with a zero-initialized Userinfo looks a little off to me. Should this field in opts
be a pointer instead to determine whether it was set intentionally?
Or should it check if either Username()
or Password()
are non-empty?
I'm not sure exactly what the right behavior is, but this seems questionable, as it's including any unexported fields in the comparison.
Related but potential existing problem to investigate: what if username is set but password is not? Is that valid? If so, is the proper behavior to encode it as username:
, as it does currently, or should the :
be omitted?
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 RFC doesn't clearly mention what is the correct syntax if password is absent , so I think either way should be fine, but adding a semicolon like username:
would be more explicit in indication username has ended and the password is empty.
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 have added a UserSet
option as changing it to a pointer can allow the caller to modify the use info.
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.
SGTM on the behavior of username:
.
changing it to a pointer can allow the caller to modify the use info.
"don't do that"?
An API that is impossible to use incorrectly is better than one that can be used incorrectly...but sometimes tradeoffs have to be made.
And with a separate field that has to be manually set, we run the risk of it being set incorrectly, so this alternative doesn't seem any better to me.
If you really want to have a bool then it should be unexported and handled automatically, like how the passwordSet
field works in the userinfo itself:
https://cs.opensource.google/go/go/+/refs/tags/go1.23.5:src/net/url/url.go;l=409
Otherwise I think a pointer, and a note telling people not to modify it after setting it, is pretty reasonable and simpler. And doesn't that simplify things? Then we can just directly copy the User
field from the URL
without all this extra work. Simpler is almost always better.
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.
Got it ! Done.
@@ -98,17 +103,18 @@ func (p *ProxyServer) handleRequest(t *testing.T, in net.Conn, waitForServerHell | |||
} | |||
|
|||
// HTTPProxy initializes and starts a proxy server, registers a cleanup to | |||
// stop it, and returns the proxy's listener and helper channels. | |||
// stop it, and returns a ProxyServer. | |||
func HTTPProxy(t *testing.T, reqCheck func(*http.Request), waitForServerHello bool) *ProxyServer { |
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.
New
?
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/transport/proxy.go
Outdated
u := t.Username() | ||
p, _ := t.Password() | ||
|
||
if user := opts.User; user != (url.Userinfo{}) { |
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.
SGTM on the behavior of username:
.
changing it to a pointer can allow the caller to modify the use info.
"don't do that"?
An API that is impossible to use incorrectly is better than one that can be used incorrectly...but sometimes tradeoffs have to be made.
And with a separate field that has to be manually set, we run the risk of it being set incorrectly, so this alternative doesn't seem any better to me.
If you really want to have a bool then it should be unexported and handled automatically, like how the passwordSet
field works in the userinfo itself:
https://cs.opensource.google/go/go/+/refs/tags/go1.23.5:src/net/url/url.go;l=409
Otherwise I think a pointer, and a note telling people not to modify it after setting it, is pretty reasonable and simpler. And doesn't that simplify things? Then we can just directly copy the User
field from the URL
without all this extra work. Simpler is almost always better.
internal/testutils/proxy.go
Outdated
}() | ||
t.Logf("Started proxy at: %q", pLis.Addr()) | ||
t.Cleanup(p.stop) | ||
p.Addr = fmt.Sprintf("localhost:%d", ParsePort(t, pLis.Addr().String())) |
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.
Oh true it is converted to IP address. But why is that a problem?
If it's for testing the delegating resolver -> DNS resolver path, then it seems like this should just use the raw listener address, and the tests that care about that should convert it.
Either way, definitely needs a comment.
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.
One last thing otherwise LGTM. Thanks!
// stop it, and returns a ProxyServer. | ||
func HTTPProxy(t *testing.T, reqCheck func(*http.Request), waitForServerHello bool) *ProxyServer { | ||
func NewHTTPProxy(t *testing.T, reqCheck func(*http.Request), waitForServerHello bool) *ProxyServer { |
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 thing it returns is a ProxyServer
from package proxyserver
so this should just be New
.
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!
…ons for target host resolution (grpc#7881) * Change proxy behaviour
…ons for target host resolution (grpc#7881) * Change proxy behaviour
…ons for target host resolution (grpc#7881) * Change proxy behaviour
…ons for target host resolution (grpc#7881) * Change proxy behaviour
Fixes: #7556
RELEASE NOTES:
WithLocalDNSResolution()
dial option added to explicitly force target URI resolution on the client.