summaryrefslogtreecommitdiff
path: root/listeners.go
diff options
context:
space:
mode:
authorWeidiDeng <weidi_deng@icloud.com>2023-05-11 04:25:09 +0800
committerGitHub <noreply@github.com>2023-05-10 14:25:09 -0600
commit808b05c3b47dc82d7e13fccee00a67c45a072a23 (patch)
tree3ad4e96a76acc7b348d01c9c42ea75daba82e9b1 /listeners.go
parent12b2f22092b4c4efaa9cb5a42870b0d21796f9c1 (diff)
caddyhttp: Update quic's TLS configs after reload (#5517) (fix #4849)
* fix http3 outdated certificates after config reload * delegate quic tls GetConfigForClient to another struct. * change type and method names fix lint --------- Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Diffstat (limited to 'listeners.go')
-rw-r--r--listeners.go84
1 files changed, 79 insertions, 5 deletions
diff --git a/listeners.go b/listeners.go
index 14a5c49..4c851bd 100644
--- a/listeners.go
+++ b/listeners.go
@@ -449,7 +449,11 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (
lnKey := listenerKey("quic+"+ln.LocalAddr().Network(), ln.LocalAddr().String())
sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
- earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(tlsConf), &quic.Config{
+ sqtc := newSharedQUICTLSConfig(tlsConf)
+ // http3.ConfigureTLSConfig only uses this field and tls App sets this field as well
+ //nolint:gosec
+ quicTlsConfig := &tls.Config{GetConfigForClient: sqtc.getConfigForClient}
+ earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{
Allow0RTT: func(net.Addr) bool { return true },
RequireAddressValidation: func(clientAddr net.Addr) bool {
var highLoad bool
@@ -462,12 +466,16 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (
if err != nil {
return nil, err
}
- return &sharedQuicListener{EarlyListener: earlyLn, key: lnKey}, nil
+ return &sharedQuicListener{EarlyListener: earlyLn, sqtc: sqtc, key: lnKey}, nil
})
if err != nil {
return nil, err
}
+ sql := sharedEarlyListener.(*sharedQuicListener)
+ // add current tls.Config to sqtc, so GetConfigForClient will always return the latest tls.Config in case of context cancellation
+ ctx, cancel := sql.sqtc.addTLSConfig(tlsConf)
+
// TODO: to serve QUIC over a unix socket, currently we need to hold onto
// the underlying net.PacketConn (which we wrap as unixConn to keep count
// of closes) because closing the quic.EarlyListener doesn't actually close
@@ -479,9 +487,8 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (
unix = uc
}
- ctx, cancel := context.WithCancel(context.Background())
return &fakeCloseQuicListener{
- sharedQuicListener: sharedEarlyListener.(*sharedQuicListener),
+ sharedQuicListener: sql,
uc: unix,
context: ctx,
contextCancel: cancel,
@@ -494,10 +501,77 @@ func ListenerUsage(network, addr string) int {
return count
}
+// contextAndCancelFunc groups context and its cancelFunc
+type contextAndCancelFunc struct {
+ context.Context
+ context.CancelFunc
+}
+
+// sharedQUICTLSConfig manages GetConfigForClient
+// see issue: https://github.com/caddyserver/caddy/pull/4849
+type sharedQUICTLSConfig struct {
+ rmu sync.RWMutex
+ tlsConfs map[*tls.Config]contextAndCancelFunc
+ activeTlsConf *tls.Config
+}
+
+// newSharedQUICTLSConfig creates a new sharedQUICTLSConfig
+func newSharedQUICTLSConfig(tlsConfig *tls.Config) *sharedQUICTLSConfig {
+ sqtc := &sharedQUICTLSConfig{
+ tlsConfs: make(map[*tls.Config]contextAndCancelFunc),
+ activeTlsConf: tlsConfig,
+ }
+ sqtc.addTLSConfig(tlsConfig)
+ return sqtc
+}
+
+// getConfigForClient is used as tls.Config's GetConfigForClient field
+func (sqtc *sharedQUICTLSConfig) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Config, error) {
+ sqtc.rmu.RLock()
+ defer sqtc.rmu.RUnlock()
+ return sqtc.activeTlsConf.GetConfigForClient(ch)
+}
+
+// addTLSConfig adds tls.Config to the map if not present and returns the corresponding context and its cancelFunc
+// so that when cancelled, the active tls.Config will change
+func (sqtc *sharedQUICTLSConfig) addTLSConfig(tlsConfig *tls.Config) (context.Context, context.CancelFunc) {
+ sqtc.rmu.Lock()
+ defer sqtc.rmu.Unlock()
+
+ if cacc, ok := sqtc.tlsConfs[tlsConfig]; ok {
+ return cacc.Context, cacc.CancelFunc
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ wrappedCancel := func() {
+ cancel()
+
+ sqtc.rmu.Lock()
+ defer sqtc.rmu.Unlock()
+
+ delete(sqtc.tlsConfs, tlsConfig)
+ if sqtc.activeTlsConf == tlsConfig {
+ // select another tls.Config, if there is none,
+ // related sharedQuicListener will be destroyed anyway
+ for tc := range sqtc.tlsConfs {
+ sqtc.activeTlsConf = tc
+ break
+ }
+ }
+ }
+ sqtc.tlsConfs[tlsConfig] = contextAndCancelFunc{ctx, wrappedCancel}
+ // there should be at most 2 tls.Configs
+ if len(sqtc.tlsConfs) > 2 {
+ Log().Warn("quic listener tls configs are more than 2", zap.Int("number of configs", len(sqtc.tlsConfs)))
+ }
+ return ctx, wrappedCancel
+}
+
// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
type sharedQuicListener struct {
quic.EarlyListener
- key string
+ sqtc *sharedQUICTLSConfig
+ key string
}
// Destruct closes the underlying QUIC listener.