]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
nwfilter: directly use poll to wait for packets instead of pcap_next
authorDaniel P. Berrangé <berrange@redhat.com>
Mon, 21 May 2018 11:46:00 +0000 (12:46 +0100)
committerDaniel P. Berrangé <berrange@redhat.com>
Thu, 7 Jun 2018 15:59:33 +0000 (16:59 +0100)
When a QEMU VM shuts down its TAP device gets deleted while nwfilter
IP address learning thread is still capturing packets. It is seen that
with TPACKET_V3 support in libcap, the pcap_next() call will not always
exit its poll() when the NIC is removed. This prevents the learning
thread from exiting which blocks the rest of libvirtd waiting on mutex
acquisition. By switching to do poll() in libvirt code, we can ensure
that we always exit the poll() at a time that is right for libvirt.

Reviewed-by: John Ferlan <jferlan@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
src/nwfilter/nwfilter_learnipaddr.c

index 085af7892ebb47e626ffcff1245665de1e012dc7..52adc373895466872ac7df6e85d86e6fa8449f13 100644 (file)
@@ -31,6 +31,7 @@
 
 #include <fcntl.h>
 #include <sys/ioctl.h>
+#include <poll.h>
 
 #include <arpa/inet.h>
 #include <net/ethernet.h>
@@ -414,6 +415,7 @@ learnIPAddressThread(void *arg)
     bool showError = true;
     enum howDetect howDetected = 0;
     virNWFilterTechDriverPtr techdriver = req->techdriver;
+    struct pollfd fds[1];
 
     if (virNWFilterLockIface(req->ifname) < 0)
        goto err_no_lock;
@@ -435,6 +437,9 @@ learnIPAddressThread(void *arg)
         goto done;
     }
 
+    fds[0].fd = pcap_fileno(handle);
+    fds[0].events = POLLIN | POLLERR;
+
     virMacAddrFormat(&req->macaddr, macaddr);
 
     if (req->howDetect == DETECT_DHCP) {
@@ -480,17 +485,45 @@ learnIPAddressThread(void *arg)
     pcap_freecode(&fp);
 
     while (req->status == 0 && vmaddr == 0) {
+        int n = poll(fds, ARRAY_CARDINALITY(fds), PKT_TIMEOUT_MS);
+
+        if (n < 0) {
+            if (errno == EAGAIN || errno == EINTR)
+                continue;
+
+            req->status = errno;
+            showError = true;
+            break;
+        }
+
+        if (n == 0) {
+            if (threadsTerminate || req->terminate) {
+                VIR_DEBUG("Terminate request seen, cancelling pcap");
+                req->status = ECANCELED;
+                showError = false;
+                break;
+            }
+            continue;
+        }
+
+        if (fds[0].revents & (POLLHUP | POLLERR)) {
+            VIR_DEBUG("Error from FD probably dev deleted");
+            req->status = ENODEV;
+            showError = false;
+            break;
+        }
+
         packet = pcap_next(handle, &header);
 
         if (!packet) {
-
+            /* Already handled with poll, but lets be sure */
             if (threadsTerminate || req->terminate) {
                 req->status = ECANCELED;
                 showError = false;
                 break;
             }
 
-            /* check whether VM's dev is still there */
+            /* Again, already handled above, but lets be sure */
             if (virNetDevValidateConfig(req->ifname, NULL, req->ifindex) <= 0) {
                 virResetLastError();
                 req->status = ENODEV;