netlink-socket: Work around upstream kernel Netlink bug.
[cascardo/ovs.git] / lib / netlink-socket.c
index e4cc4ad..93757cf 100644 (file)
@@ -310,6 +310,7 @@ nl_sock_recv__(struct nl_sock *sock, struct ofpbuf *buf, bool wait)
     struct iovec iov[2];
     struct msghdr msg;
     ssize_t retval;
+    int error;
 
     ovs_assert(buf->allocated >= sizeof *nlmsghdr);
     ofpbuf_clear(buf);
@@ -323,12 +324,23 @@ nl_sock_recv__(struct nl_sock *sock, struct ofpbuf *buf, bool wait)
     msg.msg_iov = iov;
     msg.msg_iovlen = 2;
 
+    /* Receive a Netlink message from the kernel.
+     *
+     * This works around a kernel bug in which the kernel returns an error code
+     * as if it were the number of bytes read.  It doesn't actually modify
+     * anything in the receive buffer in that case, so we can initialize the
+     * Netlink header with an impossible message length and then, upon success,
+     * check whether it changed. */
+    nlmsghdr = ofpbuf_base(buf);
     do {
+        nlmsghdr->nlmsg_len = UINT32_MAX;
         retval = recvmsg(sock->fd, &msg, wait ? 0 : MSG_DONTWAIT);
-    } while (retval < 0 && errno == EINTR);
-
-    if (retval < 0) {
-        int error = errno;
+        error = (retval < 0 ? errno
+                 : retval == 0 ? ECONNRESET /* not possible? */
+                 : nlmsghdr->nlmsg_len != UINT32_MAX ? 0
+                 : -retval);
+    } while (error == EINTR);
+    if (error) {
         if (error == ENOBUFS) {
             /* Socket receive buffer overflow dropped one or more messages that
              * the kernel tried to send to us. */
@@ -343,11 +355,10 @@ nl_sock_recv__(struct nl_sock *sock, struct ofpbuf *buf, bool wait)
         return E2BIG;
     }
 
-    nlmsghdr = ofpbuf_data(buf);
     if (retval < sizeof *nlmsghdr
         || nlmsghdr->nlmsg_len < sizeof *nlmsghdr
         || nlmsghdr->nlmsg_len > retval) {
-        VLOG_ERR_RL(&rl, "received invalid nlmsg (%"PRIuSIZE"d bytes < %"PRIuSIZE")",
+        VLOG_ERR_RL(&rl, "received invalid nlmsg (%"PRIuSIZE" bytes < %"PRIuSIZE")",
                     retval, sizeof *nlmsghdr);
         return EPROTO;
     }
@@ -797,6 +808,19 @@ exit:
     return !error;
 }
 
+/* Attempts to look ahead in 'buffer' to obtain the next reply that will be
+ * returned by nl_dump_next().  Returns true if successful, in which case
+ * 'reply' will be initialize to the message that will be obtained by the next
+ * call to nl_dump_next(), or false on failure.  Failure doesn't necessarily
+ * mean that the nl_dump_next() will fail, only that it needs to obtain a new
+ * block of dump results from the kernel. */
+bool
+nl_dump_peek(struct ofpbuf *reply, struct ofpbuf *buffer)
+{
+    struct ofpbuf tmp = *buffer;
+    return nl_msg_next(&tmp, reply);
+}
+
 /* Completes Netlink dump operation 'dump', which must have been initialized
  * with nl_dump_start().  Returns 0 if the dump operation was error-free,
  * otherwise a positive errno value describing the problem. */