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@1 */
/*
* Notice how the entries are grouped in sets of 10 ?
--
1.7.9.5