Skip to content

Commit 4ea1554

Browse files
authored
Fast-fail on connection errors (#567)
Avoid hanging on connection errors; fail fast by propagating connection errors. This also speeds up tests a lot (from >20s to about 3s), since the tests included some connection timeouts. This expands on #564 ; it does the same for plaintext connections. Fixes #387
1 parent 0521a49 commit 4ea1554

2 files changed

Lines changed: 13 additions & 60 deletions

File tree

grpcurl.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -733,20 +733,14 @@ func (c *errSignalingCreds) ClientHandshake(ctx context.Context, addr string, ra
733733
c.writeResult(err)
734734
return conn, auth, err
735735
}
736-
// Wrap TLS connections to capture post-handshake errors. With TLS 1.3,
737-
// client certificate rejection by the server happens after the client
738-
// considers the handshake complete. The server's TLS alert surfaces on the
739-
// first Read from the connection. Only TLS connections need this (plaintext
740-
// connections don't have post-handshake alerts).
741-
if _, isTLS := auth.(credentials.TLSInfo); isTLS {
742-
conn = &errSignalingConn{Conn: conn, writeResult: c.writeResult}
743-
}
744-
return conn, auth, nil
736+
// Wrap the connection to capture post-handshake errors, e.g.:
737+
// - TLS 1.3 client cert rejection (server sends alert after handshake)
738+
// - Plaintext client to TLS server (server closes conn immediately)
739+
return &errSignalingConn{Conn: conn, writeResult: c.writeResult}, auth, nil
745740
}
746741

747742
// errSignalingConn wraps a net.Conn to capture the first read error and
748-
// report it via writeResult. This allows BlockingDial to surface post-handshake
749-
// errors.
743+
// report it via writeResult.
750744
type errSignalingConn struct {
751745
net.Conn
752746
writeResult func(res interface{})

tls_settings_test.go

Lines changed: 8 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -171,58 +171,17 @@ func TestBrokenTLS_ClientPlainText(t *testing.T) {
171171
t.Fatalf("failed to create server creds: %v", err)
172172
}
173173

174-
// client connection (usually) succeeds since client is not waiting for TLS handshake
175-
// (we try several times, but if we never get a connection and the error message is
176-
// a known/expected possibility, we'll just bail)
177-
var e testEnv
178-
failCount := 0
179-
for {
180-
e, err = createTestServerAndClient(serverCreds, nil)
181-
if err == nil {
182-
// success!
183-
defer e.Close()
184-
break
185-
}
186-
187-
if strings.Contains(err.Error(), "deadline exceeded") ||
188-
strings.Contains(err.Error(), "use of closed network connection") {
189-
// It is possible that the connection never becomes healthy:
190-
// 1) grpc connects successfully
191-
// 2) grpc client tries to send HTTP/2 preface and settings frame
192-
// 3) server, expecting handshake, closes the connection
193-
// 4) in the client, the write fails, so the connection never
194-
// becomes ready
195-
// The client will attempt to reconnect on transient errors, so
196-
// may eventually bump into the connect time limit. This used to
197-
// result in a "deadline exceeded" error, but more recent versions
198-
// of the grpc library report any underlying I/O error instead, so
199-
// we also check for "use of closed network connection".
200-
failCount++
201-
if failCount > 5 {
202-
return // bail...
203-
}
204-
// we'll try again
205-
206-
} else {
207-
// some other error occurred, so we'll consider that a test failure
208-
t.Fatalf("failed to setup server and client: %v", err)
209-
}
210-
}
211-
212-
// but request fails because server closes connection upon seeing request
213-
// bytes that are not a TLS handshake
214-
cl := grpcurl_testing.NewTestServiceClient(e.cc)
215-
_, err = cl.UnaryCall(context.Background(), &grpcurl_testing.SimpleRequest{})
174+
// Plaintext client to TLS server: the server expects a TLS handshake,
175+
// gets an HTTP/2 preface instead, and closes the connection.
176+
e, err := createTestServerAndClient(serverCreds, nil)
216177
if err == nil {
217-
t.Fatal("expecting failure")
178+
e.Close()
179+
t.Fatal("expecting failure when connecting plaintext to TLS server")
218180
}
219-
// various errors possible when server closes connection
220-
if !strings.Contains(err.Error(), "transport is closing") &&
221-
!strings.Contains(err.Error(), "connection is unavailable") &&
181+
if !strings.Contains(err.Error(), "EOF") &&
222182
!strings.Contains(err.Error(), "use of closed network connection") &&
223-
!strings.Contains(err.Error(), "all SubConns are in TransientFailure") {
224-
225-
t.Fatalf("expecting transport failure, got: %v", err)
183+
!strings.Contains(err.Error(), "connection reset by peer") {
184+
t.Fatalf("expecting connection closed error, got: %v", err)
226185
}
227186
}
228187

0 commit comments

Comments
 (0)