Files
nebula/udp/connsock_darwin_test.go
T
Ryan HuberandClaude Opus 5.5 21075ba32e udp/darwin: batch receives and connected-socket sends with recvmsg_x/sendmsg_x
internal/msgx is the one copy of xnu's private batched socket calls: the
msghdr_x layout, Send/Recv through the raw syscall (EINTR retried) and
Unsupported for the errnos a kernel or sandbox refuses them with.

Every socket the listener owns reads with recvmsg_x: the listener and each
connected socket run their own reader goroutine, and hand whole batches to
ListenOut over a channel. ListenOut runs the EncReader over a batch and
flushes once, so the reader stays single-threaded without a lock that
readers contend on, and the next recvmsg_x overlaps the decrypt of the last
batch. Batch buffers (64 x MTU) come from a per-listener free list that
keeps up to 11; a reader takes one only while its socket is readable, so
idle connected sockets hold none. A batch with a header intact msghdr_x
entries can't produce, or ENOSYS/EPERM/EOPNOTSUPP, switches every reader to
one datagram per read for good.

Connected sockets send runs with sendmsg_x, which xnu batches through the
stack on a connected socket. EAGAIN/ENOBUFS keep #1901's single-datagram
stall handling; a refused sendmsg_x retries the datagram with write and
turns sendmsg_x off once that goes through.

The connected-socket knobs lose their NEBULA_CONNSOCK* environment
overrides and keep #1901's defaults.

Based on Caleb Jasik's #1895 (recvmsg_x) and #1897 (sendmsg_x), and Jack
Doan's internal/msgx shape from #1898.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:11:48 +00:00

1026 lines
36 KiB
Go

//go:build !e2e_testing
package udp
import (
"context"
"fmt"
"log/slog"
"net"
"net/netip"
"strings"
"sync"
"sync/atomic"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
// newConnSockListener opens a listener, applies cfg to its connected socket knobs, and starts ListenOut with r.
func newConnSockListener(t *testing.T, s Settings, h slog.Handler, r EncReader, cfg ...func(*connSockConfig)) *StdConn {
t.Helper()
if h == nil {
h = slog.DiscardHandler
}
if r == nil {
r = func(netip.AddrPort, []byte) {}
}
c, err := NewListener(slog.New(h), s)
require.NoError(t, err)
t.Cleanup(func() { _ = c.Close() })
u := c.(*StdConn)
for _, f := range cfg {
f(&u.socks.cfg)
}
go func() { _ = u.ListenOut(r, func() {}) }()
require.Eventually(t, func() bool { return u.listening.Load() }, 5*time.Second, time.Millisecond)
return u
}
// oneAtATime stands in for sendFd with f, which writes one datagram, so a test can fail chosen datagrams.
func oneAtATime(f func(fd int, b []byte) error) func(int, [][]byte, *sendScratch) (int, error) {
return func(fd int, bufs [][]byte, _ *sendScratch) (int, error) {
if err := f(fd, bufs[0]); err != nil {
return 0, err
}
return 1, nil
}
}
func writeFd(fd int, b []byte) error {
_, err := syscall.Write(fd, b)
return err
}
func listen(s string) Settings { return Settings{Listen: netip.MustParseAddrPort(s)} }
// run builds n datagrams to dst.
func run(dst netip.AddrPort, n int, tag string) ([][]byte, []netip.AddrPort) {
bufs := make([][]byte, n)
addrs := make([]netip.AddrPort, n)
for i := range bufs {
bufs[i] = fmt.Appendf(nil, "%s-%d", tag, i)
addrs[i] = dst
}
return bufs, addrs
}
func newPeer(t *testing.T, network, addr string) (*net.UDPConn, netip.AddrPort) {
t.Helper()
pc, err := net.ListenPacket(network, addr)
require.NoError(t, err)
t.Cleanup(func() { _ = pc.Close() })
uc := pc.(*net.UDPConn)
return uc, uc.LocalAddr().(*net.UDPAddr).AddrPort()
}
// newEstablishedPeer opens a peer as newPeer does and tells u it is an established tunnel's remote.
func newEstablishedPeer(t *testing.T, u *StdConn, network, addr string) (*net.UDPConn, netip.AddrPort) {
t.Helper()
c, dst := newPeer(t, network, addr)
u.SetEstablishedPeer(dst, true)
return c, dst
}
// readFrom reads n datagrams at peer and returns the source of each.
func readFrom(t *testing.T, peer *net.UDPConn, n int) []netip.AddrPort {
t.Helper()
buf := make([]byte, MTU)
var from []netip.AddrPort
for range n {
require.NoError(t, peer.SetReadDeadline(time.Now().Add(5*time.Second)))
_, f, err := peer.ReadFromUDPAddrPort(buf)
require.NoError(t, err)
from = append(from, netip.AddrPortFrom(f.Addr().Unmap(), f.Port()))
}
return from
}
func reusePort(t *testing.T, c syscall.Conn) int {
t.Helper()
rc, err := c.SyscallConn()
require.NoError(t, err)
var v int
var serr error
require.NoError(t, rc.Control(func(fd uintptr) {
v, serr = unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT)
}))
require.NoError(t, serr)
return v
}
// rogueBinds tries each of binds, "network address plain|reuseport", on port and returns the ones that bound.
func rogueBinds(t *testing.T, port uint16, binds []string) []string {
t.Helper()
var bound []string
for _, b := range binds {
f := strings.Fields(b)
network, addr, reuse := f[0], f[1], f[2]
lc := net.ListenConfig{Control: connSockControl(reuse == "reuseport", 0)}
pc, err := lc.ListenPacket(context.Background(), network, net.JoinHostPort(addr, fmt.Sprint(port)))
if err == nil {
_ = pc.Close()
bound = append(bound, b)
}
}
return bound
}
// The listener never sets SO_REUSEPORT, and a connected socket binds with it only to a specific address, so every
// bind a lone listener refuses is refused after NewListener returns and while connected sockets are open. Same-uid
// SO_REUSEPORT binds to a specific address are left out: xnu allows them beside a lone wildcard listener too,
// which is how a connected socket binds, and refuses them to any other uid.
func TestConnSocksNoSharedBind(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
la, err := u.LocalAddr()
require.NoError(t, err)
binds := []string{
"udp4 0.0.0.0 plain", "udp4 0.0.0.0 reuseport", "udp4 127.0.0.1 plain",
"udp6 :: plain", "udp6 :: reuseport", "udp6 ::1 plain",
}
assert.Zero(t, reusePort(t, u.UDPConn), "listener")
assert.Empty(t, rogueBinds(t, la.Port(), binds), "bound beside the listener")
peer4, dst4 := newEstablishedPeer(t, u, "udp4", "127.0.0.1:0")
peer6, dst6 := newEstablishedPeer(t, u, "udp6", "[::1]:0")
for _, p := range []struct {
c *net.UDPConn
dst netip.AddrPort
}{{peer4, dst4}, {peer6, dst6}} {
bufs, addrs := run(p.dst, u.socks.cfg.run, "p")
_, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
readFrom(t, p.c, len(bufs))
s := u.connSockFor(p.dst)
require.NotNil(t, s, "no connected socket to %v", p.dst)
assert.NotZero(t, reusePort(t, s.conn), "connected socket to %v", p.dst)
}
assert.Empty(t, rogueBinds(t, la.Port(), binds), "bound beside the connected sockets")
}
// A connected socket binds the source address the listener's route to its peer picks, for either family on a
// dual-stack listener, so the peer sees one address and port whichever socket sent.
func TestConnSocksSourceMatchesListener(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
la, err := u.LocalAddr()
require.NoError(t, err)
for _, p := range []struct{ network, addr string }{{"udp4", "127.0.0.1:0"}, {"udp6", "[::1]:0"}} {
peer, dst := newEstablishedPeer(t, u, p.network, p.addr)
require.NoError(t, u.WriteTo([]byte("probe"), dst))
require.Nil(t, u.connSockFor(dst))
viaListener := readFrom(t, peer, 1)[0]
assert.Equal(t, la.Port(), viaListener.Port())
bufs, addrs := run(dst, u.socks.cfg.run, p.network)
_, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
s := u.connSockFor(dst)
require.NotNil(t, s, "no connected socket to %v", dst)
for _, from := range readFrom(t, peer, len(bufs)) {
assert.Equal(t, viaListener, from, "source seen by %v", dst)
}
local := s.conn.LocalAddr().(*net.UDPAddr).AddrPort()
assert.Equal(t, viaListener, netip.AddrPortFrom(local.Addr().Unmap(), local.Port()))
}
}
// A run long enough opens a connected socket per peer, its replies reach the listener's reader, and a peer that
// goes away sends its traffic back through the listener with its connected socket closed, while the next peer gets
// a connected socket of its own.
func TestConnSocks(t *testing.T) {
var mu sync.Mutex
got := map[netip.AddrPort]int{}
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, func(addr netip.AddrPort, _ []byte) {
mu.Lock()
got[addr]++
mu.Unlock()
})
la, err := u.LocalAddr()
require.NoError(t, err)
to := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), la.Port())
var peers [3]*net.UDPConn
var dsts [3]netip.AddrPort
for i := range peers {
peers[i], dsts[i] = newEstablishedPeer(t, u, "udp4", "127.0.0.1:0")
}
for i, dst := range dsts[:2] {
bufs, addrs := run(dst, u.socks.cfg.run, fmt.Sprint("p", i))
n, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
assert.Equal(t, len(bufs), n)
require.NotNil(t, u.connSockFor(dst), "no connected socket to peer %d", i)
for _, from := range readFrom(t, peers[i], len(bufs)) {
assert.Equal(t, to, from)
}
for range 3 {
_, err := peers[i].WriteToUDPAddrPort([]byte("reply"), to)
require.NoError(t, err)
}
}
require.Eventually(t, func() bool {
mu.Lock()
defer mu.Unlock()
return got[dsts[0]] == 3 && got[dsts[1]] == 3
}, 5*time.Second, time.Millisecond)
// The peer's ICMP port unreachable surfaces on the connected socket's next call. The deadline is generous
// because xnu rate-limits and delivers the unreachable on its own schedule.
gone := dsts[1]
goneSock := u.connSockFor(gone)
require.NoError(t, peers[1].Close())
bufs, addrs := run(gone, u.socks.cfg.run, "gone")
require.Eventually(t, func() bool {
_, _ = u.WriteBatch(bufs, addrs)
return u.connSockFor(gone) == nil
}, 10*time.Second, 10*time.Millisecond)
assert.NotNil(t, u.connSockFor(dsts[0]))
u.socks.closers.Wait()
_, err = goneSock.conn.Write([]byte("x"))
assert.ErrorIs(t, err, net.ErrClosed, "closed connected socket left open")
bufs, addrs = run(dsts[2], u.socks.cfg.run, "p2")
_, err = u.WriteBatch(bufs, addrs)
require.NoError(t, err)
require.NotNil(t, u.connSockFor(dsts[2]))
for _, from := range readFrom(t, peers[2], len(bufs)) {
assert.Equal(t, to, from)
}
require.NoError(t, u.Rebind())
for _, dst := range dsts {
assert.Nil(t, u.connSockFor(dst), "connected socket left after Rebind")
}
}
// Listen routines on one port share one set of connected sockets: the cap and the open bucket are theirs together,
// a peer's connected socket carries every routine's sends to it, and Rebind on the first routine, which is all
// Control.RebindUDPServer calls, clears every routine's listener and closes the connected sockets.
func TestConnSocksSharedAcrossRoutines(t *testing.T) {
s := listen("0.0.0.0:0")
s.Multi = true
u0 := newConnSockListener(t, s, nil, nil, func(c *connSockConfig) { c.max = 1 })
la, err := u0.LocalAddr()
require.NoError(t, err)
s.Listen = netip.AddrPortFrom(s.Listen.Addr(), la.Port())
u1 := newConnSockListener(t, s, nil, nil)
require.Same(t, u0.socks, u1.socks)
lo, err := net.InterfaceByName("lo0")
require.NoError(t, err)
peerA, a := newPeer(t, "udp4", "127.0.0.1:0")
peerB, b := newPeer(t, "udp4", "127.0.0.1:0")
for _, dst := range []netip.AddrPort{a, b} {
// Nebula tells every writer.
u0.SetEstablishedPeer(dst, true)
u1.SetEstablishedPeer(dst, true)
}
bufs, addrs := run(a, u1.socks.cfg.run, "a")
_, err = u1.WriteBatch(bufs, addrs)
require.NoError(t, err)
readFrom(t, peerA, len(bufs))
s1 := u1.connSockFor(a)
require.NotNil(t, s1)
var sends atomic.Int64
s1.sys = oneAtATime(func(fd int, b []byte) error {
sends.Add(1)
return writeFd(fd, b)
})
require.NoError(t, u0.WriteTo([]byte("via u0"), a))
readFrom(t, peerA, 1)
assert.EqualValues(t, 1, sends.Load(), "routine 0 didn't send on routine 1's connected socket")
bufs, addrs = run(b, 2*u0.socks.cfg.run, "b")
_, err = u0.WriteBatch(bufs, addrs)
require.NoError(t, err)
readFrom(t, peerB, len(bufs))
assert.Nil(t, u0.connSockFor(b), "a second connected socket past a cap of 1")
level, opt := unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF
if u0.isV4 {
level, opt = unix.IPPROTO_IP, unix.IP_BOUND_IF
}
boundIf := func(u *StdConn) int {
v, err := unix.GetsockoptInt(int(u.sysFd), level, opt)
require.NoError(t, err)
return v
}
for _, u := range []*StdConn{u0, u1} {
require.NoError(t, unix.SetsockoptInt(int(u.sysFd), level, opt, lo.Index))
require.Equal(t, lo.Index, boundIf(u))
}
require.NoError(t, u0.Rebind())
assert.Zero(t, boundIf(u0))
assert.Zero(t, boundIf(u1), "Rebind left routine 1 scoped")
assert.Nil(t, u1.connSockFor(a), "Rebind left routine 1's connected socket open")
u0.socks.closers.Wait()
_, err = s1.conn.Write([]byte("x"))
assert.ErrorIs(t, err, net.ErrClosed)
}
// A connected socket on a specific listen.host would need SO_REUSEPORT on the listener, so connected sockets stay
// off there unless listen routines already set it.
func TestConnSocksSpecificHost(t *testing.T) {
for _, multi := range []bool{false, true} {
t.Run(fmt.Sprint("multi=", multi), func(t *testing.T) {
s := listen("127.0.0.1:0")
s.Multi = multi
u := newConnSockListener(t, s, nil, nil)
la, err := u.LocalAddr()
require.NoError(t, err)
if !multi {
assert.Zero(t, reusePort(t, u.UDPConn), "listener")
}
peer, dst := newEstablishedPeer(t, u, "udp4", "127.0.0.1:0")
bufs, addrs := run(dst, u.socks.cfg.run, "p")
_, err = u.WriteBatch(bufs, addrs)
require.NoError(t, err)
for _, from := range readFrom(t, peer, len(bufs)) {
assert.Equal(t, la, from)
}
if multi {
assert.NotNil(t, u.connSockFor(dst))
} else {
assert.Nil(t, u.connSockFor(dst))
}
})
}
}
// A v4 listener can't send to a v6 destination; however often it's asked, it says so and opens no connected
// socket.
func TestConnSocksV6DestinationOnV4Listener(t *testing.T) {
s := listen("127.0.0.1:0")
s.Multi = true
u := newConnSockListener(t, s, nil, nil)
require.True(t, u.isV4)
dst := netip.MustParseAddrPort("[::1]:9")
u.SetEstablishedPeer(dst, true)
bufs, _ := run(dst, 2*u.socks.cfg.run, "v6")
for _, b := range bufs {
assert.ErrorIs(t, u.WriteTo(b, dst), ErrInvalidIPv6RemoteForSocket)
}
assert.Nil(t, u.connSockFor(dst))
}
// darwin's tun hands over one packet per read, so a busy peer's traffic reaches the socket as single WriteTo
// calls. cfg.run of them inside one window earn a connected socket; the same count spread over more than a window
// doesn't.
func TestConnSocksOpenFromWriteTo(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
var now atomic.Int64
now.Store(int64(time.Hour))
u.socks.clock = now.Load
_, dst := newEstablishedPeer(t, u, "udp4", "127.0.0.1:0")
pkt := []byte("pkt")
for range u.socks.cfg.run - 1 {
require.NoError(t, u.WriteTo(pkt, dst))
}
now.Add(int64(u.socks.cfg.window) + 1)
require.NoError(t, u.WriteTo(pkt, dst))
assert.Nil(t, u.connSockFor(dst), "a connected socket for sends spread over two windows")
for range u.socks.cfg.run - 2 {
require.NoError(t, u.WriteTo(pkt, dst))
}
assert.Nil(t, u.connSockFor(dst))
require.NoError(t, u.WriteTo(pkt, dst))
assert.NotNil(t, u.connSockFor(dst), "no connected socket after %d sends in one window", u.socks.cfg.run)
}
// Only the remote of an established tunnel gets a connected socket, however busy another destination is, and one
// that stops being established loses its connected socket and has to be busy again to get a new one.
func TestConnSocksOnlyForEstablishedPeers(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
peer, dst := newPeer(t, "udp4", "127.0.0.1:0")
run1 := u.socks.cfg.run
send := func(n int) {
t.Helper()
bufs, addrs := run(dst, n, "p")
_, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
readFrom(t, peer, n)
}
send(2 * run1)
assert.Nil(t, u.connSockFor(dst), "a connected socket for a busy destination with no tunnel")
u.SetEstablishedPeer(dst, true)
send(run1)
s := u.connSockFor(dst)
require.NotNil(t, s, "no connected socket for a busy established peer")
u.SetEstablishedPeer(dst, false)
assert.Nil(t, u.connSockFor(dst))
u.socks.closers.Wait()
_, err := s.conn.Write([]byte("x"))
assert.ErrorIs(t, err, net.ErrClosed, "connected socket left open after its tunnel went away")
send(2 * run1)
assert.Nil(t, u.connSockFor(dst), "a connected socket after the tunnel went away")
u.SetEstablishedPeer(dst, true)
send(run1 - 1)
assert.Nil(t, u.connSockFor(dst), "a re-established peer kept its old count")
send(1)
assert.NotNil(t, u.connSockFor(dst))
}
// A tunnel going away while its busy remote is opening a connected socket never leaves one open, whether or not
// the remote is established again before the open finishes. The dial hook holds the open between reserving its
// destination and dialing until the tunnel has changed, so every round races.
func TestConnSocksEstablishedRace(t *testing.T) {
h := &countingHandler{}
u := newConnSockListener(t, listen("0.0.0.0:0"), h, nil, func(c *connSockConfig) {
c.run = 1
c.openEvery = time.Nanosecond
})
cs := u.socks
peer, dst := newPeer(t, "udp4", "127.0.0.1:0")
for i, reestablish := range []bool{false, true} {
u.SetEstablishedPeer(dst, true)
dialing, release := make(chan struct{}), make(chan struct{})
cs.dialHook = func(netip.AddrPort) {
close(dialing)
<-release
}
done := make(chan error)
go func() { done <- u.WriteTo([]byte("p"), dst) }()
<-dialing
u.SetEstablishedPeer(dst, false)
if reestablish {
u.SetEstablishedPeer(dst, true)
}
close(release)
require.NoError(t, <-done)
cs.dialHook = nil
readFrom(t, peer, 1)
assert.Nil(t, u.connSockFor(dst), "reestablish=%v", reestablish)
assert.EqualValues(t, i+1, h.discarded.Load(), "reestablish=%v", reestablish)
cs.mu.Lock()
assert.Empty(t, cs.pending)
cs.mu.Unlock()
assert.Zero(t, cs.reserved.Load())
u.SetEstablishedPeer(dst, false)
}
assert.Zero(t, h.opened.Load())
}
// Neither an open in flight nor a close holds anything SetEstablishedPeer waits on, since Nebula calls it under
// the hostmap's write lock; and a peer whose connected socket is still closing can't open another until the close
// completes. The hooks hold the open in its dials and the close before its Close.
func TestConnSocksSetEstablishedPeerMakesNoSyscalls(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil, func(c *connSockConfig) { c.openEvery = time.Nanosecond })
cs := u.socks
peer, dst := newEstablishedPeer(t, u, "udp4", "127.0.0.1:0")
_, other := newPeer(t, "udp4", "127.0.0.1:0")
dialing, releaseDial := make(chan struct{}), make(chan struct{})
cs.dialHook = func(netip.AddrPort) {
close(dialing)
<-releaseDial
}
opened := make(chan *connSock)
go func() {
bufs, addrs := run(dst, cs.cfg.run, "p")
_, _ = u.WriteBatch(bufs, addrs)
opened <- u.connSockFor(dst)
}()
<-dialing
u.SetEstablishedPeer(other, true)
u.SetEstablishedPeer(other, false)
close(releaseDial)
s := <-opened
require.NotNil(t, s)
readFrom(t, peer, cs.cfg.run)
cs.dialHook = nil
closing, releaseClose := make(chan struct{}), make(chan struct{})
cs.closeHook = func(netip.AddrPort) {
close(closing)
<-releaseClose
}
u.SetEstablishedPeer(dst, false)
<-closing
assert.Nil(t, u.connSockFor(dst))
u.SetEstablishedPeer(dst, true)
bufs, addrs := run(dst, 2*cs.cfg.run, "q")
_, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
readFrom(t, peer, len(bufs))
assert.Nil(t, u.connSockFor(dst), "a connected socket opened while the last one to its peer was closing")
close(releaseClose)
cs.closers.Wait()
cs.closeHook = nil
_, err = s.conn.Write([]byte("x"))
assert.ErrorIs(t, err, net.ErrClosed)
bufs, addrs = run(dst, cs.cfg.run, "r")
_, err = u.WriteBatch(bufs, addrs)
require.NoError(t, err)
readFrom(t, peer, len(bufs))
assert.NotNil(t, u.connSockFor(dst), "no connected socket once the last one closed")
}
// A listener whose interface scope can't be read gives a busy peer no connected socket, rather than one without
// the scope; the peer stays on the listener for the backoff. A pipe stands in for the listener's descriptor, so
// getsockopt fails with ENOTSOCK.
func TestConnSocksBoundIfFailsClosed(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
_, dst := newEstablishedPeer(t, u, "udp4", "127.0.0.1:0")
var pipe [2]int
require.NoError(t, unix.Pipe(pipe[:]))
t.Cleanup(func() {
_ = unix.Close(pipe[0])
_ = unix.Close(pipe[1])
})
listener := u.sysFd
u.sysFd = uintptr(pipe[0])
s := u.maybeOpenConnSock(u.socks.peer(dst), dst, u.socks.cfg.run)
u.sysFd = listener
assert.Nil(t, s)
assert.Nil(t, u.connSockFor(dst))
assert.Greater(t, u.socks.peer(dst).retryAt, u.socks.clock(), "no backoff after a failed open")
}
// readTags reads n datagrams at peer and returns their payloads.
func readTags(t *testing.T, peer *net.UDPConn, n int) []string {
t.Helper()
buf := make([]byte, MTU)
var tags []string
for range n {
require.NoError(t, peer.SetReadDeadline(time.Now().Add(5*time.Second)))
m, _, err := peer.ReadFromUDPAddrPort(buf)
require.NoError(t, err)
tags = append(tags, string(buf[:m]))
}
return tags
}
// One batch can hold runs to a peer with a connected socket, to an established peer without one, and to a
// destination that is no tunnel's remote, interleaved; each run goes out its own way, in order, and all count.
func TestConnSocksWriteBatchMixedDestinations(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
s, peerA := openConnSock(t, u)
peerB, b := newEstablishedPeer(t, u, "udp4", "127.0.0.1:0")
peerC, c := newPeer(t, "udp4", "127.0.0.1:0")
var onSock atomic.Int64
s.sys = oneAtATime(func(fd int, b []byte) error {
onSock.Add(1)
return writeFd(fd, b)
})
var bufs [][]byte
var addrs []netip.AddrPort
for _, r := range []struct {
dst netip.AddrPort
tag string
n int
}{{s.dst, "a", 3}, {b, "b", 2}, {c, "c", 2}, {s.dst, "A", 2}, {c, "C", 1}} {
for i := range r.n {
bufs = append(bufs, fmt.Appendf(nil, "%s%d", r.tag, i))
addrs = append(addrs, r.dst)
}
}
n, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
assert.Equal(t, len(bufs), n)
assert.EqualValues(t, 5, onSock.Load(), "datagrams on a's connected socket")
assert.Equal(t, []string{"a0", "a1", "a2", "A0", "A1"}, readTags(t, peerA, 5))
assert.Equal(t, []string{"b0", "b1"}, readTags(t, peerB, 2))
assert.Equal(t, []string{"c0", "c1", "C0"}, readTags(t, peerC, 3))
assert.Nil(t, u.connSockFor(b))
}
// A connected socket error that doesn't mean the path is gone costs only its datagram: WriteBatch skips it and
// sends the rest of the run on the connected socket, WriteTo returns it, and the connected socket stays open.
func TestConnSocksWriteError(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
s, peer := openConnSock(t, u)
s.sys = oneAtATime(func(fd int, b []byte) error {
if string(b) == "bad" {
return syscall.EMSGSIZE
}
return writeFd(fd, b)
})
bufs := [][]byte{[]byte("ok0"), []byte("bad"), []byte("ok2")}
n, err := u.WriteBatch(bufs, []netip.AddrPort{s.dst, s.dst, s.dst})
require.NoError(t, err)
assert.Equal(t, 2, n)
assert.Equal(t, []string{"ok0", "ok2"}, readTags(t, peer, 2))
assert.ErrorIs(t, u.WriteTo([]byte("bad"), s.dst), syscall.EMSGSIZE)
assert.Same(t, s, u.connSockFor(s.dst))
}
// A connected socket that turns out closed or dead partway through a run leaves the rest of the run to the
// listener, in order and all counted; a dead one is closed and its peer backs off to the listener.
func TestConnSocksWriteBatchFallsBackMidRun(t *testing.T) {
for _, fail := range []error{net.ErrClosed, syscall.ECONNREFUSED} {
t.Run(fail.Error(), func(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
s, peer := openConnSock(t, u)
var calls atomic.Int64
s.sys = oneAtATime(func(fd int, b []byte) error {
if calls.Add(1) > 2 {
return fail
}
return writeFd(fd, b)
})
bufs, addrs := run(s.dst, 5, "r")
n, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
assert.Equal(t, 5, n)
assert.Equal(t, []string{"r-0", "r-1", "r-2", "r-3", "r-4"}, readTags(t, peer, 5))
assert.EqualValues(t, 3, calls.Load(), "the connected socket was tried again after it failed")
if fail == syscall.ECONNREFUSED {
assert.Nil(t, u.connSockFor(s.dst))
u.socks.mu.Lock()
assert.Greater(t, u.socks.peer(s.dst).retryAt, u.socks.clock(), "no backoff after a dead connected socket")
u.socks.mu.Unlock()
}
})
}
}
// openConnSock earns a connected socket to a fresh loopback peer and returns it and the peer.
func openConnSock(t *testing.T, u *StdConn) (*connSock, *net.UDPConn) {
t.Helper()
peer, dst := newEstablishedPeer(t, u, "udp4", "127.0.0.1:0")
bufs, addrs := run(dst, u.socks.cfg.run, "p")
_, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
readFrom(t, peer, len(bufs))
s := u.connSockFor(dst)
require.NotNil(t, s)
return s, peer
}
// A flow-controlled interface makes a connected socket's sends wait, up to cfg.enobufsMax without one going
// through; after that each datagram is dropped without waiting until a send succeeds again, and each stall logs
// its drops once. s.sys stands in for an interface that answers every write with ENOBUFS until enobufs is
// cleared, and each sleep advances the clock.
func TestConnSockWriteENOBUFS(t *testing.T) {
h := &countingHandler{}
u := newConnSockListener(t, listen("0.0.0.0:0"), h, nil)
s, _ := openConnSock(t, u)
var now atomic.Int64
now.Store(int64(time.Hour))
sleeps := 0
u.socks.clock = now.Load
u.socks.sleep = func(d time.Duration) {
sleeps++
now.Add(int64(d))
}
var enobufs atomic.Bool
s.sys = oneAtATime(func(fd int, b []byte) error {
if enobufs.Load() {
return syscall.ENOBUFS
}
return writeFd(fd, b)
})
perStall := int(u.socks.cfg.enobufsMax / enobufsWait)
bufs, _ := run(s.dst, 3, "q")
for stall := 1; stall <= 2; stall++ {
enobufs.Store(true)
sent, errno, closed := u.connSockWrite(s, bufs)
assert.Equal(t, 3, sent)
assert.Zero(t, errno)
assert.False(t, closed)
assert.Equal(t, stall*perStall, sleeps)
sent, _, _ = u.connSockWrite(s, bufs)
assert.Equal(t, 3, sent)
assert.Equal(t, stall*perStall, sleeps, "a stalled connected socket waited again")
assert.EqualValues(t, stall, h.dropping.Load(), "drop log lines after stall %d", stall)
enobufs.Store(false)
sent, _, _ = u.connSockWrite(s, bufs[:1])
assert.Equal(t, 1, sent)
assert.Zero(t, s.stallAt.Load())
}
}
// Concurrent writers share one ENOBUFS budget measured from the first ENOBUFS since a send went through: two
// writers stalled together wait as long as one would, and a send going through while a writer sleeps gives that
// writer a fresh budget. The sleep hook parks each writer until the test has moved the clock.
func TestConnSockWriteENOBUFSConcurrentWriters(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
s, _ := openConnSock(t, u)
var now atomic.Int64
u.socks.clock = now.Load
asleep, wake := make(chan struct{}), make(chan struct{})
u.socks.sleep = func(time.Duration) {
asleep <- struct{}{}
<-wake
}
// Datagrams starting with 'e' get ENOBUFS while enobufs is set; the rest go out.
var enobufs atomic.Bool
var wrote atomic.Int64
s.sys = oneAtATime(func(fd int, b []byte) error {
if b[0] == 'e' && enobufs.Load() {
return syscall.ENOBUFS
}
wrote.Add(1)
return writeFd(fd, b)
})
write := func(tag string, done chan<- int) {
sent, _, _ := u.connSockWrite(s, [][]byte{[]byte(tag)})
done <- sent
}
budget := int64(u.socks.cfg.enobufsMax)
t.Run("a send going through ends the stall", func(t *testing.T) {
enobufs.Store(true)
now.Store(int64(time.Hour))
done := make(chan int)
go write("e1", done)
<-asleep
now.Add(budget / 2)
sent, _, _ := u.connSockWrite(s, [][]byte{[]byte("ok")})
require.Equal(t, 1, sent)
now.Add(budget)
wake <- struct{}{}
<-asleep // e1 hit ENOBUFS again 1.5x the budget after its first, and waited instead of dropping.
enobufs.Store(false)
before := wrote.Load()
wake <- struct{}{}
assert.Equal(t, 1, <-done)
assert.Equal(t, before+1, wrote.Load(), "e1 was dropped")
})
t.Run("two stalled writers spend one budget", func(t *testing.T) {
enobufs.Store(true)
now.Store(int64(2 * time.Hour))
done := make(chan int)
go write("e2", done)
go write("e3", done)
<-asleep
<-asleep
now.Add(budget * 6 / 10)
wake <- struct{}{}
wake <- struct{}{}
<-asleep // Both are 0.6x the budget into one stall, not 1.2x, so both wait again.
<-asleep
now.Add(budget / 2)
before := wrote.Load()
wake <- struct{}{}
wake <- struct{}{}
assert.Equal(t, 1, <-done)
assert.Equal(t, 1, <-done)
assert.Equal(t, before, wrote.Load(), "a datagram went out while ENOBUFS lasted")
})
}
// A full send buffer that never drains parks a connected socket send only for cfg.enobufsMax, the stall budget
// ENOBUFS gets: golang/go#73919 has darwin UDP writes parked on EAGAIN waiting forever. Past it the datagrams are
// dropped and counted as sent, concurrent writers spend that one budget, and once a send goes through the
// connected socket works as before. s.sys stands in for a send buffer that stays full while eagain is set.
func TestConnSockWriteEAGAINBounded(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
s, peer := openConnSock(t, u)
var eagain atomic.Bool
eagain.Store(true)
s.sys = oneAtATime(func(fd int, b []byte) error {
if eagain.Load() {
return syscall.EAGAIN
}
return writeFd(fd, b)
})
budget := u.socks.cfg.enobufsMax
start := time.Now()
sent, errno, closed := u.connSockWrite(s, [][]byte{[]byte("x")})
elapsed := time.Since(start)
assert.Equal(t, 1, sent)
assert.Zero(t, errno)
assert.False(t, closed)
assert.GreaterOrEqual(t, elapsed, budget, "the send didn't wait for the buffer to drain")
assert.Less(t, elapsed, time.Second, "the send waited past its deadline")
// The budget is spent, so these drop without waiting, however many writers there are.
var wg sync.WaitGroup
start = time.Now()
for range 4 {
wg.Go(func() {
bufs, _ := run(s.dst, 8, "y")
sent, _, _ := u.connSockWrite(s, bufs)
assert.Equal(t, 8, sent)
})
}
wg.Wait()
assert.Less(t, time.Since(start), time.Second)
// A send going through starts a fresh budget, which two writers stalled together spend once; the upper bound
// is far looser than that, so a descheduled runner doesn't fail it.
eagain.Store(false)
sent, _, _ = u.connSockWrite(s, [][]byte{[]byte("ok")})
require.Equal(t, 1, sent)
readTags(t, peer, 1)
eagain.Store(true)
start = time.Now()
for range 2 {
wg.Go(func() {
sent, _, _ := u.connSockWrite(s, [][]byte{[]byte("w")})
assert.Equal(t, 1, sent)
})
}
wg.Wait()
elapsed = time.Since(start)
assert.GreaterOrEqual(t, elapsed, budget)
assert.Less(t, elapsed, time.Second)
eagain.Store(false)
bufs, addrs := run(s.dst, 3, "z")
n, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
assert.Equal(t, 3, n)
assert.Equal(t, []string{"z-0", "z-1", "z-2"}, readTags(t, peer, 3))
assert.Zero(t, s.stallAt.Load())
assert.Same(t, s, u.connSockFor(s.dst))
}
// A send waiting out ENOBUFS holds nothing a close or Close needs, and returns closed once it wakes to a closed
// connected socket. The sleep hook parks the writer until both closes have finished; after the close, s.sys
// writes for real, so the closed descriptor answers.
func TestConnSockWriteDoesNotBlockClose(t *testing.T) {
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil)
s, _ := openConnSock(t, u)
asleep, wake := make(chan struct{}), make(chan struct{})
u.socks.sleep = func(time.Duration) {
asleep <- struct{}{}
<-wake
}
var gone atomic.Bool
s.sys = oneAtATime(func(fd int, b []byte) error {
if gone.Load() {
return writeFd(fd, b)
}
return syscall.ENOBUFS
})
done := make(chan bool)
go func() {
bufs, _ := run(s.dst, 1, "q")
_, _, closed := u.connSockWrite(s, bufs)
done <- closed
}()
<-asleep
u.closeConnSock(s, "test", false)
u.socks.closers.Wait()
require.NoError(t, u.Close())
gone.Store(true)
close(wake)
assert.True(t, <-done, "the writer didn't see its connected socket close")
}
// countingHandler counts connected socket open, close, discard and ENOBUFS drop log lines.
type countingHandler struct{ opened, closed, discarded, dropping atomic.Int64 }
func (h *countingHandler) Enabled(context.Context, slog.Level) bool { return true }
func (h *countingHandler) Handle(_ context.Context, r slog.Record) error {
switch r.Message {
case "connected socket: opened":
h.opened.Add(1)
case "connected socket: closed":
h.closed.Add(1)
case "connected socket: discarded, its tunnel or route changed while it opened":
h.discarded.Add(1)
case "connected socket: sends stalled, dropping datagrams until one goes through":
h.dropping.Add(1)
}
return nil
}
func (h *countingHandler) WithAttrs([]slog.Attr) slog.Handler { return h }
func (h *countingHandler) WithGroup(string) slog.Handler { return h }
// Anyone can make Nebula reply to them, as recv_error does to every datagram with an unknown index; the reader
// below stands in for that reply. However many replies a sender draws, it never earns a connected socket without
// an established tunnel.
func TestConnSocksNoneForUnestablishedSenders(t *testing.T) {
h := &countingHandler{}
reply := []byte("reply")
var c *StdConn
var ready atomic.Bool
c = newConnSockListener(t, listen("0.0.0.0:0"), h, func(from netip.AddrPort, _ []byte) {
if ready.Load() {
_ = c.WriteTo(reply, from)
}
})
ready.Store(true)
la, err := c.LocalAddr()
require.NoError(t, err)
to := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), la.Port())
pkt := []byte("junk")
buf := make([]byte, 64)
for range 10 {
pc, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
require.NoError(t, err)
for range 2 * c.socks.cfg.run {
_, err := pc.WriteToUDPAddrPort(pkt, to)
require.NoError(t, err)
require.NoError(t, pc.SetReadDeadline(time.Now().Add(5*time.Second)))
_, _, err = pc.ReadFromUDPAddrPort(buf)
require.NoError(t, err)
}
_ = pc.Close()
}
assert.Zero(t, h.opened.Load())
assert.Nil(t, c.socks.table.Load())
}
// Established peers that keep going away churn connected sockets at most as fast as the open bucket allows, and
// what connSocks keeps about each goes when its tunnel does. Each round a fresh peer is established, gets busy and
// goes away, and the clock moves 10ms.
func TestConnSocksChurnBounded(t *testing.T) {
h := &countingHandler{}
u := newConnSockListener(t, listen("0.0.0.0:0"), h, nil)
var now atomic.Int64
now.Store(int64(time.Hour))
u.socks.clock = now.Load
const rounds, step = 300, 10 * time.Millisecond
for range rounds {
peer, dst := newEstablishedPeer(t, u, "udp4", "127.0.0.1:0")
bufs, addrs := run(dst, u.socks.cfg.run, "p")
n, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
require.Equal(t, len(bufs), n)
readFrom(t, peer, len(bufs))
u.SetEstablishedPeer(dst, false)
u.socks.closers.Wait()
require.NoError(t, peer.Close())
now.Add(int64(step))
}
elapsed := time.Duration(rounds) * step
budget := int64(u.socks.cfg.max) + int64(elapsed/u.socks.cfg.openEvery) + 1
t.Logf("%d rounds over %v: %d opens, %d closes", rounds, elapsed, h.opened.Load(), h.closed.Load())
require.GreaterOrEqual(t, h.opened.Load(), int64(u.socks.cfg.max), "the rounds never churned")
require.LessOrEqual(t, h.opened.Load(), budget)
assert.Equal(t, h.opened.Load(), h.closed.Load())
assert.Nil(t, u.socks.table.Load(), "peers left after their tunnels went away")
assert.Zero(t, u.socks.reserved.Load())
}
// No send allocates, down any path: the listener with connected sockets off, to a destination that is no
// tunnel's remote, to an established peer that isn't busy, or on a connected socket, one datagram or a run.
func TestConnSockSendsDontAllocate(t *testing.T) {
if raceEnabled {
t.Skip("-race makes sync.Pool drop writers at random, so connected sends allocate")
}
pkt := make([]byte, 1400)
for _, c := range []struct {
name string
cfg func(*connSockConfig)
setup func(u *StdConn, dst netip.AddrPort)
}{
{name: "off", cfg: func(c *connSockConfig) { c.max = 0 }},
{name: "ineligible", setup: func(u *StdConn, dst netip.AddrPort) {
u.SetEstablishedPeer(netip.AddrPortFrom(dst.Addr(), dst.Port()+1), true)
}},
{name: "eligible", cfg: func(c *connSockConfig) { c.run = 1 << 30 },
setup: func(u *StdConn, dst netip.AddrPort) { u.SetEstablishedPeer(dst, true) }},
{name: "connected", setup: func(u *StdConn, dst netip.AddrPort) {
u.SetEstablishedPeer(dst, true)
bufs, addrs := run(dst, u.socks.cfg.run, "p")
_, err := u.WriteBatch(bufs, addrs)
require.NoError(t, err)
require.NotNil(t, u.connSockFor(dst))
}},
} {
t.Run(c.name, func(t *testing.T) {
var cfg []func(*connSockConfig)
if c.cfg != nil {
cfg = append(cfg, c.cfg)
}
u := newConnSockListener(t, listen("0.0.0.0:0"), nil, nil, cfg...)
_, dst := newPeer(t, "udp4", "127.0.0.1:0")
if c.setup != nil {
c.setup(u, dst)
}
bufs, addrs := run(dst, 8, "b")
assert.Zero(t, testing.AllocsPerRun(100, func() { _ = u.WriteTo(pkt, dst) }), "WriteTo")
assert.Zero(t, testing.AllocsPerRun(100, func() { _, _ = u.WriteBatch(bufs, addrs) }), "WriteBatch")
})
}
}
// Once burst tokens are spent the bucket earns one back per interval.
func TestGCRA(t *testing.T) {
var g gcra
now := int64(time.Hour)
for i := range 3 {
require.True(t, g.take(now, 3, time.Second), "token %d of the burst", i)
}
require.False(t, g.take(now, 3, time.Second))
require.False(t, g.take(now+int64(999*time.Millisecond), 3, time.Second))
require.True(t, g.take(now+int64(time.Second), 3, time.Second))
require.False(t, g.take(now+int64(time.Second), 3, time.Second))
require.True(t, g.take(now+int64(time.Hour), 3, time.Second))
}