a56b0bf7e2
Update #3514 An io.Reader is permitted to return either (n, nil) or (n, io.EOF) on EOF or other error. The tls package previously always returned (n, nil) for a read of size n if n bytes were available, not surfacing errors at the same time. Amazon's HTTPS frontends like to hang up on clients without sending the appropriate HTTP headers. (In their defense, they're allowed to hang up any time, but generally a server hangs up after a bit of inactivity, not immediately.) In any case, the Go HTTP client tries to re-use connections by looking at whether the response headers say to keep the connection open, and because the connection looks okay, under heavy load it's possible we'll reuse it immediately, writing the next request, just as the Transport's always-reading goroutine returns from tls.Conn.Read and sees (0, io.EOF). But because Amazon does send an AlertCloseNotify record before it hangs up on us, and the tls package does its own internal buffering (up to 1024 bytes) of pending data, we have the AlertCloseNotify in an unread buffer when our Conn.Read (to the HTTP Transport code) reads its final bit of data in the HTTP response body. This change makes that final Read return (n, io.EOF) when an AlertCloseNotify record is buffered right after, if we'd otherwise return (n, nil). A dependent change in the HTTP code then notes whether a client connection has seen an io.EOF and uses that as an additional signal to not reuse a HTTPS connection. With both changes, the majority of Amazon request failures go away. Without either one, 10-20 goroutines hitting the S3 API leads to such an error rate that empirically up to 5 retries are needed to complete an API call. LGTM=agl, rsc R=agl, rsc CC=golang-codereviews https://golang.org/cl/76400046
215 lines
6.3 KiB
Go
215 lines
6.3 KiB
Go
// Copyright 2012 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package tls
|
|
|
|
import (
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var rsaCertPEM = `-----BEGIN CERTIFICATE-----
|
|
MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
|
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
|
aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF
|
|
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
|
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ
|
|
hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa
|
|
rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv
|
|
zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF
|
|
MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW
|
|
r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
|
|
-----END CERTIFICATE-----
|
|
`
|
|
|
|
var rsaKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
|
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
|
|
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
|
|
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
|
|
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
|
|
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
|
|
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
|
|
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
|
|
-----END RSA PRIVATE KEY-----
|
|
`
|
|
|
|
// keyPEM is the same as rsaKeyPEM, but declares itself as just
|
|
// "PRIVATE KEY", not "RSA PRIVATE KEY". http://golang.org/issue/4477
|
|
var keyPEM = `-----BEGIN PRIVATE KEY-----
|
|
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
|
|
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
|
|
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
|
|
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
|
|
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
|
|
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
|
|
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
|
|
-----END PRIVATE KEY-----
|
|
`
|
|
|
|
var ecdsaCertPEM = `-----BEGIN CERTIFICATE-----
|
|
MIIB/jCCAWICCQDscdUxw16XFDAJBgcqhkjOPQQBMEUxCzAJBgNVBAYTAkFVMRMw
|
|
EQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0
|
|
eSBMdGQwHhcNMTIxMTE0MTI0MDQ4WhcNMTUxMTE0MTI0MDQ4WjBFMQswCQYDVQQG
|
|
EwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lk
|
|
Z2l0cyBQdHkgTHRkMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBY9+my9OoeSUR
|
|
lDQdV/x8LsOuLilthhiS1Tz4aGDHIPwC1mlvnf7fg5lecYpMCrLLhauAc1UJXcgl
|
|
01xoLuzgtAEAgv2P/jgytzRSpUYvgLBt1UA0leLYBy6mQQbrNEuqT3INapKIcUv8
|
|
XxYP0xMEUksLPq6Ca+CRSqTtrd/23uTnapkwCQYHKoZIzj0EAQOBigAwgYYCQXJo
|
|
A7Sl2nLVf+4Iu/tAX/IF4MavARKC4PPHK3zfuGfPR3oCCcsAoz3kAzOeijvd0iXb
|
|
H5jBImIxPL4WxQNiBTexAkF8D1EtpYuWdlVQ80/h/f4pBcGiXPqX5h2PQSQY7hP1
|
|
+jwM1FGS4fREIOvlBYr/SzzQRtwrvrzGYxDEDbsC0ZGRnA==
|
|
-----END CERTIFICATE-----
|
|
`
|
|
|
|
var ecdsaKeyPEM = `-----BEGIN EC PARAMETERS-----
|
|
BgUrgQQAIw==
|
|
-----END EC PARAMETERS-----
|
|
-----BEGIN EC PRIVATE KEY-----
|
|
MIHcAgEBBEIBrsoKp0oqcv6/JovJJDoDVSGWdirrkgCWxrprGlzB9o0X8fV675X0
|
|
NwuBenXFfeZvVcwluO7/Q9wkYoPd/t3jGImgBwYFK4EEACOhgYkDgYYABAFj36bL
|
|
06h5JRGUNB1X/Hwuw64uKW2GGJLVPPhoYMcg/ALWaW+d/t+DmV5xikwKssuFq4Bz
|
|
VQldyCXTXGgu7OC0AQCC/Y/+ODK3NFKlRi+AsG3VQDSV4tgHLqZBBus0S6pPcg1q
|
|
kohxS/xfFg/TEwRSSws+roJr4JFKpO2t3/be5OdqmQ==
|
|
-----END EC PRIVATE KEY-----
|
|
`
|
|
|
|
var keyPairTests = []struct {
|
|
algo string
|
|
cert string
|
|
key string
|
|
}{
|
|
{"ECDSA", ecdsaCertPEM, ecdsaKeyPEM},
|
|
{"RSA", rsaCertPEM, rsaKeyPEM},
|
|
{"RSA-untyped", rsaCertPEM, keyPEM}, // golang.org/issue/4477
|
|
}
|
|
|
|
func TestX509KeyPair(t *testing.T) {
|
|
var pem []byte
|
|
for _, test := range keyPairTests {
|
|
pem = []byte(test.cert + test.key)
|
|
if _, err := X509KeyPair(pem, pem); err != nil {
|
|
t.Errorf("Failed to load %s cert followed by %s key: %s", test.algo, test.algo, err)
|
|
}
|
|
pem = []byte(test.key + test.cert)
|
|
if _, err := X509KeyPair(pem, pem); err != nil {
|
|
t.Errorf("Failed to load %s key followed by %s cert: %s", test.algo, test.algo, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestX509MixedKeyPair(t *testing.T) {
|
|
if _, err := X509KeyPair([]byte(rsaCertPEM), []byte(ecdsaKeyPEM)); err == nil {
|
|
t.Error("Load of RSA certificate succeeded with ECDSA private key")
|
|
}
|
|
if _, err := X509KeyPair([]byte(ecdsaCertPEM), []byte(rsaKeyPEM)); err == nil {
|
|
t.Error("Load of ECDSA certificate succeeded with RSA private key")
|
|
}
|
|
}
|
|
|
|
func newLocalListener(t *testing.T) net.Listener {
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
ln, err = net.Listen("tcp6", "[::1]:0")
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return ln
|
|
}
|
|
|
|
func TestDialTimeout(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping in short mode")
|
|
}
|
|
listener := newLocalListener(t)
|
|
|
|
addr := listener.Addr().String()
|
|
defer listener.Close()
|
|
|
|
complete := make(chan bool)
|
|
defer close(complete)
|
|
|
|
go func() {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
<-complete
|
|
conn.Close()
|
|
}()
|
|
|
|
dialer := &net.Dialer{
|
|
Timeout: 10 * time.Millisecond,
|
|
}
|
|
|
|
var err error
|
|
if _, err = DialWithDialer(dialer, "tcp", addr, nil); err == nil {
|
|
t.Fatal("DialWithTimeout completed successfully")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "timed out") {
|
|
t.Errorf("resulting error not a timeout: %s", err)
|
|
}
|
|
}
|
|
|
|
// tests that Conn.Read returns (non-zero, io.EOF) instead of
|
|
// (non-zero, nil) when a Close (alertCloseNotify) is sitting right
|
|
// behind the application data in the buffer.
|
|
func TestConnReadNonzeroAndEOF(t *testing.T) {
|
|
ln := newLocalListener(t)
|
|
defer ln.Close()
|
|
|
|
srvCh := make(chan *Conn, 1)
|
|
go func() {
|
|
sconn, err := ln.Accept()
|
|
if err != nil {
|
|
t.Error(err)
|
|
srvCh <- nil
|
|
return
|
|
}
|
|
serverConfig := *testConfig
|
|
srv := Server(sconn, &serverConfig)
|
|
if err := srv.Handshake(); err != nil {
|
|
t.Error("handshake: %v", err)
|
|
srvCh <- nil
|
|
return
|
|
}
|
|
srvCh <- srv
|
|
}()
|
|
|
|
clientConfig := *testConfig
|
|
conn, err := Dial("tcp", ln.Addr().String(), &clientConfig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
srv := <-srvCh
|
|
if srv == nil {
|
|
return
|
|
}
|
|
|
|
buf := make([]byte, 6)
|
|
|
|
srv.Write([]byte("foobar"))
|
|
n, err := conn.Read(buf)
|
|
if n != 6 || err != nil || string(buf) != "foobar" {
|
|
t.Fatalf("Read = %d, %v, data %q; want 6, nil, foobar", n, err, buf)
|
|
}
|
|
|
|
srv.Write([]byte("abcdef"))
|
|
srv.Close()
|
|
n, err = conn.Read(buf)
|
|
if n != 6 || string(buf) != "abcdef" {
|
|
t.Fatalf("Read = %d, buf= %q; want 6, abcdef", n, buf)
|
|
}
|
|
if err != io.EOF {
|
|
t.Errorf("Second Read error = %v; want io.EOF", err)
|
|
}
|
|
}
|