28
"strings"
29
"sync"
30
"sync/atomic"
31
- "syscall"
31
"time"
32
33
"github.com/quic-go/quic-go"
148
}
149
150
func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
152
- var ln any
153
- var err error
154
- var address string
155
- var unixFileMode fs.FileMode
156
- var isAbtractUnixSocket bool
151
+ var (
152
+ ln any
153
+ err error
154
+ address string
155
+ unixFileMode fs.FileMode
156
+ isAbtractUnixSocket bool
157
+ )
158
159
// split unix socket addr early so lnKey
160
// is independent of permissions bits
182
183
lnKey := listenerKey(na.Network, address)
184
184
- switch na.Network {
185
- case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
186
- ln, err = listenTCPOrUnix(ctx, lnKey, na.Network, address, config)
187
- case "unixgram":
188
- ln, err = config.ListenPacket(ctx, na.Network, address)
189
- case "udp", "udp4", "udp6":
190
- sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
191
- pc, err := config.ListenPacket(ctx, na.Network, address)
192
- if err != nil {
193
- return nil, err
194
- }
195
- return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil
196
- })
197
- if err != nil {
198
- return nil, err
199
- }
200
- spc := sharedPc.(*sharedPacketConn)
201
- ln = &fakeClosePacketConn{spc: spc, UDPConn: spc.PacketConn.(*net.UDPConn)}
202
- }
185
if strings.HasPrefix(na.Network, "ip") {
186
ln, err = config.ListenPacket(ctx, na.Network, address)
187
+ } else {
188
+ ln, err = listenReusable(ctx, lnKey, na.Network, address, config)
189
}
190
if err != nil {
191
return nil, err
194
return nil, fmt.Errorf("unsupported network type: %s", na.Network)
195
}
196
213
- // TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so...
214
- if unix, ok := ln.(*net.UnixConn); ok {
215
- one := int32(1)
216
- ln = &unixConn{unix, address, lnKey, &one}
217
- unixSockets[lnKey] = unix
218
- }
219
-
197
if IsUnixNetwork(na.Network) {
198
if !isAbtractUnixSocket {
199
if err := os.Chmod(address, unixFileMode); err != nil {
532
// and the request counter will reflect current http server
533
ctx, cancel := sql.sqs.addState(tlsConf, activeRequests)
534
558
- // TODO: to serve QUIC over a unix socket, currently we need to hold onto
559
- // the underlying net.PacketConn (which we wrap as unixConn to keep count
560
- // of closes) because closing the quic.EarlyListener doesn't actually close
561
- // the underlying PacketConn, but we need to for unix sockets since we dup
562
- // the file descriptor and thus need to close the original; track issue:
563
- // https://github.com/quic-go/quic-go/issues/3560#issuecomment-1258959608
564
- var unix *unixConn
565
- if uc, ok := ln.(*unixConn); ok {
566
- unix = uc
567
- }
568
-
535
return &fakeCloseQuicListener{
536
sharedQuicListener: sql,
571
- uc: unix,
537
context: ctx,
538
contextCancel: cancel,
539
}, nil
642
return sql.packetConn.Close()
643
}
644
680
-// sharedPacketConn is like sharedListener, but for net.PacketConns.
681
-type sharedPacketConn struct {
682
- net.PacketConn
683
- key string
684
-}
685
-
686
-// Destruct closes the underlying socket.
687
-func (spc *sharedPacketConn) Destruct() error {
688
- return spc.PacketConn.Close()
689
-}
690
-
645
// fakeClosedErr returns an error value that is not temporary
646
// nor a timeout, suitable for making the caller think the
647
// listener is actually closed
661
// socket is actually left open.
662
var errFakeClosed = fmt.Errorf("listener 'closed' 😉")
663
710
-// fakeClosePacketConn is like fakeCloseListener, but for PacketConns,
711
-// or more specifically, *net.UDPConn
712
-type fakeClosePacketConn struct {
713
- closed int32 // accessed atomically; belongs to this struct only
714
- spc *sharedPacketConn // its key is used in Close
715
- *net.UDPConn // embedded, so we also become a net.PacketConn and enable several other optimizations done by quic-go
716
-}
717
-
718
-// interface guard for extra optimizations
719
-// needed by QUIC implementation: https://github.com/caddyserver/caddy/issues/3998, https://github.com/caddyserver/caddy/issues/5605
720
-var _ quic.OOBCapablePacketConn = (*fakeClosePacketConn)(nil)
721
-
722
-// 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,
723
-// but doesn't actually use these methods, the only methods needed are `ReadMsgUDP` and `SyscallConn`.
724
-var _ net.Conn = (*fakeClosePacketConn)(nil)
725
-
726
-// Unwrap returns the underlying net.UDPConn for quic-go optimization
727
-func (fcpc *fakeClosePacketConn) Unwrap() any {
728
- return fcpc.UDPConn
729
-}
730
-
731
-// Close won't close the underlying socket unless there is no more reference, then listenerPool will close it.
732
-func (fcpc *fakeClosePacketConn) Close() error {
733
- if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) {
734
- _, _ = listenerPool.Delete(fcpc.spc.key)
735
- }
736
- return nil
737
-}
738
-
664
type fakeCloseQuicListener struct {
740
- closed int32 // accessed atomically; belongs to this struct only
741
- *sharedQuicListener // embedded, so we also become a quic.EarlyListener
742
- uc *unixConn // underlying unix socket, if UDS
665
+ closed int32 // accessed atomically; belongs to this struct only
666
+ *sharedQuicListener // embedded, so we also become a quic.EarlyListener
667
context context.Context
668
contextCancel context.CancelFunc
669
}
690
if atomic.CompareAndSwapInt32(&fcql.closed, 0, 1) {
691
fcql.contextCancel()
692
_, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
769
- if fcql.uc != nil {
770
- // unix sockets need to be closed ourselves because we dup() the file
771
- // descriptor when we reuse them, so this avoids a resource leak
772
- fcql.uc.Close()
773
- }
693
}
694
return nil
695
}
715
networkTypes[network] = getListener
716
}
717
799
-type unixConn struct {
800
- *net.UnixConn
801
- filename string
802
- mapKey string
803
- count *int32 // accessed atomically
804
-}
805
-
806
-func (uc *unixConn) Close() error {
807
- newCount := atomic.AddInt32(uc.count, -1)
808
- if newCount == 0 {
809
- defer func() {
810
- unixSocketsMu.Lock()
811
- delete(unixSockets, uc.mapKey)
812
- unixSocketsMu.Unlock()
813
- _ = syscall.Unlink(uc.filename)
814
- }()
815
- }
816
- return uc.UnixConn.Close()
817
-}
818
-
819
-// unixSockets keeps track of the currently-active unix sockets
820
-// so we can transfer their FDs gracefully during reloads.
821
-var (
822
- unixSockets = make(map[string]interface {
823
- File() (*os.File, error)
824
- })
825
- unixSocketsMu sync.Mutex
826
-)
718
+var unixSocketsMu sync.Mutex
719
720
// getListenerFromPlugin returns a listener on the given network and address
721
// if a plugin has registered the network name. It may return (nil, nil) if
759
var listenerPool = NewUsagePool()
760
761
const maxPortSpan = 65535
870
-
871
-// Interface guards (see https://github.com/caddyserver/caddy/issues/3998)
872
-var (
873
- _ (interface{ SetReadBuffer(int) error }) = (*fakeClosePacketConn)(nil)
874
- _ (interface {
875
- SyscallConn() (syscall.RawConn, error)
876
- }) = (*fakeClosePacketConn)(nil)
877
-)