| 
									
										
										
										
											2022-09-21 12:55:23 -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. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-02 21:00:18 -07: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 12:55:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | package caddy | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04: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 22:17:32 -06:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06: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 22:17:32 -06:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04:00
										 |  |  | 	"slices" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06:00
										 |  |  | 	"sync/atomic" | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06:00
										 |  |  | 	"syscall" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"go.uber.org/zap" | 
					
						
							|  |  |  | 	"golang.org/x/sys/unix" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06:00
										 |  |  | // reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04:00
										 |  |  | // have it open; if not, unlink it so we can have it. | 
					
						
							|  |  |  | // No-op if not a unix network. | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06:00
										 |  |  | func reuseUnixSocket(network, addr string) (any, error) { | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06:00
										 |  |  | 	socketKey := listenerKey(network, addr) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06: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 12:55:23 -06:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06:00
										 |  |  | 			atomic.AddInt32(unixSocket.count, 1) | 
					
						
							|  |  |  | 			unixSockets[socketKey] = &unixListener{ln.(*net.UnixListener), socketKey, unixSocket.count} | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06:00
										 |  |  | 		case *unixConn: | 
					
						
							|  |  |  | 			pc, err := net.FilePacketConn(socketFile) | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06:00
										 |  |  | 			atomic.AddInt32(unixSocket.count, 1) | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04:00
										 |  |  | 			unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), socketKey, unixSocket.count} | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06:00
										 |  |  | 		return unixSockets[socketKey], nil | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06: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 12:55:23 -06:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06:00
										 |  |  | 	return nil, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04: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 22:17:32 -06:00
										 |  |  | func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) { | 
					
						
							| 
									
										
										
										
											2023-03-02 21:00:18 -07: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 12:55:03 -04: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 21:00:18 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04: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 17:47:23 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04: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 22:17:32 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04: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 22:17:32 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// other types, I guess we just return them directly | 
					
						
							|  |  |  | 	return ln, err | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // reusePort sets SO_REUSEPORT. Ineffective for unix sockets. | 
					
						
							|  |  |  | func reusePort(network, address string, conn syscall.RawConn) error { | 
					
						
							| 
									
										
										
										
											2023-02-08 18:05:09 +01:00
										 |  |  | 	if IsUnixNetwork(network) { | 
					
						
							| 
									
										
										
										
											2022-09-28 13:35:51 -06:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06:00
										 |  |  | 	return conn.Control(func(descriptor uintptr) { | 
					
						
							| 
									
										
										
										
											2023-05-23 17:56:00 +01:00
										 |  |  | 		if err := unix.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unixSOREUSEPORT, 1); err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-21 12:55:23 -06:00
										 |  |  | 			Log().Error("setting SO_REUSEPORT", | 
					
						
							|  |  |  | 				zap.String("network", network), | 
					
						
							|  |  |  | 				zap.String("address", address), | 
					
						
							|  |  |  | 				zap.Uintptr("descriptor", descriptor), | 
					
						
							|  |  |  | 				zap.Error(err)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-03-02 21:00:18 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-21 17:59:54 -06: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 12:55:03 -04:00
										 |  |  | 		file, err := uln.File() | 
					
						
							|  |  |  | 		var name string | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			name = file.Name() | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-06-21 17:59:54 -06:00
										 |  |  | 		defer func() { | 
					
						
							|  |  |  | 			unixSocketsMu.Lock() | 
					
						
							|  |  |  | 			delete(unixSockets, uln.mapKey) | 
					
						
							|  |  |  | 			unixSocketsMu.Unlock() | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04:00
										 |  |  | 			if err == nil { | 
					
						
							|  |  |  | 				_ = syscall.Unlink(name) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-06-21 17:59:54 -06: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 22:17:32 -06:00
										 |  |  | type unixConn struct { | 
					
						
							|  |  |  | 	*net.UnixConn | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04: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 22:17:32 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (uc *unixConn) Close() error { | 
					
						
							|  |  |  | 	newCount := atomic.AddInt32(uc.count, -1) | 
					
						
							|  |  |  | 	if newCount == 0 { | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04: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 22:17:32 -06:00
										 |  |  | 		defer func() { | 
					
						
							|  |  |  | 			unixSocketsMu.Lock() | 
					
						
							|  |  |  | 			delete(unixSockets, uc.mapKey) | 
					
						
							|  |  |  | 			unixSocketsMu.Unlock() | 
					
						
							| 
									
										
										
										
											2024-09-30 12:55:03 -04: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 22:17:32 -06: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 12:55:03 -04: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 21:00:18 -07: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 22:17:32 -06: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 | 
					
						
							|  |  |  | } |