[libvirt] Dumping network traffic

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!

--- 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 | readstream@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

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

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

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

On 07/23/2012 10:26 AM, Hendrik Schwartke wrote:
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 :-/)

On 07/23/2012 01:14 PM, Laine Stump wrote:
On 07/23/2012 10:26 AM, Hendrik Schwartke wrote:
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 eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 07/23/2012 03:40 PM, Eric Blake wrote:
On 07/23/2012 01:14 PM, Laine Stump wrote:
On 07/23/2012 10:26 AM, Hendrik Schwartke wrote:
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 problem: On 06/29/2012 12:32 PM, Daniel P. Berrange wrote:
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.

On Mon, Jul 23, 2012 at 03:14:15PM -0400, Laine Stump wrote:
On 07/23/2012 10:26 AM, Hendrik Schwartke wrote:
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).
I don't think that's neccessarily a problem. It is perfectly OK to have only certain configurations supported.
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.
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 :|

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

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

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 | readstream@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 <hendrik@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 <hendrik@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

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

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 On 26.07.2012 13:15, Hendrik Schwartke wrote:
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
participants (4)
-
Daniel P. Berrange
-
Eric Blake
-
Hendrik Schwartke
-
Laine Stump