@marcel91200 / caddy Mirror · read-only
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 10, 22:30 UTC

master
go 784 lines 26.1 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddy
16
17 import (
18 "context"
19 "crypto/tls"
20 "errors"
21 "fmt"
22 "io"
23 "io/fs"
24 "net"
25 "net/netip"
26 "os"
27 "strconv"
28 "strings"
29 "sync"
30 "sync/atomic"
31
32 "github.com/quic-go/quic-go"
33 "github.com/quic-go/quic-go/http3"
34 h3qlog "github.com/quic-go/quic-go/http3/qlog"
35 "go.uber.org/zap"
36 "golang.org/x/time/rate"
37
38 "github.com/caddyserver/caddy/v2/internal"
39 )
40
41 // NetworkAddress represents one or more network addresses.
42 // It contains the individual components for a parsed network
43 // address of the form accepted by ParseNetworkAddress().
44 type NetworkAddress struct {
45 // Should be a network value accepted by Go's net package or
46 // by a plugin providing a listener for that network type.
47 Network string
48
49 // The "main" part of the network address is the host, which
50 // often takes the form of a hostname, DNS name, IP address,
51 // or socket path.
52 Host string
53
54 // For addresses that contain a port, ranges are given by
55 // [StartPort, EndPort]; i.e. for a single port, StartPort
56 // and EndPort are the same. For no port, they are 0.
57 StartPort uint
58 EndPort uint
59 }
60
61 // ListenAll calls Listen for all addresses represented by this struct, i.e. all ports in the range.
62 // (If the address doesn't use ports or has 1 port only, then only 1 listener will be created.)
63 // It returns an error if any listener failed to bind, and closes any listeners opened up to that point.
64 func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) ([]any, error) {
65 var listeners []any
66 var err error
67
68 // if one of the addresses has a failure, we need to close
69 // any that did open a socket to avoid leaking resources
70 defer func() {
71 if err == nil {
72 return
73 }
74 for _, ln := range listeners {
75 if cl, ok := ln.(io.Closer); ok {
76 cl.Close()
77 }
78 }
79 }()
80
81 // an address can contain a port range, which represents multiple addresses;
82 // some addresses don't use ports at all and have a port range size of 1;
83 // whatever the case, iterate each address represented and bind a socket
84 for portOffset := uint(0); portOffset < na.PortRangeSize(); portOffset++ {
85 select {
86 case <-ctx.Done():
87 return nil, ctx.Err()
88 default:
89 }
90
91 // create (or reuse) the listener ourselves
92 var ln any
93 ln, err = na.Listen(ctx, portOffset, config)
94 if err != nil {
95 return nil, err
96 }
97 listeners = append(listeners, ln)
98 }
99
100 return listeners, nil
101 }
102
103 // Listen is similar to net.Listen, with a few differences:
104 //
105 // Listen announces on the network address using the port calculated by adding
106 // portOffset to the start port. (For network types that do not use ports, the
107 // portOffset is ignored.)
108 //
109 // First Listen checks if a plugin can provide a listener from this address. Otherwise,
110 // the provided ListenConfig is used to create the listener. Its Control function,
111 // if set, may be wrapped by an internally-used Control function. The provided
112 // context may be used to cancel long operations early. The context is not used
113 // to close the listener after it has been created.
114 //
115 // Caddy's listeners can overlap each other: multiple listeners may be created on
116 // the same socket at the same time. This is useful because during config changes,
117 // the new config is started while the old config is still running. How this is
118 // accomplished varies by platform and network type. For example, on Unix, SO_REUSEPORT
119 // is set except on Unix sockets, for which the file descriptor is duplicated and
120 // reused; on Windows, the close logic is virtualized using timeouts. Like normal
121 // listeners, be sure to Close() them when you are done.
122 //
123 // This method returns any type, as the implementations of listeners for various
124 // network types are not interchangeable. The type of listener returned is switched
125 // on the network type. Stream-based networks ("tcp", "unix", "unixpacket", etc.)
126 // return a net.Listener; datagram-based networks ("udp", "unixgram", etc.) return
127 // a net.PacketConn; and so forth. The actual concrete types are not guaranteed to
128 // be standard, exported types (wrapping is necessary to provide graceful reloads).
129 //
130 // Unix sockets will be unlinked before being created, to ensure we can bind to
131 // it even if the previous program using it exited uncleanly; it will also be
132 // unlinked upon a graceful exit (or when a new config does not use that socket).
133 // Listen synchronizes binds to unix domain sockets to avoid race conditions
134 // while an existing socket is unlinked.
135 func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
136 if na.IsUnixNetwork() {
137 unixSocketsMu.Lock()
138 defer unixSocketsMu.Unlock()
139 }
140
141 // check to see if plugin provides listener
142 if ln, err := getListenerFromPlugin(ctx, na.Network, na.Host, na.port(), portOffset, config); ln != nil || err != nil {
143 return ln, err
144 }
145
146 // create (or reuse) the listener ourselves
147 return na.listen(ctx, portOffset, config)
148 }
149
150 func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
151 var (
152 ln any
153 err error
154 address string
155 unixFileMode fs.FileMode
156 )
157
158 // split unix socket addr early so lnKey
159 // is independent of permissions bits
160 if na.IsUnixNetwork() {
161 address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host)
162 if err != nil {
163 return nil, err
164 }
165 } else if na.IsFdNetwork() {
166 address = na.Host
167 } else {
168 address = na.JoinHostPort(portOffset)
169 }
170
171 if strings.HasPrefix(na.Network, "ip") {
172 ln, err = config.ListenPacket(ctx, na.Network, address)
173 } else {
174 if na.IsUnixNetwork() {
175 // if this is a unix socket, see if we already have it open
176 ln, err = reuseUnixSocket(na.Network, address)
177 }
178
179 if ln == nil && err == nil {
180 // otherwise, create a new listener
181 lnKey := listenerKey(na.Network, address)
182 ln, err = internal.WithBindCapability(na.Network, address, func() (any, error) {
183 return listenReusable(ctx, lnKey, na.Network, address, config)
184 })
185 }
186 }
187
188 if err != nil {
189 return nil, err
190 }
191
192 if ln == nil {
193 return nil, fmt.Errorf("unsupported network type: %s", na.Network)
194 }
195
196 if IsUnixNetwork(na.Network) {
197 isAbstractUnixSocket := strings.HasPrefix(address, "@")
198 if !isAbstractUnixSocket {
199 err = os.Chmod(address, unixFileMode)
200 if err != nil {
201 return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
202 }
203 }
204 }
205
206 return ln, nil
207 }
208
209 // IsUnixNetwork returns true if na.Network is
210 // unix, unixgram, or unixpacket.
211 func (na NetworkAddress) IsUnixNetwork() bool {
212 return IsUnixNetwork(na.Network)
213 }
214
215 // IsFdNetwork returns true if na.Network is
216 // fd or fdgram.
217 func (na NetworkAddress) IsFdNetwork() bool {
218 return IsFdNetwork(na.Network)
219 }
220
221 // JoinHostPort is like net.JoinHostPort, but where the port
222 // is StartPort + offset.
223 func (na NetworkAddress) JoinHostPort(offset uint) string {
224 if na.IsUnixNetwork() || na.IsFdNetwork() {
225 return na.Host
226 }
227 return net.JoinHostPort(na.Host, strconv.FormatUint(uint64(na.StartPort+offset), 10))
228 }
229
230 // Expand returns one NetworkAddress for each port in the port range.
231 func (na NetworkAddress) Expand() []NetworkAddress {
232 size := na.PortRangeSize()
233 addrs := make([]NetworkAddress, size)
234 for portOffset := range size {
235 addrs[portOffset] = na.At(portOffset)
236 }
237 return addrs
238 }
239
240 // At returns a NetworkAddress with a port range of just 1
241 // at the given port offset; i.e. a NetworkAddress that
242 // represents precisely 1 address only.
243 func (na NetworkAddress) At(portOffset uint) NetworkAddress {
244 na2 := na
245 na2.StartPort, na2.EndPort = na.StartPort+portOffset, na.StartPort+portOffset
246 return na2
247 }
248
249 // PortRangeSize returns how many ports are in
250 // pa's port range. Port ranges are inclusive,
251 // so the size is the difference of start and
252 // end ports plus one.
253 func (na NetworkAddress) PortRangeSize() uint {
254 if na.EndPort < na.StartPort {
255 return 0
256 }
257 return (na.EndPort - na.StartPort) + 1
258 }
259
260 func (na NetworkAddress) isLoopback() bool {
261 if na.IsUnixNetwork() || na.IsFdNetwork() {
262 return true
263 }
264 if na.Host == "localhost" {
265 return true
266 }
267 if ip, err := netip.ParseAddr(na.Host); err == nil {
268 return ip.IsLoopback()
269 }
270 return false
271 }
272
273 func (na NetworkAddress) isWildcardInterface() bool {
274 if na.Host == "" {
275 return true
276 }
277 if ip, err := netip.ParseAddr(na.Host); err == nil {
278 return ip.IsUnspecified()
279 }
280 return false
281 }
282
283 func (na NetworkAddress) port() string {
284 if na.StartPort == na.EndPort {
285 return strconv.FormatUint(uint64(na.StartPort), 10)
286 }
287 return fmt.Sprintf("%d-%d", na.StartPort, na.EndPort)
288 }
289
290 // String reconstructs the address string for human display.
291 // The output can be parsed by ParseNetworkAddress(). If the
292 // address is a unix socket, any non-zero port will be dropped.
293 func (na NetworkAddress) String() string {
294 if na.Network == "tcp" && (na.Host != "" || na.port() != "") {
295 na.Network = "" // omit default network value for brevity
296 }
297 return JoinNetworkAddress(na.Network, na.Host, na.port())
298 }
299
300 // IsUnixNetwork returns true if the netw is a unix network.
301 func IsUnixNetwork(netw string) bool {
302 return strings.HasPrefix(netw, "unix")
303 }
304
305 // IsFdNetwork returns true if the netw is a fd network.
306 func IsFdNetwork(netw string) bool {
307 return strings.HasPrefix(netw, "fd")
308 }
309
310 // ParseNetworkAddress parses addr into its individual
311 // components. The input string is expected to be of
312 // the form "network/host:port-range" where any part is
313 // optional. The default network, if unspecified, is tcp.
314 // Port ranges are inclusive.
315 //
316 // Network addresses are distinct from URLs and do not
317 // use URL syntax.
318 func ParseNetworkAddress(addr string) (NetworkAddress, error) {
319 return ParseNetworkAddressWithDefaults(addr, "tcp", 0)
320 }
321
322 // ParseNetworkAddressWithDefaults is like ParseNetworkAddress but allows
323 // the default network and port to be specified.
324 func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort uint) (NetworkAddress, error) {
325 var host, port string
326 network, host, port, err := SplitNetworkAddress(addr)
327 if err != nil {
328 return NetworkAddress{}, err
329 }
330 if network == "" {
331 network = defaultNetwork
332 }
333 if IsUnixNetwork(network) {
334 _, _, err := internal.SplitUnixSocketPermissionsBits(host)
335 return NetworkAddress{
336 Network: network,
337 Host: host,
338 }, err
339 }
340 if IsFdNetwork(network) {
341 return NetworkAddress{
342 Network: network,
343 Host: host,
344 }, nil
345 }
346 var start, end uint64
347 if port == "" {
348 start = uint64(defaultPort)
349 end = uint64(defaultPort)
350 } else {
351 if len(port) > maxPortLen {
352 return NetworkAddress{}, fmt.Errorf("invalid port: longer than %d characters", maxPortLen)
353 }
354 before, after, found := strings.Cut(port, "-")
355 if !found {
356 after = before
357 }
358 start, err = strconv.ParseUint(before, 10, 16)
359 if err != nil {
360 return NetworkAddress{}, fmt.Errorf("invalid start port: %v", err)
361 }
362 end, err = strconv.ParseUint(after, 10, 16)
363 if err != nil {
364 return NetworkAddress{}, fmt.Errorf("invalid end port: %v", err)
365 }
366 if end < start {
367 return NetworkAddress{}, fmt.Errorf("end port must not be less than start port")
368 }
369 if (end-start)+1 > maxPortSpan {
370 return NetworkAddress{}, fmt.Errorf("port range exceeds %d ports", maxPortSpan)
371 }
372 }
373 return NetworkAddress{
374 Network: network,
375 Host: host,
376 StartPort: uint(start),
377 EndPort: uint(end),
378 }, nil
379 }
380
381 // SplitNetworkAddress splits a into its network, host, and port components.
382 // Note that port may be a port range (:X-Y), or omitted for unix sockets.
383 func SplitNetworkAddress(a string) (network, host, port string, err error) {
384 beforeSlash, afterSlash, slashFound := strings.Cut(a, "/")
385 if slashFound {
386 network = strings.ToLower(strings.TrimSpace(beforeSlash))
387 a = afterSlash
388 if IsUnixNetwork(network) || IsFdNetwork(network) {
389 host = a
390 return network, host, port, err
391 }
392 }
393
394 host, port, err = net.SplitHostPort(a)
395 firstErr := err
396
397 if err != nil {
398 // in general, if there was an error, it was likely "missing port",
399 // so try removing square brackets around an IPv6 host, adding a bogus
400 // port to take advantage of standard library's robust parser, then
401 // strip the artificial port.
402 host, _, err = net.SplitHostPort(net.JoinHostPort(strings.Trim(a, "[]"), "0"))
403 port = ""
404 }
405
406 if err != nil {
407 err = errors.Join(firstErr, err)
408 }
409
410 return network, host, port, err
411 }
412
413 // JoinNetworkAddress combines network, host, and port into a single
414 // address string of the form accepted by ParseNetworkAddress(). For
415 // unix sockets, the network should be "unix" (or "unixgram" or
416 // "unixpacket") and the path to the socket should be given as the
417 // host parameter.
418 func JoinNetworkAddress(network, host, port string) string {
419 var a string
420 if network != "" {
421 a = network + "/"
422 }
423 if (host != "" && port == "") || IsUnixNetwork(network) || IsFdNetwork(network) {
424 a += host
425 } else if port != "" {
426 a += net.JoinHostPort(host, port)
427 }
428 return a
429 }
430
431 // ListenQUICOptions holds optional parameters for ListenQUIC.
432 // A zero value matches historical defaults: 0-RTT is allowed,
433 // WebTransport QUIC capabilities are off, and no packet-conn
434 // wrappers are applied.
435 //
436 // NOTE: This API is EXPERIMENTAL and may be changed or removed.
437 type ListenQUICOptions struct {
438 TLSConfig *tls.Config
439 PacketConnWrappers []PacketConnWrapper
440 Allow0RTT *bool
441 EnableWebTransport bool
442 }
443
444 // ListenQUIC returns a http3.QUICEarlyListener suitable for use in a Caddy module.
445 //
446 // The network will be transformed into a QUIC-compatible type if the same address can be used with
447 // different networks. Currently this just means that for tcp, udp will be used with the same
448 // address instead.
449 //
450 // ctx, portOffset, and config match Listen. Additional QUIC-specific
451 // parameters are passed in opts.
452 //
453 // The underlying listener is pooled per address: the first creator's configuration
454 // (Allow0RTT, WebTransport capabilities) wins; subsequent callers reuse that listener.
455 //
456 // NOTE: This API is EXPERIMENTAL and may be changed or removed.
457 // NOTE: user should close the returned listener twice, once to stop accepting new connections, the second time to free up the packet conn.
458 func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, opts ListenQUICOptions) (http3.QUICListener, error) {
459 lnKey := listenerKey("quic"+na.Network, na.JoinHostPort(portOffset))
460
461 sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
462 lnAny, err := na.Listen(ctx, portOffset, config)
463 if err != nil {
464 return nil, err
465 }
466
467 ln := lnAny.(net.PacketConn)
468
469 h3ln := ln
470 if len(opts.PacketConnWrappers) == 0 {
471 for {
472 // retrieve the underlying socket, so quic-go can optimize.
473 if unwrapper, ok := h3ln.(interface{ Unwrap() net.PacketConn }); ok {
474 h3ln = unwrapper.Unwrap()
475 } else {
476 break
477 }
478 }
479 } else {
480 // wrap packet conn before QUIC
481 for _, pcWrapper := range opts.PacketConnWrappers {
482 h3ln = pcWrapper.WrapPacketConn(h3ln)
483 }
484 }
485
486 sqs := newSharedQUICState(opts.TLSConfig)
487 // http3.ConfigureTLSConfig only uses this field and tls App sets this field as well
488 //nolint:gosec
489 quicTlsConfig := &tls.Config{
490 GetConfigForClient: sqs.getConfigForClient,
491 GetEncryptedClientHelloKeys: sqs.getEncryptedClientHelloKeys,
492 }
493 // Require clients to verify their source address when we're handling more than 1000 handshakes per second.
494 // TODO: make tunable?
495 limiter := rate.NewLimiter(1000, 1000)
496 tr := &quic.Transport{
497 Conn: h3ln,
498 VerifySourceAddress: func(addr net.Addr) bool { return !limiter.Allow() },
499 }
500 allow0rtt := true
501 if opts.Allow0RTT != nil {
502 allow0rtt = *opts.Allow0RTT
503 }
504 // EnableDatagrams and EnableStreamResetPartialDelivery are required by
505 // WebTransport; only enable them when WebTransport support is requested.
506 earlyLn, err := tr.ListenEarly(
507 http3.ConfigureTLSConfig(quicTlsConfig),
508 &quic.Config{
509 InitialPacketSize: 1200,
510 Allow0RTT: allow0rtt,
511 Tracer: h3qlog.DefaultConnectionTracer,
512 EnableDatagrams: opts.EnableWebTransport,
513 EnableStreamResetPartialDelivery: opts.EnableWebTransport,
514 },
515 )
516 if err != nil {
517 return nil, err
518 }
519 // TODO: figure out when to close the listener and the transport
520 // using the original net.PacketConn to close them properly
521 return &sharedQuicListener{
522 EarlyListener: earlyLn,
523 packetConn: ln,
524 sqs: sqs,
525 key: lnKey,
526 supportsWebTransport: opts.EnableWebTransport,
527 }, nil
528 })
529 if err != nil {
530 return nil, err
531 }
532
533 sql := sharedEarlyListener.(*sharedQuicListener)
534 // add current tls.Config to sqs, so GetConfigForClient will always return the latest tls.Config in case of context cancellation
535 ctx, cancel := sql.sqs.addState(opts.TLSConfig)
536
537 return &fakeCloseQuicListener{
538 sharedQuicListener: sql,
539 context: ctx,
540 contextCancel: cancel,
541 }, nil
542 }
543
544 // ListenerUsage returns the current usage count of the given listener address.
545 func ListenerUsage(network, addr string) int {
546 count, _ := listenerPool.References(listenerKey(network, addr))
547 return count
548 }
549
550 // contextAndCancelFunc groups context and its cancelFunc
551 type contextAndCancelFunc struct {
552 context.Context
553 context.CancelCauseFunc
554 }
555
556 // sharedQUICState manages GetConfigForClient
557 // see issue: https://github.com/caddyserver/caddy/pull/4849
558 type sharedQUICState struct {
559 rmu sync.RWMutex
560 tlsConfs map[*tls.Config]contextAndCancelFunc
561 activeTlsConf *tls.Config
562 }
563
564 // newSharedQUICState creates a new sharedQUICState
565 func newSharedQUICState(tlsConfig *tls.Config) *sharedQUICState {
566 sqtc := &sharedQUICState{
567 tlsConfs: make(map[*tls.Config]contextAndCancelFunc),
568 activeTlsConf: tlsConfig,
569 }
570 sqtc.addState(tlsConfig)
571 return sqtc
572 }
573
574 // getConfigForClient is used as tls.Config's GetConfigForClient field
575 func (sqs *sharedQUICState) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Config, error) {
576 sqs.rmu.RLock()
577 defer sqs.rmu.RUnlock()
578 return sqs.activeTlsConf.GetConfigForClient(ch)
579 }
580
581 // getEncryptedClientHelloKeys is used as tls.Config's GetEncryptedClientHelloKeys field.
582 func (sqs *sharedQUICState) getEncryptedClientHelloKeys(ch *tls.ClientHelloInfo) ([]tls.EncryptedClientHelloKey, error) {
583 sqs.rmu.RLock()
584 defer sqs.rmu.RUnlock()
585 if sqs.activeTlsConf.GetEncryptedClientHelloKeys == nil {
586 return nil, nil
587 }
588 return sqs.activeTlsConf.GetEncryptedClientHelloKeys(ch)
589 }
590
591 // addState adds tls.Config and activeRequests to the map if not present and returns the corresponding context and its cancelFunc
592 // so that when cancelled, the active tls.Config will change
593 func (sqs *sharedQUICState) addState(tlsConfig *tls.Config) (context.Context, context.CancelCauseFunc) {
594 sqs.rmu.Lock()
595 defer sqs.rmu.Unlock()
596
597 if cacc, ok := sqs.tlsConfs[tlsConfig]; ok {
598 return cacc.Context, cacc.CancelCauseFunc
599 }
600
601 ctx, cancel := context.WithCancelCause(context.Background())
602 wrappedCancel := func(cause error) {
603 cancel(cause)
604
605 sqs.rmu.Lock()
606 defer sqs.rmu.Unlock()
607
608 delete(sqs.tlsConfs, tlsConfig)
609 if sqs.activeTlsConf == tlsConfig {
610 // select another tls.Config, if there is none,
611 // related sharedQuicListener will be destroyed anyway
612 for tc := range sqs.tlsConfs {
613 sqs.activeTlsConf = tc
614 break
615 }
616 }
617 }
618 sqs.tlsConfs[tlsConfig] = contextAndCancelFunc{ctx, wrappedCancel}
619 // there should be at most 2 tls.Configs
620 if len(sqs.tlsConfs) > 2 {
621 Log().Warn("quic listener tls configs are more than 2", zap.Int("number of configs", len(sqs.tlsConfs)))
622 }
623 return ctx, wrappedCancel
624 }
625
626 // sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
627 type sharedQuicListener struct {
628 *quic.EarlyListener
629 packetConn net.PacketConn // we have to hold these because quic-go won't close listeners it didn't create
630 sqs *sharedQUICState
631 key string
632 supportsWebTransport bool // set by the first creator; subsequent pool reuses keep this value
633 }
634
635 // SupportsWebTransport reports whether the shared QUIC listener was created
636 // with WebTransport capabilities (DATAGRAM support and stream-reset partial
637 // delivery). The value is fixed by the first creator of a pooled listener.
638 func (sql *sharedQuicListener) SupportsWebTransport() bool {
639 return sql.supportsWebTransport
640 }
641
642 // Destruct closes the underlying QUIC listener and its associated net.PacketConn.
643 func (sql *sharedQuicListener) Destruct() error {
644 // close EarlyListener first to stop any operations being done to the net.PacketConn
645 _ = sql.EarlyListener.Close()
646 // then close the net.PacketConn
647 return sql.packetConn.Close()
648 }
649
650 // fakeClosedErr returns an error value that is not temporary
651 // nor a timeout, suitable for making the caller think the
652 // listener is actually closed
653 func fakeClosedErr(l interface{ Addr() net.Addr }) error {
654 return &net.OpError{
655 Op: "accept",
656 Net: l.Addr().Network(),
657 Addr: l.Addr(),
658 Err: errFakeClosed,
659 }
660 }
661
662 // errFakeClosed is the underlying error value returned by
663 // fakeCloseListener.Accept() after Close() has been called,
664 // indicating that it is pretending to be closed so that the
665 // server using it can terminate, while the underlying
666 // socket is actually left open.
667 var errFakeClosed = fmt.Errorf("QUIC listener 'closed' 😉")
668
669 type fakeCloseQuicListener struct {
670 closed atomic.Int32
671 *sharedQuicListener // embedded, so we also become a quic.EarlyListener
672 context context.Context
673 contextCancel context.CancelCauseFunc
674 }
675
676 // Currently Accept ignores the passed context, however a situation where
677 // someone would need a hotswappable QUIC-only (not http3, since it uses context.Background here)
678 // server on which Accept would be called with non-empty contexts
679 // (mind that the default net listeners' Accept doesn't take a context argument)
680 // sounds way too rare for us to sacrifice efficiency here.
681 func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (*quic.Conn, error) {
682 conn, err := fcql.sharedQuicListener.Accept(fcql.context)
683 if err == nil {
684 return conn, nil
685 }
686
687 // if the listener is "closed", return a fake closed error instead
688 if fcql.closed.Load() == 1 && errors.Is(err, context.Canceled) {
689 return nil, fakeClosedErr(fcql)
690 }
691 return nil, err
692 }
693
694 func (fcql *fakeCloseQuicListener) Close() error {
695 if fcql.closed.CompareAndSwap(0, 1) {
696 fcql.contextCancel(errFakeClosed)
697 } else if fcql.closed.CompareAndSwap(1, 2) {
698 _, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
699 }
700 return nil
701 }
702
703 // RegisterNetwork registers a network type with Caddy so that if a listener is
704 // created for that network type, getListener will be invoked to get the listener.
705 // This should be called during init() and will panic if the network type is standard
706 // or reserved, or if it is already registered. EXPERIMENTAL and subject to change.
707 func RegisterNetwork(network string, getListener ListenerFunc) {
708 network = strings.TrimSpace(strings.ToLower(network))
709
710 if network == "tcp" || network == "tcp4" || network == "tcp6" ||
711 network == "udp" || network == "udp4" || network == "udp6" ||
712 network == "unix" || network == "unixpacket" || network == "unixgram" ||
713 strings.HasPrefix(network, "ip:") || strings.HasPrefix(network, "ip4:") || strings.HasPrefix(network, "ip6:") ||
714 network == "fd" || network == "fdgram" {
715 panic("network type " + network + " is reserved")
716 }
717
718 if _, ok := networkTypes[strings.ToLower(network)]; ok {
719 panic("network type " + network + " is already registered")
720 }
721
722 networkTypes[network] = getListener
723 }
724
725 var unixSocketsMu sync.Mutex
726
727 // getListenerFromPlugin returns a listener on the given network and address
728 // if a plugin has registered the network name. It may return (nil, nil) if
729 // no plugin can provide a listener.
730 func getListenerFromPlugin(ctx context.Context, network, host, port string, portOffset uint, config net.ListenConfig) (any, error) {
731 // get listener from plugin if network type is registered
732 if getListener, ok := networkTypes[network]; ok {
733 Log().Debug("getting listener from plugin", zap.String("network", network))
734 return getListener(ctx, network, host, port, portOffset, config)
735 }
736
737 return nil, nil
738 }
739
740 func listenerKey(network, addr string) string {
741 return network + "/" + addr
742 }
743
744 // ListenerFunc is a function that can return a listener given a network and address.
745 // The listeners must be capable of overlapping: with Caddy, new configs are loaded
746 // before old ones are unloaded, so listeners may overlap briefly if the configs
747 // both need the same listener. EXPERIMENTAL and subject to change.
748 type ListenerFunc func(ctx context.Context, network, host, portRange string, portOffset uint, cfg net.ListenConfig) (any, error)
749
750 var networkTypes = map[string]ListenerFunc{}
751
752 // ListenerWrapper is a type that wraps a listener
753 // so it can modify the input listener's methods.
754 // Modules that implement this interface are found
755 // in the caddy.listeners namespace. Usually, to
756 // wrap a listener, you will define your own struct
757 // type that embeds the input listener, then
758 // implement your own methods that you want to wrap,
759 // calling the underlying listener's methods where
760 // appropriate.
761 type ListenerWrapper interface {
762 WrapListener(net.Listener) net.Listener
763 }
764
765 // PacketConnWrapper is a type that wraps a packet conn
766 // so it can modify the input packet conn methods.
767 // Modules that implement this interface are found
768 // in the caddy.packetconns namespace. Usually, to
769 // wrap a packet conn, you will define your own struct
770 // type that embeds the input packet conn, then
771 // implement your own methods that you want to wrap,
772 // calling the underlying packet conn methods where
773 // appropriate.
774 type PacketConnWrapper interface {
775 WrapPacketConn(net.PacketConn) net.PacketConn
776 }
777
778 // listenerPool stores and allows reuse of active listeners.
779 var listenerPool = NewUsagePool()
780
781 const maxPortSpan = 65535
782
783 // maxPortLen is the length of the longest valid port range.
784 const maxPortLen = len("65535-65535")