diff --git a/CHANGELOG.md b/CHANGELOG.md index 7793048bc..28e3342bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## [v2.11.6](https://github.com/traefik/traefik/tree/v2.11.6) (2024-07-02) +[All Commits](https://github.com/traefik/traefik/compare/v2.11.5...v2.11.6) + +**Bug fixes:** +- **[ecs]** Fix ECS config for OIDC + IRSA ([#10814](https://github.com/traefik/traefik/pull/10814) by [mmatur](https://github.com/mmatur)) +- **[http3]** Disable QUIC 0-RTT ([#10867](https://github.com/traefik/traefik/pull/10867) by [mmatur](https://github.com/mmatur)) +- **[middleware,server]** Remove interface names from IPv6 ([#10813](https://github.com/traefik/traefik/pull/10813) by [JeroenED](https://github.com/JeroenED)) + +**Documentation:** +- **[docker,acme]** Fix a typo in the ACME docker-compose docs ([#10866](https://github.com/traefik/traefik/pull/10866) by [ciacon](https://github.com/ciacon)) +- Update Advanced Capabilities Callout ([#10846](https://github.com/traefik/traefik/pull/10846) by [tomatokoolaid](https://github.com/tomatokoolaid)) +- Update maintainers ([#10834](https://github.com/traefik/traefik/pull/10834) by [emilevauge](https://github.com/emilevauge)) +- Fix readme badge for Semaphore CI ([#10830](https://github.com/traefik/traefik/pull/10830) by [mmatur](https://github.com/mmatur)) +- Fix typo in keepAliveMaxTime docs ([#10825](https://github.com/traefik/traefik/pull/10825) by [shochdoerfer](https://github.com/shochdoerfer)) + ## [v3.0.3](https://github.com/traefik/traefik/tree/v3.0.3) (2024-06-18) [All Commits](https://github.com/traefik/traefik/compare/v3.0.2...v3.0.3) diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 0678e9a1d..9d46b0a66 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -748,7 +748,7 @@ entryPoints: [entryPoints.name] address = ":8888" [entryPoints.name.transport] - keepAliveMaxTime = 42s + keepAliveMaxTime = "42s" ``` ```bash tab="CLI" diff --git a/docs/content/user-guides/docker-compose/acme-dns/index.md b/docs/content/user-guides/docker-compose/acme-dns/index.md index d312b79d9..5be0287ba 100644 --- a/docs/content/user-guides/docker-compose/acme-dns/index.md +++ b/docs/content/user-guides/docker-compose/acme-dns/index.md @@ -5,7 +5,7 @@ description: "Learn how to create a certificate with the Let's Encrypt DNS chall # Docker-compose with Let's Encrypt: DNS Challenge -This guide aim to demonstrate how to create a certificate with the Let's Encrypt DNS challenge to use https on a simple service exposed with Traefik. +This guide aims to demonstrate how to create a certificate with the Let's Encrypt DNS challenge to use https on a simple service exposed with Traefik. Please also read the [basic example](../basic-example) for details on how to expose such a service. ## Prerequisite diff --git a/docs/content/user-guides/docker-compose/acme-http/index.md b/docs/content/user-guides/docker-compose/acme-http/index.md index f6d5e0fc2..b53c2ac45 100644 --- a/docs/content/user-guides/docker-compose/acme-http/index.md +++ b/docs/content/user-guides/docker-compose/acme-http/index.md @@ -5,7 +5,7 @@ description: "Learn how to create a certificate with the Let's Encrypt HTTP chal # Docker-compose with Let's Encrypt : HTTP Challenge -This guide aim to demonstrate how to create a certificate with the Let's Encrypt HTTP challenge to use https on a simple service exposed with Traefik. +This guide aims to demonstrate how to create a certificate with the Let's Encrypt HTTP challenge to use https on a simple service exposed with Traefik. Please also read the [basic example](../basic-example) for details on how to expose such a service. ## Prerequisite diff --git a/docs/content/user-guides/docker-compose/acme-tls/index.md b/docs/content/user-guides/docker-compose/acme-tls/index.md index 9e6e6ac4a..a781a1cd3 100644 --- a/docs/content/user-guides/docker-compose/acme-tls/index.md +++ b/docs/content/user-guides/docker-compose/acme-tls/index.md @@ -5,7 +5,7 @@ description: "Learn how to create a certificate with the Let's Encrypt TLS chall # Docker-compose with Let's Encrypt: TLS Challenge -This guide aim to demonstrate how to create a certificate with the Let's Encrypt TLS challenge to use https on a simple service exposed with Traefik. +This guide aims to demonstrate how to create a certificate with the Let's Encrypt TLS challenge to use https on a simple service exposed with Traefik. Please also read the [basic example](../basic-example) for details on how to expose such a service. ## Prerequisite diff --git a/go.mod b/go.mod index 09301931d..59f624739 100644 --- a/go.mod +++ b/go.mod @@ -47,9 +47,9 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pires/go-proxyproto v0.6.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - github.com/prometheus/client_golang v1.17.0 + github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_model v0.5.0 - github.com/quic-go/quic-go v0.42.0 + github.com/quic-go/quic-go v0.45.1 github.com/rs/zerolog v1.29.0 github.com/sirupsen/logrus v1.9.3 github.com/spiffe/go-spiffe/v2 v2.1.1 @@ -79,7 +79,7 @@ require ( go.opentelemetry.io/otel/sdk v1.27.0 go.opentelemetry.io/otel/sdk/metric v1.27.0 go.opentelemetry.io/otel/trace v1.27.0 - golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/mod v0.18.0 golang.org/x/net v0.26.0 golang.org/x/sys v0.21.0 @@ -241,7 +241,6 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect @@ -280,7 +279,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/pquerna/otp v1.4.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/redis/go-redis/v9 v9.2.1 // indirect diff --git a/go.sum b/go.sum index 9e8011f42..8cb5336ef 100644 --- a/go.sum +++ b/go.sum @@ -753,8 +753,6 @@ github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -937,8 +935,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -954,8 +952,8 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -967,8 +965,8 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM= -github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= +github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjCA= +github.com/quic-go/quic-go v0.45.1/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= @@ -1280,8 +1278,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8= -golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +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/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= diff --git a/pkg/ip/checker.go b/pkg/ip/checker.go index c51df036c..eb2728a12 100644 --- a/pkg/ip/checker.go +++ b/pkg/ip/checker.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net" + "net/netip" "strings" ) @@ -91,10 +92,11 @@ func (ip *Checker) ContainsIP(addr net.IP) bool { } func parseIP(addr string) (net.IP, error) { - userIP := net.ParseIP(addr) - if userIP == nil { + parsedAddr, err := netip.ParseAddr(addr) + if err != nil { return nil, fmt.Errorf("can't parse IP from address %s", addr) } - return userIP, nil + ip := parsedAddr.As16() + return ip[:], nil } diff --git a/pkg/ip/checker_test.go b/pkg/ip/checker_test.go index 1b044e049..50c656b17 100644 --- a/pkg/ip/checker_test.go +++ b/pkg/ip/checker_test.go @@ -258,6 +258,7 @@ func TestContainsIsAllowed(t *testing.T) { "2a03:4000:6:d080::42", "fe80::1", "fe80:aa00:00bb:4232:ff00:eeee:00ff:1111", + "fe80:aa00:00bb:4232:ff00:eeee:00ff:1111%vEthernet", "fe80::fe80", "1.2.3.1", "1.2.3.32", @@ -271,6 +272,7 @@ func TestContainsIsAllowed(t *testing.T) { rejectIPs: []string{ "2a03:4000:7:d080::", "2a03:4000:7:d080::1", + "2a03:4000:7:d080::1%vmnet1", "4242::1", "1.2.16.1", "1.2.32.1", diff --git a/pkg/provider/ecs/ecs.go b/pkg/provider/ecs/ecs.go index 26565bb16..760a5fabb 100644 --- a/pkg/provider/ecs/ecs.go +++ b/pkg/provider/ecs/ecs.go @@ -3,18 +3,21 @@ package ecs import ( "context" "fmt" + "os" "strings" "text/template" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/defaults" "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ecs" "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go/service/sts" "github.com/cenkalti/backoff/v4" "github.com/patrickmn/go-cache" "github.com/rs/zerolog" @@ -119,20 +122,24 @@ func (p *Provider) createClient(logger zerolog.Logger) (*awsClient, error) { p.Region = identity.Region } - cfg := &aws.Config{ - Credentials: credentials.NewChainCredentials( - []credentials.Provider{ - &credentials.StaticProvider{ - Value: credentials.Value{ - AccessKeyID: p.AccessKeyID, - SecretAccessKey: p.SecretAccessKey, - }, + cfg := aws.NewConfig(). + WithCredentials(credentials.NewChainCredentials([]credentials.Provider{ + &credentials.StaticProvider{ + Value: credentials.Value{ + AccessKeyID: p.AccessKeyID, + SecretAccessKey: p.SecretAccessKey, }, - &credentials.EnvProvider{}, - &credentials.SharedCredentialsProvider{}, - defaults.RemoteCredProvider(*(defaults.Config()), defaults.Handlers()), - }), - } + }, + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{}, + defaults.RemoteCredProvider(*(defaults.Config()), defaults.Handlers()), + stscreds.NewWebIdentityRoleProviderWithOptions( + sts.New(sess), + os.Getenv("AWS_ROLE_ARN"), + "", + stscreds.FetchTokenPath(os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")), + ), + })) // Set the region if it is defined by the user or resolved from the EC2 metadata. if p.Region != "" { diff --git a/pkg/server/server_entrypoint_tcp_http3.go b/pkg/server/server_entrypoint_tcp_http3.go index 827d6638b..23b23071d 100644 --- a/pkg/server/server_entrypoint_tcp_http3.go +++ b/pkg/server/server_entrypoint_tcp_http3.go @@ -9,6 +9,7 @@ import ( "net/http" "sync" + "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/static" @@ -51,12 +52,15 @@ func newHTTP3Server(ctx context.Context, configuration *static.EntryPoint, https Port: configuration.HTTP3.AdvertisedPort, Handler: httpsServer.Server.(*http.Server).Handler, TLSConfig: &tls.Config{GetConfigForClient: h3.getGetConfigForClient}, + QUICConfig: &quic.Config{ + Allow0RTT: false, + }, } previousHandler := httpsServer.Server.(*http.Server).Handler httpsServer.Server.(*http.Server).Handler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if err := h3.Server.SetQuicHeaders(rw.Header()); err != nil { + if err := h3.Server.SetQUICHeaders(rw.Header()); err != nil { log.Ctx(ctx).Error().Err(err).Msg("Failed to set HTTP3 headers") } diff --git a/pkg/server/server_entrypoint_tcp_http3_test.go b/pkg/server/server_entrypoint_tcp_http3_test.go index 66aa48ce4..f4acda15f 100644 --- a/pkg/server/server_entrypoint_tcp_http3_test.go +++ b/pkg/server/server_entrypoint_tcp_http3_test.go @@ -4,10 +4,12 @@ import ( "bufio" "context" "crypto/tls" + "crypto/x509" "net/http" "testing" "time" + "github.com/quic-go/quic-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/static" @@ -86,7 +88,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) { epConfig.SetDefaults() entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ - Address: "127.0.0.1:8090", + Address: "127.0.0.1:0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, HTTP2: &static.HTTP2Config{}, @@ -106,14 +108,20 @@ func TestHTTP3AdvertisedPort(t *testing.T) { rw.WriteHeader(http.StatusOK) }), nil) - go entryPoint.Start(context.Background()) + ctx := context.Background() + go entryPoint.Start(ctx) entryPoint.SwitchRouter(router) - conn, err := tls.Dial("tcp", "127.0.0.1:8090", &tls.Config{ + conn, err := tls.Dial("tcp", entryPoint.listener.Addr().String(), &tls.Config{ InsecureSkipVerify: true, }) require.NoError(t, err) + t.Cleanup(func() { + _ = conn.Close() + entryPoint.Shutdown(ctx) + }) + // We are racing with the http3Server readiness happening in the goroutine starting the entrypoint time.Sleep(time.Second) @@ -129,3 +137,109 @@ func TestHTTP3AdvertisedPort(t *testing.T) { assert.NotContains(t, r.Header.Get("Alt-Svc"), ":8090") assert.Contains(t, r.Header.Get("Alt-Svc"), ":8080") } + +func TestHTTP30RTT(t *testing.T) { + certContent, err := localhostCert.Read() + require.NoError(t, err) + + keyContent, err := localhostKey.Read() + require.NoError(t, err) + + tlsCert, err := tls.X509KeyPair(certContent, keyContent) + require.NoError(t, err) + + epConfig := &static.EntryPointsTransport{} + epConfig.SetDefaults() + + entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + Address: "127.0.0.1:8090", + Transport: epConfig, + ForwardedHeaders: &static.ForwardedHeaders{}, + HTTP2: &static.HTTP2Config{}, + HTTP3: &static.HTTP3Config{}, + }, nil, nil) + require.NoError(t, err) + + router, err := tcprouter.NewRouter() + require.NoError(t, err) + + router.AddHTTPTLSConfig("example.com", &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + }) + router.SetHTTPSHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + }), nil) + + ctx := context.Background() + go entryPoint.Start(ctx) + entryPoint.SwitchRouter(router) + + // We are racing with the http3Server readiness happening in the goroutine starting the entrypoint. + time.Sleep(time.Second) + + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(certContent) + + tlsConf := &tls.Config{ + RootCAs: certPool, + ServerName: "example.com", + NextProtos: []string{"h3"}, + } + + // Define custom cache. + gets := make(chan string, 100) + puts := make(chan string, 100) + cache := newClientSessionCache(tls.NewLRUClientSessionCache(10), gets, puts) + tlsConf.ClientSessionCache = cache + + // This first DialAddrEarly connection is here to populate the cache. + earlyConnection, err := quic.DialAddrEarly(context.Background(), "127.0.0.1:8090", tlsConf, &quic.Config{}) + require.NoError(t, err) + + t.Cleanup(func() { + _ = earlyConnection.CloseWithError(0, "") + entryPoint.Shutdown(ctx) + }) + + // wait for the handshake complete. + <-earlyConnection.HandshakeComplete() + + // 0RTT is always false on the first connection. + require.False(t, earlyConnection.ConnectionState().Used0RTT) + + earlyConnection, err = quic.DialAddrEarly(context.Background(), "127.0.0.1:8090", tlsConf, &quic.Config{}) + require.NoError(t, err) + + <-earlyConnection.HandshakeComplete() + + // 0RTT need to be false. + assert.False(t, earlyConnection.ConnectionState().Used0RTT) +} + +type clientSessionCache struct { + cache tls.ClientSessionCache + + gets chan<- string + puts chan<- string +} + +func newClientSessionCache(cache tls.ClientSessionCache, gets, puts chan<- string) *clientSessionCache { + return &clientSessionCache{ + cache: cache, + gets: gets, + puts: puts, + } +} + +var _ tls.ClientSessionCache = &clientSessionCache{} + +func (c *clientSessionCache) Get(sessionKey string) (*tls.ClientSessionState, bool) { + session, ok := c.cache.Get(sessionKey) + c.gets <- sessionKey + return session, ok +} + +func (c *clientSessionCache) Put(sessionKey string, cs *tls.ClientSessionState) { + c.cache.Put(sessionKey, cs) + c.puts <- sessionKey +}