diff options
Diffstat (limited to 'listeners.go')
-rw-r--r-- | listeners.go | 140 |
1 files changed, 12 insertions, 128 deletions
diff --git a/listeners.go b/listeners.go index 67c51967..84a32e45 100644 --- a/listeners.go +++ b/listeners.go @@ -28,7 +28,6 @@ import ( "strings" "sync" "sync/atomic" - "syscall" "time" "github.com/quic-go/quic-go" @@ -149,11 +148,13 @@ func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net } func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) { - var ln any - var err error - var address string - var unixFileMode fs.FileMode - var isAbtractUnixSocket bool + var ( + ln any + err error + address string + unixFileMode fs.FileMode + isAbtractUnixSocket bool + ) // split unix socket addr early so lnKey // is independent of permissions bits @@ -181,27 +182,10 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net lnKey := listenerKey(na.Network, address) - switch na.Network { - case "tcp", "tcp4", "tcp6", "unix", "unixpacket": - ln, err = listenTCPOrUnix(ctx, lnKey, na.Network, address, config) - case "unixgram": - ln, err = config.ListenPacket(ctx, na.Network, address) - case "udp", "udp4", "udp6": - sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { - pc, err := config.ListenPacket(ctx, na.Network, address) - if err != nil { - return nil, err - } - return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil - }) - if err != nil { - return nil, err - } - spc := sharedPc.(*sharedPacketConn) - ln = &fakeClosePacketConn{spc: spc, UDPConn: spc.PacketConn.(*net.UDPConn)} - } if strings.HasPrefix(na.Network, "ip") { ln, err = config.ListenPacket(ctx, na.Network, address) + } else { + ln, err = listenReusable(ctx, lnKey, na.Network, address, config) } if err != nil { return nil, err @@ -210,13 +194,6 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net return nil, fmt.Errorf("unsupported network type: %s", na.Network) } - // TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so... - if unix, ok := ln.(*net.UnixConn); ok { - one := int32(1) - ln = &unixConn{unix, address, lnKey, &one} - unixSockets[lnKey] = unix - } - if IsUnixNetwork(na.Network) { if !isAbtractUnixSocket { if err := os.Chmod(address, unixFileMode); err != nil { @@ -555,20 +532,8 @@ func ListenQUIC(ln net.PacketConn, tlsConf *tls.Config, activeRequests *int64) ( // and the request counter will reflect current http server ctx, cancel := sql.sqs.addState(tlsConf, activeRequests) - // TODO: to serve QUIC over a unix socket, currently we need to hold onto - // the underlying net.PacketConn (which we wrap as unixConn to keep count - // of closes) because closing the quic.EarlyListener doesn't actually close - // the underlying PacketConn, but we need to for unix sockets since we dup - // the file descriptor and thus need to close the original; track issue: - // https://github.com/quic-go/quic-go/issues/3560#issuecomment-1258959608 - var unix *unixConn - if uc, ok := ln.(*unixConn); ok { - unix = uc - } - return &fakeCloseQuicListener{ sharedQuicListener: sql, - uc: unix, context: ctx, contextCancel: cancel, }, nil @@ -677,17 +642,6 @@ func (sql *sharedQuicListener) Destruct() error { return sql.packetConn.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 @@ -707,39 +661,9 @@ func fakeClosedErr(l interface{ Addr() net.Addr }) error { // socket is actually left open. var errFakeClosed = fmt.Errorf("listener 'closed' 😉") -// fakeClosePacketConn is like fakeCloseListener, but for PacketConns, -// or more specifically, *net.UDPConn -type fakeClosePacketConn struct { - closed int32 // accessed atomically; belongs to this struct only - spc *sharedPacketConn // its key is used in Close - *net.UDPConn // embedded, so we also become a net.PacketConn and enable several other optimizations done by quic-go -} - -// interface guard for extra optimizations -// needed by QUIC implementation: https://github.com/caddyserver/caddy/issues/3998, https://github.com/caddyserver/caddy/issues/5605 -var _ quic.OOBCapablePacketConn = (*fakeClosePacketConn)(nil) - -// https://pkg.go.dev/golang.org/x/net/ipv4#NewPacketConn is used by quic-go and requires a net.PacketConn type assertable to a net.Conn, -// but doesn't actually use these methods, the only methods needed are `ReadMsgUDP` and `SyscallConn`. -var _ net.Conn = (*fakeClosePacketConn)(nil) - -// Unwrap returns the underlying net.UDPConn for quic-go optimization -func (fcpc *fakeClosePacketConn) Unwrap() any { - return fcpc.UDPConn -} - -// Close won't close the underlying socket unless there is no more reference, then listenerPool will close it. -func (fcpc *fakeClosePacketConn) Close() error { - if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) { - _, _ = listenerPool.Delete(fcpc.spc.key) - } - return nil -} - type fakeCloseQuicListener struct { - closed int32 // accessed atomically; belongs to this struct only - *sharedQuicListener // embedded, so we also become a quic.EarlyListener - uc *unixConn // underlying unix socket, if UDS + closed int32 // accessed atomically; belongs to this struct only + *sharedQuicListener // embedded, so we also become a quic.EarlyListener context context.Context contextCancel context.CancelFunc } @@ -766,11 +690,6 @@ 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 } @@ -796,34 +715,7 @@ func RegisterNetwork(network string, getListener ListenerFunc) { networkTypes[network] = getListener } -type unixConn struct { - *net.UnixConn - filename string - mapKey string - count *int32 // accessed atomically -} - -func (uc *unixConn) Close() error { - newCount := atomic.AddInt32(uc.count, -1) - if newCount == 0 { - defer func() { - unixSocketsMu.Lock() - delete(unixSockets, uc.mapKey) - unixSocketsMu.Unlock() - _ = syscall.Unlink(uc.filename) - }() - } - return uc.UnixConn.Close() -} - -// unixSockets keeps track of the currently-active unix sockets -// so we can transfer their FDs gracefully during reloads. -var ( - unixSockets = make(map[string]interface { - File() (*os.File, error) - }) - unixSocketsMu sync.Mutex -) +var 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 @@ -867,11 +759,3 @@ type ListenerWrapper interface { var listenerPool = NewUsagePool() const maxPortSpan = 65535 - -// Interface guards (see https://github.com/caddyserver/caddy/issues/3998) -var ( - _ (interface{ SetReadBuffer(int) error }) = (*fakeClosePacketConn)(nil) - _ (interface { - SyscallConn() (syscall.RawConn, error) - }) = (*fakeClosePacketConn)(nil) -) |