@marcel91200 / caddy Mirror · read-only / commits / 808b05c3
Fork 0

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.