mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 10:37:24 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			835 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			835 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 Matthew Holt and The Caddy Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package caddy
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/tls"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/fs"
 | |
| 	"net"
 | |
| 	"net/netip"
 | |
| 	"os"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"sync/atomic"
 | |
| 	"syscall"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/quic-go/quic-go"
 | |
| 	"github.com/quic-go/quic-go/http3"
 | |
| 	"go.uber.org/zap"
 | |
| )
 | |
| 
 | |
| // NetworkAddress represents one or more network addresses.
 | |
| // It contains the individual components for a parsed network
 | |
| // address of the form accepted by ParseNetworkAddress().
 | |
| type NetworkAddress struct {
 | |
| 	// Should be a network value accepted by Go's net package or
 | |
| 	// by a plugin providing a listener for that network type.
 | |
| 	Network string
 | |
| 
 | |
| 	// The "main" part of the network address is the host, which
 | |
| 	// often takes the form of a hostname, DNS name, IP address,
 | |
| 	// or socket path.
 | |
| 	Host string
 | |
| 
 | |
| 	// For addresses that contain a port, ranges are given by
 | |
| 	// [StartPort, EndPort]; i.e. for a single port, StartPort
 | |
| 	// and EndPort are the same. For no port, they are 0.
 | |
| 	StartPort uint
 | |
| 	EndPort   uint
 | |
| }
 | |
| 
 | |
| // ListenAll calls Listen() for all addresses represented by this struct, i.e. all ports in the range.
 | |
| // (If the address doesn't use ports or has 1 port only, then only 1 listener will be created.)
 | |
| // It returns an error if any listener failed to bind, and closes any listeners opened up to that point.
 | |
| //
 | |
| // TODO: Experimental API: subject to change or removal.
 | |
| func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) ([]any, error) {
 | |
| 	var listeners []any
 | |
| 	var err error
 | |
| 
 | |
| 	// if one of the addresses has a failure, we need to close
 | |
| 	// any that did open a socket to avoid leaking resources
 | |
| 	defer func() {
 | |
| 		if err == nil {
 | |
| 			return
 | |
| 		}
 | |
| 		for _, ln := range listeners {
 | |
| 			if cl, ok := ln.(io.Closer); ok {
 | |
| 				cl.Close()
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	// an address can contain a port range, which represents multiple addresses;
 | |
| 	// some addresses don't use ports at all and have a port range size of 1;
 | |
| 	// whatever the case, iterate each address represented and bind a socket
 | |
| 	for portOffset := uint(0); portOffset < na.PortRangeSize(); portOffset++ {
 | |
| 		select {
 | |
| 		case <-ctx.Done():
 | |
| 			return nil, ctx.Err()
 | |
| 		default:
 | |
| 		}
 | |
| 
 | |
| 		// create (or reuse) the listener ourselves
 | |
| 		var ln any
 | |
| 		ln, err = na.Listen(ctx, portOffset, config)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		listeners = append(listeners, ln)
 | |
| 	}
 | |
| 
 | |
| 	return listeners, nil
 | |
| }
 | |
| 
 | |
| // Listen is similar to net.Listen, with a few differences:
 | |
| //
 | |
| // Listen announces on the network address using the port calculated by adding
 | |
| // portOffset to the start port. (For network types that do not use ports, the
 | |
| // portOffset is ignored.)
 | |
| //
 | |
| // The provided ListenConfig is used to create the listener. Its Control function,
 | |
| // if set, may be wrapped by an internally-used Control function. The provided
 | |
| // context may be used to cancel long operations early. The context is not used
 | |
| // to close the listener after it has been created.
 | |
| //
 | |
| // Caddy's listeners can overlap each other: multiple listeners may be created on
 | |
| // the same socket at the same time. This is useful because during config changes,
 | |
| // the new config is started while the old config is still running. How this is
 | |
| // accomplished varies by platform and network type. For example, on Unix, SO_REUSEPORT
 | |
| // is set except on Unix sockets, for which the file descriptor is duplicated and
 | |
| // reused; on Windows, the close logic is virtualized using timeouts. Like normal
 | |
| // listeners, be sure to Close() them when you are done.
 | |
| //
 | |
| // This method returns any type, as the implementations of listeners for various
 | |
| // network types are not interchangeable. The type of listener returned is switched
 | |
| // on the network type. Stream-based networks ("tcp", "unix", "unixpacket", etc.)
 | |
| // return a net.Listener; datagram-based networks ("udp", "unixgram", etc.) return
 | |
| // a net.PacketConn; and so forth. The actual concrete types are not guaranteed to
 | |
| // be standard, exported types (wrapping is necessary to provide graceful reloads).
 | |
| //
 | |
| // Unix sockets will be unlinked before being created, to ensure we can bind to
 | |
| // it even if the previous program using it exited uncleanly; it will also be
 | |
| // unlinked upon a graceful exit (or when a new config does not use that socket).
 | |
| //
 | |
| // TODO: Experimental API: subject to change or removal.
 | |
| func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
 | |
| 	if na.IsUnixNetwork() {
 | |
| 		unixSocketsMu.Lock()
 | |
| 		defer unixSocketsMu.Unlock()
 | |
| 	}
 | |
| 
 | |
| 	// check to see if plugin provides listener
 | |
| 	if ln, err := getListenerFromPlugin(ctx, na.Network, na.JoinHostPort(portOffset), config); ln != nil || err != nil {
 | |
| 		return ln, err
 | |
| 	}
 | |
| 
 | |
| 	// create (or reuse) the listener ourselves
 | |
| 	return na.listen(ctx, portOffset, config)
 | |
| }
 | |
| 
 | |
| func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
 | |
| 	var ln any
 | |
| 	var err error
 | |
| 	var address string
 | |
| 	var unixFileMode fs.FileMode
 | |
| 	var isAbtractUnixSocket bool
 | |
| 
 | |
| 	// split unix socket addr early so lnKey
 | |
| 	// is independent of permissions bits
 | |
| 	if na.IsUnixNetwork() {
 | |
| 		var err error
 | |
| 		address, unixFileMode, err = splitUnixSocketPermissionsBits(na.Host)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		isAbtractUnixSocket = strings.HasPrefix(address, "@")
 | |
| 	} else {
 | |
| 		address = na.JoinHostPort(portOffset)
 | |
| 	}
 | |
| 
 | |
| 	// if this is a unix socket, see if we already have it open,
 | |
| 	// force socket permissions on it and return early
 | |
| 	if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil {
 | |
| 		if !isAbtractUnixSocket {
 | |
| 			if err := os.Chmod(address, unixFileMode); err != nil {
 | |
| 				return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 		return socket, err
 | |
| 	}
 | |
| 
 | |
| 	lnKey := listenerKey(na.Network, address)
 | |
| 
 | |
| 	switch na.Network {
 | |
| 	case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
 | |
| 		ln, err = listenTCPOrUnix(ctx, lnKey, na.Network, address, config)
 | |
| 	case "unixgram":
 | |
| 		ln, err = config.ListenPacket(ctx, na.Network, address)
 | |
| 	case "udp", "udp4", "udp6":
 | |
| 		sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
 | |
| 			pc, err := config.ListenPacket(ctx, na.Network, address)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		spc := sharedPc.(*sharedPacketConn)
 | |
| 		ln = &fakeClosePacketConn{spc: spc, UDPConn: spc.PacketConn.(*net.UDPConn)}
 | |
| 	}
 | |
| 	if strings.HasPrefix(na.Network, "ip") {
 | |
| 		ln, err = config.ListenPacket(ctx, na.Network, address)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if ln == nil {
 | |
| 		return nil, fmt.Errorf("unsupported network type: %s", na.Network)
 | |
| 	}
 | |
| 
 | |
| 	// TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so...
 | |
| 	if unix, ok := ln.(*net.UnixConn); ok {
 | |
| 		one := int32(1)
 | |
| 		ln = &unixConn{unix, address, lnKey, &one}
 | |
| 		unixSockets[lnKey] = unix
 | |
| 	}
 | |
| 
 | |
| 	if IsUnixNetwork(na.Network) {
 | |
| 		if !isAbtractUnixSocket {
 | |
| 			if err := os.Chmod(address, unixFileMode); err != nil {
 | |
| 				return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return ln, nil
 | |
| }
 | |
| 
 | |
| // IsUnixNetwork returns true if na.Network is
 | |
| // unix, unixgram, or unixpacket.
 | |
| func (na NetworkAddress) IsUnixNetwork() bool {
 | |
| 	return IsUnixNetwork(na.Network)
 | |
| }
 | |
| 
 | |
| // JoinHostPort is like net.JoinHostPort, but where the port
 | |
| // is StartPort + offset.
 | |
| func (na NetworkAddress) JoinHostPort(offset uint) string {
 | |
| 	if na.IsUnixNetwork() {
 | |
| 		return na.Host
 | |
| 	}
 | |
| 	return net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort+offset)))
 | |
| }
 | |
| 
 | |
| // Expand returns one NetworkAddress for each port in the port range.
 | |
| //
 | |
| // This is EXPERIMENTAL and subject to change or removal.
 | |
| func (na NetworkAddress) Expand() []NetworkAddress {
 | |
| 	size := na.PortRangeSize()
 | |
| 	addrs := make([]NetworkAddress, size)
 | |
| 	for portOffset := uint(0); portOffset < size; portOffset++ {
 | |
| 		addrs[portOffset] = na.At(portOffset)
 | |
| 	}
 | |
| 	return addrs
 | |
| }
 | |
| 
 | |
| // At returns a NetworkAddress with a port range of just 1
 | |
| // at the given port offset; i.e. a NetworkAddress that
 | |
| // represents precisely 1 address only.
 | |
| func (na NetworkAddress) At(portOffset uint) NetworkAddress {
 | |
| 	na2 := na
 | |
| 	na2.StartPort, na2.EndPort = na.StartPort+portOffset, na.StartPort+portOffset
 | |
| 	return na2
 | |
| }
 | |
| 
 | |
| // PortRangeSize returns how many ports are in
 | |
| // pa's port range. Port ranges are inclusive,
 | |
| // so the size is the difference of start and
 | |
| // end ports plus one.
 | |
| func (na NetworkAddress) PortRangeSize() uint {
 | |
| 	if na.EndPort < na.StartPort {
 | |
| 		return 0
 | |
| 	}
 | |
| 	return (na.EndPort - na.StartPort) + 1
 | |
| }
 | |
| 
 | |
| func (na NetworkAddress) isLoopback() bool {
 | |
| 	if na.IsUnixNetwork() {
 | |
| 		return true
 | |
| 	}
 | |
| 	if na.Host == "localhost" {
 | |
| 		return true
 | |
| 	}
 | |
| 	if ip, err := netip.ParseAddr(na.Host); err == nil {
 | |
| 		return ip.IsLoopback()
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (na NetworkAddress) isWildcardInterface() bool {
 | |
| 	if na.Host == "" {
 | |
| 		return true
 | |
| 	}
 | |
| 	if ip, err := netip.ParseAddr(na.Host); err == nil {
 | |
| 		return ip.IsUnspecified()
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (na NetworkAddress) port() string {
 | |
| 	if na.StartPort == na.EndPort {
 | |
| 		return strconv.FormatUint(uint64(na.StartPort), 10)
 | |
| 	}
 | |
| 	return fmt.Sprintf("%d-%d", na.StartPort, na.EndPort)
 | |
| }
 | |
| 
 | |
| // String reconstructs the address string for human display.
 | |
| // The output can be parsed by ParseNetworkAddress(). If the
 | |
| // address is a unix socket, any non-zero port will be dropped.
 | |
| func (na NetworkAddress) String() string {
 | |
| 	if na.Network == "tcp" && (na.Host != "" || na.port() != "") {
 | |
| 		na.Network = "" // omit default network value for brevity
 | |
| 	}
 | |
| 	return JoinNetworkAddress(na.Network, na.Host, na.port())
 | |
| }
 | |
| 
 | |
| // IsUnixNetwork returns true if the netw is a unix network.
 | |
| func IsUnixNetwork(netw string) bool {
 | |
| 	return strings.HasPrefix(netw, "unix")
 | |
| }
 | |
| 
 | |
| // Takes a unix socket address in the unusual "path|bits" format
 | |
| // (e.g. /run/caddy.sock|0222) and tries to split it into
 | |
| // socket path (host) and permissions bits (port). Colons (":")
 | |
| // can't be used as separator, as socket paths on Windows may
 | |
| // include a drive letter (e.g. `unix/c:\absolute\path.sock`).
 | |
| // Permission bits will default to 0200 if none are specified.
 | |
| // Throws an error, if the first carrying bit does not
 | |
| // include write perms (e.g. `0422` or `022`).
 | |
| // Symbolic permission representation (e.g. `u=w,g=w,o=w`)
 | |
| // is not supported and will throw an error for now!
 | |
| func splitUnixSocketPermissionsBits(addr string) (path string, fileMode fs.FileMode, err error) {
 | |
| 	addrSplit := strings.SplitN(addr, "|", 2)
 | |
| 
 | |
| 	if len(addrSplit) == 2 {
 | |
| 		// parse octal permission bit string as uint32
 | |
| 		fileModeUInt64, err := strconv.ParseUint(addrSplit[1], 8, 32)
 | |
| 		if err != nil {
 | |
| 			return "", 0, fmt.Errorf("could not parse octal permission bits in %s: %v", addr, err)
 | |
| 		}
 | |
| 		fileMode = fs.FileMode(fileModeUInt64)
 | |
| 
 | |
| 		// FileMode.String() returns a string like `-rwxr-xr--` for `u=rwx,g=rx,o=r` (`0754`)
 | |
| 		if string(fileMode.String()[2]) != "w" {
 | |
| 			return "", 0, fmt.Errorf("owner of the socket requires '-w-' (write, octal: '2') permissions at least; got '%s' in %s", fileMode.String()[1:4], addr)
 | |
| 		}
 | |
| 
 | |
| 		return addrSplit[0], fileMode, nil
 | |
| 	}
 | |
| 
 | |
| 	// default to 0200 (symbolic: `u=w,g=,o=`)
 | |
| 	// if no permission bits are specified
 | |
| 	return addr, 0200, nil
 | |
| }
 | |
| 
 | |
| // ParseNetworkAddress parses addr into its individual
 | |
| // components. The input string is expected to be of
 | |
| // the form "network/host:port-range" where any part is
 | |
| // optional. The default network, if unspecified, is tcp.
 | |
| // Port ranges are inclusive.
 | |
| //
 | |
| // Network addresses are distinct from URLs and do not
 | |
| // use URL syntax.
 | |
| func ParseNetworkAddress(addr string) (NetworkAddress, error) {
 | |
| 	return ParseNetworkAddressWithDefaults(addr, "tcp", 0)
 | |
| }
 | |
| 
 | |
| // ParseNetworkAddressWithDefaults is like ParseNetworkAddress but allows
 | |
| // the default network and port to be specified.
 | |
| func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort uint) (NetworkAddress, error) {
 | |
| 	var host, port string
 | |
| 	network, host, port, err := SplitNetworkAddress(addr)
 | |
| 	if err != nil {
 | |
| 		return NetworkAddress{}, err
 | |
| 	}
 | |
| 	if network == "" {
 | |
| 		network = defaultNetwork
 | |
| 	}
 | |
| 	if IsUnixNetwork(network) {
 | |
| 		_, _, err := splitUnixSocketPermissionsBits(host)
 | |
| 		return NetworkAddress{
 | |
| 			Network: network,
 | |
| 			Host:    host,
 | |
| 		}, err
 | |
| 	}
 | |
| 	var start, end uint64
 | |
| 	if port == "" {
 | |
| 		start = uint64(defaultPort)
 | |
| 		end = uint64(defaultPort)
 | |
| 	} else {
 | |
| 		before, after, found := strings.Cut(port, "-")
 | |
| 		if !found {
 | |
| 			after = before
 | |
| 		}
 | |
| 		start, err = strconv.ParseUint(before, 10, 16)
 | |
| 		if err != nil {
 | |
| 			return NetworkAddress{}, fmt.Errorf("invalid start port: %v", err)
 | |
| 		}
 | |
| 		end, err = strconv.ParseUint(after, 10, 16)
 | |
| 		if err != nil {
 | |
| 			return NetworkAddress{}, fmt.Errorf("invalid end port: %v", err)
 | |
| 		}
 | |
| 		if end < start {
 | |
| 			return NetworkAddress{}, fmt.Errorf("end port must not be less than start port")
 | |
| 		}
 | |
| 		if (end - start) > maxPortSpan {
 | |
| 			return NetworkAddress{}, fmt.Errorf("port range exceeds %d ports", maxPortSpan)
 | |
| 		}
 | |
| 	}
 | |
| 	return NetworkAddress{
 | |
| 		Network:   network,
 | |
| 		Host:      host,
 | |
| 		StartPort: uint(start),
 | |
| 		EndPort:   uint(end),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // SplitNetworkAddress splits a into its network, host, and port components.
 | |
| // Note that port may be a port range (:X-Y), or omitted for unix sockets.
 | |
| func SplitNetworkAddress(a string) (network, host, port string, err error) {
 | |
| 	beforeSlash, afterSlash, slashFound := strings.Cut(a, "/")
 | |
| 	if slashFound {
 | |
| 		network = strings.ToLower(strings.TrimSpace(beforeSlash))
 | |
| 		a = afterSlash
 | |
| 	}
 | |
| 	if IsUnixNetwork(network) {
 | |
| 		host = a
 | |
| 		return
 | |
| 	}
 | |
| 	host, port, err = net.SplitHostPort(a)
 | |
| 	if err == nil || a == "" {
 | |
| 		return
 | |
| 	}
 | |
| 	// in general, if there was an error, it was likely "missing port",
 | |
| 	// so try adding a bogus port to take advantage of standard library's
 | |
| 	// robust parser, then strip the artificial port before returning
 | |
| 	// (don't overwrite original error though; might still be relevant)
 | |
| 	var err2 error
 | |
| 	host, port, err2 = net.SplitHostPort(a + ":0")
 | |
| 	if err2 == nil {
 | |
| 		err = nil
 | |
| 		port = ""
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // JoinNetworkAddress combines network, host, and port into a single
 | |
| // address string of the form accepted by ParseNetworkAddress(). For
 | |
| // unix sockets, the network should be "unix" (or "unixgram" or
 | |
| // "unixpacket") and the path to the socket should be given as the
 | |
| // host parameter.
 | |
| func JoinNetworkAddress(network, host, port string) string {
 | |
| 	var a string
 | |
| 	if network != "" {
 | |
| 		a = network + "/"
 | |
| 	}
 | |
| 	if (host != "" && port == "") || IsUnixNetwork(network) {
 | |
| 		a += host
 | |
| 	} else if port != "" {
 | |
| 		a += net.JoinHostPort(host, port)
 | |
| 	}
 | |
| 	return a
 | |
| }
 | |
| 
 | |
| // DEPRECATED: Use NetworkAddress.Listen instead. This function will likely be changed or removed in the future.
 | |
| func Listen(network, addr string) (net.Listener, error) {
 | |
| 	// a 0 timeout means Go uses its default
 | |
| 	return ListenTimeout(network, addr, 0)
 | |
| }
 | |
| 
 | |
| // DEPRECATED: Use NetworkAddress.Listen instead. This function will likely be changed or removed in the future.
 | |
| func ListenTimeout(network, addr string, keepalivePeriod time.Duration) (net.Listener, error) {
 | |
| 	netAddr, err := ParseNetworkAddress(JoinNetworkAddress(network, addr, ""))
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	ln, err := netAddr.Listen(context.TODO(), 0, net.ListenConfig{KeepAlive: keepalivePeriod})
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return ln.(net.Listener), nil
 | |
| }
 | |
| 
 | |
| // DEPRECATED: Use NetworkAddress.Listen instead. This function will likely be changed or removed in the future.
 | |
| func ListenPacket(network, addr string) (net.PacketConn, error) {
 | |
| 	netAddr, err := ParseNetworkAddress(JoinNetworkAddress(network, addr, ""))
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	ln, err := netAddr.Listen(context.TODO(), 0, net.ListenConfig{})
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return ln.(net.PacketConn), nil
 | |
| }
 | |
| 
 | |
| // ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module.
 | |
| // The network will be transformed into a QUIC-compatible type (if unix, then
 | |
| // unixgram will be used; otherwise, udp will be used).
 | |
| //
 | |
| // NOTE: This API is EXPERIMENTAL and may be changed or removed.
 | |
| //
 | |
| // TODO: See if we can find a more elegant solution closer to the new NetworkAddress.Listen API.
 | |
| func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (http3.QUICEarlyListener, error) {
 | |
| 	lnKey := listenerKey("quic+"+ln.LocalAddr().Network(), ln.LocalAddr().String())
 | |
| 
 | |
| 	sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
 | |
| 		sqtc := newSharedQUICTLSConfig(tlsConf)
 | |
| 		// http3.ConfigureTLSConfig only uses this field and tls App sets this field as well
 | |
| 		//nolint:gosec
 | |
| 		quicTlsConfig := &tls.Config{GetConfigForClient: sqtc.getConfigForClient}
 | |
| 		earlyLn, err := quic.ListenEarly(ln, http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{
 | |
| 			Allow0RTT: true,
 | |
| 			RequireAddressValidation: func(clientAddr net.Addr) bool {
 | |
| 				var highLoad bool
 | |
| 				if activeRequests != nil {
 | |
| 					highLoad = atomic.LoadInt64(activeRequests) > 1000 // TODO: make tunable?
 | |
| 				}
 | |
| 				return highLoad
 | |
| 			},
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return &sharedQuicListener{EarlyListener: earlyLn, sqtc: sqtc, key: lnKey}, nil
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	sql := sharedEarlyListener.(*sharedQuicListener)
 | |
| 	// add current tls.Config to sqtc, so GetConfigForClient will always return the latest tls.Config in case of context cancellation
 | |
| 	ctx, cancel := sql.sqtc.addTLSConfig(tlsConf)
 | |
| 
 | |
| 	// TODO: to serve QUIC over a unix socket, currently we need to hold onto
 | |
| 	// the underlying net.PacketConn (which we wrap as unixConn to keep count
 | |
| 	// of closes) because closing the quic.EarlyListener doesn't actually close
 | |
| 	// the underlying PacketConn, but we need to for unix sockets since we dup
 | |
| 	// the file descriptor and thus need to close the original; track issue:
 | |
| 	// https://github.com/quic-go/quic-go/issues/3560#issuecomment-1258959608
 | |
| 	var unix *unixConn
 | |
| 	if uc, ok := ln.(*unixConn); ok {
 | |
| 		unix = uc
 | |
| 	}
 | |
| 
 | |
| 	return &fakeCloseQuicListener{
 | |
| 		sharedQuicListener: sql,
 | |
| 		uc:                 unix,
 | |
| 		context:            ctx,
 | |
| 		contextCancel:      cancel,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // ListenerUsage returns the current usage count of the given listener address.
 | |
| func ListenerUsage(network, addr string) int {
 | |
| 	count, _ := listenerPool.References(listenerKey(network, addr))
 | |
| 	return count
 | |
| }
 | |
| 
 | |
| // contextAndCancelFunc groups context and its cancelFunc
 | |
| type contextAndCancelFunc struct {
 | |
| 	context.Context
 | |
| 	context.CancelFunc
 | |
| }
 | |
| 
 | |
| // sharedQUICTLSConfig manages GetConfigForClient
 | |
| // see issue: https://github.com/caddyserver/caddy/pull/4849
 | |
| type sharedQUICTLSConfig struct {
 | |
| 	rmu           sync.RWMutex
 | |
| 	tlsConfs      map[*tls.Config]contextAndCancelFunc
 | |
| 	activeTlsConf *tls.Config
 | |
| }
 | |
| 
 | |
| // newSharedQUICTLSConfig creates a new sharedQUICTLSConfig
 | |
| func newSharedQUICTLSConfig(tlsConfig *tls.Config) *sharedQUICTLSConfig {
 | |
| 	sqtc := &sharedQUICTLSConfig{
 | |
| 		tlsConfs:      make(map[*tls.Config]contextAndCancelFunc),
 | |
| 		activeTlsConf: tlsConfig,
 | |
| 	}
 | |
| 	sqtc.addTLSConfig(tlsConfig)
 | |
| 	return sqtc
 | |
| }
 | |
| 
 | |
| // getConfigForClient is used as tls.Config's GetConfigForClient field
 | |
| func (sqtc *sharedQUICTLSConfig) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Config, error) {
 | |
| 	sqtc.rmu.RLock()
 | |
| 	defer sqtc.rmu.RUnlock()
 | |
| 	return sqtc.activeTlsConf.GetConfigForClient(ch)
 | |
| }
 | |
| 
 | |
| // addTLSConfig adds tls.Config to the map if not present and returns the corresponding context and its cancelFunc
 | |
| // so that when cancelled, the active tls.Config will change
 | |
| func (sqtc *sharedQUICTLSConfig) addTLSConfig(tlsConfig *tls.Config) (context.Context, context.CancelFunc) {
 | |
| 	sqtc.rmu.Lock()
 | |
| 	defer sqtc.rmu.Unlock()
 | |
| 
 | |
| 	if cacc, ok := sqtc.tlsConfs[tlsConfig]; ok {
 | |
| 		return cacc.Context, cacc.CancelFunc
 | |
| 	}
 | |
| 
 | |
| 	ctx, cancel := context.WithCancel(context.Background())
 | |
| 	wrappedCancel := func() {
 | |
| 		cancel()
 | |
| 
 | |
| 		sqtc.rmu.Lock()
 | |
| 		defer sqtc.rmu.Unlock()
 | |
| 
 | |
| 		delete(sqtc.tlsConfs, tlsConfig)
 | |
| 		if sqtc.activeTlsConf == tlsConfig {
 | |
| 			// select another tls.Config, if there is none,
 | |
| 			// related sharedQuicListener will be destroyed anyway
 | |
| 			for tc := range sqtc.tlsConfs {
 | |
| 				sqtc.activeTlsConf = tc
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	sqtc.tlsConfs[tlsConfig] = contextAndCancelFunc{ctx, wrappedCancel}
 | |
| 	// there should be at most 2 tls.Configs
 | |
| 	if len(sqtc.tlsConfs) > 2 {
 | |
| 		Log().Warn("quic listener tls configs are more than 2", zap.Int("number of configs", len(sqtc.tlsConfs)))
 | |
| 	}
 | |
| 	return ctx, wrappedCancel
 | |
| }
 | |
| 
 | |
| // sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
 | |
| type sharedQuicListener struct {
 | |
| 	*quic.EarlyListener
 | |
| 	sqtc *sharedQUICTLSConfig
 | |
| 	key  string
 | |
| }
 | |
| 
 | |
| // Destruct closes the underlying QUIC listener.
 | |
| func (sql *sharedQuicListener) Destruct() error {
 | |
| 	return sql.EarlyListener.Close()
 | |
| }
 | |
| 
 | |
| // sharedPacketConn is like sharedListener, but for net.PacketConns.
 | |
| type sharedPacketConn struct {
 | |
| 	net.PacketConn
 | |
| 	key string
 | |
| }
 | |
| 
 | |
| // Destruct closes the underlying socket.
 | |
| func (spc *sharedPacketConn) Destruct() error {
 | |
| 	return spc.PacketConn.Close()
 | |
| }
 | |
| 
 | |
| // fakeClosedErr returns an error value that is not temporary
 | |
| // nor a timeout, suitable for making the caller think the
 | |
| // listener is actually closed
 | |
| func fakeClosedErr(l interface{ Addr() net.Addr }) error {
 | |
| 	return &net.OpError{
 | |
| 		Op:   "accept",
 | |
| 		Net:  l.Addr().Network(),
 | |
| 		Addr: l.Addr(),
 | |
| 		Err:  errFakeClosed,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // errFakeClosed is the underlying error value returned by
 | |
| // fakeCloseListener.Accept() after Close() has been called,
 | |
| // indicating that it is pretending to be closed so that the
 | |
| // server using it can terminate, while the underlying
 | |
| // socket is actually left open.
 | |
| var errFakeClosed = fmt.Errorf("listener 'closed' 😉")
 | |
| 
 | |
| // fakeClosePacketConn is like fakeCloseListener, but for PacketConns,
 | |
| // or more specifically, *net.UDPConn
 | |
| type fakeClosePacketConn struct {
 | |
| 	closed       int32             // accessed atomically; belongs to this struct only
 | |
| 	spc          *sharedPacketConn // its key is used in Close
 | |
| 	*net.UDPConn                   // embedded, so we also become a net.PacketConn and enable several other optimizations done by quic-go
 | |
| }
 | |
| 
 | |
| // interface guard for extra optimizations
 | |
| // needed by QUIC implementation: https://github.com/caddyserver/caddy/issues/3998, https://github.com/caddyserver/caddy/issues/5605
 | |
| var _ quic.OOBCapablePacketConn = (*fakeClosePacketConn)(nil)
 | |
| 
 | |
| // https://pkg.go.dev/golang.org/x/net/ipv4#NewPacketConn is used by quic-go and requires a net.PacketConn type assertable to a net.Conn,
 | |
| // but doesn't actually use these methods, the only methods needed are `ReadMsgUDP` and `SyscallConn`.
 | |
| var _ net.Conn = (*fakeClosePacketConn)(nil)
 | |
| 
 | |
| // Close won't close the underlying socket unless there is no more reference, then listenerPool will close it.
 | |
| func (fcpc *fakeClosePacketConn) Close() error {
 | |
| 	if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) {
 | |
| 		_, _ = listenerPool.Delete(fcpc.spc.key)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| type fakeCloseQuicListener struct {
 | |
| 	closed              int32     // accessed atomically; belongs to this struct only
 | |
| 	*sharedQuicListener           // embedded, so we also become a quic.EarlyListener
 | |
| 	uc                  *unixConn // underlying unix socket, if UDS
 | |
| 	context             context.Context
 | |
| 	contextCancel       context.CancelFunc
 | |
| }
 | |
| 
 | |
| // Currently Accept ignores the passed context, however a situation where
 | |
| // someone would need a hotswappable QUIC-only (not http3, since it uses context.Background here)
 | |
| // server on which Accept would be called with non-empty contexts
 | |
| // (mind that the default net listeners' Accept doesn't take a context argument)
 | |
| // sounds way too rare for us to sacrifice efficiency here.
 | |
| func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (quic.EarlyConnection, error) {
 | |
| 	conn, err := fcql.sharedQuicListener.Accept(fcql.context)
 | |
| 	if err == nil {
 | |
| 		return conn, nil
 | |
| 	}
 | |
| 
 | |
| 	// if the listener is "closed", return a fake closed error instead
 | |
| 	if atomic.LoadInt32(&fcql.closed) == 1 && errors.Is(err, context.Canceled) {
 | |
| 		return nil, fakeClosedErr(fcql)
 | |
| 	}
 | |
| 	return nil, err
 | |
| }
 | |
| 
 | |
| func (fcql *fakeCloseQuicListener) Close() error {
 | |
| 	if atomic.CompareAndSwapInt32(&fcql.closed, 0, 1) {
 | |
| 		fcql.contextCancel()
 | |
| 		_, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
 | |
| 		if fcql.uc != nil {
 | |
| 			// unix sockets need to be closed ourselves because we dup() the file
 | |
| 			// descriptor when we reuse them, so this avoids a resource leak
 | |
| 			fcql.uc.Close()
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // RegisterNetwork registers a network type with Caddy so that if a listener is
 | |
| // created for that network type, getListener will be invoked to get the listener.
 | |
| // This should be called during init() and will panic if the network type is standard
 | |
| // or reserved, or if it is already registered. EXPERIMENTAL and subject to change.
 | |
| func RegisterNetwork(network string, getListener ListenerFunc) {
 | |
| 	network = strings.TrimSpace(strings.ToLower(network))
 | |
| 
 | |
| 	if network == "tcp" || network == "tcp4" || network == "tcp6" ||
 | |
| 		network == "udp" || network == "udp4" || network == "udp6" ||
 | |
| 		network == "unix" || network == "unixpacket" || network == "unixgram" ||
 | |
| 		strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) {
 | |
| 		panic("network type " + network + " is reserved")
 | |
| 	}
 | |
| 
 | |
| 	if _, ok := networkTypes[strings.ToLower(network)]; ok {
 | |
| 		panic("network type " + network + " is already registered")
 | |
| 	}
 | |
| 
 | |
| 	networkTypes[network] = getListener
 | |
| }
 | |
| 
 | |
| type unixConn struct {
 | |
| 	*net.UnixConn
 | |
| 	filename string
 | |
| 	mapKey   string
 | |
| 	count    *int32 // accessed atomically
 | |
| }
 | |
| 
 | |
| func (uc *unixConn) Close() error {
 | |
| 	newCount := atomic.AddInt32(uc.count, -1)
 | |
| 	if newCount == 0 {
 | |
| 		defer func() {
 | |
| 			unixSocketsMu.Lock()
 | |
| 			delete(unixSockets, uc.mapKey)
 | |
| 			unixSocketsMu.Unlock()
 | |
| 			_ = syscall.Unlink(uc.filename)
 | |
| 		}()
 | |
| 	}
 | |
| 	return uc.UnixConn.Close()
 | |
| }
 | |
| 
 | |
| // unixSockets keeps track of the currently-active unix sockets
 | |
| // so we can transfer their FDs gracefully during reloads.
 | |
| var (
 | |
| 	unixSockets = make(map[string]interface {
 | |
| 		File() (*os.File, error)
 | |
| 	})
 | |
| 	unixSocketsMu sync.Mutex
 | |
| )
 | |
| 
 | |
| // getListenerFromPlugin returns a listener on the given network and address
 | |
| // if a plugin has registered the network name. It may return (nil, nil) if
 | |
| // no plugin can provide a listener.
 | |
| func getListenerFromPlugin(ctx context.Context, network, addr string, config net.ListenConfig) (any, error) {
 | |
| 	// get listener from plugin if network type is registered
 | |
| 	if getListener, ok := networkTypes[network]; ok {
 | |
| 		Log().Debug("getting listener from plugin", zap.String("network", network))
 | |
| 		return getListener(ctx, network, addr, config)
 | |
| 	}
 | |
| 
 | |
| 	return nil, nil
 | |
| }
 | |
| 
 | |
| func listenerKey(network, addr string) string {
 | |
| 	return network + "/" + addr
 | |
| }
 | |
| 
 | |
| // ListenerFunc is a function that can return a listener given a network and address.
 | |
| // The listeners must be capable of overlapping: with Caddy, new configs are loaded
 | |
| // before old ones are unloaded, so listeners may overlap briefly if the configs
 | |
| // both need the same listener. EXPERIMENTAL and subject to change.
 | |
| type ListenerFunc func(ctx context.Context, network, addr string, cfg net.ListenConfig) (any, error)
 | |
| 
 | |
| var networkTypes = map[string]ListenerFunc{}
 | |
| 
 | |
| // ListenerWrapper is a type that wraps a listener
 | |
| // so it can modify the input listener's methods.
 | |
| // Modules that implement this interface are found
 | |
| // in the caddy.listeners namespace. Usually, to
 | |
| // wrap a listener, you will define your own struct
 | |
| // type that embeds the input listener, then
 | |
| // implement your own methods that you want to wrap,
 | |
| // calling the underlying listener's methods where
 | |
| // appropriate.
 | |
| type ListenerWrapper interface {
 | |
| 	WrapListener(net.Listener) net.Listener
 | |
| }
 | |
| 
 | |
| // listenerPool stores and allows reuse of active listeners.
 | |
| var listenerPool = NewUsagePool()
 | |
| 
 | |
| const maxPortSpan = 65535
 | |
| 
 | |
| // Interface guards (see https://github.com/caddyserver/caddy/issues/3998)
 | |
| var (
 | |
| 	_ (interface{ SetReadBuffer(int) error }) = (*fakeClosePacketConn)(nil)
 | |
| 	_ (interface {
 | |
| 		SyscallConn() (syscall.RawConn, error)
 | |
| 	}) = (*fakeClosePacketConn)(nil)
 | |
| )
 |