Skip to content
Open
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
14 changes: 14 additions & 0 deletions caddyconfig/httpcaddyfile/httptype.go
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,20 @@ func (st *ServerType) serversFromPairings(
srv.ListenerWrappersRaw = append(srv.ListenerWrappersRaw, jsonListenerWrapper)
}

// Look for any config values that provide packet conn wrappers on the server block
for _, listenerConfig := range sblock.pile["packet_conn_wrapper"] {
packetConnWrapper, ok := listenerConfig.Value.(caddy.PacketConnWrapper)
if !ok {
return nil, fmt.Errorf("config for a packet conn wrapper did not provide a value that implements caddy.PacketConnWrapper")
}
jsonPacketConnWrapper := caddyconfig.JSONModuleObject(
packetConnWrapper,
"wrapper",
packetConnWrapper.(caddy.Module).CaddyModule().ID.Name(),
warnings)
srv.PacketConnWrappersRaw = append(srv.PacketConnWrappersRaw, jsonPacketConnWrapper)
}

// set up each handler directive, making sure to honor directive order
dirRoutes := sblock.pile["route"]
siteSubroute, err := buildSubroute(dirRoutes, groupCounter, true)
Expand Down
56 changes: 39 additions & 17 deletions caddyconfig/httpcaddyfile/serveroptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,24 @@ type serverOptions struct {
ListenerAddress string

// These will all map 1:1 to the caddyhttp.Server struct
Name string
ListenerWrappersRaw []json.RawMessage
ReadTimeout caddy.Duration
ReadHeaderTimeout caddy.Duration
WriteTimeout caddy.Duration
IdleTimeout caddy.Duration
KeepAliveInterval caddy.Duration
MaxHeaderBytes int
EnableFullDuplex bool
Protocols []string
StrictSNIHost *bool
TrustedProxiesRaw json.RawMessage
TrustedProxiesStrict int
ClientIPHeaders []string
ShouldLogCredentials bool
Metrics *caddyhttp.Metrics
Trace bool // TODO: EXPERIMENTAL
Name string
ListenerWrappersRaw []json.RawMessage
PacketConnWrappersRaw []json.RawMessage
ReadTimeout caddy.Duration
ReadHeaderTimeout caddy.Duration
WriteTimeout caddy.Duration
IdleTimeout caddy.Duration
KeepAliveInterval caddy.Duration
MaxHeaderBytes int
EnableFullDuplex bool
Protocols []string
StrictSNIHost *bool
TrustedProxiesRaw json.RawMessage
TrustedProxiesStrict int
ClientIPHeaders []string
ShouldLogCredentials bool
Metrics *caddyhttp.Metrics
Trace bool // TODO: EXPERIMENTAL
}

func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
Expand Down Expand Up @@ -95,6 +96,26 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper)
}

case "packet_conn_wrappers":
for nesting := d.Nesting(); d.NextBlock(nesting); {
modID := "caddy.packetconns." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
packetConnWrapper, ok := unm.(caddy.PacketConnWrapper)
if !ok {
return nil, fmt.Errorf("module %s (%T) is not a packet conn wrapper", modID, unm)
}
jsonPacketConnWrapper := caddyconfig.JSONModuleObject(
packetConnWrapper,
"wrapper",
packetConnWrapper.(caddy.Module).CaddyModule().ID.Name(),
nil,
)
serverOpts.PacketConnWrappersRaw = append(serverOpts.PacketConnWrappersRaw, jsonPacketConnWrapper)
}

case "timeouts":
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
Expand Down Expand Up @@ -304,6 +325,7 @@ func applyServerOptions(

// set all the options
server.ListenerWrappersRaw = opts.ListenerWrappersRaw
server.PacketConnWrappersRaw = opts.PacketConnWrappersRaw
server.ReadTimeout = opts.ReadTimeout
server.ReadHeaderTimeout = opts.ReadHeaderTimeout
server.WriteTimeout = opts.WriteTimeout
Expand Down
34 changes: 27 additions & 7 deletions listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,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) (http3.QUICListener, error) {
func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config, pcWrappers []PacketConnWrapper) (http3.QUICListener, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use variadic parameters ...PacketConnWrapper so that callers won't have to change anything if no packet conn wrappers are present.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have nothing against your suggestion, But I've searched for this function callers and found there is only one. It's also mentioned that this function is experimental and subject to change. Could you please elaborate on the callers your are referring to?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In caddy core, there is only this one. But who knows what plugin authors will do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see your point and sorry for being tedious, but I've searched again, and found 23 mentions of caddy.ListenQUIC in the code with all of them being outdated Caddy forks. So there are virtually no plugins using this function yet. Why shall we take care of anything that doesn't even exist?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not requesting a change, just voice my opinion. It's not taking care of something, it's defensive programming.

lnKey := listenerKey("quic"+na.Network, na.JoinHostPort(portOffset))

sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
Expand All @@ -443,12 +443,19 @@ func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config
ln := lnAny.(net.PacketConn)

h3ln := ln
for {
// retrieve the underlying socket, so quic-go can optimize.
if unwrapper, ok := h3ln.(interface{ Unwrap() net.PacketConn }); ok {
h3ln = unwrapper.Unwrap()
} else {
break
if len(pcWrappers) == 0 {
for {
// retrieve the underlying socket, so quic-go can optimize.
if unwrapper, ok := h3ln.(interface{ Unwrap() net.PacketConn }); ok {
h3ln = unwrapper.Unwrap()
} else {
break
}
}
} else {
// wrap packet conn before QUIC
for _, pcWrapper := range pcWrappers {
h3ln = pcWrapper.WrapPacketConn(h3ln)
}
}

Expand Down Expand Up @@ -695,6 +702,19 @@ type ListenerWrapper interface {
WrapListener(net.Listener) net.Listener
}

// PacketConnWrapper is a type that wraps a packet conn
// so it can modify the input packet conn methods.
// Modules that implement this interface are found
// in the caddy.packetconns namespace. Usually, to
// wrap a packet conn, you will define your own struct
// type that embeds the input packet conn, then
// implement your own methods that you want to wrap,
// calling the underlying packet conn methods where
// appropriate.
type PacketConnWrapper interface {
WrapPacketConn(net.PacketConn) net.PacketConn
}

// listenerPool stores and allows reuse of active listeners.
var listenerPool = NewUsagePool()

Expand Down
14 changes: 14 additions & 0 deletions modules/caddyhttp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,20 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
}
}

// set up each packet conn modifier
if srv.PacketConnWrappersRaw != nil {
vals, err := ctx.LoadModule(srv, "PacketConnWrappersRaw")
if err != nil {
return fmt.Errorf("loading packet conn wrapper modules: %v", err)
}
// if any wrappers were configured, they come before the QUIC handshake;
// unlike TLS above, there is no QUIC placeholder
for _, val := range vals.([]any) {
srv.packetConnWrappers = append(srv.packetConnWrappers, val.(caddy.PacketConnWrapper))
}
}

// pre-compile the primary handler chain, and be sure to wrap it in our
// route handler so that important security checks are done, etc.
primaryRoute := emptyHandler
Expand Down
9 changes: 7 additions & 2 deletions modules/caddyhttp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type Server struct {
// of the base listener. They are applied in the given order.
ListenerWrappersRaw []json.RawMessage `json:"listener_wrappers,omitempty" caddy:"namespace=caddy.listeners inline_key=wrapper"`

// A list of packet conn wrapper modules, which can modify the behavior
// of the base packet conn. They are applied in the given order.
PacketConnWrappersRaw []json.RawMessage `json:"packet_conn_wrappers,omitempty" caddy:"namespace=caddy.packetconns inline_key=wrapper"`

// How long to allow a read from a client's upload. Setting this
// to a short, non-zero value can mitigate slowloris attacks, but
// may also affect legitimately slow clients.
Expand Down Expand Up @@ -235,7 +239,8 @@ type Server struct {
primaryHandlerChain Handler
errorHandlerChain Handler
listenerWrappers []caddy.ListenerWrapper
listeners []net.Listener // stdlib http.Server will close these
packetConnWrappers []caddy.PacketConnWrapper
listeners []net.Listener
quicListeners []http3.QUICListener // http3 now leave the quic.Listener management to us

tlsApp *caddytls.TLS
Expand Down Expand Up @@ -607,7 +612,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)
h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, tlsCfg, s.packetConnWrappers)
if err != nil {
return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err)
}
Expand Down
Loading