Skip to content

Commit 4e8579a

Browse files
authored
Implement recvmmsg and sendmmsg (emscripten-core#27395)
This implements `recvmmsg` and `sendmmsg`, which were previously `ENOSYS` stubs. Per review feedback, the implementations now live in musl's `sendmmsg.c` and `recvmmsg.c` rather than the syscall stubs: `sendmmsg.c` already contained the emulation loop (previously gated behind `LONG_MAX > INT_MAX`), which is now also enabled under `__EMSCRIPTEN__`, and `recvmmsg.c` gains an equivalent `recvmsg` loop supporting `MSG_WAITFORONE` (timeouts report `ENOSYS`). Since nothing references the syscalls anymore, the `__syscall_recvmmsg`/`__syscall_sendmmsg` surface is removed entirely: the `UNIMPLEMENTED` stubs, the `SYS_` mappings in `arch/emscripten/bits/syscall.h`, the declarations in `emscripten/syscalls.h`, and their `native_sigs.py` entries. The `recvmsg` msghdr output fixes previously bundled here were split out into emscripten-core#27399. Adds `sockets.test_noderawsockets_mmsg` batching two datagrams out via `sendmmsg` and receiving them back in a single `recvmmsg` call. _Made with AI assistance under my review_
1 parent 0b2ed01 commit 4e8579a

10 files changed

Lines changed: 145 additions & 16 deletions

File tree

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ See docs/process.md for more on how version tagging works.
3131
- llvm-libc was updated to LLVM 22.1.8. (#27374)
3232
- mimalloc was updated to 3.4.1. (#27380)
3333
- Backport fix for musl's qsort (CVE-2026-40200) (#27029)
34+
- `recvmmsg` and `sendmmsg` are now implemented in terms of `recvmsg` and
35+
`sendmsg`. (#27395)
3436

3537
6.0.3 - 07/13/26
3638
----------------

system/include/emscripten/syscalls.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ int __syscall_utimensat(int dirfd, const char *path, const struct timespec times
103103
int __syscall_fallocate(int fd, int mode, off_t offset, off_t len);
104104
int __syscall_dup3(int oldfd, int newfd, int flags);
105105
int __syscall_pipe2(int fd[2], int flags);
106-
int __syscall_recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags, struct timespec *timeout);
107106
int __syscall_prlimit64(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);
108-
int __syscall_sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags);
109107
int __syscall_socket(int domain, int type, int protocol, int unused1, int unused2, int unused3);
110108
int __syscall_socketpair(int domain, int type, int protocol, int fd[2], int unused1, int unused2);
111109
int __syscall_bind(int sockfd, const struct sockaddr *addr, socklen_t len, int unused1, int unused2, int unused3);

system/lib/libc/emscripten_syscall_stubs.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,4 @@ weak pid_t __syscall_wait4(pid_t pid, int *wstatus, int options, struct rusage *
213213

214214
UNIMPLEMENTED(acct, (const char *filename))
215215
UNIMPLEMENTED(mincore, (void *addr, size_t length, unsigned char *vec))
216-
UNIMPLEMENTED(recvmmsg, (int sockfd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags, struct timespec *timeout))
217-
UNIMPLEMENTED(sendmmsg, (int sockfd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags))
218216
UNIMPLEMENTED(socketpair, (int domain, int type, int protocol, int fd[2], int unused1, int unused2))

system/lib/libc/musl/arch/emscripten/bits/syscall.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,7 @@
6868
#define SYS_fallocate __syscall_fallocate
6969
#define SYS_dup3 __syscall_dup3
7070
#define SYS_pipe2 __syscall_pipe2
71-
#define SYS_recvmmsg __syscall_recvmmsg
7271
#define SYS_prlimit64 __syscall_prlimit64
73-
#define SYS_sendmmsg __syscall_sendmmsg
7472
#define SYS_socket __syscall_socket
7573
#define SYS_socketpair __syscall_socketpair
7674
#define SYS_bind __syscall_bind

system/lib/libc/musl/src/network/recvmmsg.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,23 @@ hidden void __convert_scm_timestamps(struct msghdr *, socklen_t);
1212

1313
int recvmmsg(int fd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags, struct timespec *timeout)
1414
{
15-
#if LONG_MAX > INT_MAX && !defined(__EMSCRIPTEN__)
15+
#ifdef __EMSCRIPTEN__
16+
/* Emscripten has no recvmmsg syscall; emulate with recvmsg. */
17+
int i;
18+
if (timeout) {
19+
errno = ENOSYS;
20+
return -1;
21+
}
22+
if (vlen > IOV_MAX) vlen = IOV_MAX;
23+
for (i=0; i<vlen; i++) {
24+
ssize_t r = recvmsg(fd, &msgvec[i].msg_hdr, flags);
25+
if (r < 0) return i ? i : -1;
26+
msgvec[i].msg_len = r;
27+
if (flags & MSG_WAITFORONE) flags |= MSG_DONTWAIT;
28+
}
29+
return i;
30+
#else
31+
#if LONG_MAX > INT_MAX
1632
struct mmsghdr *mh = msgvec;
1733
unsigned int i;
1834
for (i = vlen; i; i--, mh++)
@@ -36,4 +52,5 @@ int recvmmsg(int fd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int fla
3652
#else
3753
return syscall_cp(SYS_recvmmsg, fd, msgvec, vlen, flags, timeout);
3854
#endif
55+
#endif
3956
}

system/lib/libc/musl/src/network/sendmmsg.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
int sendmmsg(int fd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags)
88
{
9-
#if LONG_MAX > INT_MAX
9+
#if LONG_MAX > INT_MAX || defined(__EMSCRIPTEN__)
1010
/* Can't use the syscall directly because the kernel has the wrong
1111
* idea for the types of msg_iovlen, msg_controllen, and cmsg_len,
1212
* and the cmsg blocks cannot be modified in-place. */

test/codesize/test_codesize_hello_dylink_all.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"a.out.js": 268229,
3-
"a.out.nodebug.wasm": 588187,
4-
"total": 856416,
3+
"a.out.nodebug.wasm": 588309,
4+
"total": 856538,
55
"sent": [
66
"IMG_Init",
77
"IMG_Load",
@@ -2238,8 +2238,6 @@
22382238
"__syscall_munlockall",
22392239
"__syscall_munmap",
22402240
"__syscall_prlimit64",
2241-
"__syscall_recvmmsg",
2242-
"__syscall_sendmmsg",
22432241
"__syscall_setdomainname",
22442242
"__syscall_setpgid",
22452243
"__syscall_setpriority",
@@ -4109,8 +4107,6 @@
41094107
"$__syscall_msync",
41104108
"$__syscall_munmap",
41114109
"$__syscall_prlimit64",
4112-
"$__syscall_recvmmsg",
4113-
"$__syscall_sendmmsg",
41144110
"$__syscall_setdomainname",
41154111
"$__syscall_setpgid",
41164112
"$__syscall_setpriority",

test/sockets/test_udp_mmsg.c

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2026 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#define _GNU_SOURCE
9+
#include <arpa/inet.h>
10+
#include <assert.h>
11+
#include <errno.h>
12+
#include <fcntl.h>
13+
#include <stdbool.h>
14+
#include <stdio.h>
15+
#include <string.h>
16+
#include <sys/select.h>
17+
#include <sys/socket.h>
18+
#include <unistd.h>
19+
20+
#ifdef __EMSCRIPTEN__
21+
#include <emscripten.h>
22+
#endif
23+
24+
static int server_fd;
25+
static int client_fd;
26+
static struct sockaddr_in destination;
27+
static bool sent;
28+
static unsigned int received;
29+
30+
static void succeed(void) {
31+
close(server_fd);
32+
close(client_fd);
33+
puts("done");
34+
#ifdef __EMSCRIPTEN__
35+
emscripten_cancel_main_loop();
36+
#else
37+
_exit(0);
38+
#endif
39+
}
40+
41+
static void main_loop(void) {
42+
if (!sent) {
43+
struct iovec siov[2] = {
44+
{.iov_base = "one", .iov_len = 3},
45+
{.iov_base = "two", .iov_len = 3},
46+
};
47+
struct mmsghdr smsgs[2];
48+
memset(smsgs, 0, sizeof(smsgs));
49+
for (int i = 0; i < 2; i++) {
50+
smsgs[i].msg_hdr.msg_name = &destination;
51+
smsgs[i].msg_hdr.msg_namelen = sizeof(destination);
52+
smsgs[i].msg_hdr.msg_iov = &siov[i];
53+
smsgs[i].msg_hdr.msg_iovlen = 1;
54+
}
55+
assert(sendmmsg(client_fd, smsgs, 2, 0) == 2);
56+
assert(smsgs[0].msg_len == 3 && smsgs[1].msg_len == 3);
57+
sent = true;
58+
}
59+
60+
char buffers[2][3];
61+
char controls[2][16];
62+
struct sockaddr_in sources[2];
63+
struct iovec iovecs[2];
64+
struct mmsghdr messages[2];
65+
memset(messages, 0, sizeof(messages));
66+
for (int i = 0; i < 2; i++) {
67+
iovecs[i] = (struct iovec){.iov_base = buffers[i], .iov_len = sizeof(buffers[i])};
68+
messages[i].msg_hdr.msg_name = &sources[i];
69+
messages[i].msg_hdr.msg_namelen = sizeof(sources[i]);
70+
messages[i].msg_hdr.msg_iov = &iovecs[i];
71+
messages[i].msg_hdr.msg_iovlen = 1;
72+
messages[i].msg_hdr.msg_control = controls[i];
73+
messages[i].msg_hdr.msg_controllen = sizeof(controls[i]);
74+
}
75+
76+
int count = recvmmsg(server_fd, messages, 2, MSG_DONTWAIT, NULL);
77+
if (count < 0) {
78+
assert(errno == EAGAIN || errno == EWOULDBLOCK);
79+
return;
80+
}
81+
82+
for (int i = 0; i < count; i++) {
83+
assert(messages[i].msg_len == 3);
84+
assert(memcmp(buffers[i], received == 0 ? "one" : "two", 3) == 0);
85+
received++;
86+
}
87+
if (received == 2) {
88+
succeed();
89+
}
90+
}
91+
92+
int main(void) {
93+
server_fd = socket(AF_INET, SOCK_DGRAM, 0);
94+
client_fd = socket(AF_INET, SOCK_DGRAM, 0);
95+
assert(server_fd >= 0 && client_fd >= 0);
96+
97+
struct sockaddr_in address = {
98+
.sin_family = AF_INET,
99+
.sin_port = htons(0),
100+
};
101+
inet_pton(AF_INET, "127.0.0.1", &address.sin_addr);
102+
assert(bind(server_fd, (struct sockaddr*)&address, sizeof(address)) == 0);
103+
104+
socklen_t length = sizeof(destination);
105+
assert(getsockname(server_fd, (struct sockaddr*)&destination, &length) == 0);
106+
assert(fcntl(server_fd, F_SETFL, O_NONBLOCK) == 0);
107+
108+
#ifdef __EMSCRIPTEN__
109+
emscripten_set_main_loop(main_loop, 0, 0);
110+
#else
111+
while (true) {
112+
main_loop();
113+
usleep(1000);
114+
}
115+
#endif
116+
return 0;
117+
}

test/test_sockets.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,11 @@ def test_noderawsockets_udp_recvmsg(self):
505505
# and updates msg_namelen/msg_controllen/msg_flags in the caller's msghdr.
506506
self.do_runf('sockets/test_udp_recvmsg.c', 'done\n', cflags=['-sNODERAWSOCKETS'])
507507

508+
def test_noderawsockets_mmsg(self):
509+
# sendmmsg batches two datagrams out, recvmmsg receives them back in one
510+
# call, updating msg_len per message.
511+
self.do_runf('sockets/test_udp_mmsg.c', 'done\n', cflags=['-sNODERAWSOCKETS'])
512+
508513
@also_with_proxy_to_pthread
509514
def test_noderawsockets_udp_connect(self):
510515
# Connected UDP: sendto() with an address gives EISCONN, send() reaches the

tools/native_sigs.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,12 +458,10 @@
458458
'__syscall_prlimit64': '___pp',
459459
'__syscall_readlinkat': '__ppp',
460460
'__syscall_recvfrom': '__pp_pp',
461-
'__syscall_recvmmsg': '__p__p',
462461
'__syscall_recvmsg': '__p____',
463462
'__syscall_renameat': '__p_p',
464463
'__syscall_ret': 'pp',
465464
'__syscall_rmdir': '_p',
466-
'__syscall_sendmmsg': '__p__',
467465
'__syscall_sendmsg': '__p____',
468466
'__syscall_sendto': '__pp_p_',
469467
'__syscall_setdomainname': '_pp',

0 commit comments

Comments
 (0)