Discussion:
[libvirt] [PATCH] Proof of concept on dumping network traffic with libpcap
Hendrik Schwartke
2012-07-23 14:26:03 UTC
Permalink
---
include/libvirt/libvirt.h.in | 2 ++
src/driver.h | 9 +++++
src/interface/netcf_driver.c | 80 ++++++++++++++++++++++++++++++++++++++++++
src/libvirt.c | 47 +++++++++++++++++++++++++
src/libvirt_public.syms | 5 +++
src/remote/remote_driver.c | 1 +
src/remote/remote_protocol.x | 11 +++++-
tools/virsh.c | 79 +++++++++++++++++++++++++++++++++++++++++
8 files changed, 233 insertions(+), 1 deletion(-)

diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index e34438c..e71e8a2 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -4153,4 +4153,6 @@ typedef virMemoryParameter *virMemoryParameterPtr;
}
#endif

+int virInterfaceDumpTraffic(virInterfacePtr iface, virStreamPtr st, unsigned int flags);
+
#endif /* __VIR_VIRLIB_H__ */
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..0d19e49 100644
--- a/src/interface/netcf_driver.c
+++ b/src/interface/netcf_driver.c
@@ -24,12 +24,15 @@
#include <config.h>

#include <netcf.h>
+#include <fcntl.h>
+#include <pcap.h>

#include "virterror_internal.h"
#include "datatypes.h"
#include "netcf_driver.h"
#include "interface_conf.h"
#include "memory.h"
+#include "fdstream.h"

#define VIR_FROM_THIS VIR_FROM_INTERFACE

@@ -37,6 +40,8 @@
virReportErrorHelper(VIR_FROM_THIS, code, __FILE__, \
__FUNCTION__, __LINE__, __VA_ARGS__)

+#define PCAP_DEFAULT_SNAPLEN 65535
+
/* Main driver state */
struct interface_driver
{
@@ -44,6 +49,12 @@ struct interface_driver
struct netcf *netcf;
};

+struct pcapInfo {
+ char *iface;
+ virStreamPtr stream;
+ char *filter;
+ unsigned int snaplen;
+};

static void interfaceDriverLock(struct interface_driver *driver)
{
@@ -567,6 +578,74 @@ cleanup:
return ret;
}

+
+static void pcapCallback(u_char *opaque, const struct pcap_pkthdr *hdr, const u_char *p) {
+ pcap_dump((pcap_dumper_t *)opaque, hdr, p);
+ /* TODO: retval */
+ pcap_dump_flush((pcap_dumper_t *)opaque);
+}
+
+static void pcapSniffThread(void *opaque) {
+ struct pcapInfo *pcap_info = opaque;
+ char errbuf[PCAP_ERRBUF_SIZE];
+ pcap_t *handle;
+ pcap_dumper_t *dumper;
+
+ handle = pcap_open_live(pcap_info->iface, pcap_info->snaplen, -1, 0, errbuf);
+ if (handle == NULL) {
+ /* TODO: error reporting */
+ return;
+ }
+ /* TODO: compile and set filter */
+
+ /*
+ * TODO: remove named pipe hack
+ * pcap_dump_open can only write to stdout or to a named file, so
+ * a named pipe is used for testing purposes.
+ * This will likely replaced by fork() and a redirect of stdout.
+ */
+ if((dumper = pcap_dump_open(handle, "/tmp/pcapfifo")) == NULL) {
+ /* TODO: error reporting */
+ return;
+ }
+
+ /* TODO: retval */
+ pcap_loop(handle, -1, pcapCallback, (u_char*)dumper);
+
+ /* TODO: close dumper */
+
+ /* TODO: free pcap_info */
+}
+
+static int interfaceDumpTraffic(virInterfacePtr ifinfo, virStreamPtr st,
+ const char *filter, int promisc,
+ unsigned int snaplen, unsigned int flags)
+{
+ virThread thread;
+ struct pcapInfo *pcap_info;
+
+ /* TODO: retval */
+ VIR_ALLOC(pcap_info);
+ pcap_info->iface = strdup(ifinfo->name);
+ pcap_info->stream = st;
+ pcap_info->filter = filter ? strdup(filter) : NULL;
+ pcap_info->snaplen = snaplen ? snaplen : PCAP_DEFAULT_SNAPLEN;
+
+ if (virThreadCreate(&thread, false, pcapSniffThread,
+ pcap_info) != 0) {
+ /* TODO: err reporting */
+ return -1;
+ }
+
+ /* TODO: remove named pipe hack */
+ if (virFDStreamOpenFile(pcap_info->stream, "/tmp/pcapfifo", 0, 0, O_RDONLY) < 0) {
+ /* TODO: err reporting */
+ return -1;
+ }
+
+ return 0;
+}
+
#ifdef HAVE_NETCF_TRANSACTIONS
static int interfaceChangeBegin(virConnectPtr conn, unsigned int flags)
{
@@ -654,6 +733,7 @@ static virInterfaceDriver interfaceDriver = {
.interfaceCreate = interfaceCreate, /* 0.7.0 */
.interfaceDestroy = interfaceDestroy, /* 0.7.0 */
.interfaceIsActive = interfaceIsActive, /* 0.7.3 */
+ .interfaceDumpTraffic = interfaceDumpTraffic, /* 0.10.0 */
#ifdef HAVE_NETCF_TRANSACTIONS
.interfaceChangeBegin = interfaceChangeBegin, /* 0.9.2 */
.interfaceChangeCommit = interfaceChangeCommit, /* 0.9.2 */
diff --git a/src/libvirt.c b/src/libvirt.c
index df78e8a..caeca32 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -20,6 +20,7 @@
#include <sys/wait.h>
#include <time.h>
#include <gcrypt.h>
+#include <fcntl.h>

#include <libxml/parser.h>
#include <libxml/xpath.h>
@@ -44,6 +45,7 @@
#include "virnodesuspend.h"
#include "virrandom.h"
#include "viruri.h"
+#include "fdstream.h"

#ifdef WITH_TEST
# include "test/test_driver.h"
@@ -3051,6 +3053,51 @@ error:
}

/**
+ * virInterfaceDumpTraffic:
+ * @iface: a interface object
+ * @st: stream to use as output
+ * @flags: TODO
+ *
+ * TODO
+ *
+ * Returns TODO
+*/
+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;
+}
+
+/**
* virDomainScreenshot:
* @domain: a domain object
* @stream: stream to use as output
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 | ***@1 */

/*
* Notice how the entries are grouped in sets of 10 ?
diff --git a/tools/virsh.c b/tools/virsh.c
index 1e00049..de17c60 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -9253,6 +9253,83 @@ cmdInterfaceName(vshControl *ctl, const vshCmd *cmd)
}

/*
+ * "iface-dumptraffic" command
+ */
+static const vshCmdInfo info_interface_dumptraffic[] = {
+ {"help", N_("dumps traffic on an interface")},
+ {"desc", ""},
+ {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_interface_dumptraffic[] = {
+ {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface name")},
+ {"filter", VSH_OT_DATA, 0, N_("packet filter")},
+ {"file", VSH_OT_DATA, 0, N_("file to store packets. If ommited then stdout is used.")},
+ {"snaplen", VSH_OT_INT, 0, N_("capture snaplen")},
+ {"promisc", VSH_OT_BOOL, 0, N_("put the interface into promiscuous mode")},
+ {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdInterfaceDumpTraffic(vshControl *ctl, const vshCmd *cmd)
+{
+ virInterfacePtr iface;
+ const char *iface_name=NULL;
+ virStreamPtr stream = NULL;
+ int fd = STDOUT_FILENO;
+ const char* file = NULL;
+ const char* filter = NULL;
+ bool promisc;
+ unsigned int snaplen=0;
+
+ if (!vshConnectionUsability(ctl, ctl->conn))
+ return false;
+ if (vshCommandOptString(cmd, "filter", &filter) < 0)
+ return false;
+ if (vshCommandOptString(cmd, "file", &file) < 0)
+ return false;
+ if (vshCommandOptUInt(cmd, "snaplen", &snaplen) < 0)
+ return false;
+ promisc = vshCommandOptBool(cmd, "promisc");
+
+ if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, NULL,
+ VSH_BYNAME)))
+ return false;
+ iface_name = virInterfaceGetName(iface);
+
+ stream = virStreamNew(ctl->conn, 0);
+
+ if(virInterfaceDumpTraffic(iface, stream, filter, promisc, snaplen, 0)) {
+ vshError(ctl, _("error virInterfaceDumpTraffic %s"), iface_name);
+ goto cleanup;
+ }
+
+ if (file && (fd = open(file, O_WRONLY|O_CREAT|O_EXCL, 0660)) < 0) {
+ if (errno != EEXIST ||
+ (fd = open(file, O_WRONLY|O_TRUNC, 0660)) < 0) {
+ vshError(ctl, _("cannot create file %s"), file);
+ goto cleanup;
+ }
+ }
+
+ if (virStreamRecvAll(stream, vshStreamSink, &fd) < 0) {
+ vshError(ctl, _("could not receive data from interface %s"), iface_name);
+ goto cleanup;
+ }
+
+ if (virStreamFinish(stream) < 0) {
+ vshError(ctl, _("cannot close stream on interface %s"), iface_name);
+ goto cleanup;
+ }
+
+cleanup:
+ virStreamFree(stream);
+ virInterfaceFree(iface);
+
+ return true;
+}
+
+/*
* "iface-mac" command
*/
static const vshCmdInfo info_interface_mac[] = {
@@ -18352,6 +18429,8 @@ static const vshCmdDef ifaceCmds[] = {
info_interface_define, 0},
{"iface-destroy", cmdInterfaceDestroy, opts_interface_destroy,
info_interface_destroy, 0},
+ {"iface-dumptraffic", cmdInterfaceDumpTraffic,
+ opts_interface_dumptraffic, info_interface_dumptraffic, 0},
{"iface-dumpxml", cmdInterfaceDumpXML, opts_interface_dumpxml,
info_interface_dumpxml, 0},
{"iface-edit", cmdInterfaceEdit, opts_interface_edit,
--
1.7.9.5
Laine Stump
2012-07-23 19:14:15 UTC
Permalink
I'm currently working on a feature that dumps the network traffic of a virtual interface over the streaming api of libvirt.
The goal is to offer the possiblity to debug network related problems of guests without the need to have access to a shell on the host.
E.g. "virsh iface-dumptraffic virbr0 icmp | tcpdump -n -r -" prints all icmp traffic on virbr0 to stdout.
The code below is only a proof of concept, but it should be possible to recognize what I have in mind and how I want to implement it.
So I would appreciate any comments on that!
It's definitely interesting functionality, but putting it in
netcf_driver.c doesn't seem like the right place, since it doesn't use
any netcf functionality, and could just as easily be made to work
without it. On the other hand, since (the way you've implemented it) it
is related to host interfaces, it kind of makes logical sense for it to
be named virInterface<something>, and the backend of all those commands
is currently in netcf_driver.c.

When I first saw the patch, my initial reaction was that it may be
better suited to making two separate APIs, virNetworkDumpTraffic(),
which would have the same functionality you're proposing, but would
determine the name of the device to dump by looking in the config for
the named network, and virDomainDumpTraffic() which would dump the
network traffic for a specific domain (conceptually this should relieve
the programmer from learning the name of the physical device). However,
I then realized that:

1) in the case of a network, not all networks even have a bridge device
associated with them - some only have a pool of physical devices, and
those devices can't even be tapped into anyway (macvtap devices don't
support iptables, ebtables, or tapping in for tcpdump).

2) in the case of a domain, there could be multiple <interface>s, and
they have no permanent logical name, so the user of this new API would
still end up needing to grab the XML for the domain and parse out the
<target dev='xxx'/> for each interface, and that interface name would in
the end be a name on the host not the guest (so the name of the domain
would really be irrelevant to this new API), *AND* again any
type='direct' (macvtap) or type='hostdev' (PCI passthrough) interface
could not be tapped.

So, I have no good alternative - no solutions here, just maybe fodder
for more discussion. (hmm, maybe this new functionality could be put in
a separate .c file that is linked to netcf_driver.c (and would
theoretically be linked to any other interface driver that needed the
same implementation of the same functionality).

(BTW, I've often thought about a magical "libvirt networking
troubleshooter" that would pick through host config, /proc entries, and
traffic dumps at all the tappable spots along the network plumbing of a
guest and try to determine the root of problems, then just spit out an
answer. The type of functionality you're proposing could actually help
make that a reality (although in the end the results may not be worth
the effort :-/)
Eric Blake
2012-07-23 19:40:09 UTC
Permalink
Post by Laine Stump
I'm currently working on a feature that dumps the network traffic of a virtual interface over the streaming api of libvirt.
The goal is to offer the possiblity to debug network related problems of guests without the need to have access to a shell on the host.
E.g. "virsh iface-dumptraffic virbr0 icmp | tcpdump -n -r -" prints all icmp traffic on virbr0 to stdout.
The code below is only a proof of concept, but it should be possible to recognize what I have in mind and how I want to implement it.
So I would appreciate any comments on that!
It's definitely interesting functionality, but putting it in
netcf_driver.c doesn't seem like the right place, since it doesn't use
any netcf functionality, and could just as easily be made to work
without it. On the other hand, since (the way you've implemented it) it
is related to host interfaces, it kind of makes logical sense for it to
be named virInterface<something>, and the backend of all those commands
is currently in netcf_driver.c.
Then again, I have a patch that I need to revive and post a v2, which
renames things to use interface_driver instead of netcf_driver...

https://www.redhat.com/archives/libvir-list/2012-June/msg01331.html
--
Eric Blake ***@redhat.com +1-919-301-3266
Libvirt virtualization library http://libvirt.org
Laine Stump
2012-07-23 20:23:48 UTC
Permalink
Post by Eric Blake
Post by Laine Stump
I'm currently working on a feature that dumps the network traffic of a virtual interface over the streaming api of libvirt.
The goal is to offer the possiblity to debug network related problems of guests without the need to have access to a shell on the host.
E.g. "virsh iface-dumptraffic virbr0 icmp | tcpdump -n -r -" prints all icmp traffic on virbr0 to stdout.
The code below is only a proof of concept, but it should be possible to recognize what I have in mind and how I want to implement it.
So I would appreciate any comments on that!
It's definitely interesting functionality, but putting it in
netcf_driver.c doesn't seem like the right place, since it doesn't use
any netcf functionality, and could just as easily be made to work
without it. On the other hand, since (the way you've implemented it) it
is related to host interfaces, it kind of makes logical sense for it to
be named virInterface<something>, and the backend of all those commands
is currently in netcf_driver.c.
Then again, I have a patch that I need to revive and post a v2, which
renames things to use interface_driver instead of netcf_driver...
https://www.redhat.com/archives/libvir-list/2012-June/msg01331.html
That patch came in while I was on vacation, so I only went as far as
scanning it and marking it in red to read later), but not everything
should just be changed from netcf to interface. The two concepts are
related but separate. Practically speak, currently with_netcf ==
with_interface, but in reality it should be possible to have
with_interface but not with_netcf - using "interface" in the name
doesn't resolve the problem.

Hmm. Looks like danpb's response to you exactly gets at the root of the
Post by Eric Blake
I'm not a fan of this, because you are too tightly associating use of
the netcf library, with use of the interface drivers, and also
presuming a 1-1 relationship between a logical driver, and an external
library. THis breaks down if a module like the inteface driver needs
to check for multiple external libraries, and if the external
libraries are used by multiple different areas of the libvirt code.
(he even anticipate the possibility that an "interface" driver might
want to pull from two different sources for its functionality :-)

So I assume you would respin based on those comments.
Daniel P. Berrange
2012-07-23 19:50:02 UTC
Permalink
Post by Laine Stump
I'm currently working on a feature that dumps the network traffic of a virtual interface over the streaming api of libvirt.
The goal is to offer the possiblity to debug network related problems of guests without the need to have access to a shell on the host.
E.g. "virsh iface-dumptraffic virbr0 icmp | tcpdump -n -r -" prints all icmp traffic on virbr0 to stdout.
The code below is only a proof of concept, but it should be possible to recognize what I have in mind and how I want to implement it.
So I would appreciate any comments on that!
It's definitely interesting functionality, but putting it in
netcf_driver.c doesn't seem like the right place, since it doesn't use
any netcf functionality, and could just as easily be made to work
without it. On the other hand, since (the way you've implemented it) it
is related to host interfaces, it kind of makes logical sense for it to
be named virInterface<something>, and the backend of all those commands
is currently in netcf_driver.c.
When I first saw the patch, my initial reaction was that it may be
better suited to making two separate APIs, virNetworkDumpTraffic(),
which would have the same functionality you're proposing, but would
determine the name of the device to dump by looking in the config for
the named network, and virDomainDumpTraffic() which would dump the
network traffic for a specific domain (conceptually this should relieve
the programmer from learning the name of the physical device). However,
1) in the case of a network, not all networks even have a bridge device
associated with them - some only have a pool of physical devices, and
those devices can't even be tapped into anyway (macvtap devices don't
support iptables, ebtables, or tapping in for tcpdump).
I don't think that's neccessarily a problem. It is perfectly OK to
have only certain configurations supported.
Post by Laine Stump
2) in the case of a domain, there could be multiple <interface>s, and
they have no permanent logical name, so the user of this new API would
still end up needing to grab the XML for the domain and parse out the
<target dev='xxx'/> for each interface, and that interface name would in
the end be a name on the host not the guest (so the name of the domain
would really be irrelevant to this new API), *AND* again any
type='direct' (macvtap) or type='hostdev' (PCI passthrough) interface
could not be tapped.
This is no different to the usage scenario for virDomainInterfaceStats,
so I don't think that's an argument against virDomainInterfaceCapture()
In addidition there is the "alias" identifier given to each interface
which can also be used.
Post by Laine Stump
So, I have no good alternative - no solutions here, just maybe fodder
for more discussion. (hmm, maybe this new functionality could be put in
a separate .c file that is linked to netcf_driver.c (and would
theoretically be linked to any other interface driver that needed the
same implementation of the same functionality).
I say 90% of the functionality should go into a src/util/virnetdevcapture.{c,h}
file. We can then expose this via the virNetwork, virDomain & virInterface
APIs as desired. In fact it could even make sense to expose it via the
virNodeDevice APIs, since I can imagine wanting to be able to capture
traffic without actually configuring a NIC.

You might say there is overlap by having the APIs in all these different
levels, but it is useful from a access control POV. eg, if there is an
API at the virDomainPtr level, we can apply access controls per-guest.

Daniel
--
|: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org -o- http://virt-manager.org :|
|: http://autobuild.org -o- http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
Hendrik Schwartke
2012-07-26 11:15:08 UTC
Permalink
Thanks again for your comments on the prototype.
I moved a good portion of the patch to src/util/virnetdevcapture.c and left only
a simple stub in src/interface/netcf_driver.c. I think that this is now a much
cleaner implementation.

The docs are still missing. Futhermore I don't know how to avoid the
"End of file while reading data" error when closing the stream.

So, please review the whole thing.

Hendrik

Hendrik Schwartke (2):
Add virNetDevCapture
Add the command iface-capture to virsh

include/libvirt/libvirt.h.in | 13 +++
src/Makefile.am | 1 +
src/driver.h | 8 ++
src/interface/netcf_driver.c | 15 ++-
src/libvirt.c | 51 +++++++++
src/libvirt_private.syms | 4 +
src/libvirt_public.syms | 5 +
src/remote/remote_driver.c | 1 +
src/remote/remote_protocol.x | 10 +-
src/util/virnetdevcapture.c | 251 ++++++++++++++++++++++++++++++++++++++++++
src/util/virnetdevcapture.h | 32 ++++++
tools/virsh.c | 87 +++++++++++++++
12 files changed, 476 insertions(+), 2 deletions(-)
create mode 100644 src/util/virnetdevcapture.c
create mode 100644 src/util/virnetdevcapture.h
--
1.7.9.5
Hendrik Schwartke
2012-07-26 11:15:10 UTC
Permalink
The purpose of the iface-capture command is to sniff network
traffic on a (remote) interface.
E.g. "virsh iface-capture virbr0 icmp --promisc | tcpdump -n -r -"
prints all icmp pakets on stdout.
---
tools/virsh.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 87 insertions(+)

diff --git a/tools/virsh.c b/tools/virsh.c
index 1e00049..5ccec39 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -9253,6 +9253,91 @@ cmdInterfaceName(vshControl *ctl, const vshCmd *cmd)
}

/*
+ * "iface-capture" command
+ */
+static const vshCmdInfo info_interface_capture[] = {
+ {"help", N_("captures traffic on an interface")},
+ {"desc", ""},
+ {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_interface_capture[] = {
+ {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface name")},
+ {"filter", VSH_OT_DATA, 0, N_("packet filter")},
+ {"file", VSH_OT_DATA, 0, N_("file to store packets. If ommited then"
+ " stdout is used.")},
+ {"snaplen", VSH_OT_INT, 0, N_("capture snaplen. If ommited then the"
+ " whole paket is captured")},
+ {"promisc", VSH_OT_BOOL, 0, N_("put the interface into promiscuous mode."
+ " Even if not set, the interface could be"
+ " in promiscuous mode for some other"
+ " reason")},
+ {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdInterfaceCapture(vshControl *ctl, const vshCmd *cmd)
+{
+ virInterfacePtr iface;
+ const char *iface_name=NULL;
+ virStreamPtr stream = NULL;
+ int fd = STDOUT_FILENO;
+ const char* file = NULL;
+ const char* filter = NULL;
+ int flags = 0;
+ unsigned int snaplen=0;
+
+ if (!vshConnectionUsability(ctl, ctl->conn))
+ return false;
+ if (vshCommandOptString(cmd, "filter", &filter) < 0)
+ return false;
+ if (vshCommandOptString(cmd, "file", &file) < 0)
+ return false;
+ if (vshCommandOptUInt(cmd, "snaplen", &snaplen) < 0)
+ return false;
+ if(vshCommandOptBool(cmd, "promisc"))
+ flags |= VIR_NET_DEV_CAPTURE_PROMISC;
+
+ if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, NULL,
+ VSH_BYNAME)))
+ return false;
+ iface_name = virInterfaceGetName(iface);
+
+
+
+ stream = virStreamNew(ctl->conn, 0);
+
+ if(virInterfaceCapture(iface, stream, filter, snaplen, flags)) {
+ vshError(ctl, _("error virInterfaceCapture %s"), iface_name);
+ goto cleanup;
+ }
+
+ if (file && (fd = open(file, O_WRONLY|O_CREAT|O_EXCL, 0660)) < 0) {
+ if (errno != EEXIST ||
+ (fd = open(file, O_WRONLY|O_TRUNC, 0660)) < 0) {
+ vshError(ctl, _("cannot create file %s"), file);
+ goto cleanup;
+ }
+ }
+
+ if (virStreamRecvAll(stream, vshStreamSink, &fd) < 0) {
+ vshError(ctl, _("could not receive data from interface %s"), iface_name);
+ goto cleanup;
+ }
+
+ if (virStreamFinish(stream) < 0) {
+ vshError(ctl, _("cannot close stream on interface %s"), iface_name);
+ goto cleanup;
+ }
+
+cleanup:
+ virStreamFree(stream);
+ virInterfaceFree(iface);
+
+ return true;
+}
+
+/*
* "iface-mac" command
*/
static const vshCmdInfo info_interface_mac[] = {
@@ -18346,6 +18431,8 @@ static const vshCmdDef ifaceCmds[] = {
info_interface_begin, 0},
{"iface-bridge", cmdInterfaceBridge, opts_interface_bridge,
info_interface_bridge, 0},
+ {"iface-capture", cmdInterfaceCapture, opts_interface_capture,
+ info_interface_capture, 0},
{"iface-commit", cmdInterfaceCommit, opts_interface_commit,
info_interface_commit, 0},
{"iface-define", cmdInterfaceDefine, opts_interface_define,
--
1.7.9.5
Hendrik Schwartke
2012-07-26 11:15:09 UTC
Permalink
Added function virNetDevCapture and util/virnetdevcapture.{c|h}.
virNetDevCapture offers the possibility to sniff network traffic and
dump it to a stream ussing the default pcap format.
---
include/libvirt/libvirt.h.in | 13 +++
src/Makefile.am | 1 +
src/driver.h | 8 ++
src/interface/netcf_driver.c | 15 ++-
src/libvirt.c | 51 +++++++++
src/libvirt_private.syms | 4 +
src/libvirt_public.syms | 5 +
src/remote/remote_driver.c | 1 +
src/remote/remote_protocol.x | 10 +-
src/util/virnetdevcapture.c | 251 ++++++++++++++++++++++++++++++++++++++++++
src/util/virnetdevcapture.h | 32 ++++++
11 files changed, 389 insertions(+), 2 deletions(-)
create mode 100644 src/util/virnetdevcapture.c
create mode 100644 src/util/virnetdevcapture.h

diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index e34438c..03b885c 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -2319,6 +2319,19 @@ int virInterfaceChangeCommit (virConnectPtr conn,
unsigned int flags);
int virInterfaceChangeRollback(virConnectPtr conn,
unsigned int flags);
+int virInterfaceCapture (virInterfacePtr iface,
+ virStreamPtr st,
+ const char *filter,
+ unsigned int snaplen,
+ unsigned int flags);
+
+/**
+ * VIR_NET_DEV_CAPTURE_PROMISC:
+ *
+ * Macro for capturing paickets in promiscuous mode. Even if not set,
+ * the interface could be in promiscuous mode for some other reason.
+ */
+#define VIR_NET_DEV_CAPTURE_PROMISC 1

/**
* virStoragePool:
diff --git a/src/Makefile.am b/src/Makefile.am
index bfe74d3..be3d075 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -100,6 +100,7 @@ UTIL_SOURCES = \
util/virnetdevbandwidth.h util/virnetdevbandwidth.c \
util/virnetdevbridge.h util/virnetdevbridge.c \
util/virnetdevmacvlan.c util/virnetdevmacvlan.h \
+ util/virnetdevcapture.c util/virnetdevcapture.h \
util/virnetdevopenvswitch.h util/virnetdevopenvswitch.c \
util/virnetdevtap.h util/virnetdevtap.c \
util/virnetdevveth.h util/virnetdevveth.c \
diff --git a/src/driver.h b/src/driver.h
index b3c1740..64d32b3 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -1178,6 +1178,13 @@ typedef int
(*virDrvInterfaceChangeRollback)(virConnectPtr conn,
unsigned int flags);

+typedef int
+ (*virDrvInterfaceCapture) (virInterfacePtr iface,
+ virStreamPtr st,
+ const char *filter,
+ unsigned int snaplen,
+ unsigned int flags);
+
typedef struct _virInterfaceDriver virInterfaceDriver;
typedef virInterfaceDriver *virInterfaceDriverPtr;

@@ -1210,6 +1217,7 @@ struct _virInterfaceDriver {
virDrvInterfaceChangeBegin interfaceChangeBegin;
virDrvInterfaceChangeCommit interfaceChangeCommit;
virDrvInterfaceChangeRollback interfaceChangeRollback;
+ virDrvInterfaceCapture interfaceCapture;
};


diff --git a/src/interface/netcf_driver.c b/src/interface/netcf_driver.c
index 45e6442..f2236a9 100644
--- a/src/interface/netcf_driver.c
+++ b/src/interface/netcf_driver.c
@@ -30,6 +30,7 @@
#include "netcf_driver.h"
#include "interface_conf.h"
#include "memory.h"
+#include "virnetdevcapture.h"

#define VIR_FROM_THIS VIR_FROM_INTERFACE

@@ -44,7 +45,6 @@ struct interface_driver
struct netcf *netcf;
};

-
static void interfaceDriverLock(struct interface_driver *driver)
{
virMutexLock(&driver->lock);
@@ -638,6 +638,18 @@ static int interfaceChangeRollback(virConnectPtr conn, unsigned int flags)
}
#endif /* HAVE_NETCF_TRANSACTIONS */

+static int interfaceCapture(virInterfacePtr iface, virStreamPtr st,
+ const char *filter, unsigned int snaplen,
+ unsigned int flags ) {
+ int res = virNetDevCapture(iface->name, st, filter, snaplen, flags);
+ if(res < 0) {
+ interfaceReportError(res, _("unable to start capture on interface %s"
+ " with filter '%s'"), iface->name, filter);
+ return -1;
+ }
+ return 0;
+}
+
static virInterfaceDriver interfaceDriver = {
"Interface",
.open = interfaceOpenInterface, /* 0.7.0 */
@@ -659,6 +671,7 @@ static virInterfaceDriver interfaceDriver = {
.interfaceChangeCommit = interfaceChangeCommit, /* 0.9.2 */
.interfaceChangeRollback = interfaceChangeRollback, /* 0.9.2 */
#endif /* HAVE_NETCF_TRANSACTIONS */
+ .interfaceCapture = interfaceCapture, /* 0.10.0 */
};

int interfaceRegister(void) {
diff --git a/src/libvirt.c b/src/libvirt.c
index df78e8a..1a24007 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -11184,6 +11184,57 @@ error:
return -1;
}

+/**
+ * virInterfaceCapture:
+ * @iface: the interface object
+ * @st: stream to use as output
+ * @filter: packet filter in pcap format
+ * @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; if VIR_NET_DEV_CAPTURE_PROMISC is set packets will
+ * be captured in promiscuous mode. Even if not set, the interface
+ * could be in promiscuous mode for some other reason.
+ *
+ * virInterfaceCapture 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 virInterfaceCapture(virInterfacePtr iface, virStreamPtr st,
+ const char *filter, 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->interfaceCapture) {
+ if(conn->interfaceDriver->interfaceCapture(iface, st, filter, snaplen,
+ flags))
+ goto error;
+ return 0;
+ }
+
+ virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__);
+
+error:
+ virDispatchError(iface->conn);
+ return -1;
+}

/**
* virStoragePoolGetConnect:
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 03f7f3e..b57a7b4 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1342,6 +1342,10 @@ virNetDevBridgeSetSTP;
virNetDevBridgeSetSTPDelay;


+# virnetdevcapture.h
+virNetDevCapture;
+
+
# virnetdevmacvlan.h
virNetDevMacVLanCreate;
virNetDevMacVLanDelete;
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index 2913a81..e8e04cb 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:
+ virInterfaceCapture;
+} 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..7ebf93e 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 */
+ .interfaceCapture = remoteInterfaceCapture, /* 0.10.0 */
};

static virStorageDriver storage_driver = {
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 8f1d9b5..7840ee2 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -2366,6 +2366,13 @@ struct remote_domain_open_console_args {
unsigned int flags;
};

+struct remote_interface_capture_args {
+ remote_nonnull_interface iface;
+ remote_string filter;
+ unsigned int snaplen;
+ unsigned int flags;
+};
+
struct remote_storage_vol_upload_args {
remote_nonnull_storage_vol vol;
unsigned hyper offset;
@@ -2844,7 +2851,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_CAPTURE = 277 /* autogen autogen | ***@1 */

/*
* Notice how the entries are grouped in sets of 10 ?
diff --git a/src/util/virnetdevcapture.c b/src/util/virnetdevcapture.c
new file mode 100644
index 0000000..181634b
--- /dev/null
+++ b/src/util/virnetdevcapture.c
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Open Source Training Ralf Spenneberg
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Authors:
+ * Hendrik Schwartke <***@os-t.de>
+ */
+
+
+#include <config.h>
+
+#include "virnetdevcapture.h"
+#include "virterror_internal.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+#ifdef HAVE_LIBPCAP
+
+# include <stdint.h>
+# include <pcap.h>
+
+# include "datatypes.h"
+# include "fdstream.h"
+# include "memory.h"
+# include "logging.h"
+
+# 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
+
+struct pcapInfo {
+ char *iface;
+ pcap_t *handle;
+ int fd[2];
+ char *filter;
+ int promisc;
+ unsigned int snaplen;
+};
+
+static void pcapInfoFree(struct pcapInfo *pcap_info) {
+ if(!pcap_info)
+ return;
+
+ if(pcap_info->fd[1] && close(pcap_info->fd[1])) {
+ char errbuf[1024];
+ virReportSystemError(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];
+ virReportSystemError(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];
+ virReportSystemError(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) {
+ virReportSystemError(res,
+ _("error while sniffinf packets "
+ "on interface '%s': %s"),
+ pcap_info->iface,
+ pcap_geterr(pcap_info->handle));
+ }
+
+ pcapInfoFree(pcap_info);
+ return;
+}
+
+int virNetDevCapture(const char *iface, virStreamPtr st, const char *filter,
+ unsigned int snaplen, unsigned int flags)
+{
+ 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) {
+ virReportSystemError(res, "%s", _("unable to allocate memory"));
+ goto error;
+ }
+ memset(pcap_info, 0, sizeof(struct pcapInfo));
+
+ if(!(pcap_info->iface = strdup(iface)) ||
+ (filter && !(pcap_info->filter = strdup(filter)))) {
+ virReportSystemError(errno, "%s", _("unable to allocate memory"));
+ goto error;
+ }
+
+ res = pipe(pcap_info->fd);
+ if(res) {
+ virReportSystemError(errno, _("unable to create file handler: %s"),
+ virStrerror(errno, errbuf, sizeof(errbuf)));
+ goto error;
+ }
+
+ res = virFDStreamOpen(st, pcap_info->fd[0]);
+ if(res < 0) {
+ virReportSystemError(res, "%s", _("unable to open file stream"));
+ goto error;
+ }
+
+ pcap_info->promisc = flags | VIR_NET_DEV_CAPTURE_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) {
+ virReportSystemError(-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) {
+ virReportSystemError(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) {
+ virReportSystemError(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) {
+ virReportSystemError(res, "%s", _("unable to create thread"));
+ goto error;
+ }
+
+ return 0;
+
+error:
+ pcapInfoFree(pcap_info);
+ virStreamFinish(st);
+ return -1;
+}
+
+#else /* HAVE_LIBPCAP */
+
+int virNetDevCapture(const char *iface ATTRIBUTE_UNUSED,
+ virStreamPtr st ATTRIBUTE_UNUSED,
+ const char *filter ATTRIBUTE_UNUSED,
+ unsigned int snaplen ATTRIBUTE_UNUSED,
+ unsigned int flags ATTRIBUTE_UNUSED) {
+ virReportSystemError(VIR_ERR_NO_SUPPORT, "%s",
+ _("capturing network traffic is not supported"));
+ return -1;
+}
+
+#endif /* HAVE_LIBPCAP */
diff --git a/src/util/virnetdevcapture.h b/src/util/virnetdevcapture.h
new file mode 100644
index 0000000..241db8e
--- /dev/null
+++ b/src/util/virnetdevcapture.h
@@ -0,0 +1,32 @@
+/*
+ * virnetdevcapture.h: network capture support
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Open Source Training Ralf Spenneberg
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Hendrik Schwartke <***@os-t.de>
+ */
+
+#ifndef __LIBVIRT_NETDEVCAPTURE_H__
+# define __LIBVIRT_NETDEVCAPTURE_H__
+
+#include "internal.h"
+
+int virNetDevCapture(const char *iface, virStreamPtr st, const char *filter,
+ unsigned int snaplen, unsigned int flags);
+
+#endif /* __LIBVIRT_NETDEVCAPTURE_H__ */
--
1.7.9.5
Hendrik Schwartke
2012-07-30 07:34:56 UTC
Permalink
Hi,

it would be nice if someone could have a short look at my patch. I'm not
quite sure if i did the error handling correct and how to avoid the "end
of file while reading data" error.

Thanks!
Hendrik
Post by Hendrik Schwartke
Thanks again for your comments on the prototype.
I moved a good portion of the patch to src/util/virnetdevcapture.c and left only
a simple stub in src/interface/netcf_driver.c. I think that this is now a much
cleaner implementation.
The docs are still missing. Futhermore I don't know how to avoid the
"End of file while reading data" error when closing the stream.
So, please review the whole thing.
Hendrik
Add virNetDevCapture
Add the command iface-capture to virsh
include/libvirt/libvirt.h.in | 13 +++
src/Makefile.am | 1 +
src/driver.h | 8 ++
src/interface/netcf_driver.c | 15 ++-
src/libvirt.c | 51 +++++++++
src/libvirt_private.syms | 4 +
src/libvirt_public.syms | 5 +
src/remote/remote_driver.c | 1 +
src/remote/remote_protocol.x | 10 +-
src/util/virnetdevcapture.c | 251 ++++++++++++++++++++++++++++++++++++++++++
src/util/virnetdevcapture.h | 32 ++++++
tools/virsh.c | 87 +++++++++++++++
12 files changed, 476 insertions(+), 2 deletions(-)
create mode 100644 src/util/virnetdevcapture.c
create mode 100644 src/util/virnetdevcapture.h
Hendrik Schwartke
2012-07-25 13:56:51 UTC
Permalink
So here are my patches to add the possibility to dump network traffic over the streaming api.
The docs are still missing. Futhermore I got a nasty "End of file while reading data" error from libvirtd when closing the stream. Is there any way to fix it. I checked a few other functions which use streams but couldn't find a solution for this.
However, it would be nice if someone could have a look at the patches.

Thanks
Hendrik


Hendrik Schwartke (2):
Add virInterfaceDumpTraffic
Add the command iface-dumptraffic to virsh

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 ++-
tools/virsh.c | 84 ++++++++++++++++
8 files changed, 384 insertions(+), 1 deletion(-)
--
1.7.9.5
Hendrik Schwartke
2012-07-25 13:56:53 UTC
Permalink
The purpose of the iface-dumptraffic command is to sniff network
traffic on a (remote) interface.
E.g. "virsh iface-dumptraffic virbr0 icmp --promisc | tcpdump -n -r -"
prints all icmp pakets on stdout.
---
tools/virsh.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)

diff --git a/tools/virsh.c b/tools/virsh.c
index 1e00049..6024501 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -9253,6 +9253,88 @@ cmdInterfaceName(vshControl *ctl, const vshCmd *cmd)
}

/*
+ * "iface-dumptraffic" command
+ */
+static const vshCmdInfo info_interface_dumptraffic[] = {
+ {"help", N_("dumps traffic on an interface")},
+ {"desc", ""},
+ {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_interface_dumptraffic[] = {
+ {"interface", VSH_OT_DATA, VSH_OFLAG_REQ, N_("interface name")},
+ {"filter", VSH_OT_DATA, 0, N_("packet filter")},
+ {"file", VSH_OT_DATA, 0, N_("file to store packets. If ommited then"
+ " stdout is used.")},
+ {"snaplen", VSH_OT_INT, 0, N_("capture snaplen. If ommited then the"
+ " whole paket is captured")},
+ {"promisc", VSH_OT_BOOL, 0, N_("put the interface into promiscuous mode."
+ " Even if not set, the interface could be"
+ " in promiscuous mode for some other"
+ " reason")},
+ {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdInterfaceDumpTraffic(vshControl *ctl, const vshCmd *cmd)
+{
+ virInterfacePtr iface;
+ const char *iface_name=NULL;
+ virStreamPtr stream = NULL;
+ int fd = STDOUT_FILENO;
+ const char* file = NULL;
+ const char* filter = NULL;
+ bool promisc;
+ unsigned int snaplen=0;
+
+ if (!vshConnectionUsability(ctl, ctl->conn))
+ return false;
+ if (vshCommandOptString(cmd, "filter", &filter) < 0)
+ return false;
+ if (vshCommandOptString(cmd, "file", &file) < 0)
+ return false;
+ if (vshCommandOptUInt(cmd, "snaplen", &snaplen) < 0)
+ return false;
+ promisc = vshCommandOptBool(cmd, "promisc");
+
+ if (!(iface = vshCommandOptInterfaceBy(ctl, cmd, NULL, NULL,
+ VSH_BYNAME)))
+ return false;
+ iface_name = virInterfaceGetName(iface);
+
+ stream = virStreamNew(ctl->conn, 0);
+
+ if(virInterfaceDumpTraffic(iface, stream, filter, promisc, snaplen, 0)) {
+ vshError(ctl, _("error virInterfaceDumpTraffic %s"), iface_name);
+ goto cleanup;
+ }
+
+ if (file && (fd = open(file, O_WRONLY|O_CREAT|O_EXCL, 0660)) < 0) {
+ if (errno != EEXIST ||
+ (fd = open(file, O_WRONLY|O_TRUNC, 0660)) < 0) {
+ vshError(ctl, _("cannot create file %s"), file);
+ goto cleanup;
+ }
+ }
+
+ if (virStreamRecvAll(stream, vshStreamSink, &fd) < 0) {
+ vshError(ctl, _("could not receive data from interface %s"), iface_name);
+ goto cleanup;
+ }
+
+ if (virStreamFinish(stream) < 0) {
+ vshError(ctl, _("cannot close stream on interface %s"), iface_name);
+ goto cleanup;
+ }
+
+cleanup:
+ virStreamFree(stream);
+ virInterfaceFree(iface);
+
+ return true;
+}
+
+/*
* "iface-mac" command
*/
static const vshCmdInfo info_interface_mac[] = {
@@ -18352,6 +18434,8 @@ static const vshCmdDef ifaceCmds[] = {
info_interface_define, 0},
{"iface-destroy", cmdInterfaceDestroy, opts_interface_destroy,
info_interface_destroy, 0},
+ {"iface-dumptraffic", cmdInterfaceDumpTraffic,
+ opts_interface_dumptraffic, info_interface_dumptraffic, 0},
{"iface-dumpxml", cmdInterfaceDumpXML, opts_interface_dumpxml,
info_interface_dumpxml, 0},
{"iface-edit", cmdInterfaceEdit, opts_interface_edit,
--
1.7.9.5
Hendrik Schwartke
2012-07-25 13:56:52 UTC
Permalink
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 | ***@1 */

/*
* Notice how the entries are grouped in sets of 10 ?
--
1.7.9.5
Hendrik Schwartke
2012-07-26 07:21:10 UTC
Permalink
Thanks for your detailed comments on that!

And yes, it felt strange to put that pcap-code in netcf_driver.c. So I
will move the code to src/util/virnetdevcapture.{c,h}, as suggested by
Daniel and rename the the functions *.capture.
Meanwhile I had written v2. The code is still in netcf_driver.c but it's
now complete (I think). I will post v3 soon.

Hendrik
Loading...