From be6aa1bff5ae778af4c48917e29cdcdce4e2a98e Mon Sep 17 00:00:00 2001 From: Nick Garlis Date: Thu, 21 May 2026 08:51:43 +0200 Subject: [PATCH 1/5] unix: add Recvmmsg for linux Add a Recvmmsg wrapper that exposes the recvmmsg(2) system call, allowing callers to receive multiple messages from a socket in a single syscall. ps and oobs are parallel slices of per-message payload and control buffers, with vlen derived from len(ps). Each message uses a single buffer rather than a scatter-gather vector, which should be sufficient for the typical recvmmsg use case. The return values ns, oobns, recvflags, and from are parallel slices of length n (the number of messages received). This is a first step toward https://github.com/golang/go/issues/45886. The wrapper is useful as-is and lets callers experiment with batch receive patterns before a poller-integrated net package API is designed. --- unix/linux/types.go | 3 ++ unix/syscall_linux.go | 65 +++++++++++++++++++++++++++++++++ unix/syscall_linux_386.go | 8 ++++ unix/syscall_linux_amd64.go | 1 + unix/syscall_linux_arm.go | 1 + unix/syscall_linux_arm64.go | 1 + unix/syscall_linux_loong64.go | 1 + unix/syscall_linux_mips64x.go | 1 + unix/syscall_linux_mipsx.go | 1 + unix/syscall_linux_ppc.go | 1 + unix/syscall_linux_ppc64x.go | 1 + unix/syscall_linux_riscv64.go | 1 + unix/syscall_linux_sparc64.go | 1 + unix/syscall_linux_test.go | 56 ++++++++++++++++++++++++++++ unix/zsyscall_linux_amd64.go | 11 ++++++ unix/zsyscall_linux_arm.go | 11 ++++++ unix/zsyscall_linux_arm64.go | 11 ++++++ unix/zsyscall_linux_loong64.go | 11 ++++++ unix/zsyscall_linux_mips.go | 11 ++++++ unix/zsyscall_linux_mips64.go | 11 ++++++ unix/zsyscall_linux_mips64le.go | 11 ++++++ unix/zsyscall_linux_mipsle.go | 11 ++++++ unix/zsyscall_linux_ppc.go | 11 ++++++ unix/zsyscall_linux_ppc64.go | 11 ++++++ unix/zsyscall_linux_ppc64le.go | 11 ++++++ unix/zsyscall_linux_riscv64.go | 11 ++++++ unix/zsyscall_linux_sparc64.go | 11 ++++++ unix/ztypes_linux_386.go | 6 +++ unix/ztypes_linux_amd64.go | 7 ++++ unix/ztypes_linux_arm.go | 6 +++ unix/ztypes_linux_arm64.go | 7 ++++ unix/ztypes_linux_loong64.go | 7 ++++ unix/ztypes_linux_mips.go | 6 +++ unix/ztypes_linux_mips64.go | 7 ++++ unix/ztypes_linux_mips64le.go | 7 ++++ unix/ztypes_linux_mipsle.go | 6 +++ unix/ztypes_linux_ppc.go | 6 +++ unix/ztypes_linux_ppc64.go | 7 ++++ unix/ztypes_linux_ppc64le.go | 7 ++++ unix/ztypes_linux_riscv64.go | 7 ++++ unix/ztypes_linux_s390x.go | 7 ++++ unix/ztypes_linux_sparc64.go | 7 ++++ 42 files changed, 385 insertions(+) diff --git a/unix/linux/types.go b/unix/linux/types.go index 426ca295d..1d5b0dd16 100644 --- a/unix/linux/types.go +++ b/unix/linux/types.go @@ -775,6 +775,8 @@ type PacketMreq C.struct_packet_mreq type Msghdr C.struct_msghdr +type Mmsghdr C.struct_mmsghdr + type Cmsghdr C.struct_cmsghdr type Inet4Pktinfo C.struct_in_pktinfo @@ -829,6 +831,7 @@ const ( SizeofIPv6Mreq = C.sizeof_struct_ipv6_mreq SizeofPacketMreq = C.sizeof_struct_packet_mreq SizeofMsghdr = C.sizeof_struct_msghdr + SizeofMmsghdr = C.sizeof_struct_mmsghdr SizeofCmsghdr = C.sizeof_struct_cmsghdr SizeofInet4Pktinfo = C.sizeof_struct_in_pktinfo SizeofInet6Pktinfo = C.sizeof_struct_in6_pktinfo diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index 21e2bfa39..c8d5b0688 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -1617,6 +1617,71 @@ func sendmsgN(fd int, iov []Iovec, oob []byte, ptr unsafe.Pointer, salen _Sockle return n, nil } +// Recvmmsg receives multiple messages from a socket using the recvmmsg system +// call. ps holds the payload buffers and oobs the optional out-of-band control +// buffers, one entry per message; vlen is derived from len(ps). Pass a nil or +// zero-length oobs to receive no control data. +// +// The results are: +// - n is the number of messages received +// - ns[i] is the number of non-control bytes read into ps[i] +// - oobns[i] is the number of control bytes read into oobs[i]; interpret with [ParseSocketControlMessage] +// - recvflags[i] is the per-message flags returned by recvmmsg +// - from[i] is the sender address for message i, or nil for connected sockets +func Recvmmsg(fd int, ps, oobs [][]byte, flags int) (n int, ns, oobns, recvflags []int, from []Sockaddr, err error) { + vlen := len(ps) + if vlen == 0 { + return 0, nil, nil, nil, nil, EINVAL + } + if len(oobs) > 0 && len(oobs) != vlen { + return 0, nil, nil, nil, nil, EINVAL + } + + msghdrs := make([]Mmsghdr, vlen) + iovecs := make([]Iovec, vlen) + names := make([]byte, SizeofSockaddrAny*vlen) + + for i := range vlen { + if len(ps[i]) > 0 { + iovecs[i].Base = &ps[i][0] + iovecs[i].SetLen(len(ps[i])) + msghdrs[i].Hdr.Iov = &iovecs[i] + msghdrs[i].Hdr.SetIovlen(1) + } + if i < len(oobs) && len(oobs[i]) > 0 { + msghdrs[i].Hdr.Control = &oobs[i][0] + msghdrs[i].Hdr.SetControllen(len(oobs[i])) + } + msghdrs[i].Hdr.Name = &names[i*SizeofSockaddrAny] + msghdrs[i].Hdr.Namelen = uint32(SizeofSockaddrAny) + } + + n, err = recvmmsg(fd, &msghdrs[0], vlen, flags, nil) + if err != nil { + return 0, nil, nil, nil, nil, err + } + + ns = make([]int, n) + oobns = make([]int, n) + recvflags = make([]int, n) + from = make([]Sockaddr, n) + + for i := range n { + ns[i] = int(msghdrs[i].Len) + oobns[i] = int(msghdrs[i].Hdr.Controllen) + recvflags[i] = int(msghdrs[i].Hdr.Flags) + rsa := (*RawSockaddrAny)(unsafe.Pointer(&names[i*SizeofSockaddrAny])) + if rsa.Addr.Family != AF_UNSPEC { + from[i], err = anyToSockaddr(fd, rsa) + if err != nil { + return + } + } + } + + return +} + // BindToDevice binds the socket associated with fd to device. func BindToDevice(fd int, device string) (err error) { return SetsockoptString(fd, SOL_SOCKET, SO_BINDTODEVICE, device) diff --git a/unix/syscall_linux_386.go b/unix/syscall_linux_386.go index 210d545c9..6e29ef62c 100644 --- a/unix/syscall_linux_386.go +++ b/unix/syscall_linux_386.go @@ -252,6 +252,14 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { return } +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + n, e := socketcall(_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + if e != 0 { + err = e + } + return +} + func Listen(s int, n int) (err error) { _, e := socketcall(_LISTEN, uintptr(s), uintptr(n), 0, 0, 0, 0) if e != 0 { diff --git a/unix/syscall_linux_amd64.go b/unix/syscall_linux_amd64.go index a9a52f231..4de6dda19 100644 --- a/unix/syscall_linux_amd64.go +++ b/unix/syscall_linux_amd64.go @@ -71,6 +71,7 @@ func Stat(path string, stat *Stat_t) (err error) { //sys sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen _Socklen) (err error) //sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error) //sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error) +//sys recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) //sys futimesat(dirfd int, path string, times *[2]Timeval) (err error) diff --git a/unix/syscall_linux_arm.go b/unix/syscall_linux_arm.go index 54474c20f..4d641504e 100644 --- a/unix/syscall_linux_arm.go +++ b/unix/syscall_linux_arm.go @@ -41,6 +41,7 @@ func Seek(fd int, offset int64, whence int) (newoffset int64, err error) { //sysnb socketpair(domain int, typ int, flags int, fd *[2]int32) (err error) //sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error) //sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error) +//sys recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) // 64-bit file system and 32-bit uid calls // (16-bit uid calls are not always supported in newer kernels) diff --git a/unix/syscall_linux_arm64.go b/unix/syscall_linux_arm64.go index e9f30db97..e0e2482cb 100644 --- a/unix/syscall_linux_arm64.go +++ b/unix/syscall_linux_arm64.go @@ -75,6 +75,7 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) { //sys sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen _Socklen) (err error) //sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error) //sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error) +//sys recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) //sysnb Gettimeofday(tv *Timeval) (err error) diff --git a/unix/syscall_linux_loong64.go b/unix/syscall_linux_loong64.go index 6f09ca200..fcf094fd4 100644 --- a/unix/syscall_linux_loong64.go +++ b/unix/syscall_linux_loong64.go @@ -107,6 +107,7 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) { //sys sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen _Socklen) (err error) //sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error) //sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error) +//sys recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) //sysnb Gettimeofday(tv *Timeval) (err error) diff --git a/unix/syscall_linux_mips64x.go b/unix/syscall_linux_mips64x.go index ca3b56597..6d4e9a07f 100644 --- a/unix/syscall_linux_mips64x.go +++ b/unix/syscall_linux_mips64x.go @@ -55,6 +55,7 @@ func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err //sys sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen _Socklen) (err error) //sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error) //sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error) +//sys recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) //sys futimesat(dirfd int, path string, times *[2]Timeval) (err error) diff --git a/unix/syscall_linux_mipsx.go b/unix/syscall_linux_mipsx.go index 54ba667b1..b11dfcf4e 100644 --- a/unix/syscall_linux_mipsx.go +++ b/unix/syscall_linux_mipsx.go @@ -49,6 +49,7 @@ func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, //sys sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen _Socklen) (err error) //sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error) //sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error) +//sys recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) //sys Ioperm(from int, num int, on int) (err error) //sys Iopl(level int) (err error) diff --git a/unix/syscall_linux_ppc.go b/unix/syscall_linux_ppc.go index ce4628590..62f00a744 100644 --- a/unix/syscall_linux_ppc.go +++ b/unix/syscall_linux_ppc.go @@ -52,6 +52,7 @@ import ( //sys sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen _Socklen) (err error) //sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error) //sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error) +//sys recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) //sys futimesat(dirfd int, path string, times *[2]Timeval) (err error) //sysnb Gettimeofday(tv *Timeval) (err error) diff --git a/unix/syscall_linux_ppc64x.go b/unix/syscall_linux_ppc64x.go index 33f7af380..fb8fc2383 100644 --- a/unix/syscall_linux_ppc64x.go +++ b/unix/syscall_linux_ppc64x.go @@ -52,6 +52,7 @@ package unix //sys sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen _Socklen) (err error) //sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error) //sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error) +//sys recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) //sys futimesat(dirfd int, path string, times *[2]Timeval) (err error) diff --git a/unix/syscall_linux_riscv64.go b/unix/syscall_linux_riscv64.go index c658871e3..924ff6507 100644 --- a/unix/syscall_linux_riscv64.go +++ b/unix/syscall_linux_riscv64.go @@ -74,6 +74,7 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) { //sys sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen _Socklen) (err error) //sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error) //sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error) +//sys recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) //sysnb Gettimeofday(tv *Timeval) (err error) diff --git a/unix/syscall_linux_sparc64.go b/unix/syscall_linux_sparc64.go index 4964119af..74e367366 100644 --- a/unix/syscall_linux_sparc64.go +++ b/unix/syscall_linux_sparc64.go @@ -50,6 +50,7 @@ package unix //sys sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen _Socklen) (err error) //sys recvmsg(s int, msg *Msghdr, flags int) (n int, err error) //sys sendmsg(s int, msg *Msghdr, flags int) (n int, err error) +//sys recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) func Ioperm(from int, num int, on int) (err error) { diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go index 99332eace..39664bdde 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go @@ -1364,3 +1364,59 @@ func TestSockaddrALG(t *testing.T) { t.Fatalf("got: %q, want: %q", got, exp) } } + +func TestRecvmmsg(t *testing.T) { + fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) + if err != nil { + t.Fatal(err) + } + defer unix.Close(fds[0]) + defer unix.Close(fds[1]) + + expected := []string{"msg1", "msg2", "msg3"} + vlen := len(expected) + + for _, msg := range expected { + if _, err := unix.Write(fds[1], []byte(msg)); err != nil { + t.Fatalf("Write: %v", err) + } + } + + ps := make([][]byte, vlen) + oobs := make([][]byte, vlen) + for i := range ps { + ps[i] = make([]byte, 64) + } + + n, ns, oobns, recvflags, from, err := unix.Recvmmsg(fds[0], ps, oobs, 0) + if err != nil { + if errors.Is(err, unix.ENOSYS) { + t.Skipf("recvmmsg not available: %v", err) + } + t.Fatalf("Recvmmsg: %v", err) + } + + if n != vlen { + t.Errorf("Recvmmsg: got %d messages, want %d", n, vlen) + return + } + + for i := range n { + got := string(ps[i][:ns[i]]) + if got != expected[i] { + t.Errorf("message %d: got %q, want %q", i, got, expected[i]) + } + if ns[i] != len(expected[i]) { + t.Errorf("message %d: got ns=%d, want %d", i, ns[i], len(expected[i])) + } + if oobns[i] != 0 { + t.Errorf("message %d: got oobns=%d, want 0", i, oobns[i]) + } + if recvflags[i] != 0 { + t.Errorf("message %d: got recvflags=%#x, want 0", i, recvflags[i]) + } + if from[i] != nil { + t.Errorf("message %d: got non-nil from", i) + } + } +} diff --git a/unix/zsyscall_linux_amd64.go b/unix/zsyscall_linux_amd64.go index 27c05db1a..8717f421b 100644 --- a/unix/zsyscall_linux_amd64.go +++ b/unix/zsyscall_linux_amd64.go @@ -555,6 +555,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) { r0, _, e1 := Syscall6(SYS_MMAP, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(offset)) xaddr = uintptr(r0) diff --git a/unix/zsyscall_linux_arm.go b/unix/zsyscall_linux_arm.go index 840d85bfc..f3cf88e94 100644 --- a/unix/zsyscall_linux_arm.go +++ b/unix/zsyscall_linux_arm.go @@ -213,6 +213,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Fchown(fd int, uid int, gid int) (err error) { _, _, e1 := Syscall(SYS_FCHOWN32, uintptr(fd), uintptr(uid), uintptr(gid)) if e1 != 0 { diff --git a/unix/zsyscall_linux_arm64.go b/unix/zsyscall_linux_arm64.go index fe414498b..f8a6334c2 100644 --- a/unix/zsyscall_linux_arm64.go +++ b/unix/zsyscall_linux_arm64.go @@ -500,6 +500,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) { r0, _, e1 := Syscall6(SYS_MMAP, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(offset)) xaddr = uintptr(r0) diff --git a/unix/zsyscall_linux_loong64.go b/unix/zsyscall_linux_loong64.go index eb358ce05..dc07703e0 100644 --- a/unix/zsyscall_linux_loong64.go +++ b/unix/zsyscall_linux_loong64.go @@ -434,6 +434,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) { r0, _, e1 := Syscall6(SYS_MMAP, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(offset)) xaddr = uintptr(r0) diff --git a/unix/zsyscall_linux_mips.go b/unix/zsyscall_linux_mips.go index c437622f1..76b38575e 100644 --- a/unix/zsyscall_linux_mips.go +++ b/unix/zsyscall_linux_mips.go @@ -454,6 +454,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Ioperm(from int, num int, on int) (err error) { _, _, e1 := Syscall(SYS_IOPERM, uintptr(from), uintptr(num), uintptr(on)) if e1 != 0 { diff --git a/unix/zsyscall_linux_mips64.go b/unix/zsyscall_linux_mips64.go index bc4ca2558..e90cb73ae 100644 --- a/unix/zsyscall_linux_mips64.go +++ b/unix/zsyscall_linux_mips64.go @@ -499,6 +499,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) { r0, _, e1 := Syscall6(SYS_MMAP, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(offset)) xaddr = uintptr(r0) diff --git a/unix/zsyscall_linux_mips64le.go b/unix/zsyscall_linux_mips64le.go index 5051435ce..458d4a3d1 100644 --- a/unix/zsyscall_linux_mips64le.go +++ b/unix/zsyscall_linux_mips64le.go @@ -499,6 +499,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) { r0, _, e1 := Syscall6(SYS_MMAP, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(offset)) xaddr = uintptr(r0) diff --git a/unix/zsyscall_linux_mipsle.go b/unix/zsyscall_linux_mipsle.go index 33aa5418a..4470940d8 100644 --- a/unix/zsyscall_linux_mipsle.go +++ b/unix/zsyscall_linux_mipsle.go @@ -454,6 +454,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Ioperm(from int, num int, on int) (err error) { _, _, e1 := Syscall(SYS_IOPERM, uintptr(from), uintptr(num), uintptr(on)) if e1 != 0 { diff --git a/unix/zsyscall_linux_ppc.go b/unix/zsyscall_linux_ppc.go index 3bef8ef1d..129385cfa 100644 --- a/unix/zsyscall_linux_ppc.go +++ b/unix/zsyscall_linux_ppc.go @@ -519,6 +519,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func futimesat(dirfd int, path string, times *[2]Timeval) (err error) { var _p0 *byte _p0, err = BytePtrFromString(path) diff --git a/unix/zsyscall_linux_ppc64.go b/unix/zsyscall_linux_ppc64.go index fc1bd4e2c..89babe072 100644 --- a/unix/zsyscall_linux_ppc64.go +++ b/unix/zsyscall_linux_ppc64.go @@ -575,6 +575,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) { r0, _, e1 := Syscall6(SYS_MMAP, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(offset)) xaddr = uintptr(r0) diff --git a/unix/zsyscall_linux_ppc64le.go b/unix/zsyscall_linux_ppc64le.go index d78fe7dab..b88322f7f 100644 --- a/unix/zsyscall_linux_ppc64le.go +++ b/unix/zsyscall_linux_ppc64le.go @@ -575,6 +575,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) { r0, _, e1 := Syscall6(SYS_MMAP, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(offset)) xaddr = uintptr(r0) diff --git a/unix/zsyscall_linux_riscv64.go b/unix/zsyscall_linux_riscv64.go index 76dcf87d0..b0f9f500b 100644 --- a/unix/zsyscall_linux_riscv64.go +++ b/unix/zsyscall_linux_riscv64.go @@ -480,6 +480,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) { r0, _, e1 := Syscall6(SYS_MMAP, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(offset)) xaddr = uintptr(r0) diff --git a/unix/zsyscall_linux_sparc64.go b/unix/zsyscall_linux_sparc64.go index 527637623..54865702a 100644 --- a/unix/zsyscall_linux_sparc64.go +++ b/unix/zsyscall_linux_sparc64.go @@ -555,6 +555,17 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func recvmmsg(s int, mmsg *Mmsghdr, vlen int, flags int, timeout *Timespec) (n int, err error) { + r0, _, e1 := Syscall6(SYS_RECVMMSG, uintptr(s), uintptr(unsafe.Pointer(mmsg)), uintptr(vlen), uintptr(flags), uintptr(unsafe.Pointer(timeout)), 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func mmap(addr uintptr, length uintptr, prot int, flags int, fd int, offset int64) (xaddr uintptr, err error) { r0, _, e1 := Syscall6(SYS_MMAP, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(offset)) xaddr = uintptr(r0) diff --git a/unix/ztypes_linux_386.go b/unix/ztypes_linux_386.go index aede1de7f..ef7776686 100644 --- a/unix/ztypes_linux_386.go +++ b/unix/ztypes_linux_386.go @@ -163,6 +163,11 @@ type Msghdr struct { Flags int32 } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 +} + type Cmsghdr struct { Len uint32 Level int32 @@ -178,6 +183,7 @@ const ( SizeofSockaddrNFCLLCP = 0x58 SizeofIovec = 0x8 SizeofMsghdr = 0x1c + SizeofMmsghdr = 0x20 SizeofCmsghdr = 0xc ) diff --git a/unix/ztypes_linux_amd64.go b/unix/ztypes_linux_amd64.go index bb3bc4dc2..a95ff1b97 100644 --- a/unix/ztypes_linux_amd64.go +++ b/unix/ztypes_linux_amd64.go @@ -166,6 +166,12 @@ type Msghdr struct { _ [4]byte } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 + _ [4]byte +} + type Cmsghdr struct { Len uint64 Level int32 @@ -181,6 +187,7 @@ const ( SizeofSockaddrNFCLLCP = 0x60 SizeofIovec = 0x10 SizeofMsghdr = 0x38 + SizeofMmsghdr = 0x40 SizeofCmsghdr = 0x10 ) diff --git a/unix/ztypes_linux_arm.go b/unix/ztypes_linux_arm.go index 1fdf4c517..829c893d6 100644 --- a/unix/ztypes_linux_arm.go +++ b/unix/ztypes_linux_arm.go @@ -169,6 +169,11 @@ type Msghdr struct { Flags int32 } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 +} + type Cmsghdr struct { Len uint32 Level int32 @@ -184,6 +189,7 @@ const ( SizeofSockaddrNFCLLCP = 0x58 SizeofIovec = 0x8 SizeofMsghdr = 0x1c + SizeofMmsghdr = 0x20 SizeofCmsghdr = 0xc ) diff --git a/unix/ztypes_linux_arm64.go b/unix/ztypes_linux_arm64.go index 063e6f0b4..61e2a2eea 100644 --- a/unix/ztypes_linux_arm64.go +++ b/unix/ztypes_linux_arm64.go @@ -167,6 +167,12 @@ type Msghdr struct { _ [4]byte } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 + _ [4]byte +} + type Cmsghdr struct { Len uint64 Level int32 @@ -182,6 +188,7 @@ const ( SizeofSockaddrNFCLLCP = 0x60 SizeofIovec = 0x10 SizeofMsghdr = 0x38 + SizeofMmsghdr = 0x40 SizeofCmsghdr = 0x10 ) diff --git a/unix/ztypes_linux_loong64.go b/unix/ztypes_linux_loong64.go index 9cf836c70..c3f420c07 100644 --- a/unix/ztypes_linux_loong64.go +++ b/unix/ztypes_linux_loong64.go @@ -167,6 +167,12 @@ type Msghdr struct { _ [4]byte } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 + _ [4]byte +} + type Cmsghdr struct { Len uint64 Level int32 @@ -182,6 +188,7 @@ const ( SizeofSockaddrNFCLLCP = 0x60 SizeofIovec = 0x10 SizeofMsghdr = 0x38 + SizeofMmsghdr = 0x40 SizeofCmsghdr = 0x10 ) diff --git a/unix/ztypes_linux_mips.go b/unix/ztypes_linux_mips.go index 1d222fcb3..c7bd5cb9a 100644 --- a/unix/ztypes_linux_mips.go +++ b/unix/ztypes_linux_mips.go @@ -168,6 +168,11 @@ type Msghdr struct { Flags int32 } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 +} + type Cmsghdr struct { Len uint32 Level int32 @@ -183,6 +188,7 @@ const ( SizeofSockaddrNFCLLCP = 0x58 SizeofIovec = 0x8 SizeofMsghdr = 0x1c + SizeofMmsghdr = 0x20 SizeofCmsghdr = 0xc ) diff --git a/unix/ztypes_linux_mips64.go b/unix/ztypes_linux_mips64.go index 912cc4ab6..42c57f2f1 100644 --- a/unix/ztypes_linux_mips64.go +++ b/unix/ztypes_linux_mips64.go @@ -167,6 +167,12 @@ type Msghdr struct { _ [4]byte } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 + _ [4]byte +} + type Cmsghdr struct { Len uint64 Level int32 @@ -182,6 +188,7 @@ const ( SizeofSockaddrNFCLLCP = 0x60 SizeofIovec = 0x10 SizeofMsghdr = 0x38 + SizeofMmsghdr = 0x40 SizeofCmsghdr = 0x10 ) diff --git a/unix/ztypes_linux_mips64le.go b/unix/ztypes_linux_mips64le.go index 1e358ef34..cbdaf35ca 100644 --- a/unix/ztypes_linux_mips64le.go +++ b/unix/ztypes_linux_mips64le.go @@ -167,6 +167,12 @@ type Msghdr struct { _ [4]byte } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 + _ [4]byte +} + type Cmsghdr struct { Len uint64 Level int32 @@ -182,6 +188,7 @@ const ( SizeofSockaddrNFCLLCP = 0x60 SizeofIovec = 0x10 SizeofMsghdr = 0x38 + SizeofMmsghdr = 0x40 SizeofCmsghdr = 0x10 ) diff --git a/unix/ztypes_linux_mipsle.go b/unix/ztypes_linux_mipsle.go index df59f32f5..4b3ec1476 100644 --- a/unix/ztypes_linux_mipsle.go +++ b/unix/ztypes_linux_mipsle.go @@ -168,6 +168,11 @@ type Msghdr struct { Flags int32 } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 +} + type Cmsghdr struct { Len uint32 Level int32 @@ -183,6 +188,7 @@ const ( SizeofSockaddrNFCLLCP = 0x58 SizeofIovec = 0x8 SizeofMsghdr = 0x1c + SizeofMmsghdr = 0x20 SizeofCmsghdr = 0xc ) diff --git a/unix/ztypes_linux_ppc.go b/unix/ztypes_linux_ppc.go index 29355aa0b..744d09d9f 100644 --- a/unix/ztypes_linux_ppc.go +++ b/unix/ztypes_linux_ppc.go @@ -169,6 +169,11 @@ type Msghdr struct { Flags int32 } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 +} + type Cmsghdr struct { Len uint32 Level int32 @@ -184,6 +189,7 @@ const ( SizeofSockaddrNFCLLCP = 0x58 SizeofIovec = 0x8 SizeofMsghdr = 0x1c + SizeofMmsghdr = 0x20 SizeofCmsghdr = 0xc ) diff --git a/unix/ztypes_linux_ppc64.go b/unix/ztypes_linux_ppc64.go index c6083a15d..d6b209892 100644 --- a/unix/ztypes_linux_ppc64.go +++ b/unix/ztypes_linux_ppc64.go @@ -168,6 +168,12 @@ type Msghdr struct { _ [4]byte } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 + _ [4]byte +} + type Cmsghdr struct { Len uint64 Level int32 @@ -183,6 +189,7 @@ const ( SizeofSockaddrNFCLLCP = 0x60 SizeofIovec = 0x10 SizeofMsghdr = 0x38 + SizeofMmsghdr = 0x40 SizeofCmsghdr = 0x10 ) diff --git a/unix/ztypes_linux_ppc64le.go b/unix/ztypes_linux_ppc64le.go index 6321cc762..c96654b63 100644 --- a/unix/ztypes_linux_ppc64le.go +++ b/unix/ztypes_linux_ppc64le.go @@ -168,6 +168,12 @@ type Msghdr struct { _ [4]byte } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 + _ [4]byte +} + type Cmsghdr struct { Len uint64 Level int32 @@ -183,6 +189,7 @@ const ( SizeofSockaddrNFCLLCP = 0x60 SizeofIovec = 0x10 SizeofMsghdr = 0x38 + SizeofMmsghdr = 0x40 SizeofCmsghdr = 0x10 ) diff --git a/unix/ztypes_linux_riscv64.go b/unix/ztypes_linux_riscv64.go index b44f402fe..70b8c89b1 100644 --- a/unix/ztypes_linux_riscv64.go +++ b/unix/ztypes_linux_riscv64.go @@ -167,6 +167,12 @@ type Msghdr struct { _ [4]byte } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 + _ [4]byte +} + type Cmsghdr struct { Len uint64 Level int32 @@ -182,6 +188,7 @@ const ( SizeofSockaddrNFCLLCP = 0x60 SizeofIovec = 0x10 SizeofMsghdr = 0x38 + SizeofMmsghdr = 0x40 SizeofCmsghdr = 0x10 ) diff --git a/unix/ztypes_linux_s390x.go b/unix/ztypes_linux_s390x.go index b22c795a6..fe1db5c5a 100644 --- a/unix/ztypes_linux_s390x.go +++ b/unix/ztypes_linux_s390x.go @@ -166,6 +166,12 @@ type Msghdr struct { _ [4]byte } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 + _ [4]byte +} + type Cmsghdr struct { Len uint64 Level int32 @@ -181,6 +187,7 @@ const ( SizeofSockaddrNFCLLCP = 0x60 SizeofIovec = 0x10 SizeofMsghdr = 0x38 + SizeofMmsghdr = 0x40 SizeofCmsghdr = 0x10 ) diff --git a/unix/ztypes_linux_sparc64.go b/unix/ztypes_linux_sparc64.go index 0b18075b5..2d4fdc069 100644 --- a/unix/ztypes_linux_sparc64.go +++ b/unix/ztypes_linux_sparc64.go @@ -170,6 +170,12 @@ type Msghdr struct { _ [4]byte } +type Mmsghdr struct { + Hdr Msghdr + Len uint32 + _ [4]byte +} + type Cmsghdr struct { Len uint64 Level int32 @@ -185,6 +191,7 @@ const ( SizeofSockaddrNFCLLCP = 0x60 SizeofIovec = 0x10 SizeofMsghdr = 0x38 + SizeofMmsghdr = 0x40 SizeofCmsghdr = 0x10 ) From 90d5c282638fd4b6e6d6af16257e0258177b3e83 Mon Sep 17 00:00:00 2001 From: Nick Garlis Date: Thu, 21 May 2026 16:21:56 +0200 Subject: [PATCH 2/5] unix: reduce allocations for recvmmsg --- unix/syscall_linux.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index c8d5b0688..3951ca022 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -1637,9 +1637,12 @@ func Recvmmsg(fd int, ps, oobs [][]byte, flags int) (n int, ns, oobns, recvflags return 0, nil, nil, nil, nil, EINVAL } - msghdrs := make([]Mmsghdr, vlen) - iovecs := make([]Iovec, vlen) - names := make([]byte, SizeofSockaddrAny*vlen) + hdrEnd := SizeofMmsghdr * vlen + iovEnd := hdrEnd + SizeofIovec*vlen + buf := make([]byte, iovEnd+SizeofSockaddrAny*vlen) + msghdrs := unsafe.Slice((*Mmsghdr)(unsafe.Pointer(&buf[0])), vlen) + iovecs := unsafe.Slice((*Iovec)(unsafe.Pointer(uintptr(unsafe.Pointer(&buf[0]))+uintptr(hdrEnd))), vlen) + names := buf[iovEnd:] for i := range vlen { if len(ps[i]) > 0 { From 3b8469fbb665a6f77d660d9c9e841cdbda8b385b Mon Sep 17 00:00:00 2001 From: Nick Garlis Date: Wed, 27 May 2026 18:55:19 +0200 Subject: [PATCH 3/5] unix: further reduce allocations for recvmmsg --- unix/syscall_linux.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index 3951ca022..dd9be5b4e 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -1664,9 +1664,10 @@ func Recvmmsg(fd int, ps, oobs [][]byte, flags int) (n int, ns, oobns, recvflags return 0, nil, nil, nil, nil, err } - ns = make([]int, n) - oobns = make([]int, n) - recvflags = make([]int, n) + rbuf := make([]int, 3*n) + ns = rbuf[0:n:n] + oobns = rbuf[n : 2*n : 2*n] + recvflags = rbuf[2*n : 3*n : 3*n] from = make([]Sockaddr, n) for i := range n { From b4e9bdeeef8633fc8d6893a0d8c1996f2a729aa0 Mon Sep 17 00:00:00 2001 From: Nick Garlis Date: Thu, 28 May 2026 09:08:03 +0200 Subject: [PATCH 4/5] unix: improve tests for recvmmsg --- unix/syscall_linux_test.go | 132 +++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 51 deletions(-) diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go index 39664bdde..60170a0c2 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go @@ -1366,57 +1366,87 @@ func TestSockaddrALG(t *testing.T) { } func TestRecvmmsg(t *testing.T) { - fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) - if err != nil { - t.Fatal(err) - } - defer unix.Close(fds[0]) - defer unix.Close(fds[1]) - - expected := []string{"msg1", "msg2", "msg3"} - vlen := len(expected) - - for _, msg := range expected { - if _, err := unix.Write(fds[1], []byte(msg)); err != nil { - t.Fatalf("Write: %v", err) - } - } - - ps := make([][]byte, vlen) - oobs := make([][]byte, vlen) - for i := range ps { - ps[i] = make([]byte, 64) - } - - n, ns, oobns, recvflags, from, err := unix.Recvmmsg(fds[0], ps, oobs, 0) - if err != nil { - if errors.Is(err, unix.ENOSYS) { - t.Skipf("recvmmsg not available: %v", err) - } - t.Fatalf("Recvmmsg: %v", err) - } - - if n != vlen { - t.Errorf("Recvmmsg: got %d messages, want %d", n, vlen) - return - } + tests := []struct { + name string + messages int + batchSize int + }{ + { + name: "equal_messages_and_batch", + messages: 3, + batchSize: 3, + }, + { + name: "fewer_messages_than_batch", + messages: 2, + batchSize: 6, + }, + { + name: "more_messages_than_batch", + messages: 5, + batchSize: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) + if err != nil { + t.Fatal(err) + } + defer unix.Close(fds[0]) + defer unix.Close(fds[1]) + + for i := 0; i < tt.messages; i++ { + msg := fmt.Sprintf("msg%d", i+1) + if _, err := unix.Write(fds[1], []byte(msg)); err != nil { + t.Fatalf("Write: %v", err) + } + } - for i := range n { - got := string(ps[i][:ns[i]]) - if got != expected[i] { - t.Errorf("message %d: got %q, want %q", i, got, expected[i]) - } - if ns[i] != len(expected[i]) { - t.Errorf("message %d: got ns=%d, want %d", i, ns[i], len(expected[i])) - } - if oobns[i] != 0 { - t.Errorf("message %d: got oobns=%d, want 0", i, oobns[i]) - } - if recvflags[i] != 0 { - t.Errorf("message %d: got recvflags=%#x, want 0", i, recvflags[i]) - } - if from[i] != nil { - t.Errorf("message %d: got non-nil from", i) - } + read := 0 + for read < tt.messages { + ps := make([][]byte, tt.batchSize) + oobs := make([][]byte, tt.batchSize) + for i := range ps { + ps[i] = make([]byte, 64) + } + + n, ns, oobns, recvflags, from, err := unix.Recvmmsg(fds[0], ps, oobs, unix.MSG_DONTWAIT) + if err != nil { + if errors.Is(err, unix.ENOSYS) { + t.Skipf("recvmmsg not available: %v", err) + } + t.Fatalf("Recvmmsg: %v", err) + } + + wantBatchSize := min(tt.messages-read, tt.batchSize) + if n != wantBatchSize { + t.Fatalf("Recvmmsg: got %d messages, want %d", n, wantBatchSize) + } + + for i := range n { + got := string(ps[i][:ns[i]]) + want := fmt.Sprintf("msg%d", read+i+1) + if got != want { + t.Errorf("message %d: got %q, want %q", i, got, want) + } + if ns[i] != len(want) { + t.Errorf("message %d: got ns=%d, want %d", i, ns[i], len(want)) + } + if oobns[i] != 0 { + t.Errorf("message %d: got oobns=%d, want 0", i, oobns[i]) + } + if recvflags[i] != 0 { + t.Errorf("message %d: got recvflags=%#x, want 0", i, recvflags[i]) + } + if from[i] != nil { + t.Errorf("message %d: got non-nil from", i) + } + } + + read += n + } + }) } } From f6292b0e340cf53ff684cf894311ca4ff0b04f6b Mon Sep 17 00:00:00 2001 From: Nick Garlis Date: Sun, 19 Jul 2026 17:20:35 +0200 Subject: [PATCH 5/5] unix: redesign Recvmmsg to use a RecvmmsgData struct Replace the initial Recvmmsg signature (parallel ps/oobs slices with multiple result slices) with the struct-based API discussed in golang/go#80434. RecvmmsgData holds both caller-provided input buffers (Data [][]byte for scatter/gather, OOB []byte) and per-message results (N, OOBN, Flags) written back on return. A []RecvmmsgData can be pre-allocated and reused across calls without per-call allocation. The sender address is stored directly in From RawSockaddrAny, which is zeroed before each call so struct reuse is safe; callers check From.Addr.Family != AF_UNSPEC to determine whether an address was filled in. AnyToSockaddr is exported for callers that need a parsed Sockaddr. Updates golang/go#80434 --- unix/syscall_linux.go | 104 ++++++++++++++++------------- unix/syscall_linux_test.go | 133 ++++++++++++++++++++++++++++++++----- 2 files changed, 175 insertions(+), 62 deletions(-) diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index dd9be5b4e..f5c052491 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -1617,75 +1617,87 @@ func sendmsgN(fd int, iov []Iovec, oob []byte, ptr unsafe.Pointer, salen _Sockle return n, nil } +// RecvmmsgData holds the per-message buffers and results for [Recvmmsg]. +// A RecvmmsgData slice can be pre-allocated and reused across calls without +// any per-call allocation. +type RecvmmsgData struct { + Data [][]byte // payload buffers; each entry is a separate iovec + OOB []byte // control buffer + N int // set on return: payload bytes received into Data + OOBN int // set on return: OOB bytes received into OOB; interpret with [ParseSocketControlMessage] + Flags int // set on return: per-message flags + From RawSockaddrAny // set on return: raw sender address; zero (AF_UNSPEC) for connected sockets +} + // Recvmmsg receives multiple messages from a socket using the recvmmsg system -// call. ps holds the payload buffers and oobs the optional out-of-band control -// buffers, one entry per message; vlen is derived from len(ps). Pass a nil or -// zero-length oobs to receive no control data. +// call. msgs is a caller-provided slice, one entry per message slot. Data and +// OOB in each entry must be pre-allocated; the call writes N, OOBN, Flags, +// and From back into each entry. n is the number of messages received. // -// The results are: -// - n is the number of messages received -// - ns[i] is the number of non-control bytes read into ps[i] -// - oobns[i] is the number of control bytes read into oobs[i]; interpret with [ParseSocketControlMessage] -// - recvflags[i] is the per-message flags returned by recvmmsg -// - from[i] is the sender address for message i, or nil for connected sockets -func Recvmmsg(fd int, ps, oobs [][]byte, flags int) (n int, ns, oobns, recvflags []int, from []Sockaddr, err error) { - vlen := len(ps) +// To convert From to a [Sockaddr], call [AnyToSockaddr]. +func Recvmmsg(fd int, msgs []RecvmmsgData, flags int) (n int, err error) { + vlen := len(msgs) if vlen == 0 { - return 0, nil, nil, nil, nil, EINVAL + return 0, EINVAL } - if len(oobs) > 0 && len(oobs) != vlen { - return 0, nil, nil, nil, nil, EINVAL + + totalIovecs := 0 + for i := range msgs { + totalIovecs += len(msgs[i].Data) } - hdrEnd := SizeofMmsghdr * vlen - iovEnd := hdrEnd + SizeofIovec*vlen - buf := make([]byte, iovEnd+SizeofSockaddrAny*vlen) - msghdrs := unsafe.Slice((*Mmsghdr)(unsafe.Pointer(&buf[0])), vlen) - iovecs := unsafe.Slice((*Iovec)(unsafe.Pointer(uintptr(unsafe.Pointer(&buf[0]))+uintptr(hdrEnd))), vlen) - names := buf[iovEnd:] + msghdrs := make([]Mmsghdr, vlen) + var iovecs []Iovec + if totalIovecs > 0 { + iovecs = make([]Iovec, totalIovecs) + } + iovIdx := 0 for i := range vlen { - if len(ps[i]) > 0 { - iovecs[i].Base = &ps[i][0] - iovecs[i].SetLen(len(ps[i])) - msghdrs[i].Hdr.Iov = &iovecs[i] - msghdrs[i].Hdr.SetIovlen(1) + m := &msgs[i] + // clear stale address from previous call on reuse + m.From = RawSockaddrAny{} + if len(m.Data) > 0 { + startIdx := iovIdx + for _, b := range m.Data { + if len(b) > 0 { + iovecs[iovIdx].Base = &b[0] + iovecs[iovIdx].SetLen(len(b)) + } else { + iovecs[iovIdx].Base = (*byte)(unsafe.Pointer(&_zero)) + } + iovIdx++ + } + msghdrs[i].Hdr.Iov = &iovecs[startIdx] + msghdrs[i].Hdr.SetIovlen(len(m.Data)) } - if i < len(oobs) && len(oobs[i]) > 0 { - msghdrs[i].Hdr.Control = &oobs[i][0] - msghdrs[i].Hdr.SetControllen(len(oobs[i])) + if len(m.OOB) > 0 { + msghdrs[i].Hdr.Control = &m.OOB[0] + msghdrs[i].Hdr.SetControllen(len(m.OOB)) } - msghdrs[i].Hdr.Name = &names[i*SizeofSockaddrAny] + msghdrs[i].Hdr.Name = (*byte)(unsafe.Pointer(&m.From)) msghdrs[i].Hdr.Namelen = uint32(SizeofSockaddrAny) } n, err = recvmmsg(fd, &msghdrs[0], vlen, flags, nil) if err != nil { - return 0, nil, nil, nil, nil, err + return 0, err } - rbuf := make([]int, 3*n) - ns = rbuf[0:n:n] - oobns = rbuf[n : 2*n : 2*n] - recvflags = rbuf[2*n : 3*n : 3*n] - from = make([]Sockaddr, n) - for i := range n { - ns[i] = int(msghdrs[i].Len) - oobns[i] = int(msghdrs[i].Hdr.Controllen) - recvflags[i] = int(msghdrs[i].Hdr.Flags) - rsa := (*RawSockaddrAny)(unsafe.Pointer(&names[i*SizeofSockaddrAny])) - if rsa.Addr.Family != AF_UNSPEC { - from[i], err = anyToSockaddr(fd, rsa) - if err != nil { - return - } - } + msgs[i].N = int(msghdrs[i].Len) + msgs[i].OOBN = int(msghdrs[i].Hdr.Controllen) + msgs[i].Flags = int(msghdrs[i].Hdr.Flags) } return } +// AnyToSockaddr converts a raw socket address to a [Sockaddr] interface. +func AnyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { + return anyToSockaddr(fd, rsa) +} + // BindToDevice binds the socket associated with fd to device. func BindToDevice(fd int, device string) (err error) { return SetsockoptString(fd, SOL_SOCKET, SO_BINDTODEVICE, device) diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go index 60170a0c2..cf69a78ea 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go @@ -1404,15 +1404,14 @@ func TestRecvmmsg(t *testing.T) { } } + msgs := make([]unix.RecvmmsgData, tt.batchSize) + for i := range msgs { + msgs[i].Data = [][]byte{make([]byte, 64)} + } + read := 0 for read < tt.messages { - ps := make([][]byte, tt.batchSize) - oobs := make([][]byte, tt.batchSize) - for i := range ps { - ps[i] = make([]byte, 64) - } - - n, ns, oobns, recvflags, from, err := unix.Recvmmsg(fds[0], ps, oobs, unix.MSG_DONTWAIT) + n, err := unix.Recvmmsg(fds[0], msgs, unix.MSG_DONTWAIT) if err != nil { if errors.Is(err, unix.ENOSYS) { t.Skipf("recvmmsg not available: %v", err) @@ -1426,22 +1425,23 @@ func TestRecvmmsg(t *testing.T) { } for i := range n { - got := string(ps[i][:ns[i]]) + got := string(msgs[i].Data[0][:msgs[i].N]) want := fmt.Sprintf("msg%d", read+i+1) if got != want { t.Errorf("message %d: got %q, want %q", i, got, want) } - if ns[i] != len(want) { - t.Errorf("message %d: got ns=%d, want %d", i, ns[i], len(want)) + if msgs[i].N != len(want) { + t.Errorf("message %d: got N=%d, want %d", i, msgs[i].N, len(want)) } - if oobns[i] != 0 { - t.Errorf("message %d: got oobns=%d, want 0", i, oobns[i]) + if msgs[i].OOBN != 0 { + t.Errorf("message %d: got OOBN=%d, want 0", i, msgs[i].OOBN) } - if recvflags[i] != 0 { - t.Errorf("message %d: got recvflags=%#x, want 0", i, recvflags[i]) + if msgs[i].Flags != 0 { + t.Errorf("message %d: got Flags=%#x, want 0", i, msgs[i].Flags) } - if from[i] != nil { - t.Errorf("message %d: got non-nil from", i) + // socketpair is connected; kernel does not fill sender address + if msgs[i].From.Addr.Family != unix.AF_UNSPEC { + t.Errorf("message %d: got From.Addr.Family=%d, want AF_UNSPEC", i, msgs[i].From.Addr.Family) } } @@ -1450,3 +1450,104 @@ func TestRecvmmsg(t *testing.T) { }) } } + +func TestRecvmmsgScatterGather(t *testing.T) { + fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) + if err != nil { + t.Fatal(err) + } + defer unix.Close(fds[0]) + defer unix.Close(fds[1]) + + // Send two messages; each will be received into two separate buffers. + for i := range 2 { + msg := fmt.Sprintf("abcd%d", i) + if _, err := unix.Write(fds[1], []byte(msg)); err != nil { + t.Fatalf("Write: %v", err) + } + } + + msgs := []unix.RecvmmsgData{ + {Data: [][]byte{make([]byte, 2), make([]byte, 3)}}, + {Data: [][]byte{make([]byte, 2), make([]byte, 3)}}, + } + n, err := unix.Recvmmsg(fds[0], msgs, unix.MSG_DONTWAIT) + if err != nil { + if errors.Is(err, unix.ENOSYS) { + t.Skipf("recvmmsg not available: %v", err) + } + t.Fatalf("Recvmmsg: %v", err) + } + if n != 2 { + t.Fatalf("got %d messages, want 2", n) + } + for i := range n { + want := fmt.Sprintf("abcd%d", i) + // N is total bytes across both scatter buffers. + if msgs[i].N != len(want) { + t.Errorf("message %d: got N=%d, want %d", i, msgs[i].N, len(want)) + } + got := string(msgs[i].Data[0]) + string(msgs[i].Data[1][:msgs[i].N-len(msgs[i].Data[0])]) + if got != want { + t.Errorf("message %d: got %q, want %q", i, got, want) + } + } +} + +func TestRecvmmsgFrom(t *testing.T) { + // Use an unconnected UDP socket so the kernel fills in the sender address. + srv, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) + if err != nil { + t.Fatal(err) + } + defer unix.Close(srv) + addr := unix.SockaddrInet4{Port: 0, Addr: [4]byte{127, 0, 0, 1}} + if err := unix.Bind(srv, &addr); err != nil { + t.Fatal(err) + } + sa, err := unix.Getsockname(srv) + if err != nil { + t.Fatal(err) + } + port := sa.(*unix.SockaddrInet4).Port + + cli, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) + if err != nil { + t.Fatal(err) + } + defer unix.Close(cli) + + dst := &unix.SockaddrInet4{Port: port, Addr: [4]byte{127, 0, 0, 1}} + if err := unix.Sendto(cli, []byte("hello"), 0, dst); err != nil { + t.Fatal(err) + } + + msgs := []unix.RecvmmsgData{{Data: [][]byte{make([]byte, 16)}}} + n, err := unix.Recvmmsg(srv, msgs, unix.MSG_DONTWAIT) + if err != nil { + if errors.Is(err, unix.ENOSYS) { + t.Skipf("recvmmsg not available: %v", err) + } + t.Fatal(err) + } + if n != 1 { + t.Fatalf("got %d messages, want 1", n) + } + if string(msgs[0].Data[0][:msgs[0].N]) != "hello" { + t.Errorf("got payload %q, want %q", msgs[0].Data[0][:msgs[0].N], "hello") + } + if msgs[0].From.Addr.Family == unix.AF_UNSPEC { + t.Fatal("From is AF_UNSPEC; expected sender address") + } + from, err := unix.AnyToSockaddr(srv, &msgs[0].From) + if err != nil { + t.Fatalf("AnyToSockaddr: %v", err) + } + fromInet, ok := from.(*unix.SockaddrInet4) + if !ok { + t.Fatalf("expected *SockaddrInet4, got %T", from) + } + if fromInet.Addr != ([4]byte{127, 0, 0, 1}) { + t.Errorf("got sender addr %v, want 127.0.0.1", fromInet.Addr) + } +}