use a trampoline to get to the spicy _x functions

This commit is contained in:
JackDoan committed 2026-09-25 13:01:50 -05:00
1 parent 2ff4852551
commit fd29ebdacd
9 files changed
+227 -62

No files matched your search

+14 -27
View File
@@ -15,6 +15,7 @@ import (
"unsafe"
"github.com/slackhq/nebula/config"
"github.com/slackhq/nebula/internal/msgx"
"golang.org/x/sys/unix"
)
@@ -39,6 +40,7 @@ func NewListener(l *slog.Logger, s Settings) (Conn, error) {
if uc, ok := pc.(*net.UDPConn); ok {
c := &StdConn{UDPConn: uc, l: l}
c.noSendmsgX.Store(!msgx.Available())
rc, err := uc.SyscallConn()
if err != nil {
@@ -150,7 +152,7 @@ const sendBatch = 64
type sendScratch struct {
names [sendBatch]unix.RawSockaddrInet6
iovs [sendBatch]unix.Iovec
hdrs [sendBatch]msghdrX
hdrs [sendBatch]msgx.Hdr
// idx maps each entry back to its index in WriteBatch's bufs.
idx [sendBatch]int
}
@@ -190,7 +192,7 @@ func (u *StdConn) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) (int, error)
b.iovs[n].Base = &bufs[i][0]
b.iovs[n].SetLen(len(bufs[i]))
}
b.hdrs[n] = msghdrX{Name: (*byte)(unsafe.Pointer(&b.names[n])), Namelen: namelen, Iov: &b.iovs[n], Iovlen: 1}
b.hdrs[n] = msgx.Hdr{Name: (*byte)(unsafe.Pointer(&b.names[n])), Namelen: namelen, Iov: &b.iovs[n], Iovlen: 1}
b.idx[n] = i
n++
}
@@ -252,11 +254,11 @@ func (u *StdConn) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) (int, error)
// The socket is non-blocking, so a full send buffer returns EAGAIN rather than waiting.
func (u *StdConn) sendmsgX(off, end int) (int, syscall.Errno) {
for {
r0, _, errno := unix.Syscall6(unix.SYS_SENDMSG_X, u.sysFd, uintptr(unsafe.Pointer(&u.sb.hdrs[off])), uintptr(end-off), 0, 0, 0)
sent, errno := msgx.Send(u.sysFd, u.sb.hdrs[off:end], 0)
if errno == unix.EINTR {
continue
}
return int(r0), errno
return sent, errno
}
}
@@ -330,23 +332,12 @@ func NewUDPStatsEmitter(udpConns []Conn) func() {
// recvBatch is how many datagrams one recvmsg_x call may return.
const recvBatch = 64
// msghdrX mirrors xnu's struct msghdr_x (bsd/sys/socket_private.h). recvmsg_x reports each
// datagram's length in Datalen rather than in its return value.
// Keep in sync with msghdrX in overlay/tio/tio_utun_darwin.go.
type msghdrX struct {
Name *byte
Namelen uint32
Iov *unix.Iovec
Iovlen int32
Control *byte
Controllen uint32
Flags int32
Datalen uint64
}
// ListenOut drains the socket with recvmsg_x, darwin's private batched recvmsg, so one syscall reads
// up to recvBatch datagrams instead of one.
func (u *StdConn) ListenOut(r EncReader, flush func()) error {
if !msgx.Available() {
return u.listenOutSingle(r, flush)
}
rc, err := u.UDPConn.SyscallConn()
if err != nil {
return err
@@ -355,7 +346,7 @@ func (u *StdConn) ListenOut(r EncReader, flush func()) error {
bufs := make([][]byte, recvBatch)
names := make([]unix.RawSockaddrInet6, recvBatch)
iovs := make([]unix.Iovec, recvBatch)
hdrs := make([]msghdrX, recvBatch)
hdrs := make([]msgx.Hdr, recvBatch)
for i := range hdrs {
bufs[i] = make([]byte, MTU)
iovs[i].Base = &bufs[i][0]
@@ -374,12 +365,8 @@ func (u *StdConn) ListenOut(r EncReader, flush func()) error {
hdrs[i].Flags = 0
hdrs[i].Datalen = 0
}
r0, _, e := unix.Syscall6(unix.SYS_RECVMSG_X, fd, uintptr(unsafe.Pointer(&hdrs[0])), recvBatch, 0, 0, 0)
if e == unix.EAGAIN {
return false
}
n, errno = int(r0), e
return true
n, errno = msgx.Recv(fd, hdrs, 0)
return errno != unix.EAGAIN
})
if err != nil {
if errors.Is(err, net.ErrClosed) {
@@ -389,7 +376,7 @@ func (u *StdConn) ListenOut(r EncReader, flush func()) error {
continue
}
if errno == unix.ENOSYS || errno == unix.EPERM || errno == unix.EOPNOTSUPP {
// recvmsg_x is private, so a kernel or sandbox (such as an iOS extension's) may refuse it.
// recvmsg_x is private, so a kernel or sandbox may refuse it.
u.l.Warn("recvmsg_x unavailable, reading one datagram per syscall", "error", errno)
return u.listenOutSingle(r, flush)
}
@@ -419,7 +406,7 @@ func (u *StdConn) ListenOut(r EncReader, flush func()) error {
// checkMsghdrX rejects a recvmsg_x result that intact msghdr_x entries can't produce, which is how a change
// to xnu's private struct layout would show up.
func checkMsghdrX(hdrs []msghdrX, names []unix.RawSockaddrInet6, n int) error {
func checkMsghdrX(hdrs []msgx.Hdr, names []unix.RawSockaddrInet6, n int) error {
if n > len(hdrs) {
return fmt.Errorf("returned %d datagrams for %d headers", n, len(hdrs))
}
+13 -13
View File
@@ -14,6 +14,7 @@ import (
"time"
"unsafe"
"github.com/slackhq/nebula/internal/msgx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
@@ -81,7 +82,7 @@ func TestListenOutBatchesBurst(t *testing.T) {
}
}
// msghdrX round-trips through xnu's recvmsg_x on a loopback socket, v4 and v6. The kernel reads Name, Namelen,
// msgx.Hdr round-trips through xnu's recvmsg_x on a loopback socket, v4 and v6. The kernel reads Name, Namelen,
// Iov and Iovlen from each entry and writes Namelen, Flags and Datalen back, so a field at the wrong offset or a
// wrong stride between entries shows up as a wrong length, sender, or payload.
// TestListenOutSingleFallback pins the path ListenOut takes when the kernel refuses recvmsg_x.
@@ -134,8 +135,8 @@ func TestListenOutSingleFallback(t *testing.T) {
}
func TestCheckMsghdrX(t *testing.T) {
valid := func() ([]msghdrX, []unix.RawSockaddrInet6) {
hdrs := make([]msghdrX, 2)
valid := func() ([]msgx.Hdr, []unix.RawSockaddrInet6) {
hdrs := make([]msgx.Hdr, 2)
names := make([]unix.RawSockaddrInet6, 2)
hdrs[0].Namelen, hdrs[0].Datalen = unix.SizeofSockaddrInet4, 1
names[0].Family = unix.AF_INET
@@ -151,12 +152,12 @@ func TestCheckMsghdrX(t *testing.T) {
for _, tc := range []struct {
name string
n int
mutate func([]msghdrX, []unix.RawSockaddrInet6)
mutate func([]msgx.Hdr, []unix.RawSockaddrInet6)
}{
{"count past headers", 3, func([]msghdrX, []unix.RawSockaddrInet6) {}},
{"datalen past buffer", 2, func(h []msghdrX, _ []unix.RawSockaddrInet6) { h[1].Datalen = MTU + 1 }},
{"unknown family", 2, func(_ []msghdrX, a []unix.RawSockaddrInet6) { a[0].Family = unix.AF_UNIX }},
{"family and length disagree", 2, func(h []msghdrX, _ []unix.RawSockaddrInet6) { h[0].Namelen = unix.SizeofSockaddrInet6 }},
{"count past headers", 3, func([]msgx.Hdr, []unix.RawSockaddrInet6) {}},
{"datalen past buffer", 2, func(h []msgx.Hdr, _ []unix.RawSockaddrInet6) { h[1].Datalen = MTU + 1 }},
{"unknown family", 2, func(_ []msgx.Hdr, a []unix.RawSockaddrInet6) { a[0].Family = unix.AF_UNIX }},
{"family and length disagree", 2, func(h []msgx.Hdr, _ []unix.RawSockaddrInet6) { h[0].Namelen = unix.SizeofSockaddrInet6 }},
} {
t.Run(tc.name, func(t *testing.T) {
hdrs, names := valid()
@@ -238,7 +239,7 @@ func recvmsgXRoundTrip(t *testing.T, v6 bool, lens []int, nh int, settle time.Du
bufs := make([][]byte, nh)
names := make([]unix.RawSockaddrInet6, nh)
iovs := make([]unix.Iovec, nh)
hdrs := make([]msghdrX, nh)
hdrs := make([]msgx.Hdr, nh)
for i := range hdrs {
bufs[i] = make([]byte, MTU)
iovs[i].Base = &bufs[i][0]
@@ -253,7 +254,7 @@ func recvmsgXRoundTrip(t *testing.T, v6 bool, lens []int, nh int, settle time.Du
for next := 0; next < len(lens); {
for i := range hdrs {
clear(bufs[i])
hdrs[i] = msghdrX{
hdrs[i] = msgx.Hdr{
Name: (*byte)(unsafe.Pointer(&names[i])),
Namelen: unix.SizeofSockaddrInet6,
Iov: &iovs[i],
@@ -265,9 +266,8 @@ func recvmsgXRoundTrip(t *testing.T, v6 bool, lens []int, nh int, settle time.Du
var n int
var errno syscall.Errno
require.NoError(t, rc.Read(func(fd uintptr) bool {
r0, _, e := unix.Syscall6(unix.SYS_RECVMSG_X, fd, uintptr(unsafe.Pointer(&hdrs[0])), uintptr(nh), 0, 0, 0)
n, errno = int(r0), e
return e != unix.EAGAIN
n, errno = msgx.Recv(fd, hdrs[:nh], 0)
return errno != unix.EAGAIN
}), "received %d of %d datagrams", next, len(lens))
require.Zero(t, errno)
require.Positive(t, n)