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>
This commit is contained in:
Ryan HuberandClaude Opus 5.5 committed 2026-09-30 11:11:48 +00:00
1 parent 37032a22bf
commit 21075ba32e
7 files changed
+808 -125

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
// Package msgx calls xnu's private batched socket calls, sendmsg_x and recvmsg_x, which move many datagrams per
// syscall. They are not public API, so a kernel or sandbox may refuse them; callers fall back to one datagram per
// syscall when Unsupported reports that.
package msgx
import (
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
// Batch is the most messages the callers hand one sendmsg_x or recvmsg_x call.
const Batch = 64
// Hdr mirrors xnu's struct msghdr_x (bsd/sys/socket_private.h), which the SDK doesn't ship. recvmsg_x reports each
// message's length in Datalen rather than in its return value.
type Hdr struct {
Name *byte
Namelen uint32
Iov *unix.Iovec
Iovlen int32
Control *byte
Controllen uint32
Flags int32
Datalen uint64
}
// Send passes hdrs to sendmsg_x on fd and returns how many messages the kernel took. EINTR is retried.
func Send(fd uintptr, hdrs []Hdr) (int, syscall.Errno) {
return call(unix.SYS_SENDMSG_X, fd, hdrs)
}
// Recv passes hdrs to recvmsg_x on fd and returns how many messages it filled. EINTR is retried.
func Recv(fd uintptr, hdrs []Hdr) (int, syscall.Errno) {
return call(unix.SYS_RECVMSG_X, fd, hdrs)
}
func call(trap, fd uintptr, hdrs []Hdr) (int, syscall.Errno) {
if len(hdrs) == 0 {
return 0, syscall.EINVAL
}
for {
r, _, errno := unix.Syscall6(trap, fd, uintptr(unsafe.Pointer(&hdrs[0])), uintptr(len(hdrs)), 0, 0, 0)
switch errno {
case 0:
return int(r), 0
case syscall.EINTR:
default:
return 0, errno
}
}
}
// Unsupported reports whether errno means the kernel or a sandbox refuses the private calls.
func Unsupported(errno syscall.Errno) bool {
return errno == syscall.ENOSYS || errno == syscall.EPERM || errno == syscall.EOPNOTSUPP
}
+79
View File
@@ -0,0 +1,79 @@
package msgx
import (
"bytes"
"syscall"
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
// TestHdrLayout pins Hdr to struct msghdr_x's LP64 layout: 56 bytes, Datalen last at 48.
func TestHdrLayout(t *testing.T) {
var h Hdr
assert.EqualValues(t, 56, unsafe.Sizeof(h))
assert.EqualValues(t, 48, unsafe.Offsetof(h.Datalen))
}
// TestSendRecvRoundTrip sends a batch through sendmsg_x in one call and reads it back through recvmsg_x in one
// call, each message whole, in order, with Datalen set.
func TestSendRecvRoundTrip(t *testing.T) {
fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0)
require.NoError(t, err)
t.Cleanup(func() {
_ = unix.Close(fds[0])
_ = unix.Close(fds[1])
})
// A dropped datagram fails the receive instead of hanging it.
require.NoError(t, unix.SetsockoptTimeval(fds[1], unix.SOL_SOCKET, unix.SO_RCVTIMEO, &unix.Timeval{Sec: 2}))
const n = 5
var want [n][]byte
var iovs [n]unix.Iovec
hdrs := make([]Hdr, n)
for i := range n {
want[i] = bytes.Repeat([]byte{byte(i + 1)}, 10+i)
iovs[i] = unix.Iovec{Base: &want[i][0]}
iovs[i].SetLen(len(want[i]))
hdrs[i] = Hdr{Iov: &iovs[i], Iovlen: 1}
}
sent, errno := Send(uintptr(fds[0]), hdrs)
require.Zero(t, errno)
require.Equal(t, n, sent)
var bufs [n + 1][64]byte
var riovs [n + 1]unix.Iovec
rhdrs := make([]Hdr, n+1)
for i := range rhdrs {
riovs[i] = unix.Iovec{Base: &bufs[i][0]}
riovs[i].SetLen(len(bufs[i]))
rhdrs[i] = Hdr{Iov: &riovs[i], Iovlen: 1}
}
got, errno := Recv(uintptr(fds[1]), rhdrs)
require.Zero(t, errno)
require.Equal(t, n, got)
for i := range n {
l := int(rhdrs[i].Datalen)
assert.Equal(t, want[i], bufs[i][:l], "message %d", i)
}
}
// TestEmptyBatch pins that an empty batch is refused before it reaches the kernel.
func TestEmptyBatch(t *testing.T) {
_, errno := Send(0, nil)
assert.Equal(t, syscall.EINVAL, errno)
_, errno = Recv(0, nil)
assert.Equal(t, syscall.EINVAL, errno)
}
func TestUnsupported(t *testing.T) {
for _, e := range []syscall.Errno{syscall.ENOSYS, syscall.EPERM, syscall.EOPNOTSUPP} {
assert.True(t, Unsupported(e), "%v", e)
}
for _, e := range []syscall.Errno{0, syscall.EAGAIN, syscall.ENOBUFS, syscall.EINVAL} {
assert.False(t, Unsupported(e), "%v", e)
}
}