Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 18:30 UTC
Introduce packet conn wrappers (#7180)
* packet_conn_wrappers: Initial changes * packet_conn_wrappers: Unwrap a packet conn only if there are no wrappers --------- Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
vnxme committed
Dec 5, 2025 at 00:15 UTC
31960dc998b1f88e912e950f0c00815ba032aeef
5 files changed
+104
-29
caddyconfig/httpcaddyfile/httptype.go
+14
@@ -851,6 +851,20 @@ func (st *ServerType) serversFromPairings(
851
srv.ListenerWrappersRaw = append(srv.ListenerWrappersRaw, jsonListenerWrapper)
852
}
853
854
+ // Look for any config values that provide packet conn wrappers on the server block
855
+ for _, listenerConfig := range sblock.pile["packet_conn_wrapper"] {
856
+ packetConnWrapper, ok := listenerConfig.Value.(caddy.PacketConnWrapper)
857
+ if !ok {
858
+ return nil, fmt.Errorf("config for a packet conn wrapper did not provide a value that implements caddy.PacketConnWrapper")
859
+ }
860
+ jsonPacketConnWrapper := caddyconfig.JSONModuleObject(
861
+ packetConnWrapper,
862
+ "wrapper",
863
+ packetConnWrapper.(caddy.Module).CaddyModule().ID.Name(),
864
+ warnings)
865
+ srv.PacketConnWrappersRaw = append(srv.PacketConnWrappersRaw, jsonPacketConnWrapper)
866
+ }
867
+
868
// set up each handler directive, making sure to honor directive order
869
dirRoutes := sblock.pile["route"]
870
siteSubroute, err := buildSubroute(dirRoutes, groupCounter, true)
caddyconfig/httpcaddyfile/serveroptions.go
+42
-20
@@ -36,26 +36,27 @@ type serverOptions struct {
36
ListenerAddress string
37
38
// These will all map 1:1 to the caddyhttp.Server struct
39
- Name string
40
- ListenerWrappersRaw []json.RawMessage
41
- ReadTimeout caddy.Duration
42
- ReadHeaderTimeout caddy.Duration
43
- WriteTimeout caddy.Duration
44
- IdleTimeout caddy.Duration
45
- KeepAliveInterval caddy.Duration
46
- KeepAliveIdle caddy.Duration
47
- KeepAliveCount int
48
- MaxHeaderBytes int
49
- EnableFullDuplex bool
50
- Protocols []string
51
- StrictSNIHost *bool
52
- TrustedProxiesRaw json.RawMessage
53
- TrustedProxiesStrict int
54
- TrustedProxiesUnix bool
55
- ClientIPHeaders []string
56
- ShouldLogCredentials bool
57
- Metrics *caddyhttp.Metrics
58
- Trace bool // TODO: EXPERIMENTAL
39
+ Name string
40
+ ListenerWrappersRaw []json.RawMessage
41
+ PacketConnWrappersRaw []json.RawMessage
42
+ ReadTimeout caddy.Duration
43
+ ReadHeaderTimeout caddy.Duration
44
+ WriteTimeout caddy.Duration
45
+ IdleTimeout caddy.Duration
46
+ KeepAliveInterval caddy.Duration
47
+ KeepAliveIdle caddy.Duration
48
+ KeepAliveCount int
49
+ MaxHeaderBytes int
50
+ EnableFullDuplex bool
51
+ Protocols []string
52
+ StrictSNIHost *bool
53
+ TrustedProxiesRaw json.RawMessage
54
+ TrustedProxiesStrict int
55
+ TrustedProxiesUnix bool
56
+ ClientIPHeaders []string
57
+ ShouldLogCredentials bool
58
+ Metrics *caddyhttp.Metrics
59
+ Trace bool // TODO: EXPERIMENTAL
60
}
61
62
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
@@ -99,6 +100,26 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
100
serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper)
101
}
102
103
+ case "packet_conn_wrappers":
104
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
105
+ modID := "caddy.packetconns." + d.Val()
106
+ unm, err := caddyfile.UnmarshalModule(d, modID)
107
+ if err != nil {
108
+ return nil, err
109
+ }
110
+ packetConnWrapper, ok := unm.(caddy.PacketConnWrapper)
111
+ if !ok {
112
+ return nil, fmt.Errorf("module %s (%T) is not a packet conn wrapper", modID, unm)
113
+ }
114
+ jsonPacketConnWrapper := caddyconfig.JSONModuleObject(
115
+ packetConnWrapper,
116
+ "wrapper",
117
+ packetConnWrapper.(caddy.Module).CaddyModule().ID.Name(),
118
+ nil,
119
+ )
120
+ serverOpts.PacketConnWrappersRaw = append(serverOpts.PacketConnWrappersRaw, jsonPacketConnWrapper)
121
+ }
122
+
123
case "timeouts":
124
for nesting := d.Nesting(); d.NextBlock(nesting); {
125
switch d.Val() {
@@ -335,6 +356,7 @@ func applyServerOptions(
356
357
// set all the options
358
server.ListenerWrappersRaw = opts.ListenerWrappersRaw
359
+ server.PacketConnWrappersRaw = opts.PacketConnWrappersRaw
360
server.ReadTimeout = opts.ReadTimeout
361
server.ReadHeaderTimeout = opts.ReadHeaderTimeout
362
server.WriteTimeout = opts.WriteTimeout
listeners.go
+27
-7
@@ -511,7 +511,7 @@ func JoinNetworkAddress(network, host, port string) string {
511
//
512
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
513
// NOTE: user should close the returned listener twice, once to stop accepting new connections, the second time to free up the packet conn.
514
-func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config) (http3.QUICListener, error) {
514
+func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config, pcWrappers []PacketConnWrapper) (http3.QUICListener, error) {
515
lnKey := listenerKey("quic"+na.Network, na.JoinHostPort(portOffset))
516
517
sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
@@ -523,12 +523,19 @@ func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config
523
ln := lnAny.(net.PacketConn)
524
525
h3ln := ln
526
- for {
527
- // retrieve the underlying socket, so quic-go can optimize.
528
- if unwrapper, ok := h3ln.(interface{ Unwrap() net.PacketConn }); ok {
529
- h3ln = unwrapper.Unwrap()
530
- } else {
531
- break
526
+ if len(pcWrappers) == 0 {
527
+ for {
528
+ // retrieve the underlying socket, so quic-go can optimize.
529
+ if unwrapper, ok := h3ln.(interface{ Unwrap() net.PacketConn }); ok {
530
+ h3ln = unwrapper.Unwrap()
531
+ } else {
532
+ break
533
+ }
534
+ }
535
+ } else {
536
+ // wrap packet conn before QUIC
537
+ for _, pcWrapper := range pcWrappers {
538
+ h3ln = pcWrapper.WrapPacketConn(h3ln)
539
}
540
}
541
@@ -775,6 +782,19 @@ type ListenerWrapper interface {
782
WrapListener(net.Listener) net.Listener
783
}
784
785
+// PacketConnWrapper is a type that wraps a packet conn
786
+// so it can modify the input packet conn methods.
787
+// Modules that implement this interface are found
788
+// in the caddy.packetconns namespace. Usually, to
789
+// wrap a packet conn, you will define your own struct
790
+// type that embeds the input packet conn, then
791
+// implement your own methods that you want to wrap,
792
+// calling the underlying packet conn methods where
793
+// appropriate.
794
+type PacketConnWrapper interface {
795
+ WrapPacketConn(net.PacketConn) net.PacketConn
796
+}
797
+
798
// listenerPool stores and allows reuse of active listeners.
799
var listenerPool = NewUsagePool()
800
modules/caddyhttp/app.go
+14
@@ -346,6 +346,20 @@ func (app *App) Provision(ctx caddy.Context) error {
346
srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
347
}
348
}
349
+
350
+ // set up each packet conn modifier
351
+ if srv.PacketConnWrappersRaw != nil {
352
+ vals, err := ctx.LoadModule(srv, "PacketConnWrappersRaw")
353
+ if err != nil {
354
+ return fmt.Errorf("loading packet conn wrapper modules: %v", err)
355
+ }
356
+ // if any wrappers were configured, they come before the QUIC handshake;
357
+ // unlike TLS above, there is no QUIC placeholder
358
+ for _, val := range vals.([]any) {
359
+ srv.packetConnWrappers = append(srv.packetConnWrappers, val.(caddy.PacketConnWrapper))
360
+ }
361
+ }
362
+
363
// pre-compile the primary handler chain, and be sure to wrap it in our
364
// route handler so that important security checks are done, etc.
365
primaryRoute := emptyHandler
modules/caddyhttp/server.go
+7
-2
@@ -55,6 +55,10 @@ type Server struct {
55
// of the base listener. They are applied in the given order.
56
ListenerWrappersRaw []json.RawMessage `json:"listener_wrappers,omitempty" caddy:"namespace=caddy.listeners inline_key=wrapper"`
57
58
+ // A list of packet conn wrapper modules, which can modify the behavior
59
+ // of the base packet conn. They are applied in the given order.
60
+ PacketConnWrappersRaw []json.RawMessage `json:"packet_conn_wrappers,omitempty" caddy:"namespace=caddy.packetconns inline_key=wrapper"`
61
+
62
// How long to allow a read from a client's upload. Setting this
63
// to a short, non-zero value can mitigate slowloris attacks, but
64
// may also affect legitimately slow clients.
@@ -258,7 +262,8 @@ type Server struct {
262
primaryHandlerChain Handler
263
errorHandlerChain Handler
264
listenerWrappers []caddy.ListenerWrapper
261
- listeners []net.Listener // stdlib http.Server will close these
265
+ packetConnWrappers []caddy.PacketConnWrapper
266
+ listeners []net.Listener
267
quicListeners []http3.QUICListener // http3 now leave the quic.Listener management to us
268
269
tlsApp *caddytls.TLS
@@ -625,7 +630,7 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error
630
return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err)
631
}
632
addr.Network = h3net
628
- h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, tlsCfg)
633
+ h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, tlsCfg, s.packetConnWrappers)
634
if err != nil {
635
return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err)
636
}