2019-06-30 17:07:58 -05:00
|
|
|
// 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.
|
|
|
|
|
2019-06-14 12:58:28 -05:00
|
|
|
package caddy
|
2019-03-26 16:45:51 -05:00
|
|
|
|
|
|
|
import (
|
2022-03-22 18:47:57 -05:00
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
|
|
|
"errors"
|
2019-03-26 16:45:51 -05:00
|
|
|
"fmt"
|
2022-09-28 14:35:51 -05:00
|
|
|
"io"
|
2023-06-23 15:49:41 -05:00
|
|
|
"io/fs"
|
2019-03-26 16:45:51 -05:00
|
|
|
"net"
|
2022-08-17 17:10:57 -05:00
|
|
|
"net/netip"
|
2022-01-19 14:26:44 -05:00
|
|
|
"os"
|
2019-07-08 17:46:38 -05:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2022-09-28 14:35:51 -05:00
|
|
|
"sync"
|
2019-03-26 16:45:51 -05:00
|
|
|
"sync/atomic"
|
2021-02-16 15:55:49 -05:00
|
|
|
"syscall"
|
2022-09-28 14:35:51 -05:00
|
|
|
"time"
|
2022-03-22 18:47:57 -05:00
|
|
|
|
2023-02-06 11:29:20 -05:00
|
|
|
"github.com/quic-go/quic-go"
|
|
|
|
"github.com/quic-go/quic-go/http3"
|
2022-09-01 17:30:03 -05:00
|
|
|
"go.uber.org/zap"
|
2023-08-14 10:41:15 -05:00
|
|
|
|
|
|
|
"github.com/caddyserver/caddy/v2/internal"
|
2019-03-26 16:45:51 -05:00
|
|
|
)
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// 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
|
2019-03-26 16:45:51 -05:00
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// 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:
|
|
|
|
}
|
2019-09-10 09:03:37 -05:00
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// create (or reuse) the listener ourselves
|
|
|
|
var ln any
|
|
|
|
ln, err = na.Listen(ctx, portOffset, config)
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-09-28 14:35:51 -05:00
|
|
|
listeners = append(listeners, ln)
|
2019-09-10 09:03:37 -05:00
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
return listeners, nil
|
2019-09-10 09:03:37 -05:00
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// 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()
|
2022-09-08 13:36:31 -05:00
|
|
|
}
|
2022-03-22 18:47:57 -05:00
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// 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
|
|
|
|
}
|
2022-03-22 18:47:57 -05:00
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// create (or reuse) the listener ourselves
|
|
|
|
return na.listen(ctx, portOffset, config)
|
2022-08-03 12:04:51 -05:00
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
|
|
|
var ln any
|
|
|
|
var err error
|
2023-06-23 15:49:41 -05:00
|
|
|
var address string
|
|
|
|
var unixFileMode fs.FileMode
|
2023-06-24 19:25:02 -05:00
|
|
|
var isAbtractUnixSocket bool
|
2019-03-26 16:45:51 -05:00
|
|
|
|
2023-06-23 15:49:41 -05:00
|
|
|
// split unix socket addr early so lnKey
|
|
|
|
// is independent of permissions bits
|
|
|
|
if na.IsUnixNetwork() {
|
|
|
|
var err error
|
2023-08-05 19:09:16 -05:00
|
|
|
address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host)
|
2023-06-23 15:49:41 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-24 19:25:02 -05:00
|
|
|
isAbtractUnixSocket = strings.HasPrefix(address, "@")
|
2023-06-23 15:49:41 -05:00
|
|
|
} else {
|
|
|
|
address = na.JoinHostPort(portOffset)
|
|
|
|
}
|
2022-03-22 18:47:57 -05:00
|
|
|
|
2023-06-23 15:49:41 -05:00
|
|
|
// if this is a unix socket, see if we already have it open,
|
|
|
|
// force socket permissions on it and return early
|
2022-09-28 14:35:51 -05:00
|
|
|
if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil {
|
2023-06-24 19:25:02 -05:00
|
|
|
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)
|
|
|
|
}
|
2023-06-23 15:49:41 -05:00
|
|
|
}
|
2022-09-28 14:35:51 -05:00
|
|
|
return socket, err
|
2022-03-22 18:47:57 -05:00
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
lnKey := listenerKey(na.Network, address)
|
2022-03-22 18:47:57 -05:00
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
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
|
|
|
|
}
|
2023-06-30 17:31:26 -05:00
|
|
|
spc := sharedPc.(*sharedPacketConn)
|
|
|
|
ln = &fakeClosePacketConn{spc: spc, UDPConn: spc.PacketConn.(*net.UDPConn)}
|
2022-03-22 18:47:57 -05:00
|
|
|
}
|
2022-09-28 14:35:51 -05:00
|
|
|
if strings.HasPrefix(na.Network, "ip") {
|
|
|
|
ln, err = config.ListenPacket(ctx, na.Network, address)
|
2019-04-02 15:58:24 -05:00
|
|
|
}
|
2022-09-28 14:35:51 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-09-10 09:03:37 -05:00
|
|
|
}
|
2022-09-28 14:35:51 -05:00
|
|
|
if ln == nil {
|
|
|
|
return nil, fmt.Errorf("unsupported network type: %s", na.Network)
|
2021-02-16 15:55:49 -05:00
|
|
|
}
|
|
|
|
|
2023-06-21 18:47:23 -05:00
|
|
|
// 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
|
2021-02-16 15:55:49 -05:00
|
|
|
}
|
2019-04-02 16:31:02 -05:00
|
|
|
|
2023-06-23 15:49:41 -05:00
|
|
|
if IsUnixNetwork(na.Network) {
|
2023-06-24 19:25:02 -05:00
|
|
|
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)
|
|
|
|
}
|
2023-06-23 15:49:41 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
return ln, nil
|
2019-11-11 17:33:38 -05:00
|
|
|
}
|
|
|
|
|
2020-04-07 09:33:45 -05:00
|
|
|
// IsUnixNetwork returns true if na.Network is
|
2019-12-06 13:45:50 -05:00
|
|
|
// unix, unixgram, or unixpacket.
|
2020-04-07 09:33:45 -05:00
|
|
|
func (na NetworkAddress) IsUnixNetwork() bool {
|
2023-02-08 12:05:09 -05:00
|
|
|
return IsUnixNetwork(na.Network)
|
2019-12-06 13:45:50 -05:00
|
|
|
}
|
|
|
|
|
2019-11-11 17:33:38 -05:00
|
|
|
// JoinHostPort is like net.JoinHostPort, but where the port
|
|
|
|
// is StartPort + offset.
|
2020-04-07 09:33:45 -05:00
|
|
|
func (na NetworkAddress) JoinHostPort(offset uint) string {
|
|
|
|
if na.IsUnixNetwork() {
|
|
|
|
return na.Host
|
2019-12-06 13:45:50 -05:00
|
|
|
}
|
2020-04-07 09:33:45 -05:00
|
|
|
return net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort+offset)))
|
2019-11-11 17:33:38 -05:00
|
|
|
}
|
2019-07-08 17:46:38 -05:00
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// Expand returns one NetworkAddress for each port in the port range.
|
|
|
|
//
|
|
|
|
// This is EXPERIMENTAL and subject to change or removal.
|
2022-08-03 12:04:51 -05:00
|
|
|
func (na NetworkAddress) Expand() []NetworkAddress {
|
|
|
|
size := na.PortRangeSize()
|
|
|
|
addrs := make([]NetworkAddress, size)
|
|
|
|
for portOffset := uint(0); portOffset < size; portOffset++ {
|
2022-09-28 14:35:51 -05:00
|
|
|
addrs[portOffset] = na.At(portOffset)
|
2022-08-03 12:04:51 -05:00
|
|
|
}
|
|
|
|
return addrs
|
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-11-11 17:33:38 -05:00
|
|
|
// 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.
|
2020-04-07 09:33:45 -05:00
|
|
|
func (na NetworkAddress) PortRangeSize() uint {
|
2022-08-03 12:04:51 -05:00
|
|
|
if na.EndPort < na.StartPort {
|
|
|
|
return 0
|
|
|
|
}
|
2020-04-07 09:33:45 -05:00
|
|
|
return (na.EndPort - na.StartPort) + 1
|
2019-11-11 17:33:38 -05:00
|
|
|
}
|
|
|
|
|
2020-04-10 18:31:38 -05:00
|
|
|
func (na NetworkAddress) isLoopback() bool {
|
|
|
|
if na.IsUnixNetwork() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if na.Host == "localhost" {
|
|
|
|
return true
|
|
|
|
}
|
2022-08-17 17:10:57 -05:00
|
|
|
if ip, err := netip.ParseAddr(na.Host); err == nil {
|
2020-04-10 18:31:38 -05:00
|
|
|
return ip.IsLoopback()
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-04-16 12:41:32 -05:00
|
|
|
func (na NetworkAddress) isWildcardInterface() bool {
|
|
|
|
if na.Host == "" {
|
|
|
|
return true
|
|
|
|
}
|
2022-08-17 17:10:57 -05:00
|
|
|
if ip, err := netip.ParseAddr(na.Host); err == nil {
|
2020-04-16 12:41:32 -05:00
|
|
|
return ip.IsUnspecified()
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-04-10 18:31:38 -05:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2022-07-25 18:28:20 -05:00
|
|
|
// 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.
|
2020-04-07 09:33:45 -05:00
|
|
|
func (na NetworkAddress) String() string {
|
2022-07-25 18:28:20 -05:00
|
|
|
if na.Network == "tcp" && (na.Host != "" || na.port() != "") {
|
|
|
|
na.Network = "" // omit default network value for brevity
|
|
|
|
}
|
2020-04-10 18:31:38 -05:00
|
|
|
return JoinNetworkAddress(na.Network, na.Host, na.port())
|
2019-11-11 17:33:38 -05:00
|
|
|
}
|
|
|
|
|
2023-02-08 12:05:09 -05:00
|
|
|
// IsUnixNetwork returns true if the netw is a unix network.
|
|
|
|
func IsUnixNetwork(netw string) bool {
|
|
|
|
return strings.HasPrefix(netw, "unix")
|
2019-12-06 13:45:50 -05:00
|
|
|
}
|
|
|
|
|
2019-11-11 17:33:38 -05:00
|
|
|
// 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.
|
2020-04-07 09:33:45 -05:00
|
|
|
func ParseNetworkAddress(addr string) (NetworkAddress, error) {
|
2023-05-03 12:07:22 -05:00
|
|
|
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) {
|
2019-07-08 17:46:38 -05:00
|
|
|
var host, port string
|
2019-11-11 17:33:38 -05:00
|
|
|
network, host, port, err := SplitNetworkAddress(addr)
|
2019-07-08 17:46:38 -05:00
|
|
|
if err != nil {
|
2020-04-07 09:33:45 -05:00
|
|
|
return NetworkAddress{}, err
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
2022-07-25 18:28:20 -05:00
|
|
|
if network == "" {
|
2023-05-03 12:07:22 -05:00
|
|
|
network = defaultNetwork
|
2022-07-25 18:28:20 -05:00
|
|
|
}
|
2023-02-08 12:05:09 -05:00
|
|
|
if IsUnixNetwork(network) {
|
2023-08-05 19:09:16 -05:00
|
|
|
_, _, err := internal.SplitUnixSocketPermissionsBits(host)
|
2020-04-07 09:33:45 -05:00
|
|
|
return NetworkAddress{
|
2019-11-11 17:33:38 -05:00
|
|
|
Network: network,
|
|
|
|
Host: host,
|
2023-06-23 15:49:41 -05:00
|
|
|
}, err
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
2019-11-11 17:33:38 -05:00
|
|
|
var start, end uint64
|
2023-05-03 12:07:22 -05:00
|
|
|
if port == "" {
|
|
|
|
start = uint64(defaultPort)
|
|
|
|
end = uint64(defaultPort)
|
|
|
|
} else {
|
2022-08-04 12:17:35 -05:00
|
|
|
before, after, found := strings.Cut(port, "-")
|
|
|
|
if !found {
|
|
|
|
after = before
|
2022-07-25 18:28:20 -05:00
|
|
|
}
|
2022-08-04 12:17:35 -05:00
|
|
|
start, err = strconv.ParseUint(before, 10, 16)
|
2022-07-25 18:28:20 -05:00
|
|
|
if err != nil {
|
|
|
|
return NetworkAddress{}, fmt.Errorf("invalid start port: %v", err)
|
|
|
|
}
|
2022-08-04 12:17:35 -05:00
|
|
|
end, err = strconv.ParseUint(after, 10, 16)
|
2022-07-25 18:28:20 -05:00
|
|
|
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)
|
|
|
|
}
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
2020-04-07 09:33:45 -05:00
|
|
|
return NetworkAddress{
|
2019-11-11 17:33:38 -05:00
|
|
|
Network: network,
|
|
|
|
Host: host,
|
|
|
|
StartPort: uint(start),
|
|
|
|
EndPort: uint(end),
|
|
|
|
}, nil
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
|
|
|
|
2019-09-05 14:14:39 -05:00
|
|
|
// SplitNetworkAddress splits a into its network, host, and port components.
|
2019-11-11 17:33:38 -05:00
|
|
|
// Note that port may be a port range (:X-Y), or omitted for unix sockets.
|
2019-09-05 14:14:39 -05:00
|
|
|
func SplitNetworkAddress(a string) (network, host, port string, err error) {
|
2022-08-04 12:17:35 -05:00
|
|
|
beforeSlash, afterSlash, slashFound := strings.Cut(a, "/")
|
|
|
|
if slashFound {
|
|
|
|
network = strings.ToLower(strings.TrimSpace(beforeSlash))
|
|
|
|
a = afterSlash
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
2023-02-08 12:05:09 -05:00
|
|
|
if IsUnixNetwork(network) {
|
2019-07-08 17:46:38 -05:00
|
|
|
host = a
|
|
|
|
return
|
|
|
|
}
|
|
|
|
host, port, err = net.SplitHostPort(a)
|
2022-07-25 18:28:20 -05:00
|
|
|
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 = ""
|
|
|
|
}
|
2019-07-08 17:46:38 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-05 14:14:39 -05:00
|
|
|
// JoinNetworkAddress combines network, host, and port into a single
|
2019-12-06 14:00:04 -05:00
|
|
|
// 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
|
2019-11-11 17:33:38 -05:00
|
|
|
// host parameter.
|
2019-09-05 14:14:39 -05:00
|
|
|
func JoinNetworkAddress(network, host, port string) string {
|
2019-07-08 17:46:38 -05:00
|
|
|
var a string
|
|
|
|
if network != "" {
|
|
|
|
a = network + "/"
|
|
|
|
}
|
2023-02-08 12:05:09 -05:00
|
|
|
if (host != "" && port == "") || IsUnixNetwork(network) {
|
2019-10-11 15:25:39 -05:00
|
|
|
a += host
|
|
|
|
} else if port != "" {
|
|
|
|
a += net.JoinHostPort(host, port)
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
2019-11-11 17:33:38 -05:00
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// 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.
|
2023-05-30 04:41:57 -05:00
|
|
|
func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) (http3.QUICEarlyListener, error) {
|
2022-09-29 11:32:02 -05:00
|
|
|
lnKey := listenerKey("quic+"+ln.LocalAddr().Network(), ln.LocalAddr().String())
|
2022-09-28 14:35:51 -05:00
|
|
|
|
|
|
|
sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
2023-05-10 15:25:09 -05:00
|
|
|
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{
|
2023-05-30 04:41:57 -05:00
|
|
|
Allow0RTT: true,
|
2022-09-28 14:35:51 -05:00
|
|
|
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
|
|
|
|
}
|
2023-05-10 15:25:09 -05:00
|
|
|
return &sharedQuicListener{EarlyListener: earlyLn, sqtc: sqtc, key: lnKey}, nil
|
2022-09-28 14:35:51 -05:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-05-10 15:25:09 -05:00
|
|
|
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)
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// 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:
|
2023-02-06 11:29:20 -05:00
|
|
|
// https://github.com/quic-go/quic-go/issues/3560#issuecomment-1258959608
|
2022-09-28 14:35:51 -05:00
|
|
|
var unix *unixConn
|
|
|
|
if uc, ok := ln.(*unixConn); ok {
|
|
|
|
unix = uc
|
|
|
|
}
|
|
|
|
|
|
|
|
return &fakeCloseQuicListener{
|
2023-05-10 15:25:09 -05:00
|
|
|
sharedQuicListener: sql,
|
2022-09-28 14:35:51 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-05-10 15:25:09 -05:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
|
|
|
|
type sharedQuicListener struct {
|
2023-05-30 04:41:57 -05:00
|
|
|
*quic.EarlyListener
|
2023-05-10 15:25:09 -05:00
|
|
|
sqtc *sharedQUICTLSConfig
|
|
|
|
key string
|
2022-09-28 14:35:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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' 😉")
|
|
|
|
|
2023-06-30 17:31:26 -05:00
|
|
|
// fakeClosePacketConn is like fakeCloseListener, but for PacketConns,
|
|
|
|
// or more specifically, *net.UDPConn
|
2022-09-28 14:35:51 -05:00
|
|
|
type fakeClosePacketConn struct {
|
2023-06-30 17:31:26 -05:00
|
|
|
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
|
2022-09-28 14:35:51 -05:00
|
|
|
}
|
|
|
|
|
2023-06-30 17:31:26 -05:00
|
|
|
// 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.
|
2022-09-28 14:35:51 -05:00
|
|
|
func (fcpc *fakeClosePacketConn) Close() error {
|
|
|
|
if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) {
|
2023-06-30 17:31:26 -05:00
|
|
|
_, _ = listenerPool.Delete(fcpc.spc.key)
|
2022-09-28 14:35:51 -05:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-09-01 17:30:03 -05:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-09-01 17:30:03 -05:00
|
|
|
// 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.
|
2022-09-28 14:35:51 -05:00
|
|
|
type ListenerFunc func(ctx context.Context, network, addr string, cfg net.ListenConfig) (any, error)
|
2022-09-01 17:30:03 -05:00
|
|
|
|
|
|
|
var networkTypes = map[string]ListenerFunc{}
|
|
|
|
|
2020-03-15 22:26:17 -05:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// listenerPool stores and allows reuse of active listeners.
|
|
|
|
var listenerPool = NewUsagePool()
|
2019-11-11 17:33:38 -05:00
|
|
|
|
|
|
|
const maxPortSpan = 65535
|
2021-02-16 15:55:49 -05:00
|
|
|
|
|
|
|
// Interface guards (see https://github.com/caddyserver/caddy/issues/3998)
|
|
|
|
var (
|
|
|
|
_ (interface{ SetReadBuffer(int) error }) = (*fakeClosePacketConn)(nil)
|
|
|
|
_ (interface {
|
|
|
|
SyscallConn() (syscall.RawConn, error)
|
|
|
|
}) = (*fakeClosePacketConn)(nil)
|
|
|
|
)
|