Skip to content

Instantly share code, notes, and snippets.

@g7r
Created April 9, 2018 15:43
Show Gist options
  • Save g7r/3044f20a47e5d41630d9a2cdc5c24918 to your computer and use it in GitHub Desktop.
Save g7r/3044f20a47e5d41630d9a2cdc5c24918 to your computer and use it in GitHub Desktop.
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"net/http"
_ "net/http/pprof"
"reflect"
"runtime"
"sync"
"time"
"unsafe"
)
const rootCertificatePEM = `-----BEGIN CERTIFICATE-----
MIIBfjCCASSgAwIBAgIQTCf0mlSseTCSS/iEnzXvCjAKBggqhkjOPQQDAjAkMRAw
DgYDVQQKEwdBY21lIENvMRAwDgYDVQQDEwdSb290IENBMB4XDTE4MDQwOTEwMjgx
NloXDTE5MDQwOTEwMjgxNlowJDEQMA4GA1UEChMHQWNtZSBDbzEQMA4GA1UEAxMH
Um9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOyy8J+ZpSlvjgD5tSS7
gyddOrpe+tx4L9h2YLJqBgnfRBqYZ0txOmv+v2t1xJ5PtIuzKtRCSywAYGYidSRv
kf6jODA2MA4GA1UdDwEB/wQEAwICBDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNV
HRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQD0BcBwwi19XP2xEGLdgadw
teI1jtcDei5JYH9Ud3XH9QIgPimljy7NERM864peS7euFMF8PGSGNfg0Z19vMXbw
Sng=
-----END CERTIFICATE-----`
const leafCertificatePEM = `-----BEGIN CERTIFICATE-----
MIIBlTCCATugAwIBAgIQR3FbTNQOVBf0lthJh4WpSjAKBggqhkjOPQQDAjAkMRAw
DgYDVQQKEwdBY21lIENvMRAwDgYDVQQDEwdSb290IENBMB4XDTE4MDQwOTEwMjgx
NloXDTE5MDQwOTEwMjgxNlowKDEQMA4GA1UEChMHQWNtZSBDbzEUMBIGA1UEAwwL
dGVzdF9jZXJ0XzEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASNtt05vK+nnsVk
MYUHynSg9yxUdVz/iMO+vZd/xy6DAVC4eCQ7SOnjl2shdEqNrYIXckQ2yOoVs4tv
feUlqx9vo0swSTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
DAYDVR0TAQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwID
SAAwRQIgcdEGJxGJmJb7UMdL9tShc5u2MU6whUGwuogqJR6kAsECIQC287T6cWAR
3xlEEALKKiYR6HGoqZWz/dpVfUgTxnRX3w==
-----END CERTIFICATE-----`
const leafKey = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEII/ENvsz3Ovs1Ft+IvyHR0T/asbtDKUNYfnsDZimNiGAoAoGCCqGSM49
AwEHoUQDQgAEjbbdObyvp57FZDGFB8p0oPcsVHVc/4jDvr2Xf8cugwFQuHgkO0jp
45drIXRKja2CF3JENsjqFbOLb33lJasfbw==
-----END EC PRIVATE KEY-----`
func warmupServer(s *http.Server, cert tls.Certificate) error {
tcpListener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return err
}
tlsListener := tls.NewListener(tcpListener, &tls.Config{
Certificates: []tls.Certificate{cert},
})
tcpListener.Close()
s.Serve(tlsListener)
return nil
}
type http2Server struct {
MaxHandlers int
MaxConcurrentStreams uint32
MaxReadFrameSize uint32
PermitProhibitedCipherSuites bool
IdleTimeout time.Duration
MaxUploadBufferPerConnection int32
MaxUploadBufferPerStream int32
NewWriteScheduler func() interface{}
state uintptr
}
func runServer() error {
var s http.Server
s.Addr = "localhost:9999"
s.TLSConfig = &tls.Config{
NextProtos: []string{"h2"},
}
var initOnce sync.Once
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
initOnce.Do(func() {
// Hack `MaxConcurrentStreams` for the issue to be reproduced faster
srv := reflect.ValueOf(w).Elem().
FieldByName("rws").Elem().
FieldByName("conn").Elem().
FieldByName("srv")
srvPtr := unsafe.Pointer(srv.Pointer())
hackedSrv := (*http2Server)(srvPtr)
hackedSrv.MaxConcurrentStreams = 2
})
time.Sleep(200 * time.Millisecond)
w.WriteHeader(http.StatusOK)
w.Write(([]byte)(`{"succeeded": true}`))
})
cert, err := tls.X509KeyPair([]byte(leafCertificatePEM), []byte(leafKey))
if err != nil {
return err
}
if err := warmupServer(&s, cert); err != nil {
return err
}
prevNextProto := s.TLSNextProto["h2"]
s.TLSNextProto["h2"] = func(hs *http.Server, conn *tls.Conn, handler http.Handler) {
go func() {
time.Sleep(50 * time.Millisecond)
conn.SetDeadline(time.Now().Add(1 * time.Millisecond))
}()
prevNextProto(hs, conn, handler)
}
tcpListener, err := net.Listen("tcp", "127.0.0.1:9999")
if err != nil {
return err
}
tlsListener := tls.NewListener(tcpListener, &tls.Config{
Certificates: []tls.Certificate{cert},
NextProtos: []string{"h2"},
})
go func() {
if err := s.Serve(tlsListener); err != nil {
panic(err)
}
}()
return nil
}
func clientLoop(c *http.Client) {
for {
func() {
r, err := c.Get("https://localhost:9999/test")
if err != nil {
return
}
defer r.Body.Close()
_, err = ioutil.ReadAll(r.Body)
if err != nil {
return
}
}()
}
}
func run() error {
if err := runServer(); err != nil {
return err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM([]byte(rootCertificatePEM))
var c http.Client
c.Timeout = 3000 * time.Second // The issue couldn't be reproduced with c.Timeout = 0
c.Transport = &http.Transport{}
c.Get("https://localhost:9999") // Warmup
c.Transport.(*http.Transport).TLSClientConfig.RootCAs = caCertPool
c.Transport.(*http.Transport).TLSClientConfig.NextProtos = []string{"h2"}
c.Transport.(*http.Transport).TLSClientConfig.ClientSessionCache = tls.NewLRUClientSessionCache(100000)
for i := 0; i < 2000; i++ {
go clientLoop(&c)
}
fmt.Println("Navigate to http://127.0.0.1:9998/debug/pprof/goroutine?debug=1 to visualize leaked goroutines")
for {
fmt.Printf("Num goroutines: %d\n", runtime.NumGoroutine())
time.Sleep(3 * time.Second)
}
return nil
}
func main() {
go http.ListenAndServe("127.0.0.1:9998", nil)
if err := run(); err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment