Merge branch v2.11 into v3.6

This commit is contained in:
kevinpollet 2026-06-05 14:51:59 +02:00
commit 15c47f9cb4
No known key found for this signature in database
GPG key ID: 0C9A5DDD1B292453
8 changed files with 122 additions and 52 deletions

View file

@ -346,6 +346,9 @@ linters:
text: 'appendAssign: append result not assigned to the same slice'
linters:
- gocritic
- path: pkg/server/conncontext.go
linters:
- fatcontext
paths:
- pkg/provider/kubernetes/crd/generated/

View file

@ -1,3 +1,11 @@
## [v2.11.49](https://github.com/traefik/traefik/tree/v2.11.49) (2026-06-05)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.48...v2.11.49)
**Bug fixes:**
- **[http3]** Bump github.com/quic-go/quic-go to v0.59.1 ([#13300](https://github.com/traefik/traefik/pull/13300) @rtribotte)
- **[webui]** Bump axios to v1.17.0 ([#13299](https://github.com/traefik/traefik/pull/13299) @gndz07)
- **[tls]** Fix snicheck with keepalive ([#13305](https://github.com/traefik/traefik/pull/13305) @juliens)
## [v3.6.19](https://github.com/traefik/traefik/tree/v3.6.19) (2026-06-04)
[All Commits](https://github.com/traefik/traefik/compare/v3.6.17...v3.6.19)

2
go.mod
View file

@ -56,7 +56,7 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // No tag on the repo.
github.com/prometheus/client_golang v1.23.0
github.com/prometheus/client_model v0.6.2
github.com/quic-go/quic-go v0.59.0
github.com/quic-go/quic-go v0.59.1
github.com/redis/go-redis/v9 v9.8.0
github.com/rs/zerolog v1.33.0
github.com/sirupsen/logrus v1.9.4

4
go.sum
View file

@ -1819,8 +1819,8 @@ github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=

29
pkg/server/conncontext.go Normal file
View file

@ -0,0 +1,29 @@
package server
import (
"context"
"net"
)
type connContextFunc func(context.Context, net.Conn) context.Context
type multipleConnContext struct {
fns []connContextFunc
}
func (m *multipleConnContext) AddConnContextFunc(fn connContextFunc) {
m.fns = append(m.fns, fn)
}
func (m *multipleConnContext) Build() connContextFunc {
if len(m.fns) == 0 {
return nil
}
return func(ctx context.Context, c net.Conn) context.Context {
for _, contextFunc := range m.fns {
ctx = contextFunc(ctx, c)
}
return ctx
}
}

View file

@ -0,0 +1,26 @@
package server
import (
"context"
"net"
"testing"
"github.com/stretchr/testify/require"
)
type keyTest string
func TestConnContext(t *testing.T) {
var connContext multipleConnContext
connContext.AddConnContextFunc(func(ctx context.Context, _ net.Conn) context.Context {
return context.WithValue(ctx, keyTest("test"), "test")
})
connContext.AddConnContextFunc(func(ctx context.Context, _ net.Conn) context.Context {
return context.WithValue(ctx, keyTest("test2"), "test2")
})
ctx := connContext.Build()(context.Background(), nil)
require.Equal(t, "test", ctx.Value(keyTest("test")))
require.Equal(t, "test2", ctx.Value(keyTest("test2")))
}

View file

@ -116,6 +116,13 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
logger := log.Ctx(ctx).With().Str(logs.RouterName, routerHTTPName).Logger()
ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerHTTPName))
// Even if the TLS options mismatch between the configured and the resolved one is handled in the aggregator
// we also have to handle it here to be able to mark the router in error.
tlsOptionsName := traefiktls.DefaultTLSConfigName
if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != traefiktls.DefaultTLSConfigName {
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
}
domains, err := httpmuxer.ParseDomains(routerHTTPConfig.Rule)
if err != nil {
routerErr := fmt.Errorf("invalid rule %s, error: %w", routerHTTPConfig.Rule, err)
@ -155,17 +162,14 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
// # When a request for "/foo" comes, even though it won't be routed by httpRouter2,
// # if its SNI is set to foo.com, myTLSOptions will be used for the TLS connection.
// # Otherwise, it will fallback to the default TLS config.
logger.Warn().Msgf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule)
if tlsOptionsName != traefiktls.DefaultTLSConfigName {
logger.Warn().Msgf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule)
routerHTTPConfig.AddError(fmt.Errorf("no domain found in rule %v, the TLS option %s cannot be applied", routerHTTPConfig.Rule, tlsOptionsName), false)
}
}
// Even if the TLS options mismatch between the configured and the resolved one is handled in the aggregator
// we also have to handle it here to be able to mark the router in error.
tlsOptionsName := traefiktls.DefaultTLSConfigName
if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != traefiktls.DefaultTLSConfigName {
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
}
if routerHTTPConfig.TLS.ResolvedOptions != tlsOptionsName {
if len(domains) > 0 && routerHTTPConfig.TLS.ResolvedOptions != tlsOptionsName {
logger.Warn().Msg("Found different TLS options for routers on the same host, so using the default TLS options instead.")
routerHTTPConfig.AddError(errors.New("found different TLS options for routers on the same host, so using the default TLS options instead"), false)
}

View file

@ -677,6 +677,44 @@ func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.E
handler = denyFragment(handler)
var connContext multipleConnContext
connContext.AddConnContextFunc(func(ctx context.Context, c net.Conn) context.Context {
// This adds an empty struct in order to store a RoundTripper in the ConnContext in case of Kerberos or NTLM.
ctx = service.AddTransportOnContext(ctx)
if tlsConn, ok := c.(*tls.Conn); ok {
if tlsConnWithOptionsName, ok := tlsConn.NetConn().(tcp.TLSConn); ok {
return tcp.AddTLSOptionsNameInContext(ctx, tlsConnWithOptionsName.TLSOptionsName)
}
}
return ctx
})
if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) {
connContext.AddConnContextFunc(func(ctx context.Context, c net.Conn) context.Context {
cState := &connState{Start: time.Now()}
if debugConnection {
clientConnectionStatesMu.Lock()
clientConnectionStates[getConnKey(c)] = cState
clientConnectionStatesMu.Unlock()
}
return context.WithValue(ctx, connStateKey, cState)
})
}
var connState func(c net.Conn, state http.ConnState)
if debugConnection {
connState = func(c net.Conn, state http.ConnState) {
clientConnectionStatesMu.Lock()
if clientConnectionStates[getConnKey(c)] != nil {
clientConnectionStates[getConnKey(c)].State = state.String()
}
clientConnectionStatesMu.Unlock()
}
}
serverHTTP := &http.Server{
Protocols: &protocols,
Handler: handler,
@ -690,46 +728,8 @@ func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.E
MaxDecoderHeaderTableSize: int(configuration.HTTP2.MaxDecoderHeaderTableSize),
MaxEncoderHeaderTableSize: int(configuration.HTTP2.MaxEncoderHeaderTableSize),
},
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
if tlsConn, ok := c.(*tls.Conn); ok {
if tlsConnWithOptionsName, ok := tlsConn.NetConn().(tcp.TLSConn); ok {
return tcp.AddTLSOptionsNameInContext(ctx, tlsConnWithOptionsName.TLSOptionsName)
}
}
return ctx
},
}
if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) {
serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context {
cState := &connState{Start: time.Now()}
if debugConnection {
clientConnectionStatesMu.Lock()
clientConnectionStates[getConnKey(c)] = cState
clientConnectionStatesMu.Unlock()
}
return context.WithValue(ctx, connStateKey, cState)
}
if debugConnection {
serverHTTP.ConnState = func(c net.Conn, state http.ConnState) {
clientConnectionStatesMu.Lock()
if clientConnectionStates[getConnKey(c)] != nil {
clientConnectionStates[getConnKey(c)].State = state.String()
}
clientConnectionStatesMu.Unlock()
}
}
}
prevConnContext := serverHTTP.ConnContext
serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context {
// This adds an empty struct in order to store a RoundTripper in the ConnContext in case of Kerberos or NTLM.
ctx = service.AddTransportOnContext(ctx)
if prevConnContext != nil {
return prevConnContext(ctx, c)
}
return ctx
ConnContext: connContext.Build(),
ConnState: connState,
}
listener := newHTTPForwarder(ln)