]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
interfaces: on Linux, clear socket error queue on error fix/epollerrr 216/head
authorVincent Bernat <vincent@bernat.im>
Fri, 30 Dec 2016 22:03:34 +0000 (23:03 +0100)
committerVincent Bernat <vincent@bernat.im>
Sat, 31 Dec 2016 10:24:20 +0000 (11:24 +0100)
With AF_PACKET, it is possible to get an error in the error queue even
when we didn't ask to receive them (in contrast with AF_INET where we
have to set IP_RECVERR socket option). Currently, the only error we can
receive is SOL_PACKET/PACKET_TX_TIMESTAMP, but we just want to clear any
error in the socket queue to be able to read packets (otherwise, poll()
will always return POLLERR).

Fix #215.

NEWS
src/daemon/interfaces-linux.c

diff --git a/NEWS b/NEWS
index f1e02d87a40f6b12b1c0c5e9eb81fcdb8065fff6..b7e33e220a0c269e2db88c1615cd9e968d216ebe 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,7 @@ lldpd (0.9.6)
   * Fix:
     + Correctly parse LLDP-MED civic address when the length of the
       TLV exceeds the length of the address.
+    + Fix 100% CPU on some rare error condition.
 
 lldpd (0.9.5)
   * Change:
index 0dcb463e842d6a6f6045debff45cd4c4cf4ca342..fb48b699d9676c071698e1e0c4cfc0c036fe08c3 100644 (file)
@@ -70,19 +70,51 @@ iflinux_eth_send(struct lldpd *cfg, struct lldpd_hardware *hardware,
            buffer, size);
 }
 
+static void
+iflinux_error_recv(struct lldpd_hardware *hardware, int fd)
+{
+       do {
+               ssize_t n;
+               char buf[1024] = {};
+               struct msghdr msg = {
+                       .msg_control = buf,
+                       .msg_controllen = sizeof(buf)
+               };
+               if ((n = recvmsg(fd, &msg, MSG_ERRQUEUE)) <= 0) {
+                       return;
+               }
+               struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+               if (cmsg == NULL)
+                       log_warnx("interfaces", "received unknown error on %s",
+                           hardware->h_ifname);
+               else
+                       log_warnx("interfaces", "received error (level=%d/type=%d) on %s",
+                           cmsg->cmsg_level, cmsg->cmsg_type, hardware->h_ifname);
+       } while (1);
+}
+
 static int
 iflinux_generic_recv(struct lldpd_hardware *hardware,
     int fd, char *buffer, size_t size,
     struct sockaddr_ll *from)
 {
-       int n;
-       socklen_t fromlen = sizeof(*from);
+       int n, retry = 0;
+       socklen_t fromlen;
 
+retry:
+       fromlen = sizeof(*from);
+       memset(from, 0, fromlen);
        if ((n = recvfrom(fd, buffer, size, 0,
                    (struct sockaddr *)from,
                    &fromlen)) == -1) {
-               log_warn("interfaces", "error while receiving frame on %s",
-                   hardware->h_ifname);
+               if (errno == EAGAIN && retry == 0) {
+                       /* There may be an error queued in the socket. Clear it and retry. */
+                       iflinux_error_recv(hardware, fd);
+                       retry++;
+                       goto retry;
+               }
+               log_warn("interfaces", "error while receiving frame on %s (retry: %d)",
+                   hardware->h_ifname, retry);
                hardware->h_rx_discarded_cnt++;
                return -1;
        }
@@ -96,7 +128,7 @@ iflinux_eth_recv(struct lldpd *cfg, struct lldpd_hardware *hardware,
     int fd, char *buffer, size_t size)
 {
        int n;
-       struct sockaddr_ll from = {};
+       struct sockaddr_ll from;
 
        log_debug("interfaces", "receive PDU from ethernet device %s",
            hardware->h_ifname);
@@ -509,7 +541,7 @@ iface_bond_recv(struct lldpd *cfg, struct lldpd_hardware *hardware,
     int fd, char *buffer, size_t size)
 {
        int n;
-       struct sockaddr_ll from = {};
+       struct sockaddr_ll from;
        struct bond_master *master = hardware->h_data;
 
        log_debug("interfaces", "receive PDU from enslaved device %s",