udpbatch

package
v0.6.18 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package udpbatch sends and receives multiple UDP datagrams per system call on Darwin, using the kernel's sendmsg_x and recvmsg_x entry points.

Linux programs batch UDP with sendmmsg and recvmmsg. Darwin has the same capability but exposes it only through undocumented symbols declared in xnu's bsd/sys/socket_private.h, so Go UDP stacks on macOS issue one system call per datagram. This package binds those symbols.

The symbols are private. Available reports whether they resolved and passed a self-test on this system, and every call returns ErrUnavailable if they did not. Callers must keep their per-datagram path and fall back to it; this package is an optimization, never a requirement.

Connections, not descriptors

Every entry point takes a syscall.RawConn rather than a file descriptor, and performs its work inside the conn's Read or Write method. A bare int fd races Go's network poller and invites use-after-close: the descriptor may be closed and reused by an unrelated goroutine between the caller obtaining it and the kernel acting on it.

Going through syscall.RawConn also fixes blocking. A *net.UDPConn's descriptor is always non-blocking, so a loop that called recvmsg_x on the raw fd would busy-spin on EAGAIN rather than sleep. RawConn.Read parks the goroutine on the poller and retries when the socket is readable.

Sending is restricted to connected sockets

socket_private.h states that msg_name, msg_namelen, msg_control and msg_controllen must be zero on input to sendmsg_x. Send therefore takes payloads with no destinations and no control messages, and is usable only on a connected socket.

This restriction is deliberately stricter than what some implementations do in practice. Whether the kernel honors the documented prohibition has not been established empirically, and the fast paths that ignore it are not known to run in production anywhere. Until that is measured, this package promises only what the header promises: if per-datagram addressing proves to work, an addressed variant can be added compatibly, whereas withdrawing one could not.

Receiving carries no such restriction. recvmsg_x does not forbid msg_name on output, so Recv reports each datagram's source and suits an unconnected socket serving many peers.

Partial transfers are normal

Like sendmmsg, the kernel may accept fewer datagrams than were offered. Send reports how many it took; the caller retries the remainder. A short count is ordinary operation, not an error.

Version behavior

Known quirks by release, from implementations that shipped this surface and from this package's own tests:

  • macOS 10.15: recvmsg_x does not write back msg_controllen (reported by noq's implementation). This package does not expose control messages, so that quirk is absorbed here.
  • macOS 26.x (measured by this package's tests): truncation is silent. The header promises MSG_TRUNC in msg_flags when a datagram exceeds the buffer; the kernel instead clamps msg_datalen to the buffer size and sets no flag. A Message whose N equals len(Payload) may have been truncated — size Payload above the largest expected datagram, because nothing distinguishes an exact fit from a loss.

These are recorded because they demonstrate the class: private syscalls change behavior between releases without notice. The Available self-test exchanges real datagrams with the running kernel precisely because a layout check against a pinned header cannot detect that the header and the kernel disagree.

Distribution

Resolving private symbols with dlsym is exactly what Mac App Store static analysis flags. Do not link this package into an App Store build. Like the repository's private/ bindings, this surface is unstable, may change or disappear between OS releases, and carries no compatibility guarantees.

Index

Constants

View Source
const BatchSize = 32

BatchSize is the number of datagrams to offer per call that implementations have converged on. It is a starting point, not a tuned value: the point at which the benefit flattens has not been measured here, and the best size depends on payload size and whether the socket is on loopback or a real interface.

Variables

View Source
var ErrUnavailable = errors.New("udpbatch: sendmsg_x/recvmsg_x unavailable")

ErrUnavailable is returned by every call in this package when the batched datapath is not usable on the running system.

Functions

func Available

func Available() bool

Available reports whether the batched datapath is usable on this system.

On first use it resolves the underlying symbols and runs a self-test: it exchanges known datagrams over a loopback socket pair and verifies that the kernel wrote back what was expected, including per-datagram lengths and the sender's address. The self-test is the point. The committed layout record checks this package's struct against a pinned copy of Apple's header, but only an exchange with the running kernel can detect that the header and the kernel disagree, and only that check fails safe on a system this package was never tested against.

A false result is permanent for the life of the process and means every call in this package returns ErrUnavailable.

func Recv

func Recv(conn syscall.RawConn, msgs []Message) (n int, err error)

Recv receives up to len(msgs) datagrams in one system call and reports how many were received, filling the N, Addr and Flags fields of those entries.

It parks the calling goroutine on the network poller until the socket is readable, so it neither busy-spins nor blocks an OS thread.

Every payload buffer and the internal header arrays are pinned with a runtime.Pinner for the duration of the call: the kernel is handed pointers into Go memory, which the garbage collector must not move underneath it.

func SelfTestError

func SelfTestError() error

SelfTestError runs the availability check and, if it failed, reports why. It exists for diagnostics; callers should gate on Available.

func Send

func Send(conn syscall.RawConn, payloads [][]byte) (n int, err error)

Send transmits payloads on a connected socket in one system call and reports how many datagrams the kernel accepted, which may be fewer than len(payloads).

The caller retries the unaccepted tail. Destinations and control messages are not supported; see the package documentation for why.

Payloads are pinned for the duration of the call, as described on Recv.

Types

type Message

type Message struct {
	// Payload is the caller-provided buffer to receive into.
	Payload []byte

	// N is the number of bytes written into Payload.
	N int

	// Addr is the datagram's source address.
	Addr netip.AddrPort

	// Flags holds the kernel's per-message flags, including truncation.
	Flags int
}

A Message is one received datagram.

Payload is supplied by the caller and is filled by Recv; N reports how many bytes were written into it. A datagram larger than the payload is truncated, and Flags reports the truncation via syscall.MSG_TRUNC.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL