Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 03:00 UTC
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>
WeidiDeng committed
May 11, 2023 at 04:25 UTC
808b05c3b47dc82d7e13fccee00a67c45a072a23
1 file changed
+79
-5
listeners.go
+79
-5
@@ -449,7 +449,11 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (
449
lnKey := listenerKey("quic+"+ln.LocalAddr().Network(), ln.LocalAddr().String())
450
451
sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
452
- earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(tlsConf), &quic.Config{
452
+ sqtc := newSharedQUICTLSConfig(tlsConf)
453
+ // http3.ConfigureTLSConfig only uses this field and tls App sets this field as well
454
+ //nolint:gosec
455
+ quicTlsConfig := &tls.Config{GetConfigForClient: sqtc.getConfigForClient}
456
+ earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{
457
Allow0RTT: func(net.Addr) bool { return true },
458
RequireAddressValidation: func(clientAddr net.Addr) bool {
459
var highLoad bool
@@ -462,12 +466,16 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (
466
if err != nil {
467
return nil, err
468
}
465
- return &sharedQuicListener{EarlyListener: earlyLn, key: lnKey}, nil
469
+ return &sharedQuicListener{EarlyListener: earlyLn, sqtc: sqtc, key: lnKey}, nil
470
})
471
if err != nil {
472
return nil, err
473
}
474
475
+ sql := sharedEarlyListener.(*sharedQuicListener)
476
+ // add current tls.Config to sqtc, so GetConfigForClient will always return the latest tls.Config in case of context cancellation
477
+ ctx, cancel := sql.sqtc.addTLSConfig(tlsConf)
478
+
479
// TODO: to serve QUIC over a unix socket, currently we need to hold onto
480
// the underlying net.PacketConn (which we wrap as unixConn to keep count
481
// 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) (
487
unix = uc
488
}
489
482
- ctx, cancel := context.WithCancel(context.Background())
490
return &fakeCloseQuicListener{
484
- sharedQuicListener: sharedEarlyListener.(*sharedQuicListener),
491
+ sharedQuicListener: sql,
492
uc: unix,
493
context: ctx,
494
contextCancel: cancel,
@@ -494,10 +501,77 @@ func ListenerUsage(network, addr string) int {
501
return count
502
}
503
504
+// contextAndCancelFunc groups context and its cancelFunc
505
+type contextAndCancelFunc struct {
506
+ context.Context
507
+ context.CancelFunc
508
+}
509
+
510
+// sharedQUICTLSConfig manages GetConfigForClient
511
+// see issue: https://github.com/caddyserver/caddy/pull/4849
512
+type sharedQUICTLSConfig struct {
513
+ rmu sync.RWMutex
514
+ tlsConfs map[*tls.Config]contextAndCancelFunc
515
+ activeTlsConf *tls.Config
516
+}
517
+
518
+// newSharedQUICTLSConfig creates a new sharedQUICTLSConfig
519
+func newSharedQUICTLSConfig(tlsConfig *tls.Config) *sharedQUICTLSConfig {
520
+ sqtc := &sharedQUICTLSConfig{
521
+ tlsConfs: make(map[*tls.Config]contextAndCancelFunc),
522
+ activeTlsConf: tlsConfig,
523
+ }
524
+ sqtc.addTLSConfig(tlsConfig)
525
+ return sqtc
526
+}
527
+
528
+// getConfigForClient is used as tls.Config's GetConfigForClient field
529
+func (sqtc *sharedQUICTLSConfig) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Config, error) {
530
+ sqtc.rmu.RLock()
531
+ defer sqtc.rmu.RUnlock()
532
+ return sqtc.activeTlsConf.GetConfigForClient(ch)
533
+}
534
+
535
+// addTLSConfig adds tls.Config to the map if not present and returns the corresponding context and its cancelFunc
536
+// so that when cancelled, the active tls.Config will change
537
+func (sqtc *sharedQUICTLSConfig) addTLSConfig(tlsConfig *tls.Config) (context.Context, context.CancelFunc) {
538
+ sqtc.rmu.Lock()
539
+ defer sqtc.rmu.Unlock()
540
+
541
+ if cacc, ok := sqtc.tlsConfs[tlsConfig]; ok {
542
+ return cacc.Context, cacc.CancelFunc
543
+ }
544
+
545
+ ctx, cancel := context.WithCancel(context.Background())
546
+ wrappedCancel := func() {
547
+ cancel()
548
+
549
+ sqtc.rmu.Lock()
550
+ defer sqtc.rmu.Unlock()
551
+
552
+ delete(sqtc.tlsConfs, tlsConfig)
553
+ if sqtc.activeTlsConf == tlsConfig {
554
+ // select another tls.Config, if there is none,
555
+ // related sharedQuicListener will be destroyed anyway
556
+ for tc := range sqtc.tlsConfs {
557
+ sqtc.activeTlsConf = tc
558
+ break
559
+ }
560
+ }
561
+ }
562
+ sqtc.tlsConfs[tlsConfig] = contextAndCancelFunc{ctx, wrappedCancel}
563
+ // there should be at most 2 tls.Configs
564
+ if len(sqtc.tlsConfs) > 2 {
565
+ Log().Warn("quic listener tls configs are more than 2", zap.Int("number of configs", len(sqtc.tlsConfs)))
566
+ }
567
+ return ctx, wrappedCancel
568
+}
569
+
570
// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
571
type sharedQuicListener struct {
572
quic.EarlyListener
500
- key string
573
+ sqtc *sharedQUICTLSConfig
574
+ key string
575
}
576
577
// Destruct closes the underlying QUIC listener.