From 831e9cf1769fd7e9f430d88aae1fce30575430dd Mon Sep 17 00:00:00 2001 From: Percy Wegmann Date: Mon, 9 Dec 2024 10:11:49 -0600 Subject: [PATCH] QUIC experiment Signed-off-by: Percy Wegmann --- derp/derp_server.go | 10 ++++ derp/derp_test.go | 130 ++++++++++++++++++++++++++++++++++++++++++++ go.mod | 6 +- go.sum | 7 +++ 4 files changed, 152 insertions(+), 1 deletion(-) diff --git a/derp/derp_server.go b/derp/derp_server.go index 0e146d56a..ca74b9c0e 100644 --- a/derp/derp_server.go +++ b/derp/derp_server.go @@ -891,6 +891,10 @@ func (s *Server) debugLogf(format string, v ...any) { // run serves the client until there's an error. // If the client hangs up or the server is closed, run returns nil, otherwise run returns an error. func (c *sclient) run(ctx context.Context) error { + fmt.Println("ZZZZ Client Running") + defer func() { + fmt.Println("ZZZZ Client Stopped") + }() // Launch sender, but don't return from run until sender goroutine is done. var grp errgroup.Group sendCtx, cancelSender := context.WithCancel(ctx) @@ -912,7 +916,9 @@ func (c *sclient) run(ctx context.Context) error { c.startStatsLoop(sendCtx) for { + fmt.Println("ZZZZ reading header") ft, fl, err := readFrameHeader(c.br) + fmt.Println("ZZZZ read header") c.debugLogf("read frame type %d len %d err %v", ft, fl, err) if err != nil { if errors.Is(err, io.EOF) { @@ -1685,6 +1691,7 @@ func (c *sclient) sendLoop(ctx context.Context) error { inBatch := -1 // for bufferedWriteFrames for { if werr != nil { + fmt.Printf("ZZZZ send loop ending with werr: %s\n", werr) return werr } inBatch++ @@ -1692,6 +1699,7 @@ func (c *sclient) sendLoop(ctx context.Context) error { // does as many non-flushing writes as possible. select { case <-ctx.Done(): + fmt.Println("ZZZZ send loop context done") return nil case msg := <-c.peerGone: werr = c.sendPeerGone(msg.peer, msg.reason) @@ -1717,6 +1725,7 @@ func (c *sclient) sendLoop(ctx context.Context) error { // Flush any writes from the 3 sends above, or from // the blocking loop below. if werr = c.bw.Flush(); werr != nil { + fmt.Printf("ZZZZ flush failed: %s\n", werr) return werr } if inBatch != 0 { // the first loop will almost always hit default & be size zero @@ -1728,6 +1737,7 @@ func (c *sclient) sendLoop(ctx context.Context) error { // Then a blocking select with same: select { case <-ctx.Done(): + fmt.Println("ZZZZ send loop context done") return nil case msg := <-c.peerGone: werr = c.sendPeerGone(msg.peer, msg.reason) diff --git a/derp/derp_test.go b/derp/derp_test.go index 3da053de8..f50db5d73 100644 --- a/derp/derp_test.go +++ b/derp/derp_test.go @@ -8,14 +8,18 @@ import ( "bytes" "context" "crypto/rand" + "crypto/rsa" + "crypto/tls" "crypto/x509" "encoding/asn1" "encoding/json" + "encoding/pem" "errors" "expvar" "fmt" "io" "log" + "math/big" "net" "os" "reflect" @@ -24,6 +28,7 @@ import ( "testing" "time" + "github.com/quic-go/quic-go" "go4.org/mem" "golang.org/x/time/rate" "tailscale.com/disco" @@ -1445,6 +1450,107 @@ func BenchmarkSendRecvDERP(b *testing.B) { } } +func BenchmarkSendRecvQUIC(b *testing.B) { + benchmarkSendRecvSize := func(b *testing.B, packetSize int) { + serverPrivateKey := key.NewNode() + s := NewServer(serverPrivateKey, logger.Discard) + defer s.Close() + + k := key.NewNode() + clientKey := k.Public() + + ln, err := quic.ListenAddr("127.0.0.1:0", generateTLSConfig(), nil) + if err != nil { + b.Fatal(err) + } + defer ln.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go func() { + qconnIn, err := ln.Accept(context.Background()) + if err != nil { + b.Fatal(err) + } + defer qconnIn.CloseWithError(0, "") + connIn, err := qconnIn.AcceptStream(context.Background()) + if err != nil { + b.Fatal(err) + } + defer connIn.Close() + + // read and discard initial byte + if _, err := connIn.Read(make([]byte, 1)); err != nil { + b.Fatal(err) + } + + brwServer := bufio.NewReadWriter(bufio.NewReader(connIn), bufio.NewWriter(connIn)) + + s.Accept(ctx, &connWithAddr{connIn, qconnIn.LocalAddr()}, brwServer, "test-client") + }() + + tlsConf := &tls.Config{ + InsecureSkipVerify: true, + // NextProtos: []string{"quic-echo-example"}, + } + qconnOut, err := quic.DialAddr(context.Background(), ln.Addr().String(), tlsConf, nil) + if err != nil { + b.Fatal(err) + } + defer qconnOut.CloseWithError(0, "") + + connOut, err := qconnOut.OpenStream() + if err != nil { + b.Fatal(err) + } + defer connOut.Close() + + connOut.Write([]byte{0}) + brw := bufio.NewReadWriter(bufio.NewReader(connOut), bufio.NewWriter(connOut)) + client, err := NewClient(k, &connWithAddr{connOut, qconnOut.LocalAddr()}, brw, logger.Discard) + if err != nil { + b.Fatalf("client: %v", err) + } + + msg := make([]byte, packetSize) + rand.Read(msg) + b.SetBytes(int64(len(msg))) + b.ReportAllocs() + + go func() { + for { + if err := client.Send(clientKey, msg); err != nil { + fmt.Println(err) + connOut.Close() + return + } + } + }() + + b.ResetTimer() + for range b.N { + if _, err := client.Recv(); err != nil { + b.Fatal(err) + } + } + } + + // for _, size := range []int{10, 100, 1000, 10000} { + for _, size := range []int{10} { + b.Run(fmt.Sprintf("msgsize=%d", size), func(b *testing.B) { benchmarkSendRecvSize(b, size) }) + } +} + +type connWithAddr struct { + quic.Stream + localAddr net.Addr +} + +func (c *connWithAddr) LocalAddr() net.Addr { + return c.localAddr +} + // func BenchmarkSendRecvPlain(b *testing.B) { // benchmarkSendRecvSize := func(b *testing.B, packetSize int) { // ln, err := net.Listen("tcp", "127.0.0.1:0") @@ -1658,3 +1764,27 @@ func TestServerRepliesToPing(t *testing.T) { } } } + +// Setup a bare-bones TLS config for the server +func generateTLSConfig() *tls.Config { + key, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + panic(err) + } + template := x509.Certificate{SerialNumber: big.NewInt(1)} + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) + if err != nil { + panic(err) + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + panic(err) + } + return &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + // NextProtos: []string{"quic-echo-example"}, + } +} diff --git a/go.mod b/go.mod index e57573f18..9299bf5b8 100644 --- a/go.mod +++ b/go.mod @@ -96,7 +96,7 @@ require ( go4.org/mem v0.0.0-20220726221520-4f986261bf13 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.30.0 - golang.org/x/exp v0.0.0-20240119083558-1b970713d09a + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/mod v0.19.0 golang.org/x/net v0.32.0 golang.org/x/oauth2 v0.16.0 @@ -143,6 +143,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ghostiam/protogetter v0.3.5 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/gobuffalo/flect v1.0.2 // indirect github.com/goccy/go-yaml v1.12.0 // indirect @@ -155,6 +156,8 @@ require ( github.com/karamaru-alpha/copyloopvar v1.0.8 // indirect github.com/macabu/inamedparam v0.1.3 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/onsi/ginkgo/v2 v2.17.1 // indirect + github.com/quic-go/quic-go v0.48.2 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect @@ -165,6 +168,7 @@ require ( go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect + go.uber.org/mock v0.4.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect ) diff --git a/go.sum b/go.sum index 1cbb440fa..68c56422e 100644 --- a/go.sum +++ b/go.sum @@ -821,6 +821,8 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -904,6 +906,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -1042,6 +1045,8 @@ go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -1076,6 +1081,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=