Compare commits

...

5 Commits

Author SHA1 Message Date
Percy Wegmann
ba870cc513 quic-go experimentation
Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-12-09 13:33:31 -06:00
Percy Wegmann
9144456233 Revert "Different QUIC"
This reverts commit a8d4a0d1d4.
2024-12-09 11:20:46 -06:00
Percy Wegmann
a8d4a0d1d4 Different QUIC
Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-12-09 11:12:24 -06:00
Percy Wegmann
831e9cf176 QUIC experiment
Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-12-09 10:11:49 -06:00
Percy Wegmann
687fc8d809 derp/derphttp: add send/receive benchmark
Updates #?????

Signed-off-by: Percy Wegmann <percy@tailscale.com>
2024-12-08 07:04:29 -06:00
6 changed files with 370 additions and 55 deletions

View File

@@ -216,24 +216,32 @@ func (c *Client) send(dstKey key.NodePublic, pkt []byte) (ret error) {
return fmt.Errorf("packet too big: %d", len(pkt))
}
fmt.Println("ZZZZ acquiring write lock")
c.wmu.Lock()
defer c.wmu.Unlock()
fmt.Println("ZZZZ acquired write lock")
if c.rate != nil {
pktLen := frameHeaderLen + key.NodePublicRawLen + len(pkt)
if !c.rate.AllowN(c.clock.Now(), pktLen) {
return nil // drop
}
}
fmt.Println("ZZZZ writing frame header")
if err := writeFrameHeader(c.bw, frameSendPacket, uint32(key.NodePublicRawLen+len(pkt))); err != nil {
return err
}
fmt.Println("ZZZZ writing destination key")
if _, err := c.bw.Write(dstKey.AppendTo(nil)); err != nil {
return err
}
fmt.Println("ZZZZ writing packet")
if _, err := c.bw.Write(pkt); err != nil {
return err
}
return c.bw.Flush()
fmt.Println("ZZZZ flushing buffer")
err := c.bw.Flush()
fmt.Println("ZZZZ flushed buffer")
return err
}
func (c *Client) ForwardPacket(srcKey, dstKey key.NodePublic, pkt []byte) (err error) {

View File

@@ -84,7 +84,7 @@ func init() {
}
const (
perClientSendQueueDepth = 32 // packets buffered for sending
perClientSendQueueDepth = 32000 // packets buffered for sending
writeTimeout = 2 * time.Second
privilegedWriteTimeout = 30 * time.Second // for clients with the mesh key
)

View File

@@ -7,6 +7,8 @@ import (
"bufio"
"bytes"
"context"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/asn1"
"encoding/json"
@@ -19,14 +21,17 @@ import (
"os"
"reflect"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/quic-go/quic-go"
"go4.org/mem"
"golang.org/x/time/rate"
"tailscale.com/disco"
"tailscale.com/net/memnet"
"tailscale.com/syncs"
"tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@@ -1377,70 +1382,250 @@ func BenchmarkConcurrentStreams(b *testing.B) {
})
}
func BenchmarkSendRecv(b *testing.B) {
func BenchmarkSendRecvDERP(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 := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
b.Fatal(err)
}
defer ln.Close()
connOut, err := net.Dial("tcp", ln.Addr().String())
if err != nil {
b.Fatal(err)
}
defer connOut.Close()
connIn, err := ln.Accept()
if err != nil {
b.Fatal(err)
}
defer connIn.Close()
brwServer := bufio.NewReadWriter(bufio.NewReader(connIn), bufio.NewWriter(connIn))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Accept(ctx, connIn, brwServer, "test-client")
brw := bufio.NewReadWriter(bufio.NewReader(connOut), bufio.NewWriter(connOut))
client, err := NewClient(k, connOut, 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()
inFlight := syncs.NewSemaphore(28)
go func() {
for {
inFlight.Acquire()
if err := client.Send(clientKey, msg); err != nil {
connIn.Close()
connOut.Close()
return
}
}
}()
b.ResetTimer()
for range b.N {
if _, err := client.Recv(); err != nil {
b.Fatal(err)
}
inFlight.Release()
}
}
for _, size := range []int{10, 100, 1000, 10000} {
b.Run(fmt.Sprintf("msgsize=%d", size), func(b *testing.B) { benchmarkSendRecvSize(b, size) })
}
}
func benchmarkSendRecvSize(b *testing.B, packetSize int) {
serverPrivateKey := key.NewNode()
s := NewServer(serverPrivateKey, logger.Discard)
defer s.Close()
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()
k := key.NewNode()
clientKey := k.Public()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
b.Fatal(err)
}
defer ln.Close()
qcfg := &quic.Config{}
connOut, err := net.Dial("tcp", ln.Addr().String())
if err != nil {
b.Fatal(err)
}
defer connOut.Close()
connIn, err := ln.Accept()
if err != nil {
b.Fatal(err)
}
defer connIn.Close()
brwServer := bufio.NewReadWriter(bufio.NewReader(connIn), bufio.NewWriter(connIn))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Accept(ctx, connIn, brwServer, "test-client")
brw := bufio.NewReadWriter(bufio.NewReader(connOut), bufio.NewWriter(connOut))
client, err := NewClient(k, connOut, brw, logger.Discard)
if err != nil {
b.Fatalf("client: %v", err)
}
go func() {
for {
_, err := client.Recv()
if err != nil {
return
}
}
}()
msg := make([]byte, packetSize)
b.SetBytes(int64(len(msg)))
b.ReportAllocs()
b.ResetTimer()
for range b.N {
if err := client.Send(clientKey, msg); err != nil {
ln, err := quic.ListenAddr("127.0.0.1:0", generateTLSConfig(), qcfg)
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()
connIn := &connWithAddr{_connIn, "server", qconnIn.LocalAddr()}
// 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, connIn, brwServer, "test-client")
}()
tlsConf := &tls.Config{
InsecureSkipVerify: true,
// NextProtos: []string{"quic-echo-example"},
}
qconnOut, err := quic.DialAddr(context.Background(), ln.Addr().String(), tlsConf, qcfg)
if err != nil {
b.Fatal(err)
}
defer qconnOut.CloseWithError(0, "")
_connOut, err := qconnOut.OpenStream()
if err != nil {
b.Fatal(err)
}
defer _connOut.Close()
connOut := &connWithAddr{_connOut, "client", qconnOut.LocalAddr()}
connOut.Write([]byte{0})
brw := bufio.NewReadWriter(bufio.NewReader(connOut), bufio.NewWriter(connOut))
client, err := NewClient(k, connOut, 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()
// inFlight := syncs.NewSemaphore(28)
go func() {
for {
// inFlight.Acquire()
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)
}
// inFlight.Release()
}
}
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
label string
localAddr net.Addr
}
func (c *connWithAddr) LocalAddr() net.Addr {
return c.localAddr
}
func (c *connWithAddr) Write(b []byte) (int, error) {
fmt.Printf("ZZZZ Writing length %d\n", len(b))
n, err := c.Stream.Write(b)
fmt.Printf("ZZZZ Wrote %d with error %v\n", n, err)
return n, err
}
// func BenchmarkSendRecvPlain(b *testing.B) {
// benchmarkSendRecvSize := func(b *testing.B, packetSize int) {
// ln, err := net.Listen("tcp", "127.0.0.1:0")
// if err != nil {
// b.Fatal(err)
// }
// defer ln.Close()
// go func() {
// for {
// conn, err := ln.Accept()
// if err != nil {
// return
// }
// go io.Copy(conn, conn)
// }
// }()
// conn, err := net.Dial("tcp", ln.Addr().String())
// if err != nil {
// b.Fatal(err)
// }
// defer conn.Close()
// go io.Copy(io.Discard, conn)
// msg := make([]byte, packetSize)
// rand.Read(msg)
// b.SetBytes(int64(len(msg)))
// b.ReportAllocs()
// type closeable interface {
// CloseRead() error
// CloseWrite() error
// }
// go func() {
// defer conn.(closeable).CloseWrite()
// for range b.N {
// if _, err := conn.Write(msg); err != nil {
// conn.Close()
// return
// }
// }
// }()
// b.ResetTimer()
// n, _ := io.CopyN(io.Discard, conn, int64(b.N)*int64(len(msg)))
// log.Printf("copied %d\n", n)
// }
// for _, size := range []int{10, 100, 1000, 10000} {
// b.Run(fmt.Sprintf("msgsize=%d", size), func(b *testing.B) { benchmarkSendRecvSize(b, size) })
// }
// }
func BenchmarkWriteUint32(b *testing.B) {
w := bufio.NewWriter(io.Discard)
b.ReportAllocs()
@@ -1598,3 +1783,58 @@ func TestServerRepliesToPing(t *testing.T) {
}
}
}
// Setup a bare-bones TLS config for the server
func generateTLSConfig() *tls.Config {
return &tls.Config{
Certificates: []tls.Certificate{testCert},
InsecureSkipVerify: true,
CipherSuites: []uint16{
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
},
MinVersion: tls.VersionTLS13,
// Default key exchange mechanisms as of Go 1.23 minus X25519Kyber768Draft00,
// which bloats the client hello enough to spill into a second datagram.
// Tests were written with the assuption each flight in the handshake
// fits in one datagram, and it's simpler to keep that property.
CurvePreferences: []tls.CurveID{
tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521,
},
}
}
var testCert = func() tls.Certificate {
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil {
panic(err)
}
return cert
}()
// localhostCert is a PEM-encoded TLS cert with SAN IPs
// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
// generated from src/crypto/tls:
// go run generate_cert.go --ecdsa-curve P256 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
MIIBrDCCAVKgAwIBAgIPCvPhO+Hfv+NW76kWxULUMAoGCCqGSM49BAMCMBIxEDAO
BgNVBAoTB0FjbWUgQ28wIBcNNzAwMTAxMDAwMDAwWhgPMjA4NDAxMjkxNjAwMDBa
MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARh
WRF8p8X9scgW7JjqAwI9nYV8jtkdhqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGms
PyfMPe5Jrha/LmjgR1G9o4GIMIGFMA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAK
BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSOJri/wLQxq6oC
Y6ZImms/STbTljAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAA
AAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiBUguxsW6TGhixBAdORmVNnkx40
HjkKwncMSDbUaeL9jQIhAJwQ8zV9JpQvYpsiDuMmqCuW35XXil3cQ6Drz82c+fvE
-----END CERTIFICATE-----`)
// localhostKey is the private key for localhostCert.
var localhostKey = []byte(testingKey(`-----BEGIN TESTING KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgY1B1eL/Bbwf/MDcs
rnvvWhFNr1aGmJJR59PdCN9lVVqhRANCAARhWRF8p8X9scgW7JjqAwI9nYV8jtkd
hqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGmsPyfMPe5Jrha/LmjgR1G9
-----END TESTING KEY-----`))
// testingKey helps keep security scanners from getting excited about a private key in this file.
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }

View File

@@ -6,6 +6,7 @@ package derphttp
import (
"bytes"
"context"
"crypto/rand"
"crypto/tls"
"fmt"
"net"
@@ -214,7 +215,12 @@ func TestPing(t *testing.T) {
}
}
func newTestServer(t *testing.T, k key.NodePrivate) (serverURL string, s *derp.Server) {
type testingT interface {
Logf(format string, args ...any)
Fatal(args ...any)
}
func newTestServer(t testingT, k key.NodePrivate) (serverURL string, s *derp.Server) {
s = derp.NewServer(k, t.Logf)
httpsrv := &http.Server{
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
@@ -507,3 +513,51 @@ func TestDeps(t *testing.T) {
}.Check(t)
}
func BenchmarkSendRecvDERP(b *testing.B) {
benchmarkSendRecvSize := func(b *testing.B, packetSize int) {
serverPrivateKey := key.NewNode()
serverURL, s := newTestServer(b, serverPrivateKey)
defer s.Close()
k := key.NewNode()
clientKey := k.Public()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
b.Fatal(err)
}
defer ln.Close()
client, err := NewClient(k, serverURL, b.Logf, netmon.NewStatic())
if err != nil {
b.Fatal(err)
}
defer client.Close()
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 {
client.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} {
b.Run(fmt.Sprintf("msgsize=%d", size), func(b *testing.B) { benchmarkSendRecvSize(b, size) })
}
}

6
go.mod
View File

@@ -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-0.20241205065829-2dca400b5c16 // 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
)

9
go.sum
View File

@@ -821,6 +821,10 @@ 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-0.20241205065829-2dca400b5c16 h1:qKr8kL9UtS7OMpCRvR+o/ixevCsHq7GYBsvhU1d78eU=
github.com/quic-go/quic-go v0.48.2-0.20241205065829-2dca400b5c16/go.mod h1:9RyLbf3jjSZB+/l5DgQ4KFq/fguTLs6WAJaK4mfDJw8=
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 +908,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 +1047,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 +1083,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=