@marcel91200 / caddy Mirror · read-only / commits / 31960dc9
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 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 }