[libvirt] [PATCH 1/2] Add virInterfaceDumpTraffic

Hendrik Schwartke hendrik at os-t.de
Wed Jul 25 13:56:52 UTC 2012


With virInterfaceDumpTraffic network traffic can be sniffed on an
interface an send via the streaming api to a remote client.
---
 include/libvirt/libvirt.h.in |    6 ++
 src/driver.h                 |    9 ++
 src/interface/netcf_driver.c |  217 ++++++++++++++++++++++++++++++++++++++++++
 src/libvirt.c                |   52 ++++++++++
 src/libvirt_public.syms      |    5 +
 src/remote/remote_driver.c   |    1 +
 src/remote/remote_protocol.x |   11 ++-
 7 files changed, 300 insertions(+), 1 deletion(-)

diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index e34438c..521ae5d 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -2319,6 +2319,12 @@ int                     virInterfaceChangeCommit  (virConnectPtr conn,
                                                    unsigned int flags);
 int                     virInterfaceChangeRollback(virConnectPtr conn,
                                                    unsigned int flags);
+int                     virInterfaceDumpTraffic   (virInterfacePtr iface,
+                                                   virStreamPtr st,
+                                                   const char *filter,
+                                                   int promisc,
+                                                   unsigned int snaplen,
+                                                   unsigned int flags);
 
 /**
  * virStoragePool:
diff --git a/src/driver.h b/src/driver.h
index b3c1740..d43c67c 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -1178,6 +1178,14 @@ typedef int
         (*virDrvInterfaceChangeRollback)(virConnectPtr conn,
                                          unsigned int flags);
 
+typedef int
+        (*virDrvInterfaceDumpTraffic)   (virInterfacePtr iface,
+                                         virStreamPtr st,
+                                         const char *filter,
+                                         int promisc,
+                                         unsigned int snaplen,
+                                         unsigned int flags);
+
 typedef struct _virInterfaceDriver virInterfaceDriver;
 typedef virInterfaceDriver *virInterfaceDriverPtr;
 
@@ -1210,6 +1218,7 @@ struct _virInterfaceDriver {
     virDrvInterfaceChangeBegin       interfaceChangeBegin;
     virDrvInterfaceChangeCommit      interfaceChangeCommit;
     virDrvInterfaceChangeRollback    interfaceChangeRollback;
+    virDrvInterfaceDumpTraffic       interfaceDumpTraffic;
 };
 
 
diff --git a/src/interface/netcf_driver.c b/src/interface/netcf_driver.c
index 45e6442..59f7e7c 100644
--- a/src/interface/netcf_driver.c
+++ b/src/interface/netcf_driver.c
@@ -24,12 +24,19 @@
 #include <config.h>
 
 #include <netcf.h>
+#include <stdint.h>
+
+#ifdef HAVE_LIBPCAP
+# include <pcap.h>
+#endif
 
 #include "virterror_internal.h"
 #include "datatypes.h"
 #include "netcf_driver.h"
 #include "interface_conf.h"
 #include "memory.h"
+#include "fdstream.h"
+#include "logging.h"
 
 #define VIR_FROM_THIS VIR_FROM_INTERFACE
 
@@ -37,6 +44,21 @@
     virReportErrorHelper(VIR_FROM_THIS, code, __FILE__,               \
                          __FUNCTION__, __LINE__, __VA_ARGS__)
 
+
+#ifdef HAVE_LIBPCAP
+# define PCAP_DEFAULT_SNAPLEN 65535
+/*
+ * The read timeout is the time in ms to collect packets
+ * before deliver them to the client. Not all platforms
+ * support a read timeout
+ */
+# define PCAP_READ_TIMEOUT 100
+
+# define PCAP_HEADER_MAGIC 0xa1b2c3d4
+# define PCAP_HEADER_MAJOR 2
+# define PCAP_HEADER_MINOR 4
+#endif /* HAVE_LIBPCAP */
+
 /* Main driver state */
 struct interface_driver
 {
@@ -44,6 +66,16 @@ struct interface_driver
     struct netcf *netcf;
 };
 
+#ifdef HAVE_LIBPCAP
+struct pcapInfo {
+    char *iface;
+    pcap_t *handle;
+    int fd[2];
+    char *filter;
+    int promisc;
+    unsigned int snaplen;
+};
+#endif /* HAVE_LIBPCAP */
 
 static void interfaceDriverLock(struct interface_driver *driver)
 {
@@ -638,6 +670,188 @@ static int interfaceChangeRollback(virConnectPtr conn, unsigned int flags)
 }
 #endif /* HAVE_NETCF_TRANSACTIONS */
 
+#ifdef HAVE_LIBPCAP
+static void pcapInfoFree(struct pcapInfo *pcap_info) {
+    if(!pcap_info)
+        return;
+
+    if(pcap_info->fd[1] && close(pcap_info->fd[1])) {
+        char errbuf[1024];
+        interfaceReportError(errno, _("unable to close file handler: %s"),
+                             virStrerror(errno, errbuf, sizeof(errbuf)));
+    }
+    VIR_FREE(pcap_info->iface);
+    VIR_FREE(pcap_info->filter);
+    VIR_FREE(pcap_info);
+}
+
+/*
+ * file format is documented at:
+ * http://wiki.wireshark.org/Development/LibpcapFileFormat
+ */
+static int pcapWriteFileHeader(int fd, unsigned int linktype,
+                               unsigned int snaplen) {
+        struct pcap_file_header hdr;
+
+        hdr.magic = PCAP_HEADER_MAGIC;
+        hdr.version_major = PCAP_HEADER_MAJOR;
+        hdr.version_minor = PCAP_HEADER_MINOR;
+
+        /* timezone is GMT */
+        hdr.thiszone = 0;
+        hdr.sigfigs = 0;
+        hdr.linktype = linktype;
+        hdr.snaplen = snaplen;
+
+        if(safewrite(fd, &hdr, sizeof(hdr)) < 0) {
+            char errbuf[1024];
+            interfaceReportError(errno, _("unable to write file to stream: %s"),
+                                 virStrerror(errno, errbuf, sizeof(errbuf)));
+            return -1;
+        }
+        return 0;
+}
+
+/*
+ * file format is documented at:
+ * http://wiki.wireshark.org/Development/LibpcapFileFormat
+ */
+static void pcapCallback(u_char *opaque, const struct pcap_pkthdr *hdr,
+                         const u_char *p) {
+    struct pcapInfo *pcap_info = (struct pcapInfo*)opaque;
+    int fd=pcap_info->fd[1];
+    struct {
+        uint32_t sec;
+        uint32_t usec;
+        uint32_t caplen;
+        uint32_t len;
+    } header = {
+        hdr->ts.tv_sec,
+        hdr->ts.tv_usec,
+        hdr->caplen,
+        hdr->len
+    };
+    if(safewrite(fd, &header, sizeof(header)) < 0 ||
+       safewrite(fd, p, header.caplen) < 0) {
+        char errbuf[1024];
+        interfaceReportError(errno, _("unable to write packet to stream: %s"),
+                             virStrerror(errno, errbuf, sizeof(errbuf)));
+        /* stop the pcap loop and thus this thread */
+        pcap_breakloop(pcap_info->handle);
+    }
+}
+
+static void pcapThread(void *opaque) {
+    struct pcapInfo *pcap_info = opaque;
+    int res = pcap_loop(pcap_info->handle, -1, pcapCallback,
+                        (u_char*)(pcap_info));
+    /* check for -1 only, everything else is not an error */
+    if(res == -1) {
+            interfaceReportError(res,
+                                _("error while sniffinf packets "
+                                  "on interface '%s':  %s"),
+                                pcap_info->iface,
+                                pcap_geterr(pcap_info->handle));
+    }
+
+    pcapInfoFree(pcap_info);
+    return;
+}
+
+static int interfaceDumpTraffic(virInterfacePtr ifinfo, virStreamPtr st,
+                                const char *filter, int promisc,
+                                unsigned int snaplen,
+                                unsigned int flags ATTRIBUTE_UNUSED)
+{
+    struct pcapInfo *pcap_info = NULL;
+    pcap_t *handle;
+    virThread thread;
+    char errbuf[PCAP_ERRBUF_SIZE > 1024 ? PCAP_ERRBUF_SIZE : 1024];
+    int res;
+
+    res = VIR_ALLOC(pcap_info);
+    if(res) {
+        interfaceReportError(res, _("unable to allocate memory"));
+        goto error;
+    }
+    memset(pcap_info, 0, sizeof(struct pcapInfo));
+    
+    if(!(pcap_info->iface = strdup(ifinfo->name)) ||
+       (filter && !(pcap_info->filter = strdup(filter)))) {
+        interfaceReportError(errno, _("unable to allocate memory"));
+        goto error;
+    }
+
+    res = pipe(pcap_info->fd);
+    if(res) {
+        interfaceReportError(errno, _("unable to create file handler: %s"), 
+                             virStrerror(errno, errbuf, sizeof(errbuf)));
+        goto error;
+    }
+    
+    res = virFDStreamOpen(st, pcap_info->fd[0]);
+    if(res < 0) {
+        interfaceReportError(res, _("unable to open file stream"));
+        goto error;
+    }
+
+    pcap_info->promisc = promisc;
+    pcap_info->snaplen = snaplen ? snaplen : PCAP_DEFAULT_SNAPLEN;
+
+    handle = pcap_open_live(pcap_info->iface, pcap_info->snaplen,
+                            pcap_info->promisc, PCAP_READ_TIMEOUT, errbuf);
+    if (handle == NULL) {
+        interfaceReportError(-1, _("unable to open interface '%s': %s"),
+                             pcap_info->iface, errbuf);
+        goto error;
+    }
+    pcap_info->handle = handle;
+
+    if(pcap_info->filter) {
+        struct bpf_program fp;
+        bpf_u_int32 net, mask;
+
+        if(pcap_lookupnet(pcap_info->iface, &net, &mask, errbuf) < 0) {
+            VIR_WARN("couldn't determine netmask, so checking for"
+                     " broadcast addresses won't work.");
+            mask=0;
+        }
+
+        if(pcap_compile(handle, &fp, pcap_info->filter, 1, mask) == -1) {
+            interfaceReportError(res,
+                                _("unable to compile pcap filter '%s':  %s"),
+                                pcap_info->filter, pcap_geterr(handle));
+            goto error;
+        }
+
+        res = pcap_setfilter(handle, &fp);
+        pcap_freecode(&fp);
+        if(res == -1) {
+            interfaceReportError(res, _("unable to set pcap filter '%s' : %s"),
+                                 pcap_info->filter, pcap_geterr(handle));
+            goto error;
+        }
+    }
+
+    if(pcapWriteFileHeader(pcap_info->fd[1], pcap_datalink(handle),
+                           pcap_info->snaplen) < 0)
+        goto error;
+
+    res = virThreadCreate(&thread, false, pcapThread, pcap_info);
+    if(res != 0) {
+        interfaceReportError(res, _("unable to create thread"));
+        goto error;
+    }
+
+    return 0;
+
+error:
+    pcapInfoFree(pcap_info);
+    virStreamFinish(st);
+    return -1;
+}
+#endif /* HAVE_LIBPCAP */
+
 static virInterfaceDriver interfaceDriver = {
     "Interface",
     .open = interfaceOpenInterface, /* 0.7.0 */
@@ -659,6 +873,9 @@ static virInterfaceDriver interfaceDriver = {
     .interfaceChangeCommit = interfaceChangeCommit, /* 0.9.2 */
     .interfaceChangeRollback = interfaceChangeRollback, /* 0.9.2 */
 #endif /* HAVE_NETCF_TRANSACTIONS */
+#ifdef HAVE_LIBPCAP
+    .interfaceDumpTraffic = interfaceDumpTraffic, /* 0.10.0 */
+#endif /* HAVE_LIBPCAP */
 };
 
 int interfaceRegister(void) {
diff --git a/src/libvirt.c b/src/libvirt.c
index df78e8a..9918b0d 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -11184,6 +11184,58 @@ error:
     return -1;
 }
 
+/**
+ * virInterfaceDumpTraffic:
+ * @iface: the interface object
+ * @st: stream to use as output
+ * @filter: packet filter in pcap format
+ * @promisc: if not zero then put the interface in promiscuous mode. Even if
+ *           not set, the interface could be in promiscuous mode for some
+ *           other reason.
+ * @snaplen: capture snaplen. If zero then capture PCAP_iDEFAULT_SNAPLEN bytes
+ *           per paket, which means in nearly any case capturing the whole
+ *           packet.
+ * @flags: extra flags; not used yet, so callers should always pass 0
+ *
+ * virInterfaceDumpTraffic sniffs packets on iface and writes them to st
+ * using the standard pcap format.
+ *
+ * Returns 0 in case of success, -1 in case of error.
+*/
+int virInterfaceDumpTraffic(virInterfacePtr iface, virStreamPtr st,
+                            const char *filter, int promisc,
+                            unsigned int snaplen, unsigned int flags) {
+    virConnectPtr conn;
+    VIR_DEBUG("iface=%p, flags=%x", iface, flags);
+
+    virResetLastError();
+
+    if (!VIR_IS_CONNECTED_INTERFACE(iface)) {
+        virLibInterfaceError(VIR_ERR_INVALID_INTERFACE, __FUNCTION__);
+        virDispatchError(NULL);
+        return -1;
+    }
+
+    conn = iface->conn;
+    if (conn->flags & VIR_CONNECT_RO) {
+        virLibInterfaceError(VIR_ERR_OPERATION_DENIED, __FUNCTION__);
+        goto error;
+    }
+
+    if (conn->interfaceDriver && conn->interfaceDriver->interfaceDumpTraffic) {
+        if(conn->interfaceDriver->interfaceDumpTraffic(iface, st,
+                                                       filter, promisc,
+                                                       snaplen, flags))
+            goto error;
+        return 0;
+    }
+
+    virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__);
+
+error:
+    virDispatchError(iface->conn);
+    return -1;
+}
 
 /**
  * virStoragePoolGetConnect:
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index 2913a81..05b77d2 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -544,4 +544,9 @@ LIBVIRT_0.9.13 {
         virDomainSnapshotRef;
 } LIBVIRT_0.9.11;
 
+LIBVIRT_0.10.0 {
+    global:
+        virInterfaceDumpTraffic;
+} LIBVIRT_0.9.13;
+
 # .... define new API here using predicted next version number ....
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index 3314f80..31e6b9b 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -5379,6 +5379,7 @@ static virInterfaceDriver interface_driver = {
     .interfaceChangeBegin = remoteInterfaceChangeBegin, /* 0.9.2 */
     .interfaceChangeCommit = remoteInterfaceChangeCommit, /* 0.9.2 */
     .interfaceChangeRollback = remoteInterfaceChangeRollback, /* 0.9.2 */
+    .interfaceDumpTraffic = remoteInterfaceDumpTraffic, /* 0.10.0 */
 };
 
 static virStorageDriver storage_driver = {
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 8f1d9b5..3aaef0f 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -2366,6 +2366,14 @@ struct remote_domain_open_console_args {
     unsigned int flags;
 };
 
+struct remote_interface_dump_traffic_args {
+    remote_nonnull_interface iface;
+    remote_string filter;
+    int promisc;
+    unsigned int snaplen;
+    unsigned int flags;
+};
+
 struct remote_storage_vol_upload_args {
     remote_nonnull_storage_vol vol;
     unsigned hyper offset;
@@ -2844,7 +2852,8 @@ enum remote_procedure {
     REMOTE_PROC_CONNECT_LIST_ALL_DOMAINS = 273, /* skipgen skipgen priority:high */
     REMOTE_PROC_DOMAIN_LIST_ALL_SNAPSHOTS = 274, /* skipgen skipgen priority:high */
     REMOTE_PROC_DOMAIN_SNAPSHOT_LIST_ALL_CHILDREN = 275, /* skipgen skipgen priority:high */
-    REMOTE_PROC_DOMAIN_EVENT_BALLOON_CHANGE = 276 /* autogen autogen */
+    REMOTE_PROC_DOMAIN_EVENT_BALLOON_CHANGE = 276, /* autogen autogen */
+    REMOTE_PROC_INTERFACE_DUMP_TRAFFIC = 277 /* autogen autogen | readstream at 1 */
 
     /*
      * Notice how the entries are grouped in sets of 10 ?
-- 
1.7.9.5




More information about the libvir-list mailing list