2019-06-30 16:07:58 -06: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 11:58:28 -06:00
|
|
|
package caddy
|
2019-03-26 15:45:51 -06:00
|
|
|
|
|
|
|
|
import (
|
2022-03-23 02:47:57 +03:00
|
|
|
"context"
|
|
|
|
|
"crypto/tls"
|
|
|
|
|
"errors"
|
2019-03-26 15:45:51 -06:00
|
|
|
"fmt"
|
2022-09-28 13:35:51 -06:00
|
|
|
"io"
|
2023-06-23 22:49:41 +02:00
|
|
|
"io/fs"
|
2019-03-26 15:45:51 -06:00
|
|
|
"net"
|
2022-08-18 00:10:57 +02:00
|
|
|
"net/netip"
|
2022-01-19 19:26:44 +00:00
|
|
|
"os"
|
2025-10-24 08:33:20 +02:00
|
|
|
"runtime"
|
2019-07-08 16:46:38 -06:00
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
2022-09-28 13:35:51 -06:00
|
|
|
"sync"
|
2019-03-26 15:45:51 -06:00
|
|
|
"sync/atomic"
|
2022-03-23 02:47:57 +03:00
|
|
|
|
2023-02-06 11:29:20 -05:00
|
|
|
"github.com/quic-go/quic-go"
|
|
|
|
|
"github.com/quic-go/quic-go/http3"
|
2025-11-08 00:41:15 -05:00
|
|
|
h3qlog "github.com/quic-go/quic-go/http3/qlog"
|
2022-09-01 16:30:03 -06:00
|
|
|
"go.uber.org/zap"
|
2024-03-22 02:56:10 +10:00
|
|
|
"golang.org/x/time/rate"
|
2023-08-14 23:41:15 +08:00
|
|
|
|
|
|
|
|
"github.com/caddyserver/caddy/v2/internal"
|
2019-03-26 15:45:51 -06:00
|
|
|
)
|
|
|
|
|
|
2025-10-21 06:55:42 +02:00
|
|
|
// listenFdsStart is the first file descriptor number for systemd socket activation.
|
|
|
|
|
// File descriptors 0, 1, 2 are reserved for stdin, stdout, stderr.
|
|
|
|
|
const listenFdsStart = 3
|
|
|
|
|
|
2025-10-24 08:33:20 +02:00
|
|
|
// InterfaceDelimiter is used to separate interface name from binding mode
|
|
|
|
|
const InterfaceDelimiter = "||"
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// maxInterfaceNameUnix represents the maximum interface name length on Unix-like systems
|
|
|
|
|
// Based on IFNAMSIZ = 16 (15 characters + null terminator)
|
|
|
|
|
maxInterfaceNameUnix = 15
|
|
|
|
|
|
|
|
|
|
// maxInterfaceNameWindows represents the maximum interface name length on Windows
|
|
|
|
|
// These systems use a more complex naming structure with MAX_ADAPTER_NAME_LENGTH allowing 256 characters
|
|
|
|
|
maxInterfaceNameWindows = 255
|
|
|
|
|
)
|
|
|
|
|
|
2022-09-28 13:35:51 -06: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 15:45:51 -06:00
|
|
|
}
|
|
|
|
|
|
2024-09-30 12:55:03 -04:00
|
|
|
// ListenAll calls Listen for all addresses represented by this struct, i.e. all ports in the range.
|
2022-09-28 13:35:51 -06:00
|
|
|
// (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.
|
|
|
|
|
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 08:03:37 -06:00
|
|
|
|
2022-09-28 13:35:51 -06: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-10 23:24:58 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-09-28 13:35:51 -06:00
|
|
|
listeners = append(listeners, ln)
|
2019-09-10 08:03:37 -06:00
|
|
|
}
|
|
|
|
|
|
2022-09-28 13:35:51 -06:00
|
|
|
return listeners, nil
|
2019-09-10 08:03:37 -06:00
|
|
|
}
|
|
|
|
|
|
2022-09-28 13:35:51 -06: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.)
|
|
|
|
|
//
|
2024-09-30 12:55:03 -04:00
|
|
|
// First Listen checks if a plugin can provide a listener from this address. Otherwise,
|
|
|
|
|
// the provided ListenConfig is used to create the listener. Its Control function,
|
2022-09-28 13:35:51 -06:00
|
|
|
// 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).
|
2024-09-30 12:55:03 -04:00
|
|
|
// Listen synchronizes binds to unix domain sockets to avoid race conditions
|
|
|
|
|
// while an existing socket is unlinked.
|
2022-09-28 13:35:51 -06:00
|
|
|
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 12:36:31 -06:00
|
|
|
}
|
2022-03-23 02:47:57 +03:00
|
|
|
|
2025-10-24 08:33:20 +02:00
|
|
|
// If this is an interface name, resolve it to an IP address and create a listener
|
|
|
|
|
if na.IsInterfaceNetwork() {
|
|
|
|
|
return na.listenInterface(ctx, portOffset, config)
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 13:35:51 -06:00
|
|
|
// check to see if plugin provides listener
|
2024-12-12 17:34:50 -05:00
|
|
|
if ln, err := getListenerFromPlugin(ctx, na.Network, na.Host, na.port(), portOffset, config); ln != nil || err != nil {
|
2022-09-28 13:35:51 -06:00
|
|
|
return ln, err
|
|
|
|
|
}
|
2022-03-23 02:47:57 +03:00
|
|
|
|
2022-09-28 13:35:51 -06:00
|
|
|
// create (or reuse) the listener ourselves
|
|
|
|
|
return na.listen(ctx, portOffset, config)
|
2022-08-03 11:04:51 -06:00
|
|
|
}
|
|
|
|
|
|
2025-10-24 08:33:20 +02:00
|
|
|
// listenInterface resolves an interface name to an IP address and creates a listener.
|
|
|
|
|
// For all binding modes, this function always returns one listener using the first resolved IP.
|
|
|
|
|
// For multi-address modes (ipv4, ipv6, all), the expansion to multiple listeners
|
|
|
|
|
// happens at the HTTP Caddyfile parser level (see addresses.go).
|
|
|
|
|
func (na NetworkAddress) listenInterface(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
|
|
|
|
// Decode interface name and mode from Host field
|
|
|
|
|
// Format: "interface_name||mode"
|
|
|
|
|
var ifaceName string
|
|
|
|
|
mode := InterfaceBindingAuto
|
|
|
|
|
|
|
|
|
|
if strings.Contains(na.Host, InterfaceDelimiter) {
|
|
|
|
|
parts := strings.SplitN(na.Host, InterfaceDelimiter, 2)
|
|
|
|
|
if len(parts) == 2 {
|
|
|
|
|
ifaceName = parts[0]
|
|
|
|
|
mode = InterfaceBindingMode(parts[1])
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
ifaceName = na.Host
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resolve interface name to IP addresses with mode
|
|
|
|
|
ipAddresses, err := ResolveInterfaceNameWithMode(ifaceName, mode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to resolve interface %s with mode %s: %v", ifaceName, mode, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resolvedNA := na
|
|
|
|
|
resolvedNA.Host = ipAddresses[0]
|
|
|
|
|
|
|
|
|
|
Log().Debug("resolved interface to IP",
|
|
|
|
|
zap.String("interface", ifaceName),
|
|
|
|
|
zap.String("mode", string(mode)),
|
|
|
|
|
zap.String("selected_ip", ipAddresses[0]))
|
|
|
|
|
|
|
|
|
|
return resolvedNA.listen(ctx, portOffset, config)
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 13:35:51 -06:00
|
|
|
func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
core: Apply SO_REUSEPORT to UDP sockets (#5725)
* core: Apply SO_REUSEPORT to UDP sockets
For some reason, 10 months ago when I implemented SO_REUSEPORT
for TCP, I didn't realize, or forgot, that it can be used for UDP too. It is a
much better solution than using deadline hacks to reuse a socket, at
least for TCP.
Then https://github.com/mholt/caddy-l4/issues/132 was posted,
in which we see that UDP servers never actually stopped when the
L4 app was stopped. I verified this using this command:
$ nc -u 127.0.0.1 55353
combined with POSTing configs to the /load admin endpoint (which
alternated between an echo server and a proxy server so I could tell
which config was being used).
I refactored the code to use SO_REUSEPORT for UDP, but of course
we still need graceful reloads on all platforms, not just Unix, so I
also implemented a deadline hack similar to what we used for
TCP before. That implementation for TCP was not perfect, possibly
having a logical (not data) race condition; but for UDP so far it
seems to be working. Verified the same way I verified that SO_REUSEPORT
works.
I think this code is slightly cleaner and I'm fairly confident this code
is effective.
* Check error
* Fix return
* Fix var name
* implement Unwrap interface and clean up
* move unix packet conn to platform specific file
* implement Unwrap for unix packet conn
* Move sharedPacketConn into proper file
* Fix Windows
* move sharedPacketConn and fakeClosePacketConn to proper file
---------
Co-authored-by: Weidi Deng <weidi_deng@icloud.com>
2023-10-16 22:17:32 -06:00
|
|
|
var (
|
2024-09-30 12:55:03 -04:00
|
|
|
ln any
|
|
|
|
|
err error
|
|
|
|
|
address string
|
|
|
|
|
unixFileMode fs.FileMode
|
core: Apply SO_REUSEPORT to UDP sockets (#5725)
* core: Apply SO_REUSEPORT to UDP sockets
For some reason, 10 months ago when I implemented SO_REUSEPORT
for TCP, I didn't realize, or forgot, that it can be used for UDP too. It is a
much better solution than using deadline hacks to reuse a socket, at
least for TCP.
Then https://github.com/mholt/caddy-l4/issues/132 was posted,
in which we see that UDP servers never actually stopped when the
L4 app was stopped. I verified this using this command:
$ nc -u 127.0.0.1 55353
combined with POSTing configs to the /load admin endpoint (which
alternated between an echo server and a proxy server so I could tell
which config was being used).
I refactored the code to use SO_REUSEPORT for UDP, but of course
we still need graceful reloads on all platforms, not just Unix, so I
also implemented a deadline hack similar to what we used for
TCP before. That implementation for TCP was not perfect, possibly
having a logical (not data) race condition; but for UDP so far it
seems to be working. Verified the same way I verified that SO_REUSEPORT
works.
I think this code is slightly cleaner and I'm fairly confident this code
is effective.
* Check error
* Fix return
* Fix var name
* implement Unwrap interface and clean up
* move unix packet conn to platform specific file
* implement Unwrap for unix packet conn
* Move sharedPacketConn into proper file
* Fix Windows
* move sharedPacketConn and fakeClosePacketConn to proper file
---------
Co-authored-by: Weidi Deng <weidi_deng@icloud.com>
2023-10-16 22:17:32 -06:00
|
|
|
)
|
2019-03-26 15:45:51 -06:00
|
|
|
|
2023-06-23 22:49:41 +02:00
|
|
|
// split unix socket addr early so lnKey
|
|
|
|
|
// is independent of permissions bits
|
|
|
|
|
if na.IsUnixNetwork() {
|
2023-08-06 02:09:16 +02:00
|
|
|
address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host)
|
2023-06-23 22:49:41 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-09-30 12:55:03 -04:00
|
|
|
} else if na.IsFdNetwork() {
|
|
|
|
|
address = na.Host
|
2023-06-23 22:49:41 +02:00
|
|
|
} else {
|
|
|
|
|
address = na.JoinHostPort(portOffset)
|
|
|
|
|
}
|
2022-03-23 02:47:57 +03:00
|
|
|
|
2022-09-28 13:35:51 -06:00
|
|
|
if strings.HasPrefix(na.Network, "ip") {
|
|
|
|
|
ln, err = config.ListenPacket(ctx, na.Network, address)
|
core: Apply SO_REUSEPORT to UDP sockets (#5725)
* core: Apply SO_REUSEPORT to UDP sockets
For some reason, 10 months ago when I implemented SO_REUSEPORT
for TCP, I didn't realize, or forgot, that it can be used for UDP too. It is a
much better solution than using deadline hacks to reuse a socket, at
least for TCP.
Then https://github.com/mholt/caddy-l4/issues/132 was posted,
in which we see that UDP servers never actually stopped when the
L4 app was stopped. I verified this using this command:
$ nc -u 127.0.0.1 55353
combined with POSTing configs to the /load admin endpoint (which
alternated between an echo server and a proxy server so I could tell
which config was being used).
I refactored the code to use SO_REUSEPORT for UDP, but of course
we still need graceful reloads on all platforms, not just Unix, so I
also implemented a deadline hack similar to what we used for
TCP before. That implementation for TCP was not perfect, possibly
having a logical (not data) race condition; but for UDP so far it
seems to be working. Verified the same way I verified that SO_REUSEPORT
works.
I think this code is slightly cleaner and I'm fairly confident this code
is effective.
* Check error
* Fix return
* Fix var name
* implement Unwrap interface and clean up
* move unix packet conn to platform specific file
* implement Unwrap for unix packet conn
* Move sharedPacketConn into proper file
* Fix Windows
* move sharedPacketConn and fakeClosePacketConn to proper file
---------
Co-authored-by: Weidi Deng <weidi_deng@icloud.com>
2023-10-16 22:17:32 -06:00
|
|
|
} else {
|
2024-09-30 12:55:03 -04:00
|
|
|
if na.IsUnixNetwork() {
|
|
|
|
|
// if this is a unix socket, see if we already have it open
|
|
|
|
|
ln, err = reuseUnixSocket(na.Network, address)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ln == nil && err == nil {
|
|
|
|
|
// otherwise, create a new listener
|
|
|
|
|
lnKey := listenerKey(na.Network, address)
|
|
|
|
|
ln, err = listenReusable(ctx, lnKey, na.Network, address, config)
|
|
|
|
|
}
|
2019-09-10 08:03:37 -06:00
|
|
|
}
|
2024-09-30 12:55:03 -04:00
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-01 01:47:21 -04:00
|
|
|
if ln == nil {
|
|
|
|
|
return nil, fmt.Errorf("unsupported network type: %s", na.Network)
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-23 22:49:41 +02:00
|
|
|
if IsUnixNetwork(na.Network) {
|
2024-09-30 12:55:03 -04:00
|
|
|
isAbstractUnixSocket := strings.HasPrefix(address, "@")
|
2024-05-10 16:08:54 +02:00
|
|
|
if !isAbstractUnixSocket {
|
2024-09-30 12:55:03 -04:00
|
|
|
err = os.Chmod(address, unixFileMode)
|
|
|
|
|
if err != nil {
|
2023-06-25 02:25:02 +02:00
|
|
|
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
|
|
|
|
}
|
2023-06-23 22:49:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 13:35:51 -06:00
|
|
|
return ln, nil
|
2019-11-12 01:33:38 +03:00
|
|
|
}
|
|
|
|
|
|
2020-04-07 08:33:45 -06:00
|
|
|
// IsUnixNetwork returns true if na.Network is
|
2019-12-06 11:45:50 -07:00
|
|
|
// unix, unixgram, or unixpacket.
|
2020-04-07 08:33:45 -06:00
|
|
|
func (na NetworkAddress) IsUnixNetwork() bool {
|
2023-02-08 18:05:09 +01:00
|
|
|
return IsUnixNetwork(na.Network)
|
2019-12-06 11:45:50 -07:00
|
|
|
}
|
|
|
|
|
|
2025-04-12 12:24:17 +08:00
|
|
|
// IsFdNetwork returns true if na.Network is
|
2024-09-30 12:55:03 -04:00
|
|
|
// fd or fdgram.
|
|
|
|
|
func (na NetworkAddress) IsFdNetwork() bool {
|
|
|
|
|
return IsFdNetwork(na.Network)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 08:33:20 +02:00
|
|
|
// IsInterfaceNetwork returns true if na.Host appears to be a network interface name
|
|
|
|
|
// and na.Network supports interface binding (tcp/udp).
|
|
|
|
|
func (na NetworkAddress) IsInterfaceNetwork() bool {
|
|
|
|
|
if na.Network != "tcp" && na.Network != "udp" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle encoded interface name with mode: "interface_name||mode"
|
|
|
|
|
hostToCheck := na.Host
|
|
|
|
|
if strings.Contains(na.Host, InterfaceDelimiter) {
|
|
|
|
|
parts := strings.SplitN(na.Host, InterfaceDelimiter, 2)
|
|
|
|
|
if len(parts) == 2 {
|
|
|
|
|
hostToCheck = parts[0] // Extract just the interface name part
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return isInterfaceName(hostToCheck)
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-12 01:33:38 +03:00
|
|
|
// JoinHostPort is like net.JoinHostPort, but where the port
|
|
|
|
|
// is StartPort + offset.
|
2020-04-07 08:33:45 -06:00
|
|
|
func (na NetworkAddress) JoinHostPort(offset uint) string {
|
2024-09-30 12:55:03 -04:00
|
|
|
if na.IsUnixNetwork() || na.IsFdNetwork() {
|
2020-04-07 08:33:45 -06:00
|
|
|
return na.Host
|
2019-12-06 11:45:50 -07:00
|
|
|
}
|
2024-09-30 12:55:03 -04:00
|
|
|
return net.JoinHostPort(na.Host, strconv.FormatUint(uint64(na.StartPort+offset), 10))
|
2019-11-12 01:33:38 +03:00
|
|
|
}
|
2019-07-08 16:46:38 -06:00
|
|
|
|
2022-09-28 13:35:51 -06:00
|
|
|
// Expand returns one NetworkAddress for each port in the port range.
|
2022-08-03 11:04:51 -06:00
|
|
|
func (na NetworkAddress) Expand() []NetworkAddress {
|
|
|
|
|
size := na.PortRangeSize()
|
|
|
|
|
addrs := make([]NetworkAddress, size)
|
|
|
|
|
for portOffset := uint(0); portOffset < size; portOffset++ {
|
2022-09-28 13:35:51 -06:00
|
|
|
addrs[portOffset] = na.At(portOffset)
|
2022-08-03 11:04:51 -06:00
|
|
|
}
|
|
|
|
|
return addrs
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 13:35:51 -06: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-12 01:33:38 +03: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 08:33:45 -06:00
|
|
|
func (na NetworkAddress) PortRangeSize() uint {
|
2022-08-03 11:04:51 -06:00
|
|
|
if na.EndPort < na.StartPort {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
2020-04-07 08:33:45 -06:00
|
|
|
return (na.EndPort - na.StartPort) + 1
|
2019-11-12 01:33:38 +03:00
|
|
|
}
|
|
|
|
|
|
2020-04-10 17:31:38 -06:00
|
|
|
func (na NetworkAddress) isLoopback() bool {
|
2024-09-30 12:55:03 -04:00
|
|
|
if na.IsUnixNetwork() || na.IsFdNetwork() {
|
2020-04-10 17:31:38 -06:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if na.Host == "localhost" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2022-08-18 00:10:57 +02:00
|
|
|
if ip, err := netip.ParseAddr(na.Host); err == nil {
|
2020-04-10 17:31:38 -06:00
|
|
|
return ip.IsLoopback()
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 11:41:32 -06:00
|
|
|
func (na NetworkAddress) isWildcardInterface() bool {
|
|
|
|
|
if na.Host == "" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2022-08-18 00:10:57 +02:00
|
|
|
if ip, err := netip.ParseAddr(na.Host); err == nil {
|
2020-04-16 11:41:32 -06:00
|
|
|
return ip.IsUnspecified()
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 17:31:38 -06: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 17:28:20 -06: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 08:33:45 -06:00
|
|
|
func (na NetworkAddress) String() string {
|
2022-07-25 17:28:20 -06:00
|
|
|
if na.Network == "tcp" && (na.Host != "" || na.port() != "") {
|
|
|
|
|
na.Network = "" // omit default network value for brevity
|
|
|
|
|
}
|
2020-04-10 17:31:38 -06:00
|
|
|
return JoinNetworkAddress(na.Network, na.Host, na.port())
|
2019-11-12 01:33:38 +03:00
|
|
|
}
|
|
|
|
|
|
2023-02-08 18:05:09 +01:00
|
|
|
// IsUnixNetwork returns true if the netw is a unix network.
|
|
|
|
|
func IsUnixNetwork(netw string) bool {
|
|
|
|
|
return strings.HasPrefix(netw, "unix")
|
2019-12-06 11:45:50 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-30 12:55:03 -04:00
|
|
|
// IsFdNetwork returns true if the netw is a fd network.
|
|
|
|
|
func IsFdNetwork(netw string) bool {
|
|
|
|
|
return strings.HasPrefix(netw, "fd")
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-21 06:55:42 +02:00
|
|
|
// getFdByName returns the file descriptor number for the given
|
|
|
|
|
// socket name from systemd's LISTEN_FDNAMES environment variable.
|
|
|
|
|
// Socket names are provided by systemd via socket activation.
|
|
|
|
|
//
|
|
|
|
|
// The name can optionally include an index to handle multiple sockets
|
|
|
|
|
// with the same name: "web:0" for first, "web:1" for second, etc.
|
|
|
|
|
// If no index is specified, defaults to index 0 (first occurrence).
|
|
|
|
|
func getFdByName(nameWithIndex string) (int, error) {
|
|
|
|
|
if nameWithIndex == "" {
|
|
|
|
|
return 0, fmt.Errorf("socket name cannot be empty")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fdNamesStr := os.Getenv("LISTEN_FDNAMES")
|
|
|
|
|
if fdNamesStr == "" {
|
|
|
|
|
return 0, fmt.Errorf("LISTEN_FDNAMES environment variable not set")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse name and optional index
|
|
|
|
|
parts := strings.Split(nameWithIndex, ":")
|
|
|
|
|
if len(parts) > 2 {
|
|
|
|
|
return 0, fmt.Errorf("invalid socket name format '%s': too many colons", nameWithIndex)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
name := parts[0]
|
|
|
|
|
targetIndex := 0
|
|
|
|
|
|
|
|
|
|
if len(parts) > 1 {
|
|
|
|
|
var err error
|
|
|
|
|
targetIndex, err = strconv.Atoi(parts[1])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, fmt.Errorf("invalid socket index '%s': %v", parts[1], err)
|
|
|
|
|
}
|
|
|
|
|
if targetIndex < 0 {
|
|
|
|
|
return 0, fmt.Errorf("socket index cannot be negative: %d", targetIndex)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse the socket names
|
|
|
|
|
names := strings.Split(fdNamesStr, ":")
|
|
|
|
|
|
|
|
|
|
// Find the Nth occurrence of the requested name
|
|
|
|
|
matchCount := 0
|
|
|
|
|
for i, fdName := range names {
|
|
|
|
|
if fdName == name {
|
|
|
|
|
if matchCount == targetIndex {
|
|
|
|
|
return listenFdsStart + i, nil
|
|
|
|
|
}
|
|
|
|
|
matchCount++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if matchCount == 0 {
|
|
|
|
|
return 0, fmt.Errorf("socket name '%s' not found in LISTEN_FDNAMES", name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0, fmt.Errorf("socket name '%s' found %d times, but index %d requested", name, matchCount, targetIndex)
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-12 01:33:38 +03: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 08:33:45 -06:00
|
|
|
func ParseNetworkAddress(addr string) (NetworkAddress, error) {
|
2023-05-03 13:07:22 -04: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 16:46:38 -06:00
|
|
|
var host, port string
|
2019-11-12 01:33:38 +03:00
|
|
|
network, host, port, err := SplitNetworkAddress(addr)
|
2019-07-08 16:46:38 -06:00
|
|
|
if err != nil {
|
2020-04-07 08:33:45 -06:00
|
|
|
return NetworkAddress{}, err
|
2019-07-08 16:46:38 -06:00
|
|
|
}
|
2022-07-25 17:28:20 -06:00
|
|
|
if network == "" {
|
2023-05-03 13:07:22 -04:00
|
|
|
network = defaultNetwork
|
2022-07-25 17:28:20 -06:00
|
|
|
}
|
2023-02-08 18:05:09 +01:00
|
|
|
if IsUnixNetwork(network) {
|
2023-08-06 02:09:16 +02:00
|
|
|
_, _, err := internal.SplitUnixSocketPermissionsBits(host)
|
2020-04-07 08:33:45 -06:00
|
|
|
return NetworkAddress{
|
2019-11-12 01:33:38 +03:00
|
|
|
Network: network,
|
|
|
|
|
Host: host,
|
2023-06-23 22:49:41 +02:00
|
|
|
}, err
|
2019-07-08 16:46:38 -06:00
|
|
|
}
|
2024-09-30 12:55:03 -04:00
|
|
|
if IsFdNetwork(network) {
|
2025-10-21 06:55:42 +02:00
|
|
|
fdAddr := host
|
|
|
|
|
|
|
|
|
|
// Handle named socket activation (fdname/name, fdgramname/name)
|
|
|
|
|
if strings.HasPrefix(network, "fdname") || strings.HasPrefix(network, "fdgramname") {
|
|
|
|
|
fdNum, err := getFdByName(host)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return NetworkAddress{}, fmt.Errorf("named socket activation: %v", err)
|
|
|
|
|
}
|
|
|
|
|
fdAddr = strconv.Itoa(fdNum)
|
|
|
|
|
|
|
|
|
|
// Normalize network to standard fd/fdgram
|
|
|
|
|
if strings.HasPrefix(network, "fdname") {
|
|
|
|
|
network = "fd"
|
|
|
|
|
} else {
|
|
|
|
|
network = "fdgram"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-30 12:55:03 -04:00
|
|
|
return NetworkAddress{
|
|
|
|
|
Network: network,
|
2025-10-21 06:55:42 +02:00
|
|
|
Host: fdAddr,
|
2024-09-30 12:55:03 -04:00
|
|
|
}, nil
|
|
|
|
|
}
|
2025-10-24 08:33:20 +02:00
|
|
|
|
|
|
|
|
// Check if this might be an interface name for TCP/UDP networks
|
|
|
|
|
if (network == "tcp" || network == "udp") && isInterfaceName(host) {
|
|
|
|
|
if port == "" {
|
|
|
|
|
if defaultPort == 0 {
|
|
|
|
|
return NetworkAddress{}, fmt.Errorf("interface binding requires a port")
|
|
|
|
|
}
|
|
|
|
|
port = strconv.FormatUint(uint64(defaultPort), 10)
|
|
|
|
|
}
|
|
|
|
|
return parseInterfaceAddress(network, host, port)
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-12 01:33:38 +03:00
|
|
|
var start, end uint64
|
2023-05-03 13:07:22 -04:00
|
|
|
if port == "" {
|
|
|
|
|
start = uint64(defaultPort)
|
|
|
|
|
end = uint64(defaultPort)
|
|
|
|
|
} else {
|
2022-08-04 19:17:35 +02:00
|
|
|
before, after, found := strings.Cut(port, "-")
|
|
|
|
|
if !found {
|
|
|
|
|
after = before
|
2022-07-25 17:28:20 -06:00
|
|
|
}
|
2022-08-04 19:17:35 +02:00
|
|
|
start, err = strconv.ParseUint(before, 10, 16)
|
2022-07-25 17:28:20 -06:00
|
|
|
if err != nil {
|
|
|
|
|
return NetworkAddress{}, fmt.Errorf("invalid start port: %v", err)
|
|
|
|
|
}
|
2022-08-04 19:17:35 +02:00
|
|
|
end, err = strconv.ParseUint(after, 10, 16)
|
2022-07-25 17:28:20 -06: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 16:46:38 -06:00
|
|
|
}
|
2020-04-07 08:33:45 -06:00
|
|
|
return NetworkAddress{
|
2019-11-12 01:33:38 +03:00
|
|
|
Network: network,
|
|
|
|
|
Host: host,
|
|
|
|
|
StartPort: uint(start),
|
|
|
|
|
EndPort: uint(end),
|
|
|
|
|
}, nil
|
2019-07-08 16:46:38 -06:00
|
|
|
}
|
|
|
|
|
|
2019-09-05 13:14:39 -06:00
|
|
|
// SplitNetworkAddress splits a into its network, host, and port components.
|
2019-11-12 01:33:38 +03:00
|
|
|
// Note that port may be a port range (:X-Y), or omitted for unix sockets.
|
2019-09-05 13:14:39 -06:00
|
|
|
func SplitNetworkAddress(a string) (network, host, port string, err error) {
|
2022-08-04 19:17:35 +02:00
|
|
|
beforeSlash, afterSlash, slashFound := strings.Cut(a, "/")
|
|
|
|
|
if slashFound {
|
|
|
|
|
network = strings.ToLower(strings.TrimSpace(beforeSlash))
|
|
|
|
|
a = afterSlash
|
2024-10-21 10:02:29 -04:00
|
|
|
if IsUnixNetwork(network) || IsFdNetwork(network) {
|
|
|
|
|
host = a
|
2025-09-26 11:14:48 +04:00
|
|
|
return network, host, port, err
|
2024-10-21 10:02:29 -04:00
|
|
|
}
|
2019-07-08 16:46:38 -06:00
|
|
|
}
|
2024-10-21 10:02:29 -04:00
|
|
|
|
2019-07-08 16:46:38 -06:00
|
|
|
host, port, err = net.SplitHostPort(a)
|
2024-10-21 10:02:29 -04:00
|
|
|
firstErr := err
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
// in general, if there was an error, it was likely "missing port",
|
|
|
|
|
// so try removing square brackets around an IPv6 host, adding a bogus
|
|
|
|
|
// port to take advantage of standard library's robust parser, then
|
|
|
|
|
// strip the artificial port.
|
|
|
|
|
host, _, err = net.SplitHostPort(net.JoinHostPort(strings.Trim(a, "[]"), "0"))
|
2022-07-25 17:28:20 -06:00
|
|
|
port = ""
|
|
|
|
|
}
|
2024-10-21 10:02:29 -04:00
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
err = errors.Join(firstErr, err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-26 11:14:48 +04:00
|
|
|
return network, host, port, err
|
2019-07-08 16:46:38 -06:00
|
|
|
}
|
|
|
|
|
|
2019-09-05 13:14:39 -06:00
|
|
|
// JoinNetworkAddress combines network, host, and port into a single
|
2019-12-06 12:00:04 -07: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-12 01:33:38 +03:00
|
|
|
// host parameter.
|
2019-09-05 13:14:39 -06:00
|
|
|
func JoinNetworkAddress(network, host, port string) string {
|
2019-07-08 16:46:38 -06:00
|
|
|
var a string
|
|
|
|
|
if network != "" {
|
|
|
|
|
a = network + "/"
|
|
|
|
|
}
|
2024-09-30 12:55:03 -04:00
|
|
|
if (host != "" && port == "") || IsUnixNetwork(network) || IsFdNetwork(network) {
|
2019-10-11 14:25:39 -06:00
|
|
|
a += host
|
|
|
|
|
} else if port != "" {
|
|
|
|
|
a += net.JoinHostPort(host, port)
|
2019-07-08 16:46:38 -06:00
|
|
|
}
|
|
|
|
|
return a
|
|
|
|
|
}
|
2019-11-12 01:33:38 +03:00
|
|
|
|
2024-09-30 12:55:03 -04:00
|
|
|
// ListenQUIC returns a http3.QUICEarlyListener suitable for use in a Caddy module.
|
|
|
|
|
//
|
|
|
|
|
// The network will be transformed into a QUIC-compatible type if the same address can be used with
|
|
|
|
|
// different networks. Currently this just means that for tcp, udp will be used with the same
|
|
|
|
|
// address instead.
|
2022-09-28 13:35:51 -06:00
|
|
|
//
|
|
|
|
|
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
|
2025-08-14 02:35:06 +08:00
|
|
|
// NOTE: user should close the returned listener twice, once to stop accepting new connections, the second time to free up the packet conn.
|
2025-12-05 00:15:56 +03:00
|
|
|
func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config, pcWrappers []PacketConnWrapper) (http3.QUICListener, error) {
|
2023-10-16 23:28:15 +08:00
|
|
|
lnKey := listenerKey("quic"+na.Network, na.JoinHostPort(portOffset))
|
|
|
|
|
|
|
|
|
|
sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
|
|
|
|
lnAny, err := na.Listen(ctx, portOffset, config)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ln := lnAny.(net.PacketConn)
|
|
|
|
|
|
|
|
|
|
h3ln := ln
|
2025-12-05 00:15:56 +03:00
|
|
|
if len(pcWrappers) == 0 {
|
|
|
|
|
for {
|
|
|
|
|
// retrieve the underlying socket, so quic-go can optimize.
|
|
|
|
|
if unwrapper, ok := h3ln.(interface{ Unwrap() net.PacketConn }); ok {
|
|
|
|
|
h3ln = unwrapper.Unwrap()
|
|
|
|
|
} else {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// wrap packet conn before QUIC
|
|
|
|
|
for _, pcWrapper := range pcWrappers {
|
|
|
|
|
h3ln = pcWrapper.WrapPacketConn(h3ln)
|
2023-10-16 23:28:15 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-22 02:56:10 +10:00
|
|
|
sqs := newSharedQUICState(tlsConf)
|
2023-10-16 23:28:15 +08:00
|
|
|
// http3.ConfigureTLSConfig only uses this field and tls App sets this field as well
|
|
|
|
|
//nolint:gosec
|
|
|
|
|
quicTlsConfig := &tls.Config{GetConfigForClient: sqs.getConfigForClient}
|
2024-03-22 02:56:10 +10:00
|
|
|
// Require clients to verify their source address when we're handling more than 1000 handshakes per second.
|
|
|
|
|
// TODO: make tunable?
|
|
|
|
|
limiter := rate.NewLimiter(1000, 1000)
|
|
|
|
|
tr := &quic.Transport{
|
|
|
|
|
Conn: h3ln,
|
|
|
|
|
VerifySourceAddress: func(addr net.Addr) bool { return !limiter.Allow() },
|
|
|
|
|
}
|
2024-09-21 11:47:18 +08:00
|
|
|
earlyLn, err := tr.ListenEarly(
|
|
|
|
|
http3.ConfigureTLSConfig(quicTlsConfig),
|
|
|
|
|
&quic.Config{
|
|
|
|
|
Allow0RTT: true,
|
2025-11-08 00:41:15 -05:00
|
|
|
Tracer: h3qlog.DefaultConnectionTracer,
|
2024-09-21 11:47:18 +08:00
|
|
|
},
|
|
|
|
|
)
|
2023-10-16 23:28:15 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-03-22 02:56:10 +10:00
|
|
|
// TODO: figure out when to close the listener and the transport
|
2023-10-16 23:28:15 +08:00
|
|
|
// using the original net.PacketConn to close them properly
|
|
|
|
|
return &sharedQuicListener{EarlyListener: earlyLn, packetConn: ln, sqs: sqs, key: lnKey}, nil
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sql := sharedEarlyListener.(*sharedQuicListener)
|
2024-03-22 02:56:10 +10:00
|
|
|
// add current tls.Config to sqs, so GetConfigForClient will always return the latest tls.Config in case of context cancellation
|
|
|
|
|
ctx, cancel := sql.sqs.addState(tlsConf)
|
2023-05-11 04:25:09 +08:00
|
|
|
|
2022-09-28 13:35:51 -06:00
|
|
|
return &fakeCloseQuicListener{
|
2023-05-11 04:25:09 +08:00
|
|
|
sharedQuicListener: sql,
|
2022-09-28 13:35:51 -06:00
|
|
|
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-11 04:25:09 +08:00
|
|
|
// contextAndCancelFunc groups context and its cancelFunc
|
|
|
|
|
type contextAndCancelFunc struct {
|
|
|
|
|
context.Context
|
|
|
|
|
context.CancelFunc
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-22 02:56:10 +10:00
|
|
|
// sharedQUICState manages GetConfigForClient
|
2023-05-11 04:25:09 +08:00
|
|
|
// see issue: https://github.com/caddyserver/caddy/pull/4849
|
2023-10-16 23:28:15 +08:00
|
|
|
type sharedQUICState struct {
|
2024-03-22 02:56:10 +10:00
|
|
|
rmu sync.RWMutex
|
|
|
|
|
tlsConfs map[*tls.Config]contextAndCancelFunc
|
|
|
|
|
activeTlsConf *tls.Config
|
2023-10-16 23:28:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// newSharedQUICState creates a new sharedQUICState
|
2024-03-22 02:56:10 +10:00
|
|
|
func newSharedQUICState(tlsConfig *tls.Config) *sharedQUICState {
|
2023-10-16 23:28:15 +08:00
|
|
|
sqtc := &sharedQUICState{
|
2024-03-22 02:56:10 +10:00
|
|
|
tlsConfs: make(map[*tls.Config]contextAndCancelFunc),
|
|
|
|
|
activeTlsConf: tlsConfig,
|
2023-10-16 23:28:15 +08:00
|
|
|
}
|
2024-03-22 02:56:10 +10:00
|
|
|
sqtc.addState(tlsConfig)
|
2023-05-11 04:25:09 +08:00
|
|
|
return sqtc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getConfigForClient is used as tls.Config's GetConfigForClient field
|
2023-10-16 23:28:15 +08:00
|
|
|
func (sqs *sharedQUICState) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Config, error) {
|
|
|
|
|
sqs.rmu.RLock()
|
|
|
|
|
defer sqs.rmu.RUnlock()
|
|
|
|
|
return sqs.activeTlsConf.GetConfigForClient(ch)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// addState adds tls.Config and activeRequests to the map if not present and returns the corresponding context and its cancelFunc
|
2024-03-22 02:56:10 +10:00
|
|
|
// so that when cancelled, the active tls.Config will change
|
|
|
|
|
func (sqs *sharedQUICState) addState(tlsConfig *tls.Config) (context.Context, context.CancelFunc) {
|
2023-10-16 23:28:15 +08:00
|
|
|
sqs.rmu.Lock()
|
|
|
|
|
defer sqs.rmu.Unlock()
|
2023-05-11 04:25:09 +08:00
|
|
|
|
2023-10-16 23:28:15 +08:00
|
|
|
if cacc, ok := sqs.tlsConfs[tlsConfig]; ok {
|
2023-05-11 04:25:09 +08:00
|
|
|
return cacc.Context, cacc.CancelFunc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
wrappedCancel := func() {
|
|
|
|
|
cancel()
|
|
|
|
|
|
2023-10-16 23:28:15 +08:00
|
|
|
sqs.rmu.Lock()
|
|
|
|
|
defer sqs.rmu.Unlock()
|
2023-05-11 04:25:09 +08:00
|
|
|
|
2023-10-16 23:28:15 +08:00
|
|
|
delete(sqs.tlsConfs, tlsConfig)
|
|
|
|
|
if sqs.activeTlsConf == tlsConfig {
|
2024-03-22 02:56:10 +10:00
|
|
|
// select another tls.Config, if there is none,
|
2023-05-11 04:25:09 +08:00
|
|
|
// related sharedQuicListener will be destroyed anyway
|
2024-03-22 02:56:10 +10:00
|
|
|
for tc := range sqs.tlsConfs {
|
2023-10-16 23:28:15 +08:00
|
|
|
sqs.activeTlsConf = tc
|
2023-05-11 04:25:09 +08:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-16 23:28:15 +08:00
|
|
|
sqs.tlsConfs[tlsConfig] = contextAndCancelFunc{ctx, wrappedCancel}
|
2023-05-11 04:25:09 +08:00
|
|
|
// there should be at most 2 tls.Configs
|
2023-10-16 23:28:15 +08:00
|
|
|
if len(sqs.tlsConfs) > 2 {
|
|
|
|
|
Log().Warn("quic listener tls configs are more than 2", zap.Int("number of configs", len(sqs.tlsConfs)))
|
2023-05-11 04:25:09 +08:00
|
|
|
}
|
|
|
|
|
return ctx, wrappedCancel
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 13:35:51 -06:00
|
|
|
// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
|
|
|
|
|
type sharedQuicListener struct {
|
2023-05-30 12:41:57 +03:00
|
|
|
*quic.EarlyListener
|
2023-10-16 23:28:15 +08:00
|
|
|
packetConn net.PacketConn // we have to hold these because quic-go won't close listeners it didn't create
|
|
|
|
|
sqs *sharedQUICState
|
|
|
|
|
key string
|
2022-09-28 13:35:51 -06:00
|
|
|
}
|
|
|
|
|
|
2023-10-16 23:28:15 +08:00
|
|
|
// Destruct closes the underlying QUIC listener and its associated net.PacketConn.
|
2022-09-28 13:35:51 -06:00
|
|
|
func (sql *sharedQuicListener) Destruct() error {
|
2023-10-16 23:28:15 +08:00
|
|
|
// close EarlyListener first to stop any operations being done to the net.PacketConn
|
|
|
|
|
_ = sql.EarlyListener.Close()
|
|
|
|
|
// then close the net.PacketConn
|
|
|
|
|
return sql.packetConn.Close()
|
2022-09-28 13:35:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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' đ")
|
|
|
|
|
|
|
|
|
|
type fakeCloseQuicListener struct {
|
core: Apply SO_REUSEPORT to UDP sockets (#5725)
* core: Apply SO_REUSEPORT to UDP sockets
For some reason, 10 months ago when I implemented SO_REUSEPORT
for TCP, I didn't realize, or forgot, that it can be used for UDP too. It is a
much better solution than using deadline hacks to reuse a socket, at
least for TCP.
Then https://github.com/mholt/caddy-l4/issues/132 was posted,
in which we see that UDP servers never actually stopped when the
L4 app was stopped. I verified this using this command:
$ nc -u 127.0.0.1 55353
combined with POSTing configs to the /load admin endpoint (which
alternated between an echo server and a proxy server so I could tell
which config was being used).
I refactored the code to use SO_REUSEPORT for UDP, but of course
we still need graceful reloads on all platforms, not just Unix, so I
also implemented a deadline hack similar to what we used for
TCP before. That implementation for TCP was not perfect, possibly
having a logical (not data) race condition; but for UDP so far it
seems to be working. Verified the same way I verified that SO_REUSEPORT
works.
I think this code is slightly cleaner and I'm fairly confident this code
is effective.
* Check error
* Fix return
* Fix var name
* implement Unwrap interface and clean up
* move unix packet conn to platform specific file
* implement Unwrap for unix packet conn
* Move sharedPacketConn into proper file
* Fix Windows
* move sharedPacketConn and fakeClosePacketConn to proper file
---------
Co-authored-by: Weidi Deng <weidi_deng@icloud.com>
2023-10-16 22:17:32 -06:00
|
|
|
closed int32 // accessed atomically; belongs to this struct only
|
|
|
|
|
*sharedQuicListener // embedded, so we also become a quic.EarlyListener
|
2022-09-28 13:35:51 -06:00
|
|
|
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.
|
2025-06-27 00:13:08 +08:00
|
|
|
func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (*quic.Conn, error) {
|
2022-09-28 13:35:51 -06:00
|
|
|
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()
|
2025-08-14 02:35:06 +08:00
|
|
|
} else if atomic.CompareAndSwapInt32(&fcql.closed, 1, 2) {
|
2022-09-28 13:35:51 -06:00
|
|
|
_, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-01 16:30:03 -06: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" ||
|
2025-04-13 11:19:32 +08:00
|
|
|
strings.HasPrefix(network, "ip:") || strings.HasPrefix(network, "ip4:") || strings.HasPrefix(network, "ip6:") ||
|
2024-09-30 12:55:03 -04:00
|
|
|
network == "fd" || network == "fdgram" {
|
2022-09-01 16:30:03 -06:00
|
|
|
panic("network type " + network + " is reserved")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, ok := networkTypes[strings.ToLower(network)]; ok {
|
|
|
|
|
panic("network type " + network + " is already registered")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
networkTypes[network] = getListener
|
|
|
|
|
}
|
|
|
|
|
|
core: Apply SO_REUSEPORT to UDP sockets (#5725)
* core: Apply SO_REUSEPORT to UDP sockets
For some reason, 10 months ago when I implemented SO_REUSEPORT
for TCP, I didn't realize, or forgot, that it can be used for UDP too. It is a
much better solution than using deadline hacks to reuse a socket, at
least for TCP.
Then https://github.com/mholt/caddy-l4/issues/132 was posted,
in which we see that UDP servers never actually stopped when the
L4 app was stopped. I verified this using this command:
$ nc -u 127.0.0.1 55353
combined with POSTing configs to the /load admin endpoint (which
alternated between an echo server and a proxy server so I could tell
which config was being used).
I refactored the code to use SO_REUSEPORT for UDP, but of course
we still need graceful reloads on all platforms, not just Unix, so I
also implemented a deadline hack similar to what we used for
TCP before. That implementation for TCP was not perfect, possibly
having a logical (not data) race condition; but for UDP so far it
seems to be working. Verified the same way I verified that SO_REUSEPORT
works.
I think this code is slightly cleaner and I'm fairly confident this code
is effective.
* Check error
* Fix return
* Fix var name
* implement Unwrap interface and clean up
* move unix packet conn to platform specific file
* implement Unwrap for unix packet conn
* Move sharedPacketConn into proper file
* Fix Windows
* move sharedPacketConn and fakeClosePacketConn to proper file
---------
Co-authored-by: Weidi Deng <weidi_deng@icloud.com>
2023-10-16 22:17:32 -06:00
|
|
|
var unixSocketsMu sync.Mutex
|
2022-09-28 13:35:51 -06:00
|
|
|
|
|
|
|
|
// 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.
|
2024-12-12 17:34:50 -05:00
|
|
|
func getListenerFromPlugin(ctx context.Context, network, host, port string, portOffset uint, config net.ListenConfig) (any, error) {
|
2022-09-28 13:35:51 -06:00
|
|
|
// 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))
|
2024-12-12 17:34:50 -05:00
|
|
|
return getListener(ctx, network, host, port, portOffset, config)
|
2022-09-28 13:35:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func listenerKey(network, addr string) string {
|
|
|
|
|
return network + "/" + addr
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-01 16:30:03 -06: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.
|
2024-12-12 17:34:50 -05:00
|
|
|
type ListenerFunc func(ctx context.Context, network, host, portRange string, portOffset uint, cfg net.ListenConfig) (any, error)
|
2022-09-01 16:30:03 -06:00
|
|
|
|
|
|
|
|
var networkTypes = map[string]ListenerFunc{}
|
|
|
|
|
|
2025-10-24 08:33:20 +02:00
|
|
|
// InterfaceBindingMode defines how to bind to interface IP addresses.
|
|
|
|
|
// EXPERIMENTAL: Subject to change.
|
|
|
|
|
type InterfaceBindingMode string
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// InterfaceBindingAuto uses the first IPv4 address, fallback to first IPv6
|
|
|
|
|
InterfaceBindingAuto InterfaceBindingMode = "auto"
|
|
|
|
|
// InterfaceBindingFirstIPv4 binds to the first IPv4 address of the interface
|
|
|
|
|
InterfaceBindingFirstIPv4 InterfaceBindingMode = "firstipv4"
|
|
|
|
|
// InterfaceBindingFirstIPv6 binds to the first IPv6 address of the interface
|
|
|
|
|
InterfaceBindingFirstIPv6 InterfaceBindingMode = "firstipv6"
|
|
|
|
|
// InterfaceBindingIPv4 binds to all IPv4 addresses of the interface
|
|
|
|
|
InterfaceBindingIPv4 InterfaceBindingMode = "ipv4"
|
|
|
|
|
// InterfaceBindingIPv6 binds to all IPv6 addresses of the interface
|
|
|
|
|
InterfaceBindingIPv6 InterfaceBindingMode = "ipv6"
|
|
|
|
|
// InterfaceBindingAll binds to all IP addresses of the interface
|
|
|
|
|
InterfaceBindingAll InterfaceBindingMode = "all"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// selectIPByMode selects IP addresses from the list based on the binding mode.
|
|
|
|
|
// Returns a slice of IP addresses: one for auto/firstipv4/firstipv6 modes, all for ipv4/ipv6/all modes.
|
|
|
|
|
func selectIPByMode(ipAddresses []string, mode InterfaceBindingMode) ([]string, error) {
|
|
|
|
|
var ipv4Addresses []string
|
|
|
|
|
var ipv6Addresses []string
|
|
|
|
|
|
|
|
|
|
// Separate IPv4 and IPv6 addresses
|
|
|
|
|
for _, ip := range ipAddresses {
|
|
|
|
|
if parsedIP := net.ParseIP(ip); parsedIP != nil {
|
|
|
|
|
if parsedIP.To4() != nil {
|
|
|
|
|
ipv4Addresses = append(ipv4Addresses, ip)
|
|
|
|
|
} else {
|
|
|
|
|
ipv6Addresses = append(ipv6Addresses, ip)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Select based on mode
|
|
|
|
|
switch mode {
|
|
|
|
|
case InterfaceBindingAuto:
|
|
|
|
|
// Auto mode prefers first IPv4, fallback to first IPv6
|
|
|
|
|
if len(ipv4Addresses) > 0 {
|
|
|
|
|
return []string{ipv4Addresses[0]}, nil
|
|
|
|
|
}
|
|
|
|
|
if len(ipv6Addresses) > 0 {
|
|
|
|
|
return []string{ipv6Addresses[0]}, nil
|
|
|
|
|
}
|
|
|
|
|
case InterfaceBindingFirstIPv4:
|
|
|
|
|
// Return only the first IPv4 address
|
|
|
|
|
if len(ipv4Addresses) > 0 {
|
|
|
|
|
return []string{ipv4Addresses[0]}, nil
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("no IPv4 addresses available for interface binding")
|
|
|
|
|
case InterfaceBindingFirstIPv6:
|
|
|
|
|
// Return only the first IPv6 address
|
|
|
|
|
if len(ipv6Addresses) > 0 {
|
|
|
|
|
return []string{ipv6Addresses[0]}, nil
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("no IPv6 addresses available for interface binding")
|
|
|
|
|
case InterfaceBindingIPv4:
|
|
|
|
|
// Return all IPv4 addresses
|
|
|
|
|
if len(ipv4Addresses) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("no IPv4 addresses available for interface binding")
|
|
|
|
|
}
|
|
|
|
|
return ipv4Addresses, nil
|
|
|
|
|
case InterfaceBindingIPv6:
|
|
|
|
|
// Return all IPv6 addresses
|
|
|
|
|
if len(ipv6Addresses) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("no IPv6 addresses available for interface binding")
|
|
|
|
|
}
|
|
|
|
|
return ipv6Addresses, nil
|
|
|
|
|
case InterfaceBindingAll:
|
|
|
|
|
// All mode returns all IP addresses (IPv4 first, then IPv6)
|
|
|
|
|
allIPs := append(ipv4Addresses, ipv6Addresses...)
|
|
|
|
|
if len(allIPs) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("no addresses available for interface binding")
|
|
|
|
|
}
|
|
|
|
|
return allIPs, nil
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown interface binding mode: %s", mode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("no addresses available for interface binding")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ResolveInterfaceNameWithMode resolves a network interface name to its IP addresses
|
|
|
|
|
// based on the specified binding mode.
|
|
|
|
|
// EXPERIMENTAL: Subject to change.
|
|
|
|
|
func ResolveInterfaceNameWithMode(ifaceName string, mode InterfaceBindingMode) ([]string, error) {
|
|
|
|
|
interfaces, err := net.Interfaces()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to list network interfaces: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var targetInterface *net.Interface
|
|
|
|
|
for _, iface := range interfaces {
|
|
|
|
|
if iface.Name == ifaceName {
|
|
|
|
|
targetInterface = &iface
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if targetInterface == nil {
|
|
|
|
|
return nil, fmt.Errorf("interface %s not found", ifaceName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if interface is up
|
|
|
|
|
if targetInterface.Flags&net.FlagUp == 0 {
|
|
|
|
|
return nil, fmt.Errorf("interface %s is down", ifaceName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addrs, err := targetInterface.Addrs()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to get addresses for interface %s: %v", ifaceName, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect all available IP addresses
|
|
|
|
|
var allIPAddresses []string
|
|
|
|
|
for _, addr := range addrs {
|
|
|
|
|
if ipNet, ok := addr.(*net.IPNet); ok {
|
|
|
|
|
allIPAddresses = append(allIPAddresses, ipNet.IP.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use selectIPByMode to choose the appropriate IP(s)
|
|
|
|
|
selectedIPs, err := selectIPByMode(allIPAddresses, mode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("interface %s: %v", ifaceName, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return selectedIPs, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getMaxInterfaceNameLength returns the maximum allowed interface name length
|
|
|
|
|
// based on the operating system platform
|
|
|
|
|
func getMaxInterfaceNameLength() int {
|
|
|
|
|
switch runtime.GOOS {
|
|
|
|
|
case "windows":
|
|
|
|
|
return maxInterfaceNameWindows
|
|
|
|
|
default:
|
|
|
|
|
// Unix-like systems
|
|
|
|
|
return maxInterfaceNameUnix
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// isValidInterfaceChar checks if a character is valid for interface names across all platforms
|
|
|
|
|
func isValidInterfaceChar(r rune) bool {
|
|
|
|
|
// Allow alphanumeric characters, hyphens, underscores, and spaces (for Windows)
|
|
|
|
|
return (r >= 'a' && r <= 'z') ||
|
|
|
|
|
(r >= 'A' && r <= 'Z') ||
|
|
|
|
|
(r >= '0' && r <= '9') ||
|
|
|
|
|
r == '-' || r == '_' ||
|
|
|
|
|
(runtime.GOOS == "windows" && (r == ' ' || r == '(' || r == ')'))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// resolveInterfacePlaceholder resolves Caddy placeholders in interface names.
|
|
|
|
|
// Returns the resolved interface name and whether resolution was successful.
|
|
|
|
|
// Any placeholder available in the global replacer context can be used.
|
|
|
|
|
func resolveInterfacePlaceholder(s string) (string, bool) {
|
|
|
|
|
// If no placeholders, return as-is
|
|
|
|
|
if !strings.Contains(s, "{") {
|
|
|
|
|
return s, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repl := NewReplacer()
|
|
|
|
|
resolved := repl.ReplaceKnown(s, "")
|
|
|
|
|
|
|
|
|
|
// If no replacements were made or result is empty, reject it
|
|
|
|
|
if resolved == s || resolved == "" {
|
|
|
|
|
return "", false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resolved, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// isInterfaceName checks if a given string looks like a network interface name
|
|
|
|
|
func isInterfaceName(s string) bool {
|
|
|
|
|
resolved, ok := resolveInterfacePlaceholder(s)
|
|
|
|
|
if !ok {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s = resolved
|
|
|
|
|
if s == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't accept already encoded interface names (containing delimiter)
|
|
|
|
|
if strings.Contains(s, InterfaceDelimiter) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Special case: check for interface:port:mode pattern
|
|
|
|
|
if strings.Contains(s, ":") {
|
|
|
|
|
colonCount := strings.Count(s, ":")
|
|
|
|
|
if colonCount == 2 {
|
|
|
|
|
parts := strings.Split(s, ":")
|
|
|
|
|
|
|
|
|
|
// Check if the last part is a valid binding mode
|
|
|
|
|
lastPart := parts[2]
|
|
|
|
|
if lastPart == string(InterfaceBindingAuto) ||
|
2025-10-28 11:10:58 +01:00
|
|
|
lastPart == string(InterfaceBindingFirstIPv4) ||
|
|
|
|
|
lastPart == string(InterfaceBindingFirstIPv6) ||
|
2025-10-24 08:33:20 +02:00
|
|
|
lastPart == string(InterfaceBindingIPv4) ||
|
|
|
|
|
lastPart == string(InterfaceBindingIPv6) ||
|
|
|
|
|
lastPart == string(InterfaceBindingAll) {
|
|
|
|
|
// Check if the interface part is valid
|
|
|
|
|
potentialIface := parts[0]
|
|
|
|
|
// Recursively check if the interface part is valid (without the port:mode)
|
|
|
|
|
return isInterfaceName(potentialIface)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If not interface:port:mode pattern, reject strings with colons
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check length is within platform limits
|
|
|
|
|
if len(s) > getMaxInterfaceNameLength() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check each character is valid
|
|
|
|
|
for _, r := range s {
|
|
|
|
|
if !isValidInterfaceChar(r) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if it's a well-known hostname (not an interface)
|
|
|
|
|
switch s {
|
|
|
|
|
case "localhost", "local", "host":
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if it starts with a number (like IP addresses do)
|
|
|
|
|
if len(s) > 0 && s[0] >= '0' && s[0] <= '9' {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if it looks like a file descriptor (e.g., "3", "10")
|
|
|
|
|
if _, err := strconv.Atoi(s); err == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// interfaceWithMode represents parsed interface name and port with mode
|
|
|
|
|
type interfaceWithMode struct {
|
|
|
|
|
interfaceName string
|
|
|
|
|
portWithMode string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tryParseInterfaceWithModeInHost tries to parse strings like "eth0:8090:ipv4"
|
|
|
|
|
// that occur when SplitNetworkAddress treats them as IPv6-like addresses
|
|
|
|
|
func tryParseInterfaceWithModeInHost(host string) (interfaceWithMode, bool) {
|
|
|
|
|
if !strings.Contains(host, ":") {
|
|
|
|
|
return interfaceWithMode{}, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parts := strings.Split(host, ":")
|
|
|
|
|
if len(parts) != 3 {
|
|
|
|
|
return interfaceWithMode{}, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the last part is a valid binding mode
|
|
|
|
|
if parts[2] != string(InterfaceBindingAuto) &&
|
2025-10-28 11:10:58 +01:00
|
|
|
parts[2] != string(InterfaceBindingFirstIPv4) &&
|
|
|
|
|
parts[2] != string(InterfaceBindingFirstIPv6) &&
|
2025-10-24 08:33:20 +02:00
|
|
|
parts[2] != string(InterfaceBindingIPv4) &&
|
|
|
|
|
parts[2] != string(InterfaceBindingIPv6) &&
|
|
|
|
|
parts[2] != string(InterfaceBindingAll) {
|
|
|
|
|
return interfaceWithMode{}, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !isInterfaceName(parts[0]) {
|
|
|
|
|
return interfaceWithMode{}, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return interfaceWithMode{
|
|
|
|
|
interfaceName: parts[0],
|
|
|
|
|
portWithMode: parts[1] + ":" + parts[2],
|
|
|
|
|
}, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parseInterfaceAddress handles parsing network addresses that might contain interface names.
|
|
|
|
|
// It supports extended syntax: interface:port:mode where mode can be auto, ipv4, or ipv6.
|
|
|
|
|
// It returns a NetworkAddress with the interface name preserved in the Host field for later resolution.
|
|
|
|
|
func parseInterfaceAddress(network, host, port string) (NetworkAddress, error) {
|
|
|
|
|
// Special case: if host contains multiple colons, it might be interface:port:mode format
|
|
|
|
|
if strings.Count(host, ":") >= 2 {
|
|
|
|
|
if interfaceAddr, ok := tryParseInterfaceWithModeInHost(host); ok {
|
|
|
|
|
// Recursively call parseInterfaceAddress with extracted interface and port:mode
|
|
|
|
|
return parseInterfaceAddress(network, interfaceAddr.interfaceName, interfaceAddr.portWithMode)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !isInterfaceName(host) {
|
|
|
|
|
return NetworkAddress{}, fmt.Errorf("host %s is not a valid interface name", host)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse port and optional mode: "80" or "443:ipv4"
|
|
|
|
|
var portStr string
|
|
|
|
|
mode := InterfaceBindingAuto // default mode
|
|
|
|
|
|
|
|
|
|
if port == "" {
|
|
|
|
|
return NetworkAddress{}, fmt.Errorf("interface binding requires a port")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for mode suffix
|
|
|
|
|
if strings.Contains(port, ":") {
|
|
|
|
|
parts := strings.SplitN(port, ":", 2)
|
|
|
|
|
if len(parts) == 2 {
|
|
|
|
|
portStr = parts[0]
|
|
|
|
|
modeStr := parts[1]
|
|
|
|
|
switch modeStr {
|
|
|
|
|
case "auto":
|
|
|
|
|
mode = InterfaceBindingAuto
|
2025-10-28 11:10:58 +01:00
|
|
|
case "firstipv4":
|
|
|
|
|
mode = InterfaceBindingFirstIPv4
|
|
|
|
|
case "firstipv6":
|
|
|
|
|
mode = InterfaceBindingFirstIPv6
|
2025-10-24 08:33:20 +02:00
|
|
|
case "ipv4":
|
|
|
|
|
mode = InterfaceBindingIPv4
|
|
|
|
|
case "ipv6":
|
|
|
|
|
mode = InterfaceBindingIPv6
|
|
|
|
|
case "all":
|
|
|
|
|
mode = InterfaceBindingAll
|
|
|
|
|
default:
|
2025-10-28 11:10:58 +01:00
|
|
|
return NetworkAddress{}, fmt.Errorf("unknown interface binding mode: %s (supported: auto, firstipv4, firstipv6, ipv4, ipv6, all)", modeStr)
|
2025-10-24 08:33:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
portStr = port
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var start, end uint64
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
before, after, found := strings.Cut(portStr, "-")
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Encode the interface name and mode in the Host field
|
|
|
|
|
// Format: "interface_name||mode" so we can decode it later in listenInterface
|
|
|
|
|
hostWithMode := fmt.Sprintf("%s%s%s", host, InterfaceDelimiter, string(mode))
|
|
|
|
|
|
|
|
|
|
return NetworkAddress{
|
|
|
|
|
Network: network,
|
|
|
|
|
Host: hostWithMode,
|
|
|
|
|
StartPort: uint(start),
|
|
|
|
|
EndPort: uint(end),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-15 21:26:17 -06: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
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-05 00:15:56 +03:00
|
|
|
// PacketConnWrapper is a type that wraps a packet conn
|
|
|
|
|
// so it can modify the input packet conn methods.
|
|
|
|
|
// Modules that implement this interface are found
|
|
|
|
|
// in the caddy.packetconns namespace. Usually, to
|
|
|
|
|
// wrap a packet conn, you will define your own struct
|
|
|
|
|
// type that embeds the input packet conn, then
|
|
|
|
|
// implement your own methods that you want to wrap,
|
|
|
|
|
// calling the underlying packet conn methods where
|
|
|
|
|
// appropriate.
|
|
|
|
|
type PacketConnWrapper interface {
|
|
|
|
|
WrapPacketConn(net.PacketConn) net.PacketConn
|
|
|
|
|
}
|
|
|
|
|
|
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-10 23:24:58 -07:00
|
|
|
// listenerPool stores and allows reuse of active listeners.
|
|
|
|
|
var listenerPool = NewUsagePool()
|
2019-11-12 01:33:38 +03:00
|
|
|
|
|
|
|
|
const maxPortSpan = 65535
|