diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go index 06ceea3c3..1febf4097 100644 --- a/caddyconfig/httpcaddyfile/serveroptions.go +++ b/caddyconfig/httpcaddyfile/serveroptions.go @@ -57,6 +57,9 @@ type serverOptions struct { ShouldLogCredentials bool Metrics *caddyhttp.Metrics Trace bool // TODO: EXPERIMENTAL + // If set, overrides whether QUIC listeners allow 0-RTT (early data). + // If nil, the default behavior is used (currently allowed). + Allow0RTT *bool } func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { @@ -309,6 +312,17 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { } serverOpts.Trace = true + case "0rtt": + // only supports "off" for now + if !d.NextArg() { + return nil, d.ArgErr() + } + if d.Val() != "off" { + return nil, d.Errf("unsupported 0rtt argument '%s' (only 'off' is supported)", d.Val()) + } + boolVal := false + serverOpts.Allow0RTT = &boolVal + default: return nil, d.Errf("unrecognized servers option '%s'", d.Val()) } @@ -373,6 +387,7 @@ func applyServerOptions( server.TrustedProxiesStrict = opts.TrustedProxiesStrict server.TrustedProxiesUnix = opts.TrustedProxiesUnix server.Metrics = opts.Metrics + server.Allow0RTT = opts.Allow0RTT if opts.ShouldLogCredentials { if server.Logs == nil { server.Logs = new(caddyhttp.ServerLogConfig) diff --git a/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest index 6b2ffaec4..4991b308e 100644 --- a/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest @@ -21,6 +21,7 @@ keepalive_interval 20s keepalive_idle 20s keepalive_count 10 + 0rtt off } } @@ -90,7 +91,8 @@ foo.com { "h2", "h2c", "h3" - ] + ], + "allow_0rtt": false } } } diff --git a/listeners.go b/listeners.go index b64011939..e214caf5c 100644 --- a/listeners.go +++ b/listeners.go @@ -511,7 +511,7 @@ func JoinNetworkAddress(network, host, port string) string { // // NOTE: This API is EXPERIMENTAL and may be changed or removed. // NOTE: user should close the returned listener twice, once to stop accepting new connections, the second time to free up the packet conn. -func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config, pcWrappers []PacketConnWrapper) (http3.QUICListener, error) { +func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config, pcWrappers []PacketConnWrapper, allow0rttconf *bool) (http3.QUICListener, error) { lnKey := listenerKey("quic"+na.Network, na.JoinHostPort(portOffset)) sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { @@ -550,10 +550,14 @@ func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config Conn: h3ln, VerifySourceAddress: func(addr net.Addr) bool { return !limiter.Allow() }, } + allow0rtt := true + if allow0rttconf != nil { + allow0rtt = *allow0rttconf + } earlyLn, err := tr.ListenEarly( http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{ - Allow0RTT: true, + Allow0RTT: allow0rtt, Tracer: h3qlog.DefaultConnectionTracer, }, ) diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index de318a953..70635d959 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -253,6 +253,16 @@ type Server struct { // A nil value or element indicates that Protocols will be used instead. ListenProtocols [][]string `json:"listen_protocols,omitempty"` + // If set, overrides whether QUIC listeners allow 0-RTT (early data). + // If nil, the default behavior is used (currently allowed). + // + // One reason to disable 0-RTT is if a remote IP matcher is used, + // which introduces a dependency on the remote address being verified + // if routing happens before the TLS handshake completes. An HTTP 425 + // response is written in that case, but some clients misbehave and + // don't perform a retry, so disabling 0-RTT can smooth it out. + Allow0RTT *bool `json:"allow_0rtt,omitempty"` + // If set, metrics observations will be enabled. // This setting is EXPERIMENTAL and subject to change. // DEPRECATED: Use the app-level `metrics` field. @@ -650,7 +660,7 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err) } addr.Network = h3net - h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, tlsCfg, s.packetConnWrappers) + h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, tlsCfg, s.packetConnWrappers, s.Allow0RTT) if err != nil { return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err) }