google.golang.org/grpc@v1.72.2/internal/transport/proxy_ext_test.go (about) 1 /* 2 * 3 * Copyright 2024 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package transport_test 20 21 import ( 22 "context" 23 "encoding/base64" 24 "fmt" 25 "net" 26 "net/http" 27 "net/netip" 28 "net/url" 29 "testing" 30 "time" 31 32 "golang.org/x/net/http/httpproxy" 33 "google.golang.org/grpc" 34 "google.golang.org/grpc/credentials/insecure" 35 "google.golang.org/grpc/internal/grpctest" 36 "google.golang.org/grpc/internal/resolver/delegatingresolver" 37 "google.golang.org/grpc/internal/stubserver" 38 "google.golang.org/grpc/internal/testutils" 39 "google.golang.org/grpc/internal/testutils/proxyserver" 40 testgrpc "google.golang.org/grpc/interop/grpc_testing" 41 testpb "google.golang.org/grpc/interop/grpc_testing" 42 "google.golang.org/grpc/resolver" 43 "google.golang.org/grpc/resolver/manual" 44 ) 45 46 const defaultTestTimeout = 10 * time.Second 47 48 type s struct { 49 grpctest.Tester 50 } 51 52 func Test(t *testing.T) { 53 grpctest.RunSubTests(t, s{}) 54 } 55 56 func startBackendServer(t *testing.T) *stubserver.StubServer { 57 t.Helper() 58 backend := &stubserver.StubServer{ 59 EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) { return &testpb.Empty{}, nil }, 60 } 61 if err := backend.StartServer(); err != nil { 62 t.Fatalf("failed to start backend: %v", err) 63 } 64 t.Logf("Started TestService backend at: %q", backend.Address) 65 t.Cleanup(backend.Stop) 66 return backend 67 } 68 69 func isIPAddr(addr string) bool { 70 _, err := netip.ParseAddr(addr) 71 return err == nil 72 } 73 74 func overrideTestHTTPSProxy(t *testing.T, proxyAddr string) { 75 t.Helper() 76 hpfe := func(req *http.Request) (*url.URL, error) { 77 return &url.URL{ 78 Scheme: "https", 79 Host: proxyAddr, 80 }, nil 81 } 82 originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment 83 delegatingresolver.HTTPSProxyFromEnvironment = hpfe 84 t.Cleanup(func() { delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe }) 85 } 86 87 // Tests the scenario where grpc.Dial is performed using a proxy with the 88 // default resolver in the target URI. The test verifies that the connection is 89 // established to the proxy server, sends the unresolved target URI in the HTTP 90 // CONNECT request and is successfully connected to the backend server. 91 func (s) TestGRPCDialWithProxy(t *testing.T) { 92 backend := startBackendServer(t) 93 unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address)) 94 proxyCalled := false 95 reqCheck := func(req *http.Request) { 96 proxyCalled = true 97 host, _, err := net.SplitHostPort(req.URL.Host) 98 if err != nil { 99 t.Error(err) 100 } 101 if got, want := host, "localhost"; got != want { 102 t.Errorf(" Unexpected request host: %s, want = %s ", got, want) 103 } 104 } 105 pServer := proxyserver.New(t, reqCheck, false) 106 // Use "localhost:<port>" to verify the proxy address is handled 107 // correctly by the delegating resolver and connects to the proxy server 108 // correctly even when unresolved. 109 pAddr := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, pServer.Addr)) 110 111 overrideTestHTTPSProxy(t, pAddr) 112 113 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 114 defer cancel() 115 conn, err := grpc.Dial(unresolvedTargetURI, grpc.WithTransportCredentials(insecure.NewCredentials())) 116 if err != nil { 117 t.Fatalf("grpc.Dial(%s) failed: %v", unresolvedTargetURI, err) 118 } 119 defer conn.Close() 120 121 // Send an empty RPC to the backend through the proxy. 122 client := testgrpc.NewTestServiceClient(conn) 123 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil { 124 t.Fatalf("EmptyCall failed: %v", err) 125 } 126 127 if !proxyCalled { 128 t.Fatalf("Proxy not connected") 129 } 130 } 131 132 // Tests the scenario where `grpc.Dial` is performed with a proxy and the "dns" 133 // scheme for the target. The test verifies that the proxy URI is correctly 134 // resolved and that the target URI resolution on the client preserves the 135 // original behavior of `grpc.Dial`. It also ensures that a connection is 136 // established to the proxy server, with the resolved target URI sent in the 137 // HTTP CONNECT request, successfully connecting to the backend server. 138 func (s) TestGRPCDialWithDNSAndProxy(t *testing.T) { 139 backend := startBackendServer(t) 140 unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address)) 141 proxyCalled := false 142 reqCheck := func(req *http.Request) { 143 proxyCalled = true 144 145 host, _, err := net.SplitHostPort(req.URL.Host) 146 if err != nil { 147 t.Error(err) 148 } 149 if got, want := isIPAddr(host), true; got != want { 150 t.Errorf("isIPAddr(%q) = %t, want = %t", host, got, want) 151 } 152 } 153 pServer := proxyserver.New(t, reqCheck, false) 154 155 overrideTestHTTPSProxy(t, pServer.Addr) 156 157 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 158 defer cancel() 159 conn, err := grpc.Dial("dns:///"+unresolvedTargetURI, grpc.WithTransportCredentials(insecure.NewCredentials())) 160 if err != nil { 161 t.Fatalf("grpc.Dial(%s) failed: %v", "dns:///"+unresolvedTargetURI, err) 162 } 163 defer conn.Close() 164 165 // Send an empty RPC to the backend through the proxy. 166 client := testgrpc.NewTestServiceClient(conn) 167 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil { 168 t.Fatalf("EmptyCall failed: %v", err) 169 } 170 171 if !proxyCalled { 172 t.Fatalf("Proxy not connected") 173 } 174 } 175 176 // Tests the scenario where `grpc.NewClient` is used with the default DNS 177 // resolver for the target URI and a proxy is configured. The test verifies 178 // that the client resolves proxy URI, connects to the proxy server, sends the 179 // unresolved target URI in the HTTP CONNECT request, and successfully 180 // establishes a connection to the backend server. 181 func (s) TestNewClientWithProxy(t *testing.T) { 182 backend := startBackendServer(t) 183 unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address)) 184 proxyCalled := false 185 reqCheck := func(req *http.Request) { 186 proxyCalled = true 187 host, _, err := net.SplitHostPort(req.URL.Host) 188 if err != nil { 189 t.Error(err) 190 } 191 if got, want := host, "localhost"; got != want { 192 t.Errorf(" Unexpected request host: %s, want = %s ", got, want) 193 } 194 } 195 pServer := proxyserver.New(t, reqCheck, false) 196 // Use "localhost:<port>" to verify the proxy address is handled 197 // correctly by the delegating resolver and connects to the proxy server 198 // correctly even when unresolved. 199 pAddr := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, pServer.Addr)) 200 201 overrideTestHTTPSProxy(t, pAddr) 202 203 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 204 defer cancel() 205 conn, err := grpc.NewClient(unresolvedTargetURI, grpc.WithTransportCredentials(insecure.NewCredentials())) 206 if err != nil { 207 t.Fatalf("grpc.NewClient(%s) failed: %v", unresolvedTargetURI, err) 208 } 209 defer conn.Close() 210 211 // Send an empty RPC to the backend through the proxy. 212 client := testgrpc.NewTestServiceClient(conn) 213 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil { 214 t.Fatalf("EmptyCall failed: %v", err) 215 } 216 if !proxyCalled { 217 t.Fatalf("Proxy not connected") 218 } 219 } 220 221 // Tests the scenario where grpc.NewClient is used with a custom target URI 222 // scheme and a proxy is configured. The test verifies that the client 223 // successfully connects to the proxy server, resolves the proxy URI correctly, 224 // includes the resolved target URI in the HTTP CONNECT request, and 225 // establishes a connection to the backend server. 226 func (s) TestNewClientWithProxyAndCustomResolver(t *testing.T) { 227 backend := startBackendServer(t) 228 unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address)) 229 proxyCalled := false 230 reqCheck := func(req *http.Request) { 231 proxyCalled = true 232 host, _, err := net.SplitHostPort(req.URL.Host) 233 if err != nil { 234 t.Error(err) 235 } 236 if got, want := isIPAddr(host), true; got != want { 237 t.Errorf("isIPAddr(%q) = %t, want = %t", host, got, want) 238 } 239 } 240 pServer := proxyserver.New(t, reqCheck, false) 241 242 overrideTestHTTPSProxy(t, pServer.Addr) 243 244 // Create and update a custom resolver for target URI. 245 targetResolver := manual.NewBuilderWithScheme("test") 246 resolver.Register(targetResolver) 247 targetResolver.InitialState(resolver.State{Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: backend.Address}}}}}) 248 249 // Dial to the proxy server. 250 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 251 defer cancel() 252 conn, err := grpc.NewClient(targetResolver.Scheme()+":///"+unresolvedTargetURI, grpc.WithTransportCredentials(insecure.NewCredentials())) 253 if err != nil { 254 t.Fatalf("grpc.NewClient(%s) failed: %v", targetResolver.Scheme()+":///"+unresolvedTargetURI, err) 255 } 256 defer conn.Close() 257 258 // Send an empty RPC to the backend through the proxy. 259 client := testgrpc.NewTestServiceClient(conn) 260 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil { 261 t.Fatalf("EmptyCall() failed: %v", err) 262 } 263 264 if !proxyCalled { 265 t.Fatalf("Proxy not connected") 266 } 267 } 268 269 // Tests the scenario where grpc.NewClient is used with the default "dns" 270 // resolver and the dial option grpc.WithLocalDNSResolution() is set, 271 // enabling target resolution on the client. The test verifies that target 272 // resolution happens on the client by sending resolved target URI in HTTP 273 // CONNECT request, the proxy URI is resolved correctly, and the connection is 274 // successfully established with the backend server through the proxy. 275 func (s) TestNewClientWithProxyAndTargetResolutionEnabled(t *testing.T) { 276 backend := startBackendServer(t) 277 unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address)) 278 proxyCalled := false 279 reqCheck := func(req *http.Request) { 280 proxyCalled = true 281 host, _, err := net.SplitHostPort(req.URL.Host) 282 if err != nil { 283 t.Error(err) 284 } 285 if got, want := isIPAddr(host), true; got != want { 286 t.Errorf("isIPAddr(%q) = %t, want = %t", host, got, want) 287 } 288 } 289 pServer := proxyserver.New(t, reqCheck, false) 290 291 overrideTestHTTPSProxy(t, pServer.Addr) 292 293 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 294 defer cancel() 295 conn, err := grpc.NewClient(unresolvedTargetURI, grpc.WithLocalDNSResolution(), grpc.WithTransportCredentials(insecure.NewCredentials())) 296 if err != nil { 297 t.Fatalf("grpc.NewClient(%s) failed: %v", unresolvedTargetURI, err) 298 } 299 defer conn.Close() 300 301 // Send an empty RPC to the backend through the proxy. 302 client := testgrpc.NewTestServiceClient(conn) 303 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil { 304 t.Fatalf("EmptyCall failed: %v", err) 305 } 306 307 if !proxyCalled { 308 t.Fatalf("Proxy not connected") 309 } 310 } 311 312 // Tests the scenario where grpc.NewClient is used with grpc.WithNoProxy() set, 313 // explicitly disabling proxy usage. The test verifies that the client does not 314 // dial the proxy but directly connects to the backend server. It also checks 315 // that the proxy resolution function is not called and that the proxy server 316 // never receives a connection request. 317 func (s) TestNewClientWithNoProxy(t *testing.T) { 318 backend := startBackendServer(t) 319 unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address)) 320 reqCheck := func(_ *http.Request) { t.Error("proxy server should not have received a Connect request") } 321 pServer := proxyserver.New(t, reqCheck, false) 322 323 overrideTestHTTPSProxy(t, pServer.Addr) 324 325 dopts := []grpc.DialOption{ 326 grpc.WithTransportCredentials(insecure.NewCredentials()), 327 grpc.WithNoProxy(), // Disable proxy. 328 } 329 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 330 defer cancel() 331 conn, err := grpc.NewClient(unresolvedTargetURI, dopts...) 332 if err != nil { 333 t.Fatalf("grpc.NewClient(%s) failed: %v", unresolvedTargetURI, err) 334 } 335 defer conn.Close() 336 337 // Create a test service client and make an RPC call. 338 client := testgrpc.NewTestServiceClient(conn) 339 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil { 340 t.Fatalf("EmptyCall() failed: %v", err) 341 } 342 } 343 344 // Tests the scenario where grpc.NewClient is used with grpc.WithContextDialer() 345 // set. The test verifies that the client bypasses proxy dialing and uses the 346 // custom dialer instead. It ensures that the proxy server is never dialed, the 347 // proxy resolution function is not triggered, and the custom dialer is invoked 348 // as expected. 349 func (s) TestNewClientWithContextDialer(t *testing.T) { 350 backend := startBackendServer(t) 351 unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address)) 352 reqCheck := func(_ *http.Request) { t.Error("proxy server should not have received a Connect request") } 353 pServer := proxyserver.New(t, reqCheck, false) 354 355 overrideTestHTTPSProxy(t, pServer.Addr) 356 357 // Create a custom dialer that directly dials the backend. 358 customDialer := func(_ context.Context, unresolvedTargetURI string) (net.Conn, error) { 359 return net.Dial("tcp", unresolvedTargetURI) 360 } 361 362 dopts := []grpc.DialOption{ 363 grpc.WithTransportCredentials(insecure.NewCredentials()), 364 grpc.WithContextDialer(customDialer), // Use a custom dialer. 365 } 366 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 367 defer cancel() 368 conn, err := grpc.NewClient(unresolvedTargetURI, dopts...) 369 if err != nil { 370 t.Fatalf("grpc.NewClient(%s) failed: %v", unresolvedTargetURI, err) 371 } 372 defer conn.Close() 373 374 client := testgrpc.NewTestServiceClient(conn) 375 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil { 376 t.Fatalf("EmptyCall() failed: %v", err) 377 } 378 } 379 380 // Tests the scenario where grpc.NewClient is used with the default DNS resolver 381 // for targetURI and a proxy. The test verifies that the client connects to the 382 // proxy server, sends the unresolved target URI in the HTTP CONNECT request, 383 // and successfully connects to the backend. Additionally, it checks that the 384 // correct user information is included in the Proxy-Authorization header of 385 // the CONNECT request. The test also ensures that target resolution does not 386 // happen on the client. 387 func (s) TestBasicAuthInNewClientWithProxy(t *testing.T) { 388 unresolvedTargetURI := "example.test" 389 const ( 390 user = "notAUser" 391 password = "notAPassword" 392 ) 393 proxyCalled := false 394 reqCheck := func(req *http.Request) { 395 proxyCalled = true 396 if got, want := req.URL.Host, "example.test"; got != want { 397 t.Errorf(" Unexpected request host: %s, want = %s ", got, want) 398 } 399 wantProxyAuthStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+password)) 400 if got := req.Header.Get("Proxy-Authorization"); got != wantProxyAuthStr { 401 gotDecoded, err := base64.StdEncoding.DecodeString(got) 402 if err != nil { 403 t.Errorf("failed to decode Proxy-Authorization header: %v", err) 404 } 405 wantDecoded, _ := base64.StdEncoding.DecodeString(wantProxyAuthStr) 406 t.Errorf("unexpected auth %q (%q), want %q (%q)", got, gotDecoded, wantProxyAuthStr, wantDecoded) 407 } 408 } 409 pServer := proxyserver.New(t, reqCheck, false) 410 411 t.Setenv("HTTPS_PROXY", user+":"+password+"@"+pServer.Addr) 412 413 // Use the httpproxy package functions instead of `http.ProxyFromEnvironment` 414 // because the latter reads proxy-related environment variables only once at 415 // initialization. This behavior causes issues when running test multiple 416 // times, as changes to environment variables during tests would be ignored. 417 // By using `httpproxy.FromEnvironment()`, we ensure proxy settings are read dynamically. 418 origHTTPSProxyFromEnvironment := delegatingresolver.HTTPSProxyFromEnvironment 419 delegatingresolver.HTTPSProxyFromEnvironment = func(req *http.Request) (*url.URL, error) { 420 return httpproxy.FromEnvironment().ProxyFunc()(req.URL) 421 } 422 defer func() { 423 delegatingresolver.HTTPSProxyFromEnvironment = origHTTPSProxyFromEnvironment 424 }() 425 426 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 427 defer cancel() 428 conn, err := grpc.NewClient(unresolvedTargetURI, grpc.WithTransportCredentials(insecure.NewCredentials())) 429 if err != nil { 430 t.Fatalf("grpc.NewClient(%s) failed: %v", unresolvedTargetURI, err) 431 } 432 defer conn.Close() 433 434 // Send an empty RPC to the backend through the proxy. 435 client := testgrpc.NewTestServiceClient(conn) 436 client.EmptyCall(ctx, &testpb.Empty{}) 437 438 if !proxyCalled { 439 t.Fatalf("Proxy not connected") 440 } 441 }