-
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
*: Correct gRFC A1 implementation #7881
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 #7881 +/- ##
==========================================
+ Coverage 82.05% 82.11% +0.06%
==========================================
Files 381 382 +1
Lines 38539 38628 +89
==========================================
+ Hits 31622 31720 +98
+ Misses 5602 5591 -11
- Partials 1315 1317 +2
|
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.
p, _ := t.Password() | ||
if user := attributes.User(address); user != nil { | ||
u := user.Username() | ||
p, _ := user.Password() |
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.
hmmm...we should not ignore the bool. What happens if password is not set?
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.
It will append an empty string with the username. I have referenced it from the earlier code itself.
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.
then user.Password() should just return empty string? it looks weird to ignore or atleast we should log a warning. So, if password is empty will the connection still succeed or we should do early failure?
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.
From what I understand, it will still succeed.
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
test/proxy_test.go
Outdated
} | ||
|
||
// TestGrpcDialWithProxy tests grpc.Dial using a proxy and default | ||
// resolver in the target URI.and verifies that it connects 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.
typo space and remove period after target URI
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 := resolver.Address{ | ||
Addr: "test-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.
Addr: "test-address", | ||
Attributes: attributes.New(userAndConnectAddrKey, attr{user: user, 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.
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.
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
// verifies that the client connects to the proxy server, includes the resolved | ||
// target URI in the HTTP CONNECT request, and successfully establishes a | ||
// connection to the backend server. | ||
func (s) TestGrpcNewClientWithProxyAndCustomResolver(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.
nit: TestGRPC
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 default i.e DNS resolver for targetURI and a proxy | ||
// and verifies that it connects to proxy server and sends unresolved target URI | ||
// in the HTTP CONNECT req and connects to backend. | ||
func (s) TestGrpcNewClientWithProxy(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.
nit: TestGRPC
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
case <-resolutionCh: | ||
t.Logf("target resolution happened on client") | ||
default: | ||
t.Fatalf("Client-side resolution should be called but wasn'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.
"target resolution did not happen 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.
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.
p, _ := t.Password() | ||
if user := attributes.User(address); user != nil { | ||
u := user.Username() | ||
p, _ := user.Password() |
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.
then user.Password() should just return empty string? it looks weird to ignore or atleast we should log a warning. So, if password is empty will the connection still succeed or we should do early failure?
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 haven't reviewed the tests yet, will take a look at them next.
clientconn.go
Outdated
// | ||
// WithTargetResolutionEnabled in `grpc.Dial` ensures that it preserves | ||
// behavior: when default scheme passthrough is used, skip hostname | ||
// resolution, when any other scheme like "dns" is used for resolution, |
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.
any other scheme like "dns"
From my understanding, WithTargetResolutionEnabled
ONLY effects the dns
resolver's behaviour.
If this is the case, we should mention this in the godoc of WithTargetResolutionEnabled
and change this comment to say when "dns" is used
. Comments should be precise and 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.
clientconn.go
Outdated
// WithTargetResolutionEnabled in `grpc.Dial` ensures that it preserves | ||
// behavior: when default scheme passthrough is used, skip hostname | ||
// resolution, when any other scheme like "dns" is used for resolution, | ||
// perform resolution on the client as expected. |
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: Remove the the ending as expected
, it doesn't seem to add any extra information.
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_test.go
Outdated
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.
Is there a reason for moving proxy tests out of internal/transport
? It's preferable to keep tests in separate packages as it allows tests to run in parallel.
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.
It looks like these tests are deleted, not moved. We need these tests.
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.
Whatever was being tested in the tests is being tested in the E2E tests, basic auth and connect requests and connecting to the proxy server. The mapAddress function was moved to delegating resolver and that is being tested in delegating resolver. Do we need to test is again in unit tests?
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.
TestHTTPConnectWithServerHello is added to verify that the optimization added in #7424 doesn't effect correctness. It is difficult to test it in en e2e fashion because it needs the server to send the first message after creation of the TCP connection and the proxy to buffer this message along with the CONNECT response. I think the other tests can be replaced.
dialoptions.go
Outdated
@@ -384,7 +384,8 @@ func WithNoProxy() DialOption { | |||
} | |||
|
|||
// WithTargetResolutionEnabled returns a DialOption which enables target | |||
// resolution on client. This is ignored if WithNoProxy is used. | |||
// resolution on client even when "dns" scheme is used. This is ignored if |
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 we should mention when a proxy is used along with the the "dns" scheme
.
internal/transport/proxy_test.go
Outdated
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.
TestHTTPConnectWithServerHello is added to verify that the optimization added in #7424 doesn't effect correctness. It is difficult to test it in en e2e fashion because it needs the server to send the first message after creation of the TCP connection and the proxy to buffer this message along with the CONNECT response. I think the other tests can be replaced.
internal/transport/proxy.go
Outdated
u := user.Username() | ||
p, pSet := user.Password() | ||
if !pSet && logger.V(2) { | ||
logger.Warningf("password not set for basic authentication for proxy dialing") |
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.
Does this warrant a log? I believe the password is allowed to be empty in HTTP Basic auth.
test/proxy_test.go
Outdated
} | ||
|
||
// Creates and starts a proxy server. | ||
func newProxyServer(lis net.Listener, reqCheck func(*http.Request) error, errCh chan error, doneCh chan struct{}, backendAddr string, resOnClient bool, proxyStarted 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.
Can you move these tests back to the transport
directory? It will be easier to review the delta in the proxy server code instead of the whole code.
test/proxy_test.go
Outdated
if resOnClient { | ||
out, err = net.Dial("tcp", req.URL.Host) | ||
} else { | ||
out, err = net.Dial("tcp", backendAddr) | ||
} |
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 proxy server should always connect to the address received in the request. The address in the request can be either an IP or a hostname depending on how the client is configured.
test/proxy_test.go
Outdated
// and server to client. | ||
go io.Copy(p.in, p.out) | ||
go io.Copy(p.out, p.in) | ||
close(doneCh) |
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.
What is the intended use of doneCh
here? The go routines for the proxy are running in the background when doneCh
is closed. If the intention is to track the completion of the CONNECT request, we should rename the variable to something more appropriate like connectDone
.
test/proxy_test.go
Outdated
} | ||
t.Logf("Started TestService backend at: %q", backend.Address) | ||
t.Cleanup(backend.Stop) | ||
return backend.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.
This is always an ip:port
. Instead, you could use testutils.ParsePort
to get the port and return localhost:port
from here. This would allow you to test if the proxy receives an IP address or host based on the test case.
test/proxy_test.go
Outdated
t.Errorf("EmptyCall failed: %v", err) | ||
} | ||
|
||
select { |
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.
Please add another branch to check for timeouts and fail the test in finite time.
test/proxy_test.go
Outdated
if err := p.requestCheck(req); err != nil { | ||
resp := http.Response{StatusCode: http.StatusMethodNotAllowed} | ||
resp.Write(p.in) | ||
p.in.Close() |
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 we should log an error here instead of using the error channel, similar to the existing implementation. The write to the error channel can block indefinitely if no one reads and the test may pass if it doesn't read the errors in the channel, hiding real failures.
test/proxy_test.go
Outdated
}() | ||
|
||
// Configure manual resolvers for both proxy and target backends | ||
targetResolver := setupDNS(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.
We can use the real DNS resolver to resolve "localhost". Why use a fake hostname and manual resolver?
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 cannot use a local host because the http.FromProxyEnvirnoment
as well as httpproxy.FromEnviornment
returns a nil proxy URL if the target URI is local host. The documentation says :
http.FromProxyEnvirnoment
As a special case, if req.URL.Host is "localhost" (with or without a port number), then a nil URL and nil error will be returned.
http.FromProxyEnvirnoment
As a special case, if req.URL.Host is "localhost" or a loopback address (with or without a port number), then a nil URL and nil error will be returned.
test/proxy_test.go
Outdated
os.Setenv("HTTPS_PROXY", pLis.Addr().String()) | ||
defer func() { os.Setenv("HTTPS_PROXY", proxyEnv) }() |
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.
Can use t.SetEnv
that automatically resets the value as part of test cleanup.
Fixes: #7556
RELEASE NOTES:
WithTargetResolutionEnabled()
dial option added to explicitly force target URI resolution on the client.