diff --git a/include/sparse/sys/socket.h b/include/sparse/sys/socket.h
index 4178f57e2bda..6ff245ae939b 100644
--- a/include/sparse/sys/socket.h
+++ b/include/sparse/sys/socket.h
@@ -27,6 +27,7 @@
 
 typedef unsigned short int sa_family_t;
 typedef __socklen_t socklen_t;
+struct timespec;
 
 struct sockaddr {
     sa_family_t sa_family;
@@ -126,7 +127,8 @@ enum {
     MSG_PEEK,
     MSG_TRUNC,
     MSG_WAITALL,
-    MSG_DONTWAIT
+    MSG_DONTWAIT,
+    MSG_WAITFORONE
 };
 
 enum {
@@ -171,4 +173,7 @@ int sockatmark(int);
 int socket(int, int, int);
 int socketpair(int, int, int, int[2]);
 
+int sendmmsg(int, struct mmsghdr *, unsigned int, int);
+int recvmmsg(int, struct mmsghdr *, unsigned int, int, struct timespec *);
+
 #endif /* <sys/socket.h> for sparse */
diff --git a/lib/socket-util.c b/lib/socket-util.c
index 6b7378de934b..f6f6f3b0a33f 100644
--- a/lib/socket-util.c
+++ b/lib/socket-util.c
@@ -1283,3 +1283,59 @@ wrap_sendmmsg(int fd, struct mmsghdr *msgs, unsigned int n, unsigned int flags)
 }
 #endif
 #endif
+
+#ifndef _WIN32 /* Avoid using recvmsg on Windows entirely. */
+static int
+emulate_recvmmsg(int fd, struct mmsghdr *msgs, unsigned int n,
+                 int flags, struct timespec *timeout OVS_UNUSED)
+{
+    ovs_assert(!timeout);       /* XXX not emulated */
+
+    bool waitforone = flags & MSG_WAITFORONE;
+    flags &= ~MSG_WAITFORONE;
+
+    for (unsigned int i = 0; i < n; i++) {
+        ssize_t retval = recvmsg(fd, &msgs[i].msg_hdr, flags);
+        if (retval < 0) {
+            return i ? i : retval;
+        }
+        msgs[i].msg_len = retval;
+
+        if (waitforone) {
+            flags |= MSG_DONTWAIT;
+        }
+    }
+    return n;
+}
+
+#ifndef HAVE_SENDMMSG
+int
+recvmmsg(int fd, struct mmsghdr *msgs, unsigned int n,
+         int flags, struct timespec *timeout)
+{
+    return emulate_recvmmsg(fd, msgs, n, flags, timeout);
+}
+#else
+/* recvmmsg was redefined in lib/socket-util.c, should undef recvmmsg here
+ * to avoid recursion */
+#undef recvmmsg
+int
+wrap_recvmmsg(int fd, struct mmsghdr *msgs, unsigned int n,
+              int flags, struct timespec *timeout)
+{
+    ovs_assert(!timeout);       /* XXX not emulated */
+
+    static bool recvmmsg_broken = false;
+    if (!recvmmsg_broken) {
+        int save_errno = errno;
+        int retval = recvmmsg(fd, msgs, n, flags, timeout);
+        if (retval >= 0 || errno != ENOSYS) {
+            return retval;
+        }
+        recvmmsg_broken = true;
+        errno = save_errno;
+    }
+    return emulate_recvmmsg(fd, msgs, n, flags, timeout);
+}
+#endif
+#endif
diff --git a/lib/socket-util.h b/lib/socket-util.h
index a65433d90738..71bd68926aaa 100644
--- a/lib/socket-util.h
+++ b/lib/socket-util.h
@@ -104,19 +104,20 @@ int make_unix_socket(int style, bool nonblock,
                      const char *bind_path, const char *connect_path);
 int get_unix_name_len(const struct sockaddr_un *sun, socklen_t sun_len);
 
-/* Universal sendmmsg support.
+/* Universal sendmmsg and recvmmsg support.
  *
- * Some platforms, such as new enough Linux and FreeBSD, support sendmmsg, but
- * other platforms (or older ones) do not.  We add the following infrastructure
- * to allow all code to use sendmmsg, regardless of platform support:
+ * Some platforms, such as new enough Linux and FreeBSD, support sendmmsg and
+ * recvmmsg, but other platforms (or older ones) do not.  We add the following
+ * infrastructure to allow all code to use sendmmsg and recvmmsg, regardless of
+ * platform support:
  *
- *   - For platforms that lack sendmmsg entirely, we emulate it.
+ * - For platforms that lack these functions entirely, we emulate them.
  *
- *   - Some platforms have sendmmsg() in the C library but not in the kernel.
- *     For example, this is true if a Linux system has a newer glibc with an
- *     old kernel.  To compensate, even if sendmmsg() appears to be available,
- *     we still wrap it with a handler that uses our emulation if sendmmsg()
- *     returns ENOSYS.
+ * - Some platforms have sendmmsg() and recvmmsg() in the C library but not in
+ *   the kernel.  For example, this is true if a Linux system has a newer glibc
+ *   with an old kernel.  To compensate, even if these functions appear to be
+ *   available, we still wrap them with handlers that uses our emulation if the
+ *   underlying function returns ENOSYS.
  */
 #ifndef HAVE_STRUCT_MMSGHDR_MSG_LEN
 struct mmsghdr {
@@ -126,9 +127,12 @@ struct mmsghdr {
 #endif
 #ifndef HAVE_SENDMMSG
 int sendmmsg(int, struct mmsghdr *, unsigned int, unsigned int);
+int recvmmsg(int, struct mmsghdr *, unsigned int, int, struct timespec *);
 #else
 #define sendmmsg wrap_sendmmsg
 int wrap_sendmmsg(int, struct mmsghdr *, unsigned int, unsigned int);
+#define recvmmsg wrap_recvmmsg
+int wrap_recvmmsg(int, struct mmsghdr *, unsigned int, int, struct timespec *);
 #endif
 
 /* Helpers for calling ioctl() on an AF_INET socket. */
