2022-09-21 13:55:23 -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.
|
|
|
|
|
2023-03-02 23:00:18 -05:00
|
|
|
// Even though the filename ends in _unix.go, we still have to specify the
|
|
|
|
// build constraint here, because the filename convention only works for
|
|
|
|
// literal GOOS values, and "unix" is a shortcut unique to build tags.
|
2024-01-06 05:09:20 -05:00
|
|
|
//go:build unix && !solaris
|
2022-09-21 13:55:23 -05:00
|
|
|
|
|
|
|
package caddy
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
2024-09-30 11:55:03 -05:00
|
|
|
"fmt"
|
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 23:17:32 -05:00
|
|
|
"io"
|
2022-09-21 13:55:23 -05:00
|
|
|
"io/fs"
|
|
|
|
"net"
|
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 23:17:32 -05:00
|
|
|
"os"
|
2024-09-30 11:55:03 -05:00
|
|
|
"slices"
|
|
|
|
"strconv"
|
|
|
|
"sync"
|
2022-09-28 14:35:51 -05:00
|
|
|
"sync/atomic"
|
2022-09-21 13:55:23 -05:00
|
|
|
"syscall"
|
|
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
)
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already
|
2024-09-30 11:55:03 -05:00
|
|
|
// have it open; if not, unlink it so we can have it.
|
|
|
|
// No-op if not a unix network.
|
2022-09-28 14:35:51 -05:00
|
|
|
func reuseUnixSocket(network, addr string) (any, error) {
|
2022-09-21 13:55:23 -05:00
|
|
|
socketKey := listenerKey(network, addr)
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
socket, exists := unixSockets[socketKey]
|
|
|
|
if exists {
|
|
|
|
// make copy of file descriptor
|
|
|
|
socketFile, err := socket.File() // does dup() deep down
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// use copied fd to make new Listener or PacketConn, then replace
|
|
|
|
// it in the map so that future copies always come from the most
|
|
|
|
// recent fd (as the previous ones will be closed, and we'd get
|
|
|
|
// "use of closed network connection" errors) -- note that we
|
|
|
|
// preserve the *pointer* to the counter (not just the value) so
|
|
|
|
// that all socket wrappers will refer to the same value
|
|
|
|
switch unixSocket := socket.(type) {
|
|
|
|
case *unixListener:
|
|
|
|
ln, err := net.FileListener(socketFile)
|
2022-09-21 13:55:23 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-09-28 14:35:51 -05:00
|
|
|
atomic.AddInt32(unixSocket.count, 1)
|
|
|
|
unixSockets[socketKey] = &unixListener{ln.(*net.UnixListener), socketKey, unixSocket.count}
|
2022-09-21 13:55:23 -05:00
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
case *unixConn:
|
|
|
|
pc, err := net.FilePacketConn(socketFile)
|
2022-09-21 13:55:23 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-09-28 14:35:51 -05:00
|
|
|
atomic.AddInt32(unixSocket.count, 1)
|
2024-09-30 11:55:03 -05:00
|
|
|
unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), socketKey, unixSocket.count}
|
2022-09-21 13:55:23 -05:00
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
return unixSockets[socketKey], nil
|
2022-09-21 13:55:23 -05:00
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
// from what I can tell after some quick research, it's quite common for programs to
|
|
|
|
// leave their socket file behind after they close, so the typical pattern is to
|
|
|
|
// unlink it before you bind to it -- this is often crucial if the last program using
|
|
|
|
// it was killed forcefully without a chance to clean up the socket, but there is a
|
|
|
|
// race, as the comment in net.UnixListener.close() explains... oh well, I guess?
|
|
|
|
if err := syscall.Unlink(addr); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
2022-09-21 13:55:23 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-09-28 14:35:51 -05:00
|
|
|
return nil, nil
|
|
|
|
}
|
2022-09-21 13:55:23 -05:00
|
|
|
|
2024-09-30 11:55:03 -05:00
|
|
|
// listenReusable creates a new listener for the given network and address, and adds it to listenerPool.
|
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 23:17:32 -05:00
|
|
|
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
2023-03-02 23:00:18 -05:00
|
|
|
// even though SO_REUSEPORT lets us bind the socket multiple times,
|
|
|
|
// we still put it in the listenerPool so we can count how many
|
|
|
|
// configs are using this socket; necessary to ensure we can know
|
|
|
|
// whether to enforce shutdown delays, for example (see #5393).
|
2024-09-30 11:55:03 -05:00
|
|
|
var (
|
|
|
|
ln io.Closer
|
|
|
|
err error
|
|
|
|
socketFile *os.File
|
|
|
|
)
|
|
|
|
|
|
|
|
fd := slices.Contains([]string{"fd", "fdgram"}, network)
|
|
|
|
if fd {
|
|
|
|
socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid file descriptor: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func() {
|
|
|
|
socketFilesMu.Lock()
|
|
|
|
defer socketFilesMu.Unlock()
|
|
|
|
|
|
|
|
socketFdWide := uintptr(socketFd)
|
|
|
|
var ok bool
|
|
|
|
|
|
|
|
socketFile, ok = socketFiles[socketFdWide]
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
socketFile = os.NewFile(socketFdWide, lnKey)
|
|
|
|
if socketFile != nil {
|
|
|
|
socketFiles[socketFdWide] = socketFile
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if socketFile == nil {
|
|
|
|
return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs
|
|
|
|
oldControl := config.Control
|
|
|
|
config.Control = func(network, address string, c syscall.RawConn) error {
|
|
|
|
if oldControl != nil {
|
|
|
|
if err := oldControl(network, address, c); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return reusePort(network, address, c)
|
|
|
|
}
|
2023-03-02 23:00:18 -05:00
|
|
|
}
|
|
|
|
|
2024-09-30 11:55:03 -05:00
|
|
|
datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network)
|
|
|
|
if datagram {
|
|
|
|
if fd {
|
|
|
|
ln, err = net.FilePacketConn(socketFile)
|
|
|
|
} else {
|
|
|
|
ln, err = config.ListenPacket(ctx, network, address)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if fd {
|
|
|
|
ln, err = net.FileListener(socketFile)
|
|
|
|
} else {
|
|
|
|
ln, err = config.Listen(ctx, network, address)
|
|
|
|
}
|
2023-06-21 18:47:23 -05:00
|
|
|
}
|
|
|
|
|
2024-09-30 11:55:03 -05:00
|
|
|
if err == nil {
|
|
|
|
listenerPool.LoadOrStore(lnKey, nil)
|
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 23:17:32 -05:00
|
|
|
}
|
|
|
|
|
2024-09-30 11:55:03 -05:00
|
|
|
if datagram {
|
|
|
|
if !fd {
|
|
|
|
// TODO: Not 100% sure this is necessary, but we do this for net.UnixListener, so...
|
|
|
|
if unix, ok := ln.(*net.UnixConn); ok {
|
|
|
|
one := int32(1)
|
|
|
|
ln = &unixConn{unix, lnKey, &one}
|
|
|
|
unixSockets[lnKey] = ln.(*unixConn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// lightly wrap the connection so that when it is closed,
|
|
|
|
// we can decrement the usage pool counter
|
|
|
|
if specificLn, ok := ln.(net.PacketConn); ok {
|
|
|
|
ln = deletePacketConn{specificLn, lnKey}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !fd {
|
|
|
|
// if new listener is a unix socket, make sure we can reuse it later
|
|
|
|
// (we do our own "unlink on close" -- not required, but more tidy)
|
|
|
|
if unix, ok := ln.(*net.UnixListener); ok {
|
|
|
|
unix.SetUnlinkOnClose(false)
|
|
|
|
one := int32(1)
|
|
|
|
ln = &unixListener{unix, lnKey, &one}
|
|
|
|
unixSockets[lnKey] = ln.(*unixListener)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// lightly wrap the listener so that when it is closed,
|
|
|
|
// we can decrement the usage pool counter
|
|
|
|
if specificLn, ok := ln.(net.Listener); ok {
|
|
|
|
ln = deleteListener{specificLn, lnKey}
|
|
|
|
}
|
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 23:17:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// other types, I guess we just return them directly
|
|
|
|
return ln, err
|
2022-09-21 13:55:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// reusePort sets SO_REUSEPORT. Ineffective for unix sockets.
|
|
|
|
func reusePort(network, address string, conn syscall.RawConn) error {
|
2023-02-08 12:05:09 -05:00
|
|
|
if IsUnixNetwork(network) {
|
2022-09-28 14:35:51 -05:00
|
|
|
return nil
|
|
|
|
}
|
2022-09-21 13:55:23 -05:00
|
|
|
return conn.Control(func(descriptor uintptr) {
|
2023-05-23 11:56:00 -05:00
|
|
|
if err := unix.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unixSOREUSEPORT, 1); err != nil {
|
2022-09-21 13:55:23 -05:00
|
|
|
Log().Error("setting SO_REUSEPORT",
|
|
|
|
zap.String("network", network),
|
|
|
|
zap.String("address", address),
|
|
|
|
zap.Uintptr("descriptor", descriptor),
|
|
|
|
zap.Error(err))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2023-03-02 23:00:18 -05:00
|
|
|
|
2023-06-21 18:59:54 -05:00
|
|
|
type unixListener struct {
|
|
|
|
*net.UnixListener
|
|
|
|
mapKey string
|
|
|
|
count *int32 // accessed atomically
|
|
|
|
}
|
|
|
|
|
|
|
|
func (uln *unixListener) Close() error {
|
|
|
|
newCount := atomic.AddInt32(uln.count, -1)
|
|
|
|
if newCount == 0 {
|
2024-09-30 11:55:03 -05:00
|
|
|
file, err := uln.File()
|
|
|
|
var name string
|
|
|
|
if err == nil {
|
|
|
|
name = file.Name()
|
|
|
|
}
|
2023-06-21 18:59:54 -05:00
|
|
|
defer func() {
|
|
|
|
unixSocketsMu.Lock()
|
|
|
|
delete(unixSockets, uln.mapKey)
|
|
|
|
unixSocketsMu.Unlock()
|
2024-09-30 11:55:03 -05:00
|
|
|
if err == nil {
|
|
|
|
_ = syscall.Unlink(name)
|
|
|
|
}
|
2023-06-21 18:59:54 -05:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
return uln.UnixListener.Close()
|
|
|
|
}
|
|
|
|
|
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 23:17:32 -05:00
|
|
|
type unixConn struct {
|
|
|
|
*net.UnixConn
|
2024-09-30 11:55:03 -05:00
|
|
|
mapKey string
|
|
|
|
count *int32 // accessed atomically
|
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 23:17:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (uc *unixConn) Close() error {
|
|
|
|
newCount := atomic.AddInt32(uc.count, -1)
|
|
|
|
if newCount == 0 {
|
2024-09-30 11:55:03 -05:00
|
|
|
file, err := uc.File()
|
|
|
|
var name string
|
|
|
|
if err == nil {
|
|
|
|
name = file.Name()
|
|
|
|
}
|
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 23:17:32 -05:00
|
|
|
defer func() {
|
|
|
|
unixSocketsMu.Lock()
|
|
|
|
delete(unixSockets, uc.mapKey)
|
|
|
|
unixSocketsMu.Unlock()
|
2024-09-30 11:55:03 -05:00
|
|
|
if err == nil {
|
|
|
|
_ = syscall.Unlink(name)
|
|
|
|
}
|
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 23:17:32 -05:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
return uc.UnixConn.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (uc *unixConn) Unwrap() net.PacketConn {
|
|
|
|
return uc.UnixConn
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
})
|
|
|
|
|
2024-09-30 11:55:03 -05:00
|
|
|
// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor.
|
|
|
|
var socketFiles = map[uintptr]*os.File{}
|
|
|
|
|
|
|
|
// socketFilesMu synchronizes socketFiles insertions
|
|
|
|
var socketFilesMu sync.Mutex
|
|
|
|
|
2023-03-02 23:00:18 -05:00
|
|
|
// deleteListener is a type that simply deletes itself
|
|
|
|
// from the listenerPool when it closes. It is used
|
|
|
|
// solely for the purpose of reference counting (i.e.
|
|
|
|
// counting how many configs are using a given socket).
|
|
|
|
type deleteListener struct {
|
|
|
|
net.Listener
|
|
|
|
lnKey string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dl deleteListener) Close() error {
|
|
|
|
_, _ = listenerPool.Delete(dl.lnKey)
|
|
|
|
return dl.Listener.Close()
|
|
|
|
}
|
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 23:17:32 -05:00
|
|
|
|
|
|
|
// deletePacketConn is like deleteListener, but
|
|
|
|
// for net.PacketConns.
|
|
|
|
type deletePacketConn struct {
|
|
|
|
net.PacketConn
|
|
|
|
lnKey string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dl deletePacketConn) Close() error {
|
|
|
|
_, _ = listenerPool.Delete(dl.lnKey)
|
|
|
|
return dl.PacketConn.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dl deletePacketConn) Unwrap() net.PacketConn {
|
|
|
|
return dl.PacketConn
|
|
|
|
}
|