Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions caddyconfig/httpcaddyfile/serveroptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ type serverOptions struct {
// If set, overrides whether QUIC listeners allow 0-RTT (early data).
// If nil, the default behavior is used (currently allowed).
Allow0RTT *bool

// The initial packet size (in bytes) to use for QUIC connections.
InitialPacketSize uint16

// Disables path MTU discovery for QUIC connections.
DisablePathMTUDiscovery bool
}

func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
Expand Down Expand Up @@ -401,6 +407,54 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
boolVal := false
serverOpts.Allow0RTT = &boolVal

case "initial_packet_size":
var sizeStr string
if !d.AllArgs(&sizeStr) {
return nil, d.ArgErr()
}
size, err := humanize.ParseBytes(sizeStr)
if err != nil {
return nil, d.Errf("parsing initial_packet_size: %v", err)
}
if size < 1200 || size > 65535 {
return nil, d.Errf("initial_packet_size must be between 1200 and 65535 bytes")
}
serverOpts.InitialPacketSize = uint16(size)

case "disable_pmtud":
if d.NextArg() {
return nil, d.ArgErr()
}
serverOpts.DisablePathMTUDiscovery = true

case "quic":
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "initial_packet_size":
var sizeStr string
if !d.AllArgs(&sizeStr) {
return nil, d.ArgErr()
}
size, err := humanize.ParseBytes(sizeStr)
if err != nil {
return nil, d.Errf("parsing initial_packet_size: %v", err)
}
if size < 1200 || size > 65535 {
return nil, d.Errf("initial_packet_size must be between 1200 and 65535 bytes")
}
serverOpts.InitialPacketSize = uint16(size)

case "disable_pmtud":
if d.NextArg() {
return nil, d.ArgErr()
}
serverOpts.DisablePathMTUDiscovery = true

default:
return nil, d.Errf("unrecognized quic option '%s'", d.Val())
}
}

default:
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
}
Expand Down Expand Up @@ -474,6 +528,8 @@ func applyServerOptions(
server.TrustedProxiesUnix = opts.TrustedProxiesUnix
server.Metrics = opts.Metrics
server.Allow0RTT = opts.Allow0RTT
server.InitialPacketSize = opts.InitialPacketSize
server.DisablePathMTUDiscovery = opts.DisablePathMTUDiscovery
if opts.ShouldLogCredentials {
if server.Logs == nil {
server.Logs = new(caddyhttp.ServerLogConfig)
Expand Down
33 changes: 21 additions & 12 deletions listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,12 @@ func JoinNetworkAddress(network, host, port string) string {
//
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
type ListenQUICOptions struct {
TLSConfig *tls.Config
PacketConnWrappers []PacketConnWrapper
Allow0RTT *bool
EnableWebTransport bool
TLSConfig *tls.Config
PacketConnWrappers []PacketConnWrapper
Allow0RTT *bool
EnableWebTransport bool
InitialPacketSize uint16
DisablePathMTUDiscovery bool
}

// ListenQUIC returns a http3.QUICEarlyListener suitable for use in a Caddy module.
Expand All @@ -448,7 +450,7 @@ type ListenQUICOptions struct {
// parameters are passed in opts.
//
// The underlying listener is pooled per address: the first creator's configuration
// (Allow0RTT, WebTransport capabilities) wins; subsequent callers reuse that listener.
// (Allow0RTT, WebTransport capabilities, InitialPacketSize, DisablePathMTUDiscovery) wins; subsequent callers reuse that listener.
//
// 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.
Expand Down Expand Up @@ -500,15 +502,22 @@ func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config
}
// EnableDatagrams and EnableStreamResetPartialDelivery are required by
// WebTransport; only enable them when WebTransport support is requested.
quicConfig := &quic.Config{
InitialPacketSize: 1200,
Allow0RTT: allow0rtt,
Tracer: h3qlog.DefaultConnectionTracer,
EnableDatagrams: opts.EnableWebTransport,
EnableStreamResetPartialDelivery: opts.EnableWebTransport,
}
if opts.InitialPacketSize > 0 {
quicConfig.InitialPacketSize = opts.InitialPacketSize
}
if opts.DisablePathMTUDiscovery {
quicConfig.DisablePathMTUDiscovery = true
}
earlyLn, err := tr.ListenEarly(
http3.ConfigureTLSConfig(quicTlsConfig),
&quic.Config{
InitialPacketSize: 1200,
Allow0RTT: allow0rtt,
Tracer: h3qlog.DefaultConnectionTracer,
EnableDatagrams: opts.EnableWebTransport,
EnableStreamResetPartialDelivery: opts.EnableWebTransport,
},
quicConfig,
)
if err != nil {
return nil, err
Expand Down
24 changes: 24 additions & 0 deletions listeners_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,30 @@ func TestListenQUICOptions_ZeroValueAllows0RTT(t *testing.T) {
}
}

// TestListenQUICOptions_PacketSizeAndPMTUD checks that ListenQUIC accepts
// InitialPacketSize and DisablePathMTUDiscovery options without error.
func TestListenQUICOptions_PacketSizeAndPMTUD(t *testing.T) {
na, err := ParseNetworkAddress("udp/127.0.0.1:0")
if err != nil {
t.Fatalf("ParseNetworkAddress: %v", err)
}

ln, err := na.ListenQUIC(context.Background(), 0, net.ListenConfig{}, ListenQUICOptions{
TLSConfig: &tls.Config{}, //nolint:gosec // test listener; no client handshake
InitialPacketSize: 1350,
DisablePathMTUDiscovery: true,
})
if err != nil {
t.Fatalf("ListenQUIC: %v", err)
}
if err := ln.Close(); err != nil {
t.Errorf("first Close: %v", err)
}
if err := ln.Close(); err != nil {
t.Errorf("second Close: %v", err)
}
}

// TestListenQUICWebTransportCapability verifies that WebTransport QUIC
// capabilities are fixed by the first creator of a pooled QUIC listener.
func TestListenQUICWebTransportCapability(t *testing.T) {
Expand Down
140 changes: 140 additions & 0 deletions modules/caddyhttp/caddyfile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package caddyhttp_test

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)

func TestCaddyfile_ServersQUICOptions(t *testing.T) {
for _, tc := range []struct {
name string
input string
expectErr bool
expectedInitialPacket uint16
expectedDisablePMTUD bool
}{
{
name: "direct options",
input: `{
servers {
initial_packet_size 1350
disable_pmtud
}
}
:80`,
expectedInitialPacket: 1350,
expectedDisablePMTUD: true,
},
{
name: "direct options with byte suffix",
input: `{
servers {
initial_packet_size 1420B
}
}
:80`,
expectedInitialPacket: 1420,
expectedDisablePMTUD: false,
},
{
name: "quic block options",
input: `{
servers {
quic {
initial_packet_size 1300
disable_pmtud
}
}
}
:80`,
expectedInitialPacket: 1300,
expectedDisablePMTUD: true,
},
{
name: "invalid packet size too low",
input: `{
servers {
initial_packet_size 1000
}
}
:80`,
expectErr: true,
},
{
name: "invalid packet size too high",
input: `{
servers {
initial_packet_size 70000
}
}
:80`,
expectErr: true,
},
{
name: "invalid packet size not a number",
input: `{
servers {
initial_packet_size notanumber
}
}
:80`,
expectErr: true,
},
{
name: "disable_pmtud unexpected arg",
input: `{
servers {
disable_pmtud true
}
}
:80`,
expectErr: true,
},
{
name: "quic block unknown option",
input: `{
servers {
quic {
unknown_option
}
}
}
:80`,
expectErr: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
adapter := caddyfile.Adapter{
ServerType: httpcaddyfile.ServerType{},
}
out, _, err := adapter.Adapt([]byte(tc.input), nil)
if tc.expectErr {
assert.Error(t, err)
return
}
require.NoError(t, err)

var config struct {
Apps struct {
HTTP struct {
Servers map[string]*caddyhttp.Server `json:"servers"`
} `json:"http"`
} `json:"apps"`
}
err = json.Unmarshal(out, &config)
require.NoError(t, err)

server, ok := config.Apps.HTTP.Servers["srv0"]
require.True(t, ok, "expected server srv0 in config")
assert.Equal(t, tc.expectedInitialPacket, server.InitialPacketSize)
assert.Equal(t, tc.expectedDisablePMTUD, server.DisablePathMTUDiscovery)
})
}
}
31 changes: 27 additions & 4 deletions modules/caddyhttp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,21 @@ type Server struct {
// don't perform a retry, so disabling 0-RTT can smooth it out.
Allow0RTT *bool `json:"allow_0rtt,omitempty"`

// The initial packet size (in bytes) to use for QUIC connections.
// Operators should use this only after packet or qlog evidence
// indicates that default path MTU discovery is failing or overshooting,
// as this setting affects new QUIC connections for every client on this
// server. Values below 1200 are invalid.
InitialPacketSize uint16 `json:"initial_packet_size,omitempty"`

// Disables path MTU discovery for QUIC connections.
// Operators should use this only after packet or qlog evidence
// indicates that path MTU discovery is failing or overshooting,
// as this setting affects new QUIC connections for every client on this
// server. Only disabling PMTUD prevents packet size growth beyond
// InitialPacketSize.
DisablePathMTUDiscovery bool `json:"disable_pmtud,omitempty"`

// If set, metrics observations will be enabled.
// This setting is EXPERIMENTAL and subject to change.
// DEPRECATED: Use the app-level `metrics` field.
Expand Down Expand Up @@ -1113,10 +1128,12 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error
}
addr.Network = h3net
h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, caddy.ListenQUICOptions{
TLSConfig: tlsCfg,
PacketConnWrappers: s.packetConnWrappers,
Allow0RTT: s.Allow0RTT,
EnableWebTransport: s.webTransportEnabled(),
TLSConfig: tlsCfg,
PacketConnWrappers: s.packetConnWrappers,
Allow0RTT: s.Allow0RTT,
EnableWebTransport: s.webTransportEnabled(),
InitialPacketSize: s.InitialPacketSize,
DisablePathMTUDiscovery: s.DisablePathMTUDiscovery,
})
if err != nil {
return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err)
Expand Down Expand Up @@ -1231,6 +1248,12 @@ func (s *Server) buildHTTP3ServerWithWebTransport(tlsCfg *tls.Config, enableWT b
InitialPacketSize: 1200,
Tracer: h3qlog.DefaultConnectionTracer,
}
if s.InitialPacketSize > 0 {
qc.InitialPacketSize = s.InitialPacketSize
}
if s.DisablePathMTUDiscovery {
qc.DisablePathMTUDiscovery = true
}
h3 := &http3.Server{
Handler: s,
TLSConfig: tlsCfg,
Expand Down
Loading