/* * * Copyright 2019 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ // This file contains tests related to the following proposals: // https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md // https://github.com/grpc/proposal/blob/master/A9-server-side-conn-mgt.md // https://github.com/grpc/proposal/blob/master/A18-tcp-user-timeout.md package transport import ( "context" "crypto/tls" "crypto/x509" "fmt" "io" "net" "os" "strings" "testing" "time" "golang.org/x/net/http2" "google.golang.org/grpc/credentials" "google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/grpctest" "google.golang.org/grpc/internal/syscall" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/testdata" ) const defaultTestTimeout = 10 * time.Second const defaultTestShortTimeout = 10 * time.Millisecond // TestMaxConnectionIdle tests that a server will send GoAway to an idle // client. An idle client is one who doesn't make any RPC calls for a duration // of MaxConnectionIdle time. func (s) TestMaxConnectionIdle(t *testing.T) { serverConfig := &ServerConfig{ KeepaliveParams: keepalive.ServerParameters{ MaxConnectionIdle: 30 * time.Millisecond, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, suspended, ConnectOptions{}) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() stream, err := client.NewStream(ctx, &CallHdr{}) if err != nil { t.Fatalf("client.NewStream() failed: %v", err) } stream.Close(io.EOF) // Verify the server sends a GoAway to client after MaxConnectionIdle timeout // kicks in. select { case <-ctx.Done(): t.Fatalf("context expired before receiving GoAway from the server.") case <-client.GoAway(): reason, debugMsg := client.GetGoAwayReason() if reason != GoAwayNoReason { t.Fatalf("GoAwayReason is %v, want %v", reason, GoAwayNoReason) } if !strings.Contains(debugMsg, "max_idle") { t.Fatalf("GoAwayDebugMessage is %v, want %v", debugMsg, "max_idle") } } } // TestMaxConnectionIdleBusyClient tests that a server will not send GoAway to // a busy client. func (s) TestMaxConnectionIdleBusyClient(t *testing.T) { serverConfig := &ServerConfig{ KeepaliveParams: keepalive.ServerParameters{ MaxConnectionIdle: 100 * time.Millisecond, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, suspended, ConnectOptions{}) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() _, err := client.NewStream(ctx, &CallHdr{}) if err != nil { t.Fatalf("client.NewStream() failed: %v", err) } // Verify the server does not send a GoAway to client even after MaxConnectionIdle // timeout kicks in. ctx, cancel = context.WithTimeout(context.Background(), time.Second) defer cancel() select { case <-client.GoAway(): t.Fatalf("A busy client received a GoAway.") case <-ctx.Done(): } } // TestMaxConnectionAge tests that a server will send GoAway after a duration // of MaxConnectionAge. func (s) TestMaxConnectionAge(t *testing.T) { maxConnAge := 100 * time.Millisecond serverConfig := &ServerConfig{ KeepaliveParams: keepalive.ServerParameters{ MaxConnectionAge: maxConnAge, MaxConnectionAgeGrace: 10 * time.Millisecond, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, suspended, ConnectOptions{}) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() if _, err := client.NewStream(ctx, &CallHdr{}); err != nil { t.Fatalf("client.NewStream() failed: %v", err) } // Verify the server sends a GoAway to client even after client remains idle // for more than MaxConnectionIdle time. select { case <-client.GoAway(): reason, debugMsg := client.GetGoAwayReason() if reason != GoAwayNoReason { t.Fatalf("GoAwayReason is %v, want %v", reason, GoAwayNoReason) } if !strings.Contains(debugMsg, "max_age") { t.Fatalf("GoAwayDebugMessage is %v, want %v", debugMsg, "max_age") } case <-ctx.Done(): t.Fatalf("timed out before getting a GoAway from the server.") } } const ( defaultWriteBufSize = 32 * 1024 defaultReadBufSize = 32 * 1024 ) // TestKeepaliveServerClosesUnresponsiveClient tests that a server closes // the connection with a client that doesn't respond to keepalive pings. // // This test creates a regular net.Conn connection to the server and sends the // clientPreface and the initial Settings frame, and then remains unresponsive. func (s) TestKeepaliveServerClosesUnresponsiveClient(t *testing.T) { serverConfig := &ServerConfig{ KeepaliveParams: keepalive.ServerParameters{ Time: 100 * time.Millisecond, Timeout: 10 * time.Millisecond, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, suspended, ConnectOptions{}) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() addr := server.addr() conn, err := net.Dial("tcp", addr) if err != nil { t.Fatalf("net.Dial(tcp, %v) failed: %v", addr, err) } defer conn.Close() if n, err := conn.Write(clientPreface); err != nil || n != len(clientPreface) { t.Fatalf("conn.Write(clientPreface) failed: n=%v, err=%v", n, err) } framer := newFramer(conn, defaultWriteBufSize, defaultReadBufSize, false, 0) if err := framer.fr.WriteSettings(http2.Setting{}); err != nil { t.Fatal("framer.WriteSettings(http2.Setting{}) failed:", err) } framer.writer.Flush() // We read from the net.Conn till we get an error, which is expected when // the server closes the connection as part of the keepalive logic. errCh := make(chan error, 1) go func() { b := make([]byte, 24) for { if _, err = conn.Read(b); err != nil { errCh <- err return } } }() // Server waits for KeepaliveParams.Time seconds before sending out a ping, // and then waits for KeepaliveParams.Timeout for a ping ack. ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() select { case err := <-errCh: if err != io.EOF { t.Fatalf("client.Read(_) = _,%v, want io.EOF", err) } case <-ctx.Done(): t.Fatalf("Test timed out before server closed the connection.") } } // TestKeepaliveServerWithResponsiveClient tests that a server doesn't close // the connection with a client that responds to keepalive pings. func (s) TestKeepaliveServerWithResponsiveClient(t *testing.T) { serverConfig := &ServerConfig{ KeepaliveParams: keepalive.ServerParameters{ Time: 100 * time.Millisecond, Timeout: 100 * time.Millisecond, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, suspended, ConnectOptions{}) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() // Give keepalive logic some time by sleeping. time.Sleep(500 * time.Millisecond) if err := checkForHealthyStream(client); err != nil { t.Fatalf("Stream creation failed: %v", err) } } func channelzSubChannel(t *testing.T) *channelz.SubChannel { ch := channelz.RegisterChannel(nil, "test chan") sc := channelz.RegisterSubChannel(ch, "test subchan") t.Cleanup(func() { channelz.RemoveEntry(sc.ID) channelz.RemoveEntry(ch.ID) }) return sc } // TestKeepaliveClientClosesUnresponsiveServer creates a server which does not // respond to keepalive pings, and makes sure that the client closes the // transport once the keepalive logic kicks in. Here, we set the // `PermitWithoutStream` parameter to true which ensures that the keepalive // logic is running even without any active streams. func (s) TestKeepaliveClientClosesUnresponsiveServer(t *testing.T) { connCh := make(chan net.Conn, 1) copts := ConnectOptions{ ChannelzParent: channelzSubChannel(t), KeepaliveParams: keepalive.ClientParameters{ Time: 10 * time.Millisecond, Timeout: 10 * time.Millisecond, PermitWithoutStream: true, }, } client, cancel := setUpWithNoPingServer(t, copts, connCh) defer cancel() defer client.Close(fmt.Errorf("closed manually by test")) conn, ok := <-connCh if !ok { t.Fatalf("Server didn't return connection object") } defer conn.Close() if err := pollForStreamCreationError(client); err != nil { t.Fatal(err) } } // TestKeepaliveClientOpenWithUnresponsiveServer creates a server which does // not respond to keepalive pings, and makes sure that the client does not // close the transport. Here, we do not set the `PermitWithoutStream` parameter // to true which ensures that the keepalive logic is turned off without any // active streams, and therefore the transport stays open. func (s) TestKeepaliveClientOpenWithUnresponsiveServer(t *testing.T) { connCh := make(chan net.Conn, 1) copts := ConnectOptions{ ChannelzParent: channelzSubChannel(t), KeepaliveParams: keepalive.ClientParameters{ Time: 10 * time.Millisecond, Timeout: 10 * time.Millisecond, }, } client, cancel := setUpWithNoPingServer(t, copts, connCh) defer cancel() defer client.Close(fmt.Errorf("closed manually by test")) conn, ok := <-connCh if !ok { t.Fatalf("Server didn't return connection object") } defer conn.Close() // Give keepalive some time. time.Sleep(500 * time.Millisecond) if err := checkForHealthyStream(client); err != nil { t.Fatalf("Stream creation failed: %v", err) } } // TestKeepaliveClientClosesWithActiveStreams creates a server which does not // respond to keepalive pings, and makes sure that the client closes the // transport even when there is an active stream. func (s) TestKeepaliveClientClosesWithActiveStreams(t *testing.T) { connCh := make(chan net.Conn, 1) copts := ConnectOptions{ ChannelzParent: channelzSubChannel(t), KeepaliveParams: keepalive.ClientParameters{ Time: 500 * time.Millisecond, Timeout: 500 * time.Millisecond, }, } // TODO(i/6099): Setup a server which can ping and no-ping based on a flag to // reduce the flakiness in this test. client, cancel := setUpWithNoPingServer(t, copts, connCh) defer cancel() defer client.Close(fmt.Errorf("closed manually by test")) conn, ok := <-connCh if !ok { t.Fatalf("Server didn't return connection object") } defer conn.Close() ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() // Create a stream, but send no data on it. if _, err := client.NewStream(ctx, &CallHdr{}); err != nil { t.Fatalf("Stream creation failed: %v", err) } if err := pollForStreamCreationError(client); err != nil { t.Fatal(err) } } // TestKeepaliveClientStaysHealthyWithResponsiveServer creates a server which // responds to keepalive pings, and makes sure than a client transport stays // healthy without any active streams. func (s) TestKeepaliveClientStaysHealthyWithResponsiveServer(t *testing.T) { server, client, cancel := setUpWithOptions(t, 0, &ServerConfig{ KeepalivePolicy: keepalive.EnforcementPolicy{ MinTime: 50 * time.Millisecond, PermitWithoutStream: true, }, }, normal, ConnectOptions{ KeepaliveParams: keepalive.ClientParameters{ Time: 55 * time.Millisecond, Timeout: time.Second, PermitWithoutStream: true, }}) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() // Give keepalive some time. time.Sleep(500 * time.Millisecond) if err := checkForHealthyStream(client); err != nil { t.Fatalf("Stream creation failed: %v", err) } } // TestKeepaliveClientFrequency creates a server which expects at most 1 client // ping for every 100 ms, while the client is configured to send a ping // every 50 ms. So, this configuration should end up with the client // transport being closed. But we had a bug wherein the client was sending one // ping every [Time+Timeout] instead of every [Time] period, and this test // explicitly makes sure the fix works and the client sends a ping every [Time] // period. func (s) TestKeepaliveClientFrequency(t *testing.T) { grpctest.TLogger.ExpectError("Client received GoAway with error code ENHANCE_YOUR_CALM and debug data equal to ASCII \"too_many_pings\"") serverConfig := &ServerConfig{ KeepalivePolicy: keepalive.EnforcementPolicy{ MinTime: 100 * time.Millisecond, PermitWithoutStream: true, }, } clientOptions := ConnectOptions{ KeepaliveParams: keepalive.ClientParameters{ Time: 50 * time.Millisecond, Timeout: time.Second, PermitWithoutStream: true, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, normal, clientOptions) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() if err := waitForGoAwayTooManyPings(client); err != nil { t.Fatal(err) } } // TestKeepaliveServerEnforcementWithAbusiveClientNoRPC verifies that the // server closes a client transport when it sends too many keepalive pings // (when there are no active streams), based on the configured // EnforcementPolicy. func (s) TestKeepaliveServerEnforcementWithAbusiveClientNoRPC(t *testing.T) { grpctest.TLogger.ExpectError("Client received GoAway with error code ENHANCE_YOUR_CALM and debug data equal to ASCII \"too_many_pings\"") serverConfig := &ServerConfig{ KeepalivePolicy: keepalive.EnforcementPolicy{ MinTime: time.Second, }, } clientOptions := ConnectOptions{ KeepaliveParams: keepalive.ClientParameters{ Time: 20 * time.Millisecond, Timeout: 100 * time.Millisecond, PermitWithoutStream: true, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, normal, clientOptions) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() if err := waitForGoAwayTooManyPings(client); err != nil { t.Fatal(err) } } // TestKeepaliveServerEnforcementWithAbusiveClientWithRPC verifies that the // server closes a client transport when it sends too many keepalive pings // (even when there is an active stream), based on the configured // EnforcementPolicy. func (s) TestKeepaliveServerEnforcementWithAbusiveClientWithRPC(t *testing.T) { grpctest.TLogger.ExpectError("Client received GoAway with error code ENHANCE_YOUR_CALM and debug data equal to ASCII \"too_many_pings\"") serverConfig := &ServerConfig{ KeepalivePolicy: keepalive.EnforcementPolicy{ MinTime: time.Second, }, } clientOptions := ConnectOptions{ KeepaliveParams: keepalive.ClientParameters{ Time: 50 * time.Millisecond, Timeout: 100 * time.Millisecond, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, suspended, clientOptions) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() if _, err := client.NewStream(ctx, &CallHdr{}); err != nil { t.Fatalf("Stream creation failed: %v", err) } if err := waitForGoAwayTooManyPings(client); err != nil { t.Fatal(err) } } // TestKeepaliveServerEnforcementWithObeyingClientNoRPC verifies that the // server does not close a client transport (with no active streams) which // sends keepalive pings in accordance to the configured keepalive // EnforcementPolicy. func (s) TestKeepaliveServerEnforcementWithObeyingClientNoRPC(t *testing.T) { serverConfig := &ServerConfig{ KeepalivePolicy: keepalive.EnforcementPolicy{ MinTime: 40 * time.Millisecond, PermitWithoutStream: true, }, } clientOptions := ConnectOptions{ KeepaliveParams: keepalive.ClientParameters{ Time: 50 * time.Millisecond, Timeout: time.Second, PermitWithoutStream: true, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, normal, clientOptions) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() // Sleep for client to send ~10 keepalive pings. time.Sleep(500 * time.Millisecond) // Verify that the server does not close the client transport. if err := checkForHealthyStream(client); err != nil { t.Fatalf("Stream creation failed: %v", err) } } // TestKeepaliveServerEnforcementWithObeyingClientWithRPC verifies that the // server does not close a client transport (with active streams) which // sends keepalive pings in accordance to the configured keepalive // EnforcementPolicy. func (s) TestKeepaliveServerEnforcementWithObeyingClientWithRPC(t *testing.T) { serverConfig := &ServerConfig{ KeepalivePolicy: keepalive.EnforcementPolicy{ MinTime: 40 * time.Millisecond, }, } clientOptions := ConnectOptions{ KeepaliveParams: keepalive.ClientParameters{ Time: 50 * time.Millisecond, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, suspended, clientOptions) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() if err := checkForHealthyStream(client); err != nil { t.Fatalf("Stream creation failed: %v", err) } // Give keepalive enough time. time.Sleep(500 * time.Millisecond) if err := checkForHealthyStream(client); err != nil { t.Fatalf("Stream creation failed: %v", err) } } // TestKeepaliveServerEnforcementWithDormantKeepaliveOnClient verifies that the // server does not closes a client transport, which has been configured to send // more pings than allowed by the server's EnforcementPolicy. This client // transport does not have any active streams and `PermitWithoutStream` is set // to false. This should ensure that the keepalive functionality on the client // side enters a dormant state. func (s) TestKeepaliveServerEnforcementWithDormantKeepaliveOnClient(t *testing.T) { serverConfig := &ServerConfig{ KeepalivePolicy: keepalive.EnforcementPolicy{ MinTime: 100 * time.Millisecond, }, } clientOptions := ConnectOptions{ KeepaliveParams: keepalive.ClientParameters{ Time: 10 * time.Millisecond, Timeout: 10 * time.Millisecond, }, } server, client, cancel := setUpWithOptions(t, 0, serverConfig, normal, clientOptions) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() // No active streams on the client. Give keepalive enough time. time.Sleep(500 * time.Millisecond) if err := checkForHealthyStream(client); err != nil { t.Fatalf("Stream creation failed: %v", err) } } // TestTCPUserTimeout tests that the TCP_USER_TIMEOUT socket option is set to // the keepalive timeout, as detailed in proposal A18. func (s) TestTCPUserTimeout(t *testing.T) { tests := []struct { tls bool time time.Duration timeout time.Duration clientWantTimeout time.Duration serverWantTimeout time.Duration }{ { false, 10 * time.Second, 10 * time.Second, 10 * 1000 * time.Millisecond, 10 * 1000 * time.Millisecond, }, { false, 0, 0, 0, 20 * 1000 * time.Millisecond, }, { false, infinity, infinity, 0, 0, }, { true, 10 * time.Second, 10 * time.Second, 10 * 1000 * time.Millisecond, 10 * 1000 * time.Millisecond, }, { true, 0, 0, 0, 20 * 1000 * time.Millisecond, }, { true, infinity, infinity, 0, 0, }, } for _, tt := range tests { sopts := &ServerConfig{ KeepaliveParams: keepalive.ServerParameters{ Time: tt.time, Timeout: tt.timeout, }, } copts := ConnectOptions{ KeepaliveParams: keepalive.ClientParameters{ Time: tt.time, Timeout: tt.timeout, }, } if tt.tls { copts.TransportCredentials = makeTLSCreds(t, "x509/client1_cert.pem", "x509/client1_key.pem", "x509/server_ca_cert.pem") sopts.Credentials = makeTLSCreds(t, "x509/server1_cert.pem", "x509/server1_key.pem", "x509/client_ca_cert.pem") } server, client, cancel := setUpWithOptions( t, 0, sopts, normal, copts, ) defer func() { client.Close(fmt.Errorf("closed manually by test")) server.stop() cancel() }() var sc *http2Server var srawConn net.Conn // Wait until the server transport is setup. for { server.mu.Lock() if len(server.conns) == 0 { server.mu.Unlock() time.Sleep(time.Millisecond) continue } for k := range server.conns { var ok bool sc, ok = k.(*http2Server) if !ok { t.Fatalf("Failed to convert %v to *http2Server", k) } srawConn = server.conns[k] } server.mu.Unlock() break } ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() stream, err := client.NewStream(ctx, &CallHdr{}) if err != nil { t.Fatalf("client.NewStream() failed: %v", err) } stream.Close(io.EOF) // check client TCP user timeout only when non TLS // TODO : find a way to get the underlying conn for client when TLS if !tt.tls { cltOpt, err := syscall.GetTCPUserTimeout(client.conn) if err != nil { t.Fatalf("syscall.GetTCPUserTimeout() failed: %v", err) } if cltOpt < 0 { t.Skipf("skipping test on unsupported environment") } if gotTimeout := time.Duration(cltOpt) * time.Millisecond; gotTimeout != tt.clientWantTimeout { t.Fatalf("syscall.GetTCPUserTimeout() = %d, want %d", gotTimeout, tt.clientWantTimeout) } } scConn := sc.conn if tt.tls { if _, ok := sc.conn.(*net.TCPConn); ok { t.Fatalf("sc.conn is should have wrapped conn with TLS") } scConn = srawConn } // verify the type of scConn (on which TCP user timeout will be got) if _, ok := scConn.(*net.TCPConn); !ok { t.Fatalf("server underlying conn is of type %T, want net.TCPConn", scConn) } srvOpt, err := syscall.GetTCPUserTimeout(scConn) if err != nil { t.Fatalf("syscall.GetTCPUserTimeout() failed: %v", err) } if gotTimeout := time.Duration(srvOpt) * time.Millisecond; gotTimeout != tt.serverWantTimeout { t.Fatalf("syscall.GetTCPUserTimeout() = %d, want %d", gotTimeout, tt.serverWantTimeout) } } } func makeTLSCreds(t *testing.T, certPath, keyPath, rootsPath string) credentials.TransportCredentials { cert, err := tls.LoadX509KeyPair(testdata.Path(certPath), testdata.Path(keyPath)) if err != nil { t.Fatalf("tls.LoadX509KeyPair(%q, %q) failed: %v", certPath, keyPath, err) } b, err := os.ReadFile(testdata.Path(rootsPath)) if err != nil { t.Fatalf("os.ReadFile(%q) failed: %v", rootsPath, err) } roots := x509.NewCertPool() if !roots.AppendCertsFromPEM(b) { t.Fatal("failed to append certificates") } return credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: roots, InsecureSkipVerify: true, }) } // checkForHealthyStream attempts to create a stream and return error if any. // The stream created is closed right after to avoid any leakages. func checkForHealthyStream(client *http2Client) error { ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() stream, err := client.NewStream(ctx, &CallHdr{}) stream.Close(err) return err } func pollForStreamCreationError(client *http2Client) error { ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() for { if _, err := client.NewStream(ctx, &CallHdr{}); err != nil { break } time.Sleep(50 * time.Millisecond) } if ctx.Err() != nil { return fmt.Errorf("test timed out before stream creation returned an error") } return nil } // waitForGoAwayTooManyPings waits for client to receive a GoAwayTooManyPings // from server. It also asserts that stream creation fails after receiving a // GoAway. func waitForGoAwayTooManyPings(client *http2Client) error { ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() select { case <-client.GoAway(): if reason, _ := client.GetGoAwayReason(); reason != GoAwayTooManyPings { return fmt.Errorf("goAwayReason is %v, want %v", reason, GoAwayTooManyPings) } case <-ctx.Done(): return fmt.Errorf("test timed out before getting GoAway with reason:GoAwayTooManyPings from server") } if _, err := client.NewStream(ctx, &CallHdr{}); err == nil { return fmt.Errorf("stream creation succeeded after receiving a GoAway from the server") } return nil }