19
"crypto/tls"
20
"errors"
21
"fmt"
22
+ "io"
23
"net"
24
"net/netip"
25
"os"
26
"strconv"
27
"strings"
28
+ "sync"
29
"sync/atomic"
30
"syscall"
31
+ "time"
32
33
"github.com/lucas-clemente/quic-go"
34
"github.com/lucas-clemente/quic-go/http3"
35
"go.uber.org/zap"
36
)
37
35
-// Listen is like net.Listen, except Caddy's listeners can overlap
36
-// each other: multiple listeners may be created on the same socket
37
-// at the same time. This is useful because during config changes,
38
-// the new config is started while the old config is still running.
39
-// When Caddy listeners are closed, the closing logic is virtualized
40
-// so the underlying socket isn't actually closed until all uses of
41
-// the socket have been finished. Always be sure to close listeners
42
-// when you are done with them, just like normal listeners.
43
-func Listen(network, addr string) (net.Listener, error) {
44
- // a 0 timeout means Go uses its default
45
- return ListenTimeout(network, addr, 0)
46
-}
47
-
48
-// getListenerFromPlugin returns a listener on the given network and address
49
-// if a plugin has registered the network name. It may return (nil, nil) if
50
-// no plugin can provide a listener.
51
-func getListenerFromPlugin(network, addr string) (net.Listener, error) {
52
- network = strings.TrimSpace(strings.ToLower(network))
53
-
54
- // get listener from plugin if network type is registered
55
- if getListener, ok := networkTypes[network]; ok {
56
- Log().Debug("getting listener from plugin", zap.String("network", network))
57
- return getListener(network, addr)
58
- }
59
-
60
- return nil, nil
38
+// NetworkAddress represents one or more network addresses.
39
+// It contains the individual components for a parsed network
40
+// address of the form accepted by ParseNetworkAddress().
41
+type NetworkAddress struct {
42
+ // Should be a network value accepted by Go's net package or
43
+ // by a plugin providing a listener for that network type.
44
+ Network string
45
+
46
+ // The "main" part of the network address is the host, which
47
+ // often takes the form of a hostname, DNS name, IP address,
48
+ // or socket path.
49
+ Host string
50
+
51
+ // For addresses that contain a port, ranges are given by
52
+ // [StartPort, EndPort]; i.e. for a single port, StartPort
53
+ // and EndPort are the same. For no port, they are 0.
54
+ StartPort uint
55
+ EndPort uint
56
}
57
63
-// ListenPacket returns a net.PacketConn suitable for use in a Caddy module.
64
-// It is like Listen except for PacketConns.
65
-// Always be sure to close the PacketConn when you are done.
66
-func ListenPacket(network, addr string) (net.PacketConn, error) {
67
- lnKey := listenerKey(network, addr)
58
+// ListenAll calls Listen() for all addresses represented by this struct, i.e. all ports in the range.
59
+// (If the address doesn't use ports or has 1 port only, then only 1 listener will be created.)
60
+// It returns an error if any listener failed to bind, and closes any listeners opened up to that point.
61
+//
62
+// TODO: Experimental API: subject to change or removal.
63
+func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) ([]any, error) {
64
+ var listeners []any
65
+ var err error
66
+
67
+ // if one of the addresses has a failure, we need to close
68
+ // any that did open a socket to avoid leaking resources
69
+ defer func() {
70
+ if err == nil {
71
+ return
72
+ }
73
+ for _, ln := range listeners {
74
+ if cl, ok := ln.(io.Closer); ok {
75
+ cl.Close()
76
+ }
77
+ }
78
+ }()
79
+
80
+ // an address can contain a port range, which represents multiple addresses;
81
+ // some addresses don't use ports at all and have a port range size of 1;
82
+ // whatever the case, iterate each address represented and bind a socket
83
+ for portOffset := uint(0); portOffset < na.PortRangeSize(); portOffset++ {
84
+ select {
85
+ case <-ctx.Done():
86
+ return nil, ctx.Err()
87
+ default:
88
+ }
89
69
- sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
70
- pc, err := net.ListenPacket(network, addr)
90
+ // create (or reuse) the listener ourselves
91
+ var ln any
92
+ ln, err = na.Listen(ctx, portOffset, config)
93
if err != nil {
72
- // https://github.com/caddyserver/caddy/pull/4534
73
- if isUnixNetwork(network) && isListenBindAddressAlreadyInUseError(err) {
74
- return nil, fmt.Errorf("%w: this can happen if Caddy was forcefully killed", err)
75
- }
94
return nil, err
95
}
78
- return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil
79
- })
80
- if err != nil {
81
- return nil, err
96
+ listeners = append(listeners, ln)
97
}
98
84
- return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil
99
+ return listeners, nil
100
}
101
87
-// ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module.
88
-// Note that the context passed to Accept is currently ignored, so using
89
-// a context other than context.Background is meaningless.
90
-// This API is EXPERIMENTAL and may change.
91
-func ListenQUIC(addr string, tlsConf *tls.Config, activeRequests *int64) (quic.EarlyListener, error) {
92
- lnKey := listenerKey("udp", addr)
93
-
94
- sharedEl, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
95
- el, err := quic.ListenAddrEarly(addr, http3.ConfigureTLSConfig(tlsConf), &quic.Config{
96
- RequireAddressValidation: func(clientAddr net.Addr) bool {
97
- var highLoad bool
98
- if activeRequests != nil {
99
- highLoad = atomic.LoadInt64(activeRequests) > 1000 // TODO: make tunable?
100
- }
101
- return highLoad
102
- },
103
- })
104
- if err != nil {
105
- return nil, err
106
- }
107
- return &sharedQuicListener{EarlyListener: el, key: lnKey}, nil
108
- })
109
- if err != nil {
110
- return nil, err
102
+// Listen is similar to net.Listen, with a few differences:
103
+//
104
+// Listen announces on the network address using the port calculated by adding
105
+// portOffset to the start port. (For network types that do not use ports, the
106
+// portOffset is ignored.)
107
+//
108
+// The provided ListenConfig is used to create the listener. Its Control function,
109
+// if set, may be wrapped by an internally-used Control function. The provided
110
+// context may be used to cancel long operations early. The context is not used
111
+// to close the listener after it has been created.
112
+//
113
+// Caddy's listeners can overlap each other: multiple listeners may be created on
114
+// the same socket at the same time. This is useful because during config changes,
115
+// the new config is started while the old config is still running. How this is
116
+// accomplished varies by platform and network type. For example, on Unix, SO_REUSEPORT
117
+// is set except on Unix sockets, for which the file descriptor is duplicated and
118
+// reused; on Windows, the close logic is virtualized using timeouts. Like normal
119
+// listeners, be sure to Close() them when you are done.
120
+//
121
+// This method returns any type, as the implementations of listeners for various
122
+// network types are not interchangeable. The type of listener returned is switched
123
+// on the network type. Stream-based networks ("tcp", "unix", "unixpacket", etc.)
124
+// return a net.Listener; datagram-based networks ("udp", "unixgram", etc.) return
125
+// a net.PacketConn; and so forth. The actual concrete types are not guaranteed to
126
+// be standard, exported types (wrapping is necessary to provide graceful reloads).
127
+//
128
+// Unix sockets will be unlinked before being created, to ensure we can bind to
129
+// it even if the previous program using it exited uncleanly; it will also be
130
+// unlinked upon a graceful exit (or when a new config does not use that socket).
131
+//
132
+// TODO: Experimental API: subject to change or removal.
133
+func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
134
+ if na.IsUnixNetwork() {
135
+ unixSocketsMu.Lock()
136
+ defer unixSocketsMu.Unlock()
137
}
138
113
- ctx, cancel := context.WithCancel(context.Background())
114
- return &fakeCloseQuicListener{
115
- sharedQuicListener: sharedEl.(*sharedQuicListener),
116
- context: ctx,
117
- contextCancel: cancel,
118
- }, nil
119
-}
139
+ // check to see if plugin provides listener
140
+ if ln, err := getListenerFromPlugin(ctx, na.Network, na.JoinHostPort(portOffset), config); ln != nil || err != nil {
141
+ return ln, err
142
+ }
143
121
-// ListenerUsage returns the current usage count of the given listener address.
122
-func ListenerUsage(network, addr string) int {
123
- count, _ := listenerPool.References(listenerKey(network, addr))
124
- return count
144
+ // create (or reuse) the listener ourselves
145
+ return na.listen(ctx, portOffset, config)
146
}
147
127
-func listenerKey(network, addr string) string {
128
- return network + "/" + addr
129
-}
148
+func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
149
+ var ln any
150
+ var err error
151
131
-type fakeCloseQuicListener struct {
132
- closed int32 // accessed atomically; belongs to this struct only
133
- *sharedQuicListener // embedded, so we also become a quic.EarlyListener
134
- context context.Context
135
- contextCancel context.CancelFunc
136
-}
152
+ address := na.JoinHostPort(portOffset)
153
138
-// Currently Accept ignores the passed context, however a situation where
139
-// someone would need a hotswappable QUIC-only (not http3, since it uses context.Background here)
140
-// server on which Accept would be called with non-empty contexts
141
-// (mind that the default net listeners' Accept doesn't take a context argument)
142
-// sounds way too rare for us to sacrifice efficiency here.
143
-func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (quic.EarlyConnection, error) {
144
- conn, err := fcql.sharedQuicListener.Accept(fcql.context)
145
- if err == nil {
146
- return conn, nil
154
+ // if this is a unix socket, see if we already have it open
155
+ if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil {
156
+ return socket, err
157
}
158
149
- // if the listener is "closed", return a fake closed error instead
150
- if atomic.LoadInt32(&fcql.closed) == 1 && errors.Is(err, context.Canceled) {
151
- return nil, fakeClosedErr(fcql)
152
- }
153
- return nil, err
154
-}
159
+ lnKey := listenerKey(na.Network, address)
160
156
-func (fcql *fakeCloseQuicListener) Close() error {
157
- if atomic.CompareAndSwapInt32(&fcql.closed, 0, 1) {
158
- fcql.contextCancel()
159
- _, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
161
+ switch na.Network {
162
+ case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
163
+ ln, err = listenTCPOrUnix(ctx, lnKey, na.Network, address, config)
164
+ case "unixgram":
165
+ ln, err = config.ListenPacket(ctx, na.Network, address)
166
+ case "udp", "udp4", "udp6":
167
+ sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
168
+ pc, err := config.ListenPacket(ctx, na.Network, address)
169
+ if err != nil {
170
+ return nil, err
171
+ }
172
+ return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil
173
+ })
174
+ if err != nil {
175
+ return nil, err
176
+ }
177
+ ln = &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}
178
}
161
- return nil
162
-}
163
-
164
-// fakeClosedErr returns an error value that is not temporary
165
-// nor a timeout, suitable for making the caller think the
166
-// listener is actually closed
167
-func fakeClosedErr(l interface{ Addr() net.Addr }) error {
168
- return &net.OpError{
169
- Op: "accept",
170
- Net: l.Addr().Network(),
171
- Addr: l.Addr(),
172
- Err: errFakeClosed,
179
+ if strings.HasPrefix(na.Network, "ip") {
180
+ ln, err = config.ListenPacket(ctx, na.Network, address)
181
}
174
-}
175
-
176
-// ErrFakeClosed is the underlying error value returned by
177
-// fakeCloseListener.Accept() after Close() has been called,
178
-// indicating that it is pretending to be closed so that the
179
-// server using it can terminate, while the underlying
180
-// socket is actually left open.
181
-var errFakeClosed = fmt.Errorf("listener 'closed' 😉")
182
-
183
-// fakeClosePacketConn is like fakeCloseListener, but for PacketConns.
184
-type fakeClosePacketConn struct {
185
- closed int32 // accessed atomically; belongs to this struct only
186
- *sharedPacketConn // embedded, so we also become a net.PacketConn
187
-}
188
-
189
-func (fcpc *fakeClosePacketConn) Close() error {
190
- if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) {
191
- _, _ = listenerPool.Delete(fcpc.sharedPacketConn.key)
182
+ if err != nil {
183
+ return nil, err
184
}
193
- return nil
194
-}
195
-
196
-// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998
197
-func (fcpc fakeClosePacketConn) SetReadBuffer(bytes int) error {
198
- if conn, ok := fcpc.PacketConn.(interface{ SetReadBuffer(int) error }); ok {
199
- return conn.SetReadBuffer(bytes)
185
+ if ln == nil {
186
+ return nil, fmt.Errorf("unsupported network type: %s", na.Network)
187
}
201
- return fmt.Errorf("SetReadBuffer() not implemented for %T", fcpc.PacketConn)
202
-}
188
204
-// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998
205
-func (fcpc fakeClosePacketConn) SyscallConn() (syscall.RawConn, error) {
206
- if conn, ok := fcpc.PacketConn.(interface {
207
- SyscallConn() (syscall.RawConn, error)
208
- }); ok {
209
- return conn.SyscallConn()
189
+ // if new listener is a unix socket, make sure we can reuse it later
190
+ // (we do our own "unlink on close" -- not required, but more tidy)
191
+ one := int32(1)
192
+ switch unix := ln.(type) {
193
+ case *net.UnixListener:
194
+ unix.SetUnlinkOnClose(false)
195
+ ln = &unixListener{unix, lnKey, &one}
196
+ unixSockets[lnKey] = ln.(*unixListener)
197
+ case *net.UnixConn:
198
+ ln = &unixConn{unix, address, lnKey, &one}
199
+ unixSockets[lnKey] = ln.(*unixConn)
200
}
211
- return nil, fmt.Errorf("SyscallConn() not implemented for %T", fcpc.PacketConn)
212
-}
213
-
214
-// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
215
-type sharedQuicListener struct {
216
- quic.EarlyListener
217
- key string
218
-}
219
-
220
-// Destruct closes the underlying QUIC listener.
221
-func (sql *sharedQuicListener) Destruct() error {
222
- return sql.EarlyListener.Close()
223
-}
224
-
225
-// sharedPacketConn is like sharedListener, but for net.PacketConns.
226
-type sharedPacketConn struct {
227
- net.PacketConn
228
- key string
229
-}
230
-
231
-// Destruct closes the underlying socket.
232
-func (spc *sharedPacketConn) Destruct() error {
233
- return spc.PacketConn.Close()
234
-}
201
236
-// NetworkAddress contains the individual components
237
-// for a parsed network address of the form accepted
238
-// by ParseNetworkAddress(). Network should be a
239
-// network value accepted by Go's net package. Port
240
-// ranges are given by [StartPort, EndPort].
241
-type NetworkAddress struct {
242
- Network string
243
- Host string
244
- StartPort uint
245
- EndPort uint
202
+ return ln, nil
203
}
204
205
// IsUnixNetwork returns true if na.Network is
217
return net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort+offset)))
218
}
219
220
+// Expand returns one NetworkAddress for each port in the port range.
221
+//
222
+// This is EXPERIMENTAL and subject to change or removal.
223
func (na NetworkAddress) Expand() []NetworkAddress {
224
size := na.PortRangeSize()
225
addrs := make([]NetworkAddress, size)
226
for portOffset := uint(0); portOffset < size; portOffset++ {
267
- na2 := na
268
- na2.StartPort, na2.EndPort = na.StartPort+portOffset, na.StartPort+portOffset
269
- addrs[portOffset] = na2
227
+ addrs[portOffset] = na.At(portOffset)
228
}
229
return addrs
230
}
231
232
+// At returns a NetworkAddress with a port range of just 1
233
+// at the given port offset; i.e. a NetworkAddress that
234
+// represents precisely 1 address only.
235
+func (na NetworkAddress) At(portOffset uint) NetworkAddress {
236
+ na2 := na
237
+ na2.StartPort, na2.EndPort = na.StartPort+portOffset, na.StartPort+portOffset
238
+ return na2
239
+}
240
+
241
// PortRangeSize returns how many ports are in
242
// pa's port range. Port ranges are inclusive,
243
// so the size is the difference of start and
293
return netw == "unix" || netw == "unixgram" || netw == "unixpacket"
294
}
295
329
-func isListenBindAddressAlreadyInUseError(err error) bool {
330
- switch networkOperationError := err.(type) {
331
- case *net.OpError:
332
- switch syscallError := networkOperationError.Err.(type) {
333
- case *os.SyscallError:
334
- if syscallError.Syscall == "bind" {
335
- return true
336
- }
337
- }
338
- }
339
-
340
- return false
341
-}
342
-
296
// ParseNetworkAddress parses addr into its individual
297
// components. The input string is expected to be of
298
// the form "network/host:port-range" where any part is
392
return a
393
}
394
395
+// DEPRECATED: Use NetworkAddress.Listen instead. This function will likely be changed or removed in the future.
396
+func Listen(network, addr string) (net.Listener, error) {
397
+ // a 0 timeout means Go uses its default
398
+ return ListenTimeout(network, addr, 0)
399
+}
400
+
401
+// DEPRECATED: Use NetworkAddress.Listen instead. This function will likely be changed or removed in the future.
402
+func ListenTimeout(network, addr string, keepalivePeriod time.Duration) (net.Listener, error) {
403
+ netAddr, err := ParseNetworkAddress(JoinNetworkAddress(network, addr, ""))
404
+ if err != nil {
405
+ return nil, err
406
+ }
407
+
408
+ ln, err := netAddr.Listen(context.TODO(), 0, net.ListenConfig{KeepAlive: keepalivePeriod})
409
+ if err != nil {
410
+ return nil, err
411
+ }
412
+
413
+ return ln.(net.Listener), nil
414
+}
415
+
416
+// DEPRECATED: Use NetworkAddress.Listen instead. This function will likely be changed or removed in the future.
417
+func ListenPacket(network, addr string) (net.PacketConn, error) {
418
+ netAddr, err := ParseNetworkAddress(JoinNetworkAddress(network, addr, ""))
419
+ if err != nil {
420
+ return nil, err
421
+ }
422
+
423
+ ln, err := netAddr.Listen(context.TODO(), 0, net.ListenConfig{})
424
+ if err != nil {
425
+ return nil, err
426
+ }
427
+
428
+ return ln.(net.PacketConn), nil
429
+}
430
+
431
+// ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module.
432
+// The network will be transformed into a QUIC-compatible type (if unix, then
433
+// unixgram will be used; otherwise, udp will be used).
434
+//
435
+// NOTE: This API is EXPERIMENTAL and may be changed or removed.
436
+//
437
+// TODO: See if we can find a more elegant solution closer to the new NetworkAddress.Listen API.
438
+func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (quic.EarlyListener, error) {
439
+ lnKey := listenerKey(ln.LocalAddr().Network(), ln.LocalAddr().String())
440
+
441
+ sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
442
+ earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(tlsConf), &quic.Config{
443
+ RequireAddressValidation: func(clientAddr net.Addr) bool {
444
+ var highLoad bool
445
+ if activeRequests != nil {
446
+ highLoad = atomic.LoadInt64(activeRequests) > 1000 // TODO: make tunable?
447
+ }
448
+ return highLoad
449
+ },
450
+ })
451
+ if err != nil {
452
+ return nil, err
453
+ }
454
+
455
+ return &sharedQuicListener{EarlyListener: earlyLn, key: lnKey}, nil
456
+ })
457
+ if err != nil {
458
+ return nil, err
459
+ }
460
+
461
+ // TODO: to serve QUIC over a unix socket, currently we need to hold onto
462
+ // the underlying net.PacketConn (which we wrap as unixConn to keep count
463
+ // of closes) because closing the quic.EarlyListener doesn't actually close
464
+ // the underlying PacketConn, but we need to for unix sockets since we dup
465
+ // the file descriptor and thus need to close the original; track issue:
466
+ // https://github.com/lucas-clemente/quic-go/issues/3560#issuecomment-1258959608
467
+ var unix *unixConn
468
+ if uc, ok := ln.(*unixConn); ok {
469
+ unix = uc
470
+ }
471
+
472
+ ctx, cancel := context.WithCancel(context.Background())
473
+ return &fakeCloseQuicListener{
474
+ sharedQuicListener: sharedEarlyListener.(*sharedQuicListener),
475
+ uc: unix,
476
+ context: ctx,
477
+ contextCancel: cancel,
478
+ }, nil
479
+}
480
+
481
+// ListenerUsage returns the current usage count of the given listener address.
482
+func ListenerUsage(network, addr string) int {
483
+ count, _ := listenerPool.References(listenerKey(network, addr))
484
+ return count
485
+}
486
+
487
+// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
488
+type sharedQuicListener struct {
489
+ quic.EarlyListener
490
+ key string
491
+}
492
+
493
+// Destruct closes the underlying QUIC listener.
494
+func (sql *sharedQuicListener) Destruct() error {
495
+ return sql.EarlyListener.Close()
496
+}
497
+
498
+// sharedPacketConn is like sharedListener, but for net.PacketConns.
499
+type sharedPacketConn struct {
500
+ net.PacketConn
501
+ key string
502
+}
503
+
504
+// Destruct closes the underlying socket.
505
+func (spc *sharedPacketConn) Destruct() error {
506
+ return spc.PacketConn.Close()
507
+}
508
+
509
+// fakeClosedErr returns an error value that is not temporary
510
+// nor a timeout, suitable for making the caller think the
511
+// listener is actually closed
512
+func fakeClosedErr(l interface{ Addr() net.Addr }) error {
513
+ return &net.OpError{
514
+ Op: "accept",
515
+ Net: l.Addr().Network(),
516
+ Addr: l.Addr(),
517
+ Err: errFakeClosed,
518
+ }
519
+}
520
+
521
+// errFakeClosed is the underlying error value returned by
522
+// fakeCloseListener.Accept() after Close() has been called,
523
+// indicating that it is pretending to be closed so that the
524
+// server using it can terminate, while the underlying
525
+// socket is actually left open.
526
+var errFakeClosed = fmt.Errorf("listener 'closed' 😉")
527
+
528
+// fakeClosePacketConn is like fakeCloseListener, but for PacketConns.
529
+type fakeClosePacketConn struct {
530
+ closed int32 // accessed atomically; belongs to this struct only
531
+ *sharedPacketConn // embedded, so we also become a net.PacketConn
532
+}
533
+
534
+func (fcpc *fakeClosePacketConn) Close() error {
535
+ if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) {
536
+ _, _ = listenerPool.Delete(fcpc.sharedPacketConn.key)
537
+ }
538
+ return nil
539
+}
540
+
541
+// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998
542
+func (fcpc fakeClosePacketConn) SetReadBuffer(bytes int) error {
543
+ if conn, ok := fcpc.PacketConn.(interface{ SetReadBuffer(int) error }); ok {
544
+ return conn.SetReadBuffer(bytes)
545
+ }
546
+ return fmt.Errorf("SetReadBuffer() not implemented for %T", fcpc.PacketConn)
547
+}
548
+
549
+// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998
550
+func (fcpc fakeClosePacketConn) SyscallConn() (syscall.RawConn, error) {
551
+ if conn, ok := fcpc.PacketConn.(interface {
552
+ SyscallConn() (syscall.RawConn, error)
553
+ }); ok {
554
+ return conn.SyscallConn()
555
+ }
556
+ return nil, fmt.Errorf("SyscallConn() not implemented for %T", fcpc.PacketConn)
557
+}
558
+
559
+type fakeCloseQuicListener struct {
560
+ closed int32 // accessed atomically; belongs to this struct only
561
+ *sharedQuicListener // embedded, so we also become a quic.EarlyListener
562
+ uc *unixConn // underlying unix socket, if UDS
563
+ context context.Context
564
+ contextCancel context.CancelFunc
565
+}
566
+
567
+// Currently Accept ignores the passed context, however a situation where
568
+// someone would need a hotswappable QUIC-only (not http3, since it uses context.Background here)
569
+// server on which Accept would be called with non-empty contexts
570
+// (mind that the default net listeners' Accept doesn't take a context argument)
571
+// sounds way too rare for us to sacrifice efficiency here.
572
+func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (quic.EarlyConnection, error) {
573
+ conn, err := fcql.sharedQuicListener.Accept(fcql.context)
574
+ if err == nil {
575
+ return conn, nil
576
+ }
577
+
578
+ // if the listener is "closed", return a fake closed error instead
579
+ if atomic.LoadInt32(&fcql.closed) == 1 && errors.Is(err, context.Canceled) {
580
+ return nil, fakeClosedErr(fcql)
581
+ }
582
+ return nil, err
583
+}
584
+
585
+func (fcql *fakeCloseQuicListener) Close() error {
586
+ if atomic.CompareAndSwapInt32(&fcql.closed, 0, 1) {
587
+ fcql.contextCancel()
588
+ _, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
589
+ if fcql.uc != nil {
590
+ // unix sockets need to be closed ourselves because we dup() the file
591
+ // descriptor when we reuse them, so this avoids a resource leak
592
+ fcql.uc.Close()
593
+ }
594
+ }
595
+ return nil
596
+}
597
+
598
// RegisterNetwork registers a network type with Caddy so that if a listener is
599
// created for that network type, getListener will be invoked to get the listener.
600
// This should be called during init() and will panic if the network type is standard
616
networkTypes[network] = getListener
617
}
618
619
+type unixListener struct {
620
+ *net.UnixListener
621
+ mapKey string
622
+ count *int32 // accessed atomically
623
+}
624
+
625
+func (uln *unixListener) Close() error {
626
+ newCount := atomic.AddInt32(uln.count, -1)
627
+ if newCount == 0 {
628
+ defer func() {
629
+ addr := uln.Addr().String()
630
+ unixSocketsMu.Lock()
631
+ delete(unixSockets, uln.mapKey)
632
+ unixSocketsMu.Unlock()
633
+ _ = syscall.Unlink(addr)
634
+ }()
635
+ }
636
+ return uln.UnixListener.Close()
637
+}
638
+
639
+type unixConn struct {
640
+ *net.UnixConn
641
+ filename string
642
+ mapKey string
643
+ count *int32 // accessed atomically
644
+}
645
+
646
+func (uc *unixConn) Close() error {
647
+ newCount := atomic.AddInt32(uc.count, -1)
648
+ if newCount == 0 {
649
+ defer func() {
650
+ unixSocketsMu.Lock()
651
+ delete(unixSockets, uc.mapKey)
652
+ unixSocketsMu.Unlock()
653
+ _ = syscall.Unlink(uc.filename)
654
+ }()
655
+ }
656
+ return uc.UnixConn.Close()
657
+}
658
+
659
+// unixSockets keeps track of the currently-active unix sockets
660
+// so we can transfer their FDs gracefully during reloads.
661
+var (
662
+ unixSockets = make(map[string]interface {
663
+ File() (*os.File, error)
664
+ })
665
+ unixSocketsMu sync.Mutex
666
+)
667
+
668
+// getListenerFromPlugin returns a listener on the given network and address
669
+// if a plugin has registered the network name. It may return (nil, nil) if
670
+// no plugin can provide a listener.
671
+func getListenerFromPlugin(ctx context.Context, network, addr string, config net.ListenConfig) (any, error) {
672
+ // get listener from plugin if network type is registered
673
+ if getListener, ok := networkTypes[network]; ok {
674
+ Log().Debug("getting listener from plugin", zap.String("network", network))
675
+ return getListener(ctx, network, addr, config)
676
+ }
677
+
678
+ return nil, nil
679
+}
680
+
681
+func listenerKey(network, addr string) string {
682
+ return network + "/" + addr
683
+}
684
+
685
// ListenerFunc is a function that can return a listener given a network and address.
686
// The listeners must be capable of overlapping: with Caddy, new configs are loaded
687
// before old ones are unloaded, so listeners may overlap briefly if the configs
688
// both need the same listener. EXPERIMENTAL and subject to change.
467
-type ListenerFunc func(network, addr string) (net.Listener, error)
689
+type ListenerFunc func(ctx context.Context, network, addr string, cfg net.ListenConfig) (any, error)
690
691
var networkTypes = map[string]ListenerFunc{}
692