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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list