[libvirt] [PATCH v2 0/2] Libvirt Wireshark dissector

From: "Yuto KAWAMURA(kawamuray)" <kawamuray.dadada@gmail.com> Introduce Wireshark dissector plugin which adds support to Wireshark for dissecting libvirt RPC protocol. This feature was presented by Michal Privoznik year before last[1]. But it did only support dissecting packet headers. This time I enhanced that dissector to support dissecting packet payload. Furthermore, I provide code generator of dissector. So you can get fresh build of dissector from libvirt RPC specification file at any version you like. [1] http://www.redhat.com/archives/libvir-list/2011-October/msg00301.html Yuto KAWAMURA(kawamuray) (2): Introduce Libvirt Wireshark dissector Add sample output of Wireshark dissector Makefile.am | 3 +- cfg.mk | 6 +- configure.ac | 72 +- tools/wireshark/Makefile.am | 29 + tools/wireshark/README.md | 31 + tools/wireshark/samples/libvirt-sample.pdml | 206 ++++++ tools/wireshark/src/.gitignore | 4 + tools/wireshark/src/Makefile.am | 42 ++ tools/wireshark/src/moduleinfo.h | 37 + tools/wireshark/src/packet-libvirt.c | 513 ++++++++++++++ tools/wireshark/src/packet-libvirt.h | 128 ++++ tools/wireshark/util/genxdrstub.pl | 1010 +++++++++++++++++++++++++++ tools/wireshark/util/make-dissector-reg | 199 ++++++ 13 files changed, 2275 insertions(+), 5 deletions(-) create mode 100644 tools/wireshark/Makefile.am create mode 100644 tools/wireshark/README.md create mode 100644 tools/wireshark/samples/libvirt-sample.pdml create mode 100644 tools/wireshark/src/.gitignore create mode 100644 tools/wireshark/src/Makefile.am create mode 100644 tools/wireshark/src/moduleinfo.h create mode 100644 tools/wireshark/src/packet-libvirt.c create mode 100644 tools/wireshark/src/packet-libvirt.h create mode 100755 tools/wireshark/util/genxdrstub.pl create mode 100755 tools/wireshark/util/make-dissector-reg -- 1.8.1.5

From: "Yuto KAWAMURA(kawamuray)" <kawamuray.dadada@gmail.com> Introduce Wireshark dissector plugin which adds support to Wireshark for dissecting libvirt RPC protocol. Added following files to build Wireshark dissector from libvirt source tree. * tools/wireshark/*: Source tree of Wireshark dissector plugin. Added followings to configure.ac or Makefile.am. configure.ac * --with-wireshark-dissector: Enable support for building Wireshark dissector. * --with-ws-plugindir: Specify wireshark plugin directory that dissector will installed. * Added tools/wireshark/{Makefile,src/Makefile} to AC_CONFIG_FILES. Makefile.am * Added tools/wireshark/ to SUBDIR. --- Makefile.am | 3 +- cfg.mk | 6 +- configure.ac | 72 ++- tools/wireshark/Makefile.am | 29 + tools/wireshark/README.md | 31 + tools/wireshark/src/.gitignore | 4 + tools/wireshark/src/Makefile.am | 42 ++ tools/wireshark/src/moduleinfo.h | 37 ++ tools/wireshark/src/packet-libvirt.c | 513 ++++++++++++++++ tools/wireshark/src/packet-libvirt.h | 128 ++++ tools/wireshark/util/genxdrstub.pl | 1010 +++++++++++++++++++++++++++++++ tools/wireshark/util/make-dissector-reg | 199 ++++++ 12 files changed, 2069 insertions(+), 5 deletions(-) create mode 100644 tools/wireshark/Makefile.am create mode 100644 tools/wireshark/README.md create mode 100644 tools/wireshark/src/.gitignore create mode 100644 tools/wireshark/src/Makefile.am create mode 100644 tools/wireshark/src/moduleinfo.h create mode 100644 tools/wireshark/src/packet-libvirt.c create mode 100644 tools/wireshark/src/packet-libvirt.h create mode 100755 tools/wireshark/util/genxdrstub.pl create mode 100755 tools/wireshark/util/make-dissector-reg diff --git a/Makefile.am b/Makefile.am index 4e24ecf..c7cdd13 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,7 +22,8 @@ GENHTML = genhtml SUBDIRS = gnulib/lib include src daemon tools docs gnulib/tests \ python tests po examples/domain-events/events-c examples/hellolibvirt \ examples/dominfo examples/domsuspend examples/python examples/apparmor \ - examples/xml/nwfilter examples/openauth examples/systemtap + examples/xml/nwfilter examples/openauth examples/systemtap \ + tools/wireshark ACLOCAL_AMFLAGS = -I m4 -I gnulib/m4 diff --git a/cfg.mk b/cfg.mk index e6584e8..40cbce2 100644 --- a/cfg.mk +++ b/cfg.mk @@ -969,7 +969,7 @@ exclude_file_name_regexp--sc_prohibit_newline_at_end_of_diagnostic = \ ^src/rpc/gendispatch\.pl$$ exclude_file_name_regexp--sc_prohibit_nonreentrant = \ - ^((po|tests)/|docs/.*(py|html\.in)|run.in$$) + ^((po|tests)/|docs/.*(py|html\.in)|run.in$$|tools/wireshark/util/genxdrstub\.pl$$) exclude_file_name_regexp--sc_prohibit_raw_allocation = \ ^(docs/hacking\.html\.in)|(src/util/viralloc\.[ch]|examples/.*|tests/securityselinuxhelper\.c|tests/vircgroupmock\.c)$$ @@ -980,7 +980,7 @@ exclude_file_name_regexp--sc_prohibit_readlink = \ exclude_file_name_regexp--sc_prohibit_setuid = ^src/util/virutil\.c$$ exclude_file_name_regexp--sc_prohibit_sprintf = \ - ^(docs/hacking\.html\.in)|(examples/systemtap/.*stp)|(src/dtrace2systemtap\.pl)|(src/rpc/gensystemtap\.pl)$$ + ^(docs/hacking\.html\.in)|(examples/systemtap/.*stp)|(src/dtrace2systemtap\.pl)|(src/rpc/gensystemtap\.pl)|(tools/wireshark/util/genxdrstub\.pl)$$ exclude_file_name_regexp--sc_prohibit_strncpy = ^src/util/virstring\.c$$ @@ -1013,7 +1013,7 @@ exclude_file_name_regexp--sc_correct_id_types = \ exclude_file_name_regexp--sc_m4_quote_check = m4/virt-lib.m4 exclude_file_name_regexp--sc_prohibit_include_public_headers_quote = \ - ^src/internal\.h$$ + ^(src/internal\.h$$|tools/wireshark/src/packet-libvirt.h$$) exclude_file_name_regexp--sc_prohibit_include_public_headers_brackets = \ ^(python/|tools/|examples/|include/libvirt/(virterror|libvirt-(qemu|lxc))\.h$$) diff --git a/configure.ac b/configure.ac index 553015a..ad67357 100644 --- a/configure.ac +++ b/configure.ac @@ -2569,6 +2569,70 @@ AM_CONDITIONAL([HAVE_LIBNL], [test "$have_libnl" = "yes"]) AC_SUBST([LIBNL_CFLAGS]) AC_SUBST([LIBNL_LIBS]) +dnl wireshark dissector + +AC_ARG_WITH([wireshark-dissector], + [AS_HELP_STRING([--with-wireshark-dissector], + [enable wireshark dissector plugin support @<:@default=check@:>@])], + [ with_wireshark_dissector=$withval ], + [ with_wireshark_dissector=check ]) + +AC_DEFUN([LIBVIRT_WS_HANDLE_ERROR], [ + if test "$with_wireshark_dissector" = "yes"; then + AC_MSG_ERROR([$1]) + else + with_wireshark_dissector=no + fi +]) +if test "$with_wireshark_dissector" != "no"; then + dnl Check for XDR headers existence + AC_CHECK_HEADERS([rpc/types.h]) + + dnl Check for glib-2.0 existence + PKG_CHECK_MODULES([GLIB], [glib-2.0], [ + WS_DISSECTOR_CPPFLAGS="$WS_DISSECTOR_CPPFLAGS `$PKG_CONFIG --cflags glib-2.0`" + ], [ + LIBVIRT_WS_HANDLE_ERROR([pkg-config 'glib-2.0' is required for wireshark-dissector support]) + ]) + + dnl Search for wireshark(or tshark) command + AC_PATH_PROG([WIRESHARK], [wireshark]) + AC_PATH_PROG([WIRESHARK], [tshark]) + if test -z "$WIRESHARK"; then + LIBVIRT_WS_HANDLE_ERROR([command not found wireshark or tshark]) + else + dnl Check for wireshark headers + save_CPPFLAGS="$CPPFLAGS" + WS_DISSECTOR_CPPFLAGS="$WS_DISSECTOR_CPPFLAGS -I`dirname $WIRESHARK`/../include/wireshark" + CPPFLAGS="$CPPFLAGS $WS_DISSECTOR_CPPFLAGS" + AC_CHECK_HEADERS([wireshark/config.h],, [ + LIBVIRT_WS_HANDLE_ERROR([wireshark/config.h is required for wireshark-dissector support]) + ]) + AC_CHECK_HEADERS([wireshark/epan/packet.h wireshark/epan/dissectors/packet-tcp.h],, [ + LIBVIRT_WS_HANDLE_ERROR([wireshark/epan/{packet,packet-tcp}.h are required for wireshark-dissector support]) + ], [ + #include <wireshark/config.h> + ]) + CPPFLAGS="$save_CPPFLAGS" + fi + if test "$with_wireshark_dissector" != "no"; then + with_wireshark_dissector=yes + fi +fi +AC_SUBST([WS_DISSECTOR_CPPFLAGS]) +AM_CONDITIONAL([WITH_WIRESHARK_DISSECTOR], [test "$with_wireshark_dissector" = "yes"]) + +AC_ARG_WITH([ws-plugindir], + [AS_HELP_STRING([--with-ws-plugindir], + [wireshark plugins directory that plugin will installed])], + [ ws_plugindir=$withval ]) + +if test "$with_wireshark_dissector" != "no" && test -z "$ws_plugindir"; then + ws_version=`$WIRESHARK -v | head -1 | cut -f 2 -d' '` + ws_plugindir=`dirname $WIRESHARK`/../lib/wireshark/plugins/$ws_version +fi +AC_SUBST([ws_plugindir]) + # Check for Linux vs. BSD ifreq members AC_CHECK_MEMBERS([struct ifreq.ifr_newname, struct ifreq.ifr_ifindex, @@ -2654,7 +2718,9 @@ AC_CONFIG_FILES([\ examples/python/Makefile \ examples/hellolibvirt/Makefile \ examples/systemtap/Makefile \ - examples/xml/nwfilter/Makefile]) + examples/xml/nwfilter/Makefile \ + tools/wireshark/Makefile \ + tools/wireshark/src/Makefile]) AC_OUTPUT AC_MSG_NOTICE([]) @@ -2814,6 +2880,10 @@ AC_MSG_NOTICE([ XML Catalog: $XML_CATALOG_FILE]) AC_MSG_NOTICE([ Init script: $with_init_script]) AC_MSG_NOTICE([Char device locks: $with_chrdev_lock_files]) AC_MSG_NOTICE([]) +AC_MSG_NOTICE([Developer Tools]) +AC_MSG_NOTICE([]) +AC_MSG_NOTICE([Wireshark dissector: $with_wireshark_dissector]) +AC_MSG_NOTICE([]) AC_MSG_NOTICE([Privileges]) AC_MSG_NOTICE([]) AC_MSG_NOTICE([ QEMU: $QEMU_USER:$QEMU_GROUP]) diff --git a/tools/wireshark/Makefile.am b/tools/wireshark/Makefile.am new file mode 100644 index 0000000..71addec --- /dev/null +++ b/tools/wireshark/Makefile.am @@ -0,0 +1,29 @@ +## Process this file with automake to produce Makefile.in + +# Copyright (C) 2013 Yuto KAWAMURA(kawamuray) <kawamuray.dadada@gmail.com> +# +# 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, see +# <http://www.gnu.org/licenses/>. +# +# Author: Yuto KAWAMURA(kawamuray) +if WITH_WIRESHARK_DISSECTOR +SUBDIRS = src + +# I think wireshark plugin is special case that doesn't need to install +# *.la(libtool archive) files. +# Maybe each plugin functionality should correspond to single file in +# plugins directory. So this hook keeps plugins directory clean. +install-exec-hook: + rm -f $(ws_plugindir)/libvirt.la +endif WITH_WIRESHARK_DISSECTOR diff --git a/tools/wireshark/README.md b/tools/wireshark/README.md new file mode 100644 index 0000000..9f4c9b6 --- /dev/null +++ b/tools/wireshark/README.md @@ -0,0 +1,31 @@ +About +===== +This is the project of Google Summer of Code 2013 accepted by QEMU.org and +libvirt community. The goal of this project is, provide Wireshark dissector for +Libvirt RPC protocol. It will provide Libvirt packet overview/detail analysing +in Wireshark. Furthermore, it will be able to build(generated) from RPC protocol +definition placed in Libvirt source tree to support latest protocol +specification. + +See also: +- http://www.google-melange.com/gsoc/project/google/gsoc2013/kawamuray/7001 +- http://wiki.qemu.org/Features/LibvirtWiresharkDissector + +Installation +============= +Run ./configure with --with-wireshark-dissector option enabled. +Then dissector will compiled with libvirt itself. + +Add/Remove protocol from dissector's support +-------------------------------------------- +Modify variable WS\_DISSECTOR\_PROTO\_FILES in tools/wireshark/src/Makefile.am. + +Changing installation directory +------------------------------- +You can change installation directory of pluggable shared object(libvirt.so) by +specifying --with-ws-plugindir=<path>. + +You can install libvirt.so into your local wireshark plugin directory: + + ./configure --with-wireshark-dissector \ + --with-ws-plugindir=$HOME/.wireshark/plugins diff --git a/tools/wireshark/src/.gitignore b/tools/wireshark/src/.gitignore new file mode 100644 index 0000000..cd51e37 --- /dev/null +++ b/tools/wireshark/src/.gitignore @@ -0,0 +1,4 @@ +*.so +*.o +plugin.c +libvirt diff --git a/tools/wireshark/src/Makefile.am b/tools/wireshark/src/Makefile.am new file mode 100644 index 0000000..86f69ae --- /dev/null +++ b/tools/wireshark/src/Makefile.am @@ -0,0 +1,42 @@ +## Process this file with automake to produce Makefile.in + +# Copyright (C) 2013 Yuto KAWAMURA(kawamuray) <kawamuray.dadada@gmail.com> +# +# 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, see +# <http://www.gnu.org/licenses/>. +# +# Author: Yuto KAWAMURA(kawamuray) +ws_plugin_LTLIBRARIES = libvirt.la +libvirt_la_SOURCES = packet-libvirt.c plugin.c +libvirt_la_CPPFLAGS = $(WS_DISSECTOR_CPPFLAGS) +libvirt_la_LDFLAGS = -avoid-version + +packet-libvirt.c: packet-libvirt.h libvirt/protocol.h + +plugin.c: packet-libvirt.c + $(srcdir)/../util/make-dissector-reg . plugin $< + +WS_DISSECTOR_PROTO_FILES = \ + $(top_srcdir)/src/remote/remote_protocol.x \ + $(top_srcdir)/src/remote/qemu_protocol.x \ + $(top_srcdir)/src/remote/lxc_protocol.x \ + $(top_srcdir)/src/rpc/virkeepaliveprotocol.x + +libvirt/protocol.h: $(srcdir)/../util/genxdrstub.pl $(WS_DISSECTOR_PROTO_FILES) + $(MKDIR_P) libvirt + LIBVIRT_VERSION=$(LIBVIRT_VERSION) \ + $(PERL) $(srcdir)/../util/genxdrstub.pl $(WS_DISSECTOR_PROTO_FILES) + +clean-local: + -rm -rf libvirt plugin.c diff --git a/tools/wireshark/src/moduleinfo.h b/tools/wireshark/src/moduleinfo.h new file mode 100644 index 0000000..9ab642c --- /dev/null +++ b/tools/wireshark/src/moduleinfo.h @@ -0,0 +1,37 @@ +/* moduleinfo.h --- Define constants about wireshark plugin module + * + * Copyright (C) 2013 Yuto KAWAMURA(kawamuray) <kawamuray.dadada@gmail.com> + * + * 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, see + * <http://www.gnu.org/licenses/>. + * + * Author: Yuto KAWAMURA(kawamuray) <kawamuray.dadada gmail.com> + */ + +/* Included *after* config.h, in order to re-define these macros */ + +#ifdef PACKAGE +# undef PACKAGE +#endif + +/* Name of package */ +#define PACKAGE "libvirt" + + +#ifdef VERSION +# undef VERSION +#endif + +/* Version number of package */ +#define VERSION "0.0.1" diff --git a/tools/wireshark/src/packet-libvirt.c b/tools/wireshark/src/packet-libvirt.c new file mode 100644 index 0000000..cd3e6ce --- /dev/null +++ b/tools/wireshark/src/packet-libvirt.c @@ -0,0 +1,513 @@ +/* packet-libvirt.c --- Libvirt packet dissector routines. + * + * Copyright (C) 2013 Yuto KAWAMURA(kawamuray) <kawamuray.dadada@gmail.com> + * + * 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, see + * <http://www.gnu.org/licenses/>. + * + * Authors: + * Michal Privoznik <mprivozn redhat com> + * Yuto KAWAMURA(kawamuray) <kawamuray.dadada gmail.com> + */ +#include <config.h> + +#include <wireshark/config.h> +#include <wireshark/epan/proto.h> +#include <wireshark/epan/packet.h> +#include <wireshark/epan/dissectors/packet-tcp.h> +#include <glib.h> +#include <glib/gprintf.h> +#ifdef HAVE_RPC_TYPES_H +# include <rpc/types.h> +#endif +#include <rpc/xdr.h> +#include "packet-libvirt.h" + +static int proto_libvirt = -1; +static int hf_libvirt_length = -1; +static int hf_libvirt_program = -1; +static int hf_libvirt_version = -1; +static int hf_libvirt_type = -1; +static int hf_libvirt_serial = -1; +static int hf_libvirt_status = -1; +static int hf_libvirt_stream = -1; +static int hf_libvirt_num_of_fds = -1; +static int hf_libvirt_unknown = -1; +static gint ett_libvirt = -1; + +#define XDR_PRIMITIVE_DISSECTOR(xtype, ctype, ftype) \ + static gboolean \ + dissect_xdr_##xtype(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf) \ + { \ + goffset start; \ + ctype val; \ + start = xdr_getpos(xdrs); \ + if (xdr_##xtype(xdrs, &val)) { \ + proto_tree_add_##ftype(tree, hf, tvb, start, xdr_getpos(xdrs) - start, val); \ + return TRUE; \ + } else { \ + proto_tree_add_item(tree, hf_libvirt_unknown, tvb, start, -1, ENC_NA); \ + return FALSE; \ + } \ + } + +XDR_PRIMITIVE_DISSECTOR(int, gint32, int) +XDR_PRIMITIVE_DISSECTOR(u_int, guint32, uint) +XDR_PRIMITIVE_DISSECTOR(short, gint16, int) +XDR_PRIMITIVE_DISSECTOR(u_short, guint16, uint) +XDR_PRIMITIVE_DISSECTOR(char, gchar, int) +XDR_PRIMITIVE_DISSECTOR(u_char, guchar, uint) +XDR_PRIMITIVE_DISSECTOR(hyper, gint64, int64) +XDR_PRIMITIVE_DISSECTOR(u_hyper, guint64, uint64) +XDR_PRIMITIVE_DISSECTOR(float, gfloat, float) +XDR_PRIMITIVE_DISSECTOR(double, gdouble, double) +XDR_PRIMITIVE_DISSECTOR(bool, bool_t, boolean) + +static gboolean +dissect_xdr_string(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, + guint32 maxlen) +{ + goffset start; + gchar *val = NULL; + + start = xdr_getpos(xdrs); + if (xdr_string(xdrs, &val, maxlen)) { + proto_tree_add_string(tree, hf, tvb, start, xdr_getpos(xdrs) - start, val); + xdr_free((xdrproc_t)xdr_string, (char *)&val); + return TRUE; + } else { + proto_tree_add_item(tree, hf_libvirt_unknown, tvb, start, -1, ENC_NA); + return FALSE; + } +} + +static gchar * +format_xdr_bytes(guint8 *bytes, guint32 length) +{ + gchar *buf; + guint32 i; + + if (length == 0) + return ""; + buf = ep_alloc(length*2 + 1); + for (i = 0; i < length; i++) { + /* We know that buf has enough size to contain + 2 * length + '\0' characters. */ + g_sprintf(buf, "%02x", bytes[i]); + buf += 2; + } + return buf - length*2; +} + +static gboolean +dissect_xdr_opaque(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, + guint32 size) +{ + goffset start; + gboolean rc; + guint8 *val; + + val = g_malloc(size); + start = xdr_getpos(xdrs); + if ((rc = xdr_opaque(xdrs, (caddr_t)val, size))) { + proto_tree_add_bytes_format_value(tree, hf, tvb, start, xdr_getpos(xdrs) - start, + NULL, "%s", format_xdr_bytes(val, size)); + } else { + proto_tree_add_item(tree, hf_libvirt_unknown, tvb, start, -1, ENC_NA); + } + + g_free(val); + return rc; +} + +static gboolean +dissect_xdr_bytes(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, + guint32 maxlen) +{ + goffset start; + guint8 *val = NULL; + guint32 length; + + start = xdr_getpos(xdrs); + if (xdr_bytes(xdrs, (char **)&val, &length, maxlen)) { + proto_tree_add_bytes_format_value(tree, hf, tvb, start, xdr_getpos(xdrs) - start, + NULL, "%s", format_xdr_bytes(val, length)); + /* Seems I can't call xdr_free() for this case. + It will raises SEGV by referencing out of bounds argument stack */ + xdrs->x_op = XDR_FREE; + xdr_bytes(xdrs, (char **)&val, &length, maxlen); + xdrs->x_op = XDR_DECODE; + return TRUE; + } else { + proto_tree_add_item(tree, hf_libvirt_unknown, tvb, start, -1, ENC_NA); + return FALSE; + } +} + +static gboolean +dissect_xdr_pointer(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, + vir_xdr_dissector_t dissect) +{ + goffset start; + bool_t isnull; + + start = xdr_getpos(xdrs); + if (!xdr_bool(xdrs, &isnull)) { + proto_tree_add_item(tree, hf_libvirt_unknown, tvb, start, -1, ENC_NA); + return FALSE; + } + if (isnull) { + proto_item *ti; + ti = proto_tree_add_item(tree, hf, tvb, start, xdr_getpos(xdrs) - start, ENC_NA); + proto_item_append_text(ti, ": (null)"); + return TRUE; + } else { + return dissect(tvb, tree, xdrs, hf); + } +} + +static gboolean +dissect_xdr_iterable(tvbuff_t *tvb, proto_item *ti, XDR *xdrs, gint ett, int rhf, + guint32 length, vir_xdr_dissector_t dissect, goffset start) +{ + proto_tree *tree; + guint32 i; + + tree = proto_item_add_subtree(ti, ett); + for (i = 0; i < length; i++) { + if (!dissect(tvb, tree, xdrs, rhf)) + return FALSE; + } + proto_item_set_len(ti, xdr_getpos(xdrs) - start); + return TRUE; +} + +static gboolean +dissect_xdr_vector(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, gint ett, + int rhf, gchar *rtype, guint32 size, vir_xdr_dissector_t dissect) +{ + goffset start; + proto_item *ti; + + start = xdr_getpos(xdrs); + ti = proto_tree_add_item(tree, hf, tvb, start, -1, ENC_NA); + proto_item_append_text(ti, " :: %s[%u]", rtype, size); + return dissect_xdr_iterable(tvb, ti, xdrs, ett, rhf, size, dissect, start); +} + +static gboolean +dissect_xdr_array(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, gint ett, + int rhf, gchar *rtype, guint32 maxlen, vir_xdr_dissector_t dissect) +{ + goffset start; + proto_item *ti; + guint32 length; + + start = xdr_getpos(xdrs); + + if (!xdr_u_int(xdrs, &length)) + return FALSE; + if (length > maxlen) + return FALSE; + + ti = proto_tree_add_item(tree, hf, tvb, start, -1, ENC_NA); + proto_item_append_text(ti, " :: %s<%u>", rtype, length); + return dissect_xdr_iterable(tvb, ti, xdrs, ett, rhf, length, dissect, start); +} + +static vir_xdr_dissector_t +find_payload_dissector(guint32 proc, guint32 type, + const vir_dissector_index_t *pds, gsize length) +{ + const vir_dissector_index_t *pd; + guint32 first, last, direction; + + if (pds == NULL || length < 1) + return NULL; + + first = pds[0].proc; + last = pds[length-1].proc; + if (proc < first || proc > last) { + return NULL; + } + + pd = &pds[proc-first]; + /* There is no guarantee to proc numbers has no gap */ + if (pd->proc != proc) { + direction = (pd->proc < proc) ? 1 : -1; + while (pd->proc != proc) { + if (pd->proc == first || pd->proc == last) + return NULL; + pd += direction; + } + } + + switch (type) { + case VIR_NET_CALL: + case VIR_NET_CALL_WITH_FDS: + return pd->args; + case VIR_NET_REPLY: + case VIR_NET_REPLY_WITH_FDS: + return pd->ret; + case VIR_NET_MESSAGE: + return pd->msg; + } + return NULL; +} + +static void +dissect_libvirt_stream(tvbuff_t *tvb, proto_tree *tree, gint payload_length) +{ + proto_tree_add_item(tree, hf_libvirt_stream, tvb, VIR_HEADER_LEN, + payload_length - VIR_HEADER_LEN, ENC_NA); +} + +static gint32 +dissect_libvirt_num_of_fds(tvbuff_t *tvb, proto_tree *tree) +{ + gint32 nfds; + nfds = tvb_get_ntohl(tvb, VIR_HEADER_LEN); + proto_tree_add_int(tree, hf_libvirt_num_of_fds, tvb, VIR_HEADER_LEN, 4, nfds); + return nfds; +} + +static void +dissect_libvirt_fds(tvbuff_t *tvb, gint start, gint32 nfds) +{ + /* TODO: NOP for now */ +} + +static void +dissect_libvirt_payload_xdr_data(tvbuff_t *tvb, proto_tree *tree, gint payload_length, + gint32 status, vir_xdr_dissector_t dissect) +{ + gint32 nfds = 0; + gint start = VIR_HEADER_LEN; + tvbuff_t *payload_tvb; + caddr_t payload_data; + XDR xdrs; + + if (status == VIR_NET_CALL_WITH_FDS || + status == VIR_NET_REPLY_WITH_FDS) { + nfds = dissect_libvirt_num_of_fds(tvb, tree); + start += 4; + payload_length -= 4; + } + + payload_tvb = tvb_new_subset(tvb, start, -1, payload_length); + payload_data = (caddr_t)tvb_memdup(payload_tvb, 0, payload_length); + xdrmem_create(&xdrs, payload_data, payload_length, XDR_DECODE); + + dissect(payload_tvb, tree, &xdrs, -1); + + xdr_destroy(&xdrs); + g_free(payload_data); + + if (nfds != 0) { + dissect_libvirt_fds(tvb, start + payload_length, nfds); + } +} + +static void +dissect_libvirt_payload(tvbuff_t *tvb, proto_tree *tree, + guint32 prog, guint32 proc, guint32 type, guint32 status) +{ + gssize payload_length; + + payload_length = tvb_length(tvb) - VIR_HEADER_LEN; + if (payload_length <= 0) + return; /* No payload */ + + if (status == VIR_NET_OK) { + vir_xdr_dissector_t xd = find_payload_dissector(proc, type, get_program_data(prog, VIR_PROGRAM_DISSECTORS), + *(gsize *)get_program_data(prog, VIR_PROGRAM_DISSECTORS_LEN)); + if (xd == NULL) + goto unknown; + dissect_libvirt_payload_xdr_data(tvb, tree, payload_length, status, xd); + } else if (status == VIR_NET_ERROR) { + dissect_libvirt_payload_xdr_data(tvb, tree, payload_length, status, VIR_ERROR_MESSAGE_DISSECTOR); + } else if (type == VIR_NET_STREAM) { /* implicitly, status == VIR_NET_CONTINUE */ + dissect_libvirt_stream(tvb, tree, payload_length); + } else { + goto unknown; + } + return; + +unknown: + dbg("Cannot determine payload: Prog=%u, Proc=%u, Type=%u, Status=%u", prog, proc, type, status); + proto_tree_add_item(tree, hf_libvirt_unknown, tvb, VIR_HEADER_LEN, -1, ENC_NA); +} + +static void +dissect_libvirt_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + goffset offset; + guint32 prog, proc, type, serial, status; + const value_string *vs; + + col_set_str(pinfo->cinfo, COL_PROTOCOL, "Libvirt"); + col_clear(pinfo->cinfo, COL_INFO); + + offset = 4; /* End of length field */ + prog = tvb_get_ntohl(tvb, offset); offset += 4; + offset += 4; /* Ignore version header field */ + proc = tvb_get_ntohl(tvb, offset); offset += 4; + type = tvb_get_ntohl(tvb, offset); offset += 4; + serial = tvb_get_ntohl(tvb, offset); offset += 4; + status = tvb_get_ntohl(tvb, offset); offset += 4; + + col_add_fstr(pinfo->cinfo, COL_INFO, "Prog=%s", + val_to_str(prog, program_strings, "%x")); + + vs = get_program_data(prog, VIR_PROGRAM_PROCSTRINGS); + if (vs == NULL) { + col_append_fstr(pinfo->cinfo, COL_INFO, " Proc=%u", proc); + } else { + col_append_fstr(pinfo->cinfo, COL_INFO, " Proc=%s", val_to_str(proc, vs, "%d")); + } + + col_append_fstr(pinfo->cinfo, COL_INFO, " Type=%s Serial=%u Status=%s", + val_to_str(type, type_strings, "%d"), serial, + val_to_str(status, status_strings, "%d")); + + if (tree) { + gint hf_proc; + proto_item *ti; + proto_tree *libvirt_tree; + + ti = proto_tree_add_item(tree, proto_libvirt, tvb, 0, tvb_length(tvb), ENC_NA); + libvirt_tree = proto_item_add_subtree(ti, ett_libvirt); + + offset = 0; + proto_tree_add_item(libvirt_tree, hf_libvirt_length, tvb, offset, 4, ENC_NA); offset += 4; + proto_tree_add_item(libvirt_tree, hf_libvirt_program, tvb, offset, 4, ENC_NA); offset += 4; + proto_tree_add_item(libvirt_tree, hf_libvirt_version, tvb, offset, 4, ENC_NA); offset += 4; + + hf_proc = *(int *)get_program_data(prog, VIR_PROGRAM_PROCHFVAR); + if (hf_proc == -1) { + proto_tree_add_none_format(libvirt_tree, -1, tvb, offset, 4, "Unknown proc: %u", proc); + } else { + proto_tree_add_item(libvirt_tree, hf_proc, tvb, offset, 4, ENC_NA); + } + offset += 4; + + proto_tree_add_item(libvirt_tree, hf_libvirt_type, tvb, offset, 4, ENC_NA); offset += 4; + proto_tree_add_item(libvirt_tree, hf_libvirt_serial, tvb, offset, 4, ENC_NA); offset += 4; + proto_tree_add_item(libvirt_tree, hf_libvirt_status, tvb, offset, 4, ENC_NA); offset += 4; + + /* Dissect payload remaining */ + dissect_libvirt_payload(tvb, libvirt_tree, prog, proc, type, status); + } +} + +static guint32 +get_message_len(packet_info *pinfo __attribute__((unused)), tvbuff_t *tvb, int offset) +{ + return tvb_get_ntohl(tvb, offset); +} + +static void +dissect_libvirt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + /* Another magic const - 4; simply, how much bytes + * is needed to tell the length of libvirt packet. */ + tcp_dissect_pdus(tvb, pinfo, tree, TRUE, 4, get_message_len, dissect_libvirt_message); +} + +void +proto_register_libvirt(void) +{ + static hf_register_info hf[] = { + { &hf_libvirt_length, + { "length", "libvirt.length", + FT_UINT32, BASE_DEC, + NULL, 0x0, + NULL, HFILL} + }, + { &hf_libvirt_program, + { "program", "libvirt.program", + FT_UINT32, BASE_HEX, + VALS(program_strings), 0x0, + NULL, HFILL} + }, + { &hf_libvirt_version, + { "version", "libvirt.version", + FT_UINT32, BASE_DEC, + NULL, 0x0, + NULL, HFILL} + }, + { &hf_libvirt_type, + { "type", "libvirt.type", + FT_INT32, BASE_DEC, + VALS(type_strings), 0x0, + NULL, HFILL} + }, + { &hf_libvirt_serial, + { "serial", "libvirt.serial", + FT_UINT32, BASE_DEC, + NULL, 0x0, + NULL, HFILL} + }, + { &hf_libvirt_status, + { "status", "libvirt.status", + FT_INT32, BASE_DEC, + VALS(status_strings), 0x0, + NULL, HFILL} + }, + + VIR_DYNAMIC_HFSET + + { &hf_libvirt_stream, + { "stream", "libvirt.stream", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL} + }, + { &hf_libvirt_num_of_fds, + { "num_of_fds", "libvirt.num_of_fds", + FT_INT32, BASE_DEC, + NULL, 0x0, + NULL, HFILL} + }, + { &hf_libvirt_unknown, + { "unknown", "libvirt.unknown", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL} + }, + }; + + static gint *ett[] = { + VIR_DYNAMIC_ETTSET + &ett_libvirt + }; + + proto_libvirt = proto_register_protocol( + "Libvirt", /* name */ + "libvirt", /* short name */ + "libvirt" /* abbrev */ + ); + + proto_register_field_array(proto_libvirt, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); +} + +void +proto_reg_handoff_libvirt(void) +{ + static dissector_handle_t libvirt_handle; + + libvirt_handle = create_dissector_handle(dissect_libvirt, proto_libvirt); + dissector_add_uint("tcp.port", LIBVIRT_PORT, libvirt_handle); +} diff --git a/tools/wireshark/src/packet-libvirt.h b/tools/wireshark/src/packet-libvirt.h new file mode 100644 index 0000000..0cab637 --- /dev/null +++ b/tools/wireshark/src/packet-libvirt.h @@ -0,0 +1,128 @@ +/* packet-libvirt.h --- Libvirt packet dissector header file. + * + * Copyright (C) 2013 Yuto KAWAMURA(kawamuray) <kawamuray.dadada@gmail.com> + * + * 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, see + * <http://www.gnu.org/licenses/>. + * + * Author: Yuto KAWAMURA(kawamuray) + */ +#ifndef _PACKET_LIBVIRT_H_ +# define _PACKET_LIBVIRT_H_ + +# ifndef LIBVIRT_PORT +# define LIBVIRT_PORT 16509 +# endif + +# define VIR_HEADER_LEN 28 + +# ifdef DEBUG +# define dbg(fmt, ...) \ + g_print("[LIBVIRT] " fmt " at " __FILE__ " line %d\n", ##__VA_ARGS__, __LINE__) +# else +# define dbg(fmt, ...) +# endif + +typedef gboolean (*vir_xdr_dissector_t)(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); + +typedef struct vir_dissector_index vir_dissector_index_t; +struct vir_dissector_index { + guint32 proc; + vir_xdr_dissector_t args; + vir_xdr_dissector_t ret; + vir_xdr_dissector_t msg; +}; + +enum vir_net_message_type { + VIR_NET_CALL = 0, + VIR_NET_REPLY = 1, + VIR_NET_MESSAGE = 2, + VIR_NET_STREAM = 3, + VIR_NET_CALL_WITH_FDS = 4, + VIR_NET_REPLY_WITH_FDS = 5, +}; + +enum vir_net_message_status { + VIR_NET_OK = 0, + VIR_NET_ERROR = 1, + VIR_NET_CONTINUE = 2, +}; + +enum vir_program_data_index { + VIR_PROGRAM_PROCHFVAR, + VIR_PROGRAM_PROCSTRINGS, + VIR_PROGRAM_DISSECTORS, + VIR_PROGRAM_DISSECTORS_LEN, + VIR_PROGRAM_LAST, +}; + +static const value_string type_strings[] = { + { VIR_NET_CALL, "CALL" }, + { VIR_NET_REPLY, "REPLY" }, + { VIR_NET_MESSAGE, "MESSAGE" }, + { VIR_NET_STREAM, "STREAM" }, + { VIR_NET_CALL_WITH_FDS, "CALL_WITH_FDS" }, + { VIR_NET_REPLY_WITH_FDS, "REPLY_WITH_FDS" }, + { -1, NULL } +}; + +static const value_string status_strings[] = { + { VIR_NET_OK, "OK" }, + { VIR_NET_ERROR, "ERROR" }, + { VIR_NET_CONTINUE, "CONTINUE" }, + { -1, NULL } +}; + +/* TODO: These symbols will automatically included in generated headers in the feature */ +# define VIR_SECURITY_MODEL_BUFLEN (256 + 1) +# define VIR_SECURITY_LABEL_BUFLEN (4096 + 1) +# define VIR_SECURITY_DOI_BUFLEN (256 + 1) +# define VIR_UUID_BUFLEN (16) +enum { + VIR_TYPED_PARAM_INT = 1, /* integer case */ + VIR_TYPED_PARAM_UINT = 2, /* unsigned integer case */ + VIR_TYPED_PARAM_LLONG = 3, /* long long case */ + VIR_TYPED_PARAM_ULLONG = 4, /* unsigned long long case */ + VIR_TYPED_PARAM_DOUBLE = 5, /* double case */ + VIR_TYPED_PARAM_BOOLEAN = 6, /* boolean(character) case */ + VIR_TYPED_PARAM_STRING = 7, /* string case */ +}; +/* / */ + +# define VIR_ERROR_MESSAGE_DISSECTOR dissect_xdr_remote_error + +static gboolean dissect_xdr_int(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_u_int(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_short(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_u_short(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_char(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_u_char(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_hyper(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_u_hyper(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_float(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_double(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_bool(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf); +static gboolean dissect_xdr_string(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, guint32 maxlen); +static gboolean dissect_xdr_opaque(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, guint32 size); +static gboolean dissect_xdr_bytes(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, guint32 maxlen); +static gboolean dissect_xdr_pointer(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, + vir_xdr_dissector_t dp); +static gboolean dissect_xdr_vector(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, gint ett, + int rhf, gchar *rtype, guint32 size, vir_xdr_dissector_t dp); +static gboolean dissect_xdr_array(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, gint ett, + int rhf, gchar *rtype, guint32 maxlen, vir_xdr_dissector_t dp); + +# include "libvirt/protocol.h" + +#endif /* _PACKET_LIBVIRT_H_ */ diff --git a/tools/wireshark/util/genxdrstub.pl b/tools/wireshark/util/genxdrstub.pl new file mode 100755 index 0000000..33918f2 --- /dev/null +++ b/tools/wireshark/util/genxdrstub.pl @@ -0,0 +1,1010 @@ +#!/usr/bin/env perl +# genxdrstub.pl --- Generate C header file which used by packet-libvirt.[ch] +# +# Copyright (C) 2013 Yuto KAWAMURA(kawamuray) <kawamuray.dadada@gmail.com> +# +# 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, see +# <http://www.gnu.org/licenses/>. +# +# Author: Yuto KAWAMURA(kawamuray) +# +# For XDR syntax, see http://tools.ietf.org/html/rfc4506#section-6.3 +# This script does not strictly check syntax of xdr protocol specification. +# Make sure the specification files you have are correctly compilable with rpcgen(1). +# If something fails with this script in spite of you had confirmed that the `make' with libvirt was succeed, +# please report your error output to kawamuray<kawamuray.dadada@gmail.com>. +use strict; +use warnings; +use File::Spec; + +my $DEBUG = 0; # Enable if you want to see debug output +sub dbg { print STDERR @_ if $DEBUG } + +die "ERROR: No arguments" unless @ARGV; + +# Context object referenced from entire this script +my $c = Context->new; + +for my $proto (@ARGV) { + # We need to do this heuristic parsing to determine + # variable name of enum <protocol>_procedures. + my ($name) = $proto =~ m{(?:vir)?([^/]+?)_?protocol\.x$}; + unless ($name) { + warn "WARNING: Cannot extract protocol name from $proto, skipping."; + next; + } + $c->add_to_set(progs => $name); + + my $source; + { + open my $fh, '<', $proto + or die "Cannot open $proto: $!"; + local $/; + $source = <$fh>; + close $fh; + } + + $c->add_header_file($name, sub { + dbg "*** Start parsing $proto\n"; + my @lexs = Lexicalizer->parse($source); + for my $lex (@lexs) { + next if $lex->ident eq "enum $name\_procedure"; + + if ($lex->isa('Sym::Variable')) { + $c->print(sprintf "#define %s (%s)\n", $lex->ident, $lex->value); + } elsif ($lex->isa('Sym::Type')) { + # Top level of name path is type identification of itself + $lex->define_dissector($lex->idstrip); + } else { + die "Unkown lexical appeared: $lex"; + } + } + + my $procs = $c->symbol("enum $name\_procedure") + or die "Cannot find procedures enumeration: enum $name\_procedure"; + # Procedure numbers are expected to be containing gaps, but needed to be sorted in ascending order. + my @procedures = sort { $a->value <=> $b->value } @{ $procs->members }; + my @dissectors = map { + (my $ident = lc($_->ident)) =~ s/^$name\_proc/$name/; + +{ + value => $_->value, + map { $_ => $c->rinc($c->symbols->{"$ident\_$_"} ? "dissect_xdr_$ident\_$_" : 'NULL') } + qw{ args ret msg } + }; + } @procedures; + $c->print(PT->render('code.dissectorlist', { + name => $name, + dissectors => \@dissectors, + })); + $c->print(PT->render('code.procedure_strings', { + name => $name, + procedures => \@procedures, + })); + }); +} + +$c->add_header_file('protocol', sub { + for my $prog (@{ $c->get_set('progs') }) { + $c->print("#include \"libvirt/$prog.h\"\n"); + } + + # hf_ variables set + $c->print(PT->render('macro.hfvars', { + programs => $c->get_set('progs'), + hfvars => [ grep $_->{segment}{refcnt}, @{ $c->get_set('hfvars') } ], + })); + # ett_ variables set + $c->print(PT->render('macro.ettvars', { + ettvars => [ map $_->{sym}, grep $_->{refcnt}, @{ $c->get_set('ettvars') } ], + })); + # value_string program_strings + $c->print(PT->render('code.program_strings', { programs => $c->get_set('progs') })); + $c->print("static int hf_$_\_procedure = -1;\n") for @{ $c->get_set('progs') }; + $c->print(PT->render('code.program_data', { programs => $c->get_set('progs') })); +}); + +$c->finalize; exit 0; + +# Used for handy class building +sub register_profile { + my %prof = @_; + my $caller = caller; + no strict 'refs'; + if ($prof{isa}) { + push @{ "$caller\::ISA" }, $prof{isa}; + } + while (my ($name, $v) = each %{ $prof{consts} || {} }) { + *{ "$caller\::$name" } = sub { $v }; + } + for my $attr (@{ $prof{attrs} || [] }) { + *{ "$caller\::$attr" } = sub { + if (@_ > 1) { $_[0]->{$attr} = $_[1]; $_[0] } + else { $_[0]->{$attr} } + }; + } + while (my ($klass, $meths) = each %{ $prof{roles} || {} }) { + for my $meth (@$meths) { + # This assignment cannot be like: *{ "$caller\::$meth" } = \&{ "$klass\::$meth" }. + # "$klass\::$meth" maybe not defined yet(e.g. Methods defined by PT) + *{ "$caller\::$meth" } = sub { goto &{ "$klass\::$meth" } }; + } + } +} + +# Minimal template engine for code generating +package PT; # is PicoTemplate +use Carp; +our $Token; +our %Templates; +INIT { # Load templates from __END__ section + $Token = join '', map { chr(65 + rand(26)) } 1..64; + my $current; + while (my $l = <main::DATA>) { + if ($l =~ /^\@\@\s*(.+)/) { + $current = \($Templates{$1} = ''); + } else { + $$current .= $l if $current; + } + } + for my $name (keys %Templates) { + $Templates{$name} = __PACKAGE__->compile($Templates{$name}); + if ($name =~ /^([\w:]+)#([^#]+)$/) { + no strict 'refs'; + my $meth = "$1\::$2"; + unless (defined &$meth) { + *$meth = $Templates{$name}; + } + } + } +} +sub compile { + my ($class, $tmpl) = @_; + + $tmpl =~ s{<%(=)?(.*?)%>\n?|((?:(?!<%).)+)}{ + $2 ? $1 ? "\$$Token .= qq{\@{[do{ $2 }]}};" : $2 + : "\$$Token .= substr <<$Token, 0, -1;\n".quotemeta($3)."\n$Token\n"; + }gse; + eval "sub { my \$$Token = ''; $tmpl \$$Token }" + or croak "ERROR: Cannot compile template: $@"; +} +sub render { + my ($class, $name, $vars, @args) = @_; + local $_ = $vars || {}; + my $renderer = $Templates{$name} + or die "No such template: $name"; + $renderer->(@args); +} +# / package PT + +package Sym; +BEGIN{::register_profile( + attrs => [qw[ ident ]], +)} + +sub new { + my ($class, %args) = @_; + + CORE::bless \%args, $class; +} + +sub bless { + my ($self, $klass) = @_; + + CORE::bless $self, "Sym::$klass" + if ref($self) ne "Sym::$klass"; + $self; +} + +sub idstrip { + my $ident = shift()->ident; + $ident =~ s/^(?:struct|enum|union)\s+// if $ident; + $ident; +} +# / package Sym + +package Sym::Type; +BEGIN{::register_profile( + isa => 'Sym', + attrs => [qw[ alias ]], +)} + +sub is_primitive { !(shift)->alias } + +sub dealias { + my ($self) = @_; + + $self->is_primitive ? $self : $self->alias->dealias; +} + +sub xdr_type { + my ($self) = @_; + + if (!$self->is_primitive) { + return $self->dealias->xdr_type; + } + + my $type = ref $self; + if ($type eq __PACKAGE__) { + $type = $self->ident; + } else { + $type =~ s/^.*:://; + } + uc($type); +} + +sub render_caller { + my ($self, $hfid) = @_; + my $name = $c->rinc( 'dissect_xdr_'.($self->idstrip || lc($self->xdr_type)) ); + "$name(tvb, tree, xdrs, hf)"; +} + +sub ft_type { + my ($self) = @_; + return $self->dealias->ft_type unless $self->is_primitive; + my $xt = $self->xdr_type; + +{ + INT => 'INT32', + U_INT => 'UINT32', + SHORT => 'INT16', + U_SHORT => 'UINT16', + CHAR => 'INT8', + U_CHAR => 'UINT8', + HYPER => 'INT64', + U_HYPER => 'UINT64', + BOOL => 'BOOLEAN', + }->{$xt} || $xt; +} + +sub hf_base { + my ($self) = @_; + $self->is_primitive + ? $self->ft_type =~ /INT/ ? 'DEC' : 'NONE' + : $self->dealias->hf_base; +} + +sub define_dissector { + my ($self, @path) = @_; + $self->declare_hfvar(@path); + my $path = join '__', @path; + my $code = $self->render_dissector($path); + $c->print({ sym => "dissect_xdr_$path", body => $code }) + if $code; +} + +sub declare_hfvar { + my ($self, @path) = @_; + my $path = join '__', @path; + $c->add_to_set(hfvars => { + segment => $c->print({ + sym => "hf_$path", + body => "static int hf_$path = -1;\n" + }), + name => $path[-1], + abbrev => join('.', @path), + ft_type => $self->ft_type, + hf_base => $self->hf_base, + }); +} +# / package Sym + +package Sym::Type::HasAnonTypes; # Types which possibly have anonymous subtypes +BEGIN{::register_profile( + isa => 'Sym::Type', +)} + +sub declare_anontypes { + my ($self, @path) = @_; + + for my $m (@{ $self->members }) { + unless (defined $m->type->ident) { + $m->type->ident(join '__', @path, $m->ident); + } + $m->type->define_dissector(@path, $m->ident); + } +} + +sub define_dissector { + my ($self, @path) = @_; + + $self->declare_anontypes(@path); + $self->SUPER::define_dissector(@path); +} + +package Sym::Type::HasSubtree; # Types which should be declare ett variables + +sub declare_ettvar { + my ($self) = @_; + my $ettvar = 'ett_'.$self->idstrip; + $c->add_to_set(ettvars => $c->print({ + sym => $ettvar, + body => "static gint $ettvar = -1;\n", + })); +} + +package Sym::Type::HasReference; # Types which references subtype +BEGIN{::register_profile( + attrs => [qw[ reftype ]], + consts => { ft_type => 'NONE' }, +)} + +sub render_caller { + my ($self) = @_; + my ($klass) = ref($self) =~ /([^:]+)$/; + sprintf '%s(tvb, tree, xdrs, hf, %s)', + $c->rinc('dissect_xdr_'.lc($klass)), + $c->rinc('dissect_xdr_'.$self->reftype->idstrip); +} + +package Sym::Type::HasLength; # Types which has length attribute +BEGIN{::register_profile( + attrs => [qw[ length ]], + consts => { ft_type => 'NONE' }, +)} + +sub render_caller { + my ($self, $hfid) = @_; + my ($klass) = ref($self) =~ /([^:]+)$/; + sprintf '%s(tvb, tree, xdrs, hf, %s)', + $c->rinc('dissect_xdr_'.lc($klass)), $self->length || '~0'; +} + +package Sym::Type::Struct; +BEGIN{::register_profile( + isa => 'Sym::Type', + attrs => [qw[ members ]], + consts => { ft_type => 'NONE' }, + roles => { + 'Sym::Type::HasAnonTypes' => [qw[ declare_anontypes ]], + 'Sym::Type::HasSubtree' => [qw[ declare_ettvar ]], + }, +)} + +sub define_dissector { + my ($self, @path) = @_; + $self->declare_anontypes(@path); + $self->declare_ettvar; + $self->SUPER::define_dissector(@path); +} + +package Sym::Type::Enum; +BEGIN{::register_profile( + isa => 'Sym::Type', + attrs => [qw[ members ]], + consts => { ft_type => 'UINT32' }, +)} +package Sym::Type::Union; +BEGIN{::register_profile( + isa => 'Sym::Type', + attrs => [qw[ decl case_specs ]], + consts => { ft_type => 'NONE' }, + roles => { + 'Sym::Type::HasAnonTypes' => [qw[ declare_anontypes define_dissector ]], + }, +)} +sub members { + my ($self) = @_; + [ map { $_->[1] } @{ $self->case_specs } ]; +} + +package Sym::Type::String; +BEGIN{::register_profile( + isa => 'Sym::Type', + consts => { ft_type => 'STRING' }, + roles => { + 'Sym::Type::HasLength' => [qw[ length render_caller ]], + }, +)} +package Sym::Type::Opaque; +BEGIN{::register_profile( + isa => 'Sym::Type', + consts => { ft_type => 'BYTES' }, + roles => { + 'Sym::Type::HasLength' => [qw[ length render_caller ]], + }, +)} +package Sym::Type::Bytes; +BEGIN{::register_profile( + isa => 'Sym::Type', + consts => { ft_type => 'BYTES' }, + roles => { + 'Sym::Type::HasLength' => [qw[ length render_caller ]], + }, +)} +package Sym::Type::Pointer; +BEGIN{::register_profile( + isa => 'Sym::Type', + roles => { + 'Sym::Type::HasReference' => [qw[ reftype ft_type render_caller ]], + }, +)} +package Sym::Type::Array; # a.k.a Variable-Length Array +BEGIN{::register_profile( + isa => 'Sym::Type', + roles => { + 'Sym::Type::HasLength' => [qw[ length ft_type ]], + 'Sym::Type::HasReference' => [qw[ reftype ]], + 'Sym::Type::HasSubtree' => [qw[ declare_ettvar ]], + }, +)} + +sub render_caller { + my ($self, $hfid) = @_; + my ($pname) = reverse split /__/, $hfid; + sprintf 'dissect_xdr_array(tvb, tree, xdrs, hf, %s, %s, "%s", %s, %s)', + $c->rinc('ett_'.$self->idstrip), + $c->rinc("hf_$hfid\__$pname"), + $self->reftype->idstrip, + $self->length || '~0', + $c->rinc('dissect_xdr_'.$self->reftype->idstrip); +} + +sub define_dissector { + my ($self, @path) = @_; + $self->reftype->declare_hfvar(@path, $path[-1]); + $self->declare_ettvar; + $self->SUPER::define_dissector(@path); +} + +package Sym::Type::Vector; # a.k.a Fixed-Length Array +BEGIN{::register_profile( + isa => 'Sym::Type', + roles => { + 'Sym::Type::HasLength' => [qw[ length ft_type ]], + 'Sym::Type::HasReference' => [qw[ reftype ]], + 'Sym::Type::Array' => [qw[ define_dissector ]], + 'Sym::Type::HasSubtree' => [qw[ declare_ettvar ]], + }, +)} + +sub render_caller { + my ($self, $hfid) = @_; + my ($pname) = reverse split /__/, $hfid; + sprintf 'dissect_xdr_vector(tvb, tree, xdrs, hf, %s, %s, "%s", %s, %s)', + $c->rinc('ett_'.$self->idstrip), + $c->rinc("hf_$hfid\__$pname"), + $self->reftype->idstrip, + $self->length || '~0', + $c->rinc('dissect_xdr_'.$self->reftype->idstrip); +} + +package Sym::Variable; +BEGIN{::register_profile( + isa => 'Sym', + attrs => [qw[ type value ]], +)} + +package Context; +BEGIN{::register_profile( + attrs => [qw[ symbols ]], +)} + +sub new { + my ($class) = @_; + + bless { + symbols => {}, + segments => {}, + }, $class; +} + +sub symbol { + my ($self, $ident) = @_; + my $sym = $self->symbols->{$ident} ||= Sym->new; + $sym->ident($ident); + # In XDR syntax specification, defining struct/enum/union will automatically + # create alias having symbol which excludes its prefix type specifier. + # e.g: + # struct foo { int bar; }; will convert to: + # struct foo { int bar; }; typedef struct foo foo; + if ($ident =~ s/^(?:struct|enum|union)\s+//) { + $self->symbol($ident)->bless('Type')->alias($sym); + } + $sym; +} + +sub add_to_set { + my ($self, $set, @elems) = @_; + $self->{sets} ||= {}; + $self->{sets}{$set} ||= []; + push @{ $self->{sets}{$set} }, @elems; +} + +sub get_set { + my ($self, $set) = @_; + $self->{sets}{$set} || []; +} + +# $c->print(...string...); # Does work as regular 'print' +# $c->print({ sym => symbol, body => ...string... }); +# Does treat segment as code block should be referenced. +# It will not printed unless it is referenced from other code by $c->rinc(); +sub print { + my $self = shift; + my $content; + if (ref $_[0]) { + $content = $self->{segments}{ $_[0]{sym} } ||= $_[0]; + $content->{refcnt} //= 0; + $content->{body} = $_[0]{body}; + } else { + $content = join '', @_; + } + push @{ $self->{header_contents} }, $content; + $content; +} + +sub rinc { + my ($self, $sym) = @_; + ($self->{segments}{$sym} ||= { sym => $sym, refcnt => 0 })->{refcnt}++; + $sym; +} + +sub add_header_file { + my ($self, $name, $block) = @_; + + $self->{headers} ||= []; + + local $self->{header_contents} = []; + $self->print("/* *DO NOT MODIFY* this file directly. \n"); + $self->print(" * This file was generated by $0 from libvirt version $ENV{LIBVIRT_VERSION} */\n"); + my $ucname = uc $name; + $self->print("#ifndef _$ucname\_H_\n"); + $self->print("#define _$ucname\_H_\n"); + $block->(); + $self->print("#endif /* _$ucname\_H_ */"); + push @{ $self->{headers} }, [ $name, delete $self->{header_contents} ]; +} + +sub finalize { + my ($self) = @_; + + # Referenced from macro defined in packet-libvirt.h + $self->rinc('dissect_xdr_remote_error'); + + for my $header (@{ $self->{headers} || [] }) { + my ($name, $contents) = @$header; + my $file = File::Spec->catfile($ENV{PWD}, 'libvirt', "$name.h"); + open my $fh, '>', $file + or die "Cannot open file $file: $!"; + CORE::print $fh map { ref($_) ? ($_->{refcnt} ? $_->{body} : ()) : $_ } @$contents; + CORE::print $fh "\n"; + close $fh; + } +} +# / package Context + +package Lexicalizer; +our $Depth; + +INIT { # Wrap all lexicalizer subroutine by debugger function + $Depth = 0; + no strict 'refs'; + no warnings 'redefine'; + for my $name (keys %{ __PACKAGE__.'::' }) { + next if $name =~ /^(?:parse|adv)$/; + my $fullname = __PACKAGE__."::$name"; + next unless defined &$fullname; + my $sub = \&$fullname; + *$fullname = sub { + my (undef, undef, $line) = caller; + ::dbg ' 'x($Depth*2), "$name L$line", "\n"; + local $Depth = $Depth + 1; + $sub->(@_); + }; + } +} + +# Check if passed regexp does match to next token and advance position. +# Return matched string if matched. Die else. +sub adv { + my ($rx) = @_; + ::dbg ' 'x($Depth*2+1), "- adv( $rx ) = "; + # Remove Comments Comments C++ style, PP directives + s{\A(?:\s*(?:/\*.*?\*/|(?://|%).*?(?:\n+|\z)))*\s*}{}s; + if (s/^(?:$rx)//s) { + ::dbg "'$&'\n"; + return $&; + } + ::dbg "UNMATCH\n"; + die; +} + +sub lexor { + my $snapshot = $_; + while (my $handler = shift) { + my $ret = eval { $handler->() }; + if (defined $ret) { + return $ret; + } + $_ = $snapshot; + } + die; +} + +sub decimal_constant { + adv '\-?[0-9]+'; +} + +sub hexadecimal_constant { + adv '\-?0x[0-9A-Fa-f]+'; +} + +sub octal_constant { + adv '\-?0[0-9]+'; +} + +sub constant { + lexor \&hexadecimal_constant, \&octal_constant, \&decimal_constant; +} + +sub identifier { + adv '[_a-zA-Z][_a-zA-Z0-9]*'; +} + +sub value { + lexor \&constant, \&identifier; +} + +sub enum_type_spec { + adv 'enum'; + my $body = lexor \&enum_body, \&identifier; + if (ref $body eq 'ARRAY') { + Sym::Type::Enum->new(members => $body); + } else { + $c->symbol("enum $body")->bless('Type::Enum'); + } +} + +sub enum_body { + adv '{'; + my @members; + do { + my $ident = identifier(); + adv '='; + my $value = value(); + push @members, $c->symbol($ident)->bless('Variable')->value($value); + } while adv('[},]') eq ','; + \@members; +} + +sub struct_type_spec { + adv 'struct'; + my $body = lexor \&struct_body, \&identifier; + if (ref $body eq 'ARRAY') { + Sym::Type::Struct->new(members => $body); + } else { + $c->symbol("struct $body")->bless('Type::Struct'); + } +} + +sub struct_body { + adv '{'; + local $c->{symbols} = { %{ $c->{symbols} } }; + my @members; + while (my $decl = lexor \&declaration, sub { adv('}') }) { + last if $decl eq '}'; + adv ';'; + push @members, $decl; + } + \@members; +} + +sub case_spec { + my @cases; + while (my $case = eval { adv 'case' }) { + push @cases, value(); + adv ':'; + } + my $decl = declaration(); + adv ';'; + [ \@cases, $decl ]; +} + +sub union_type_spec { + adv 'union'; + local $c->{symbols} = { %{ $c->{symbols} } }; + my $body = lexor \&union_body, \&identifier; + if (ref $body eq 'ARRAY') { + Sym::Type::Union->new(decl => $body->[0], case_specs => $body->[1]); + } else { + $c->symbol("union $body")->bless('Type::Union'); + } +} + +sub union_body { + adv 'switch'; adv '\('; + my $decl = declaration(); + adv '\)'; adv '{'; + my @case_specs; + while (my $spec = eval { case_spec() }) { + push @case_specs, $spec; + } + # TODO: parse default + adv '}'; + [ $decl, \@case_specs ]; +} + +sub constant_def { + adv 'const'; + my $ident = identifier(); + adv '='; + my $value = lexor \&constant, \&identifier; + adv ';'; + + $c->symbol($ident)->bless('Variable')->value($value); +} + +sub type_def { + my $ret = lexor sub { + adv 'typedef'; + my $var = declaration(); + my $type = $var->type; + $var->bless('Type')->alias($type); + }, sub { + adv 'enum'; + my $ident = identifier(); + my $body = enum_body(); + $c->symbol("enum $ident")->bless('Type::Enum')->members($body); + }, sub { + adv 'struct'; + my $ident = identifier(); + my $body = struct_body(); + $c->symbol("struct $ident")->bless('Type::Struct')->members($body); + }, sub { + adv 'union'; + my $ident = identifier(); + my $body = union_body(); + $c->symbol("union $ident")->bless('Type::Union') + ->decl($body->[0])->case_specs($body->[1]); + }; + adv ';'; + $ret; +} + +sub type_specifier { + lexor sub { + my $ts = adv '(?:unsigned\s+)?(?:int|hyper|char|short)|float|double|quadruple|bool'; + $ts =~ s/^unsigned\s+/u_/; + $c->symbol($ts)->bless('Type'); + }, \&enum_type_spec, \&struct_type_spec, \&union_type_spec, sub { + my $ident = identifier(); + $c->symbol($ident)->bless('Type'); + }; +} + +sub declaration { + lexor sub { + my $type = lexor sub { + my $type = adv 'opaque|string'; + my $klass = ucfirst $type; + "Sym::Type::$klass"->new; + }, \&type_specifier; + my $ident = identifier(); + # I know that type 'string' does not accept '[]'(fixed length), but I don't care about that + if (my $ex = eval { adv '[<\[]' }) { + my $value = eval { value() }; + die if !$value && $ex ne '<'; # Length could be null if it is variable length + + adv($ex eq '<' ? '>' : '\]'); + if (ref($type) eq 'Sym::Type') { # Expect Array or Vector + my $vtype = ($ex eq '<') ? 'Array' : 'Vector'; + $type = "Sym::Type::$vtype"->new(length => $value, reftype => $type); + } else { + $type->length($value); + $type->bless('Type::Bytes') if $type->isa('Sym::Type::Opaque') && $ex eq '<'; + } + } elsif ($type->can('length')) { # Found String or Opaque but not followed by length specifier + die; + } + + $c->symbol($ident)->bless('Variable')->type($type); + }, sub { + my $type = type_specifier(); + adv '\*'; + my $ident = identifier(); + + $c->symbol($ident)->bless('Variable')->type( + Sym::Type::Pointer->new(reftype => $type)); + }, sub { + adv 'void'; + $c->symbol('void')->bless('Type'); + }; +} + +sub definition { + lexor \&type_def, \&constant_def; +} + +sub parse { + my ($class, $source) = @_; + + my $nlines = @{[$source =~ /\n/g]}; + my @lexs; + while ($source =~ /\S/s) { + (local $_ = $source) =~ s/\A\s*//s; + my $lex = eval { definition() }; + if (!$lex) { + my $line = $nlines - @{[/\n/g]} + 1; + my ($near) = /\A((?:.+?\n){0,5})/s; + die "ERROR: Unexpected character near line $line.\n", + "Please check debug output by enabling \$DEBUG flag at top of script.\n", + join("\n", map { ">> $_" } split /\n/, $near); + } + ::dbg ' 'x($Depth*2), sprintf "*** Found %s<%s>\n", ref($lex), $lex->ident; + push @lexs, $lex; + $source = $_; + } + @lexs; +} + +# Followings are code templates handled by PT +__END__<<DUMMY # Dummy heredoc to disable perl syntax highlighting +@@ Sym::Type#render_dissector +<% +my ($self, $ident) = @_; +return if $self->is_primitive; +%> +static gboolean dissect_xdr_<%= $ident %>(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf) +{ + return <%= $self->dealias->render_caller($self->ident eq $ident ? undef : $ident) %>; +} +@@ Sym::Type::Struct#render_dissector +<% my ($self, $ident) = @_; + my $hfvar = $c->rinc('hf_'.$self->idstrip); +%> +static gboolean dissect_xdr_<%= $ident %>(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf) +{ + goffset start; + proto_item *ti; + + start = xdr_getpos(xdrs); + if (hf == -1) { + ti = proto_tree_add_item(tree, <%= $hfvar %>, tvb, start, -1, ENC_NA); + } else { + header_field_info *hfinfo; + hfinfo = proto_registrar_get_nth(<%= $hfvar %>); + ti = proto_tree_add_item(tree, hf, tvb, start, -1, ENC_NA); + proto_item_append_text(ti, " :: %s", hfinfo->name); + } + tree = proto_item_add_subtree(ti, <%= $c->rinc('ett_'.$self->idstrip) %>); +<% for my $m (@{ $self->members }) { %> + + hf = <%= $c->rinc('hf_'.$ident.'__'.$m->ident) %>; + if (!<%= $m->type->render_caller($ident.'__'.$m->ident) %>) return FALSE; +<% } %> + proto_item_set_len(ti, xdr_getpos(xdrs) - start); + return TRUE; +} +@@ Sym::Type::Enum#render_dissector +<% my ($self, $ident) = @_; %> +static gboolean dissect_xdr_<%= $ident %>(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf) +{ + goffset start; + enum { DUMMY } es; + + start = xdr_getpos(xdrs); + if (xdr_enum(xdrs, (enum_t *)&es)) { + switch ((guint)es) { +<% for my $m (@{ $self->members }) { %> + case <%= $m->value %>: + proto_tree_add_uint_format_value(tree, hf, tvb, start, xdr_getpos(xdrs) - start, (guint)es, "<%= $m->idstrip %>(<%= $m->value %>)"); + return TRUE; +<% } %> + } + } else { + proto_tree_add_text(tree, tvb, start, -1, "(unknown)"); + } + return FALSE; +} +@@ Sym::Type::Union#render_dissector +<% +my ($self, $ident) = @_; +my $decl_type = $self->decl->type->idstrip; +%> +static gboolean dissect_xdr_<%= $ident %>(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf) +{ + gboolean rc = TRUE; + goffset start; + <%= $decl_type %> type = 0; + + start = xdr_getpos(xdrs); + if (!xdr_<%= $decl_type %>(xdrs, &type)) + return FALSE; + switch (type) { +<% for my $cs (@{ $self->case_specs }) { + my ($vals, $decl) = @$cs; +%> +<% for my $v (@$vals) { %> + case <%= $v %>: +<% } %> + hf = <%= $c->rinc('hf_'.$ident.'__'.$decl->ident) %>; + rc = <%= $decl->type->render_caller($ident.'__'.$decl->ident) %>; break; +<% } %> + } + if (!rc) { + proto_tree_add_text(tree, tvb, start, -1, "(unknown)"); + } + return rc; +} +@@ macro.hfvars +#define VIR_DYNAMIC_HFSET \ +<% for my $prog (@{ $_->{programs} }) { %> + { &hf_<%= $prog %>_procedure,\ + { "procedure", "libvirt.procedure",\ + FT_INT32, BASE_DEC,\ + VALS(<%= $prog %>_procedure_strings), 0x0,\ + NULL, HFILL}\ + },\ +<% } %> +<% for my $hf (@{ $_->{hfvars} }) { %> + { &<%= $hf->{segment}{sym} %>,\ + { "<%= $hf->{name} %>", "libvirt.<%= $hf->{abbrev} %>",\ + FT_<%= $hf->{ft_type} %>, BASE_<%= $hf->{hf_base} %>,\ + NULL, 0x0,\ + NULL, HFILL}\ + },\ +<% } %> +/* End of #define VIR_DYNAMIC_HFSET */ + +@@ macro.ettvars +#define VIR_DYNAMIC_ETTSET \ +<% for my $ett (@{ $_->{ettvars} }) { %> +&<%= $ett %>,\ +<% } %> +/* End of #define VIR_DYNAMIC_ETTSET */ + +@@ code.dissectorlist +static const vir_dissector_index_t <%= $_->{name} %>_dissectors[] = { +<% for my $d (@{ $_->{dissectors} }) { %> + { <%= $d->{value} %>, <%= $d->{args} %>, <%= $d->{ret} %>, <%= $d->{msg} %> }, +<% } %> +}; +static const gsize <%= $_->{name} %>_dissectors_len = array_length(<%= $_->{name} %>_dissectors); +@@ code.procedure_strings +static const value_string <%= $_->{name} %>_procedure_strings[] = { +<% for my $proc (@{ $_->{procedures} }) { + my $ident = $proc->ident; + $ident =~ s/^$_->{name}_proc_//i; +%> + { <%= $proc->value %>, "<%= $ident %>" }, +<% } %> + { 0, NULL } +}; +@@ code.program_strings +static const value_string program_strings[] = { +<% for my $prog (map uc, @{ $_->{programs} }) { %> + { <%= $c->symbol("$prog\_PROGRAM")->value %>, "<%= $prog %>" }, +<% } %> + { 0, NULL } +}; +@@ code.program_data +static const void *program_data[][VIR_PROGRAM_LAST] = { +<% for my $p (@{ $_->{programs} }) { %> + { &hf_<%= $p %>_procedure, <%= $p %>_procedure_strings, <%= $p %>_dissectors, &<%= $p %>_dissectors_len }, +<% } %> +}; + +static const void * +get_program_data(guint32 prog, enum vir_program_data_index index) +{ + if (index < VIR_PROGRAM_LAST) { + switch (prog) { +<% my $i = 0; %> +<% for my $prog (@{ $_->{programs} }) { %> + case <%= uc($prog) %>_PROGRAM: + return program_data[<%= $i++ %>][index]; +<% } %> + } + } + return NULL; +} diff --git a/tools/wireshark/util/make-dissector-reg b/tools/wireshark/util/make-dissector-reg new file mode 100755 index 0000000..e40425c --- /dev/null +++ b/tools/wireshark/util/make-dissector-reg @@ -0,0 +1,199 @@ +#! /bin/sh +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# +# Copied from Wireshark(http://www.wireshark.org/) + +# +# The first argument is the directory in which the source files live. +# +srcdir="$1" +shift + +# +# The second argument is either "plugin" or "dissectors"; if it's +# "plugin", we build a plugin.c for a plugin, and if it's +# "dissectors", we build a register.c for libwireshark. +# +registertype="$1" +shift +if [ "$registertype" = plugin ] +then + outfile="plugin.c" +elif [ "$registertype" = dissectors ] +then + outfile="register.c" +else + echo "Unknown output type '$registertype'" 1>&2 + exit 1 +fi + +# +# All subsequent arguments are the files to scan. +# +rm -f ${outfile}-tmp +echo '/* Do not modify this file. */' >${outfile}-tmp +echo '/* It is created automatically by the Makefile. */'>>${outfile}-tmp +if [ "$registertype" = plugin ] +then + cat <<"EOF" >>${outfile}-tmp +#include "config.h" + +#include <gmodule.h> + +#include "moduleinfo.h" +/* plugins are DLLs */ +#define WS_BUILD_DLL +#include "ws_symbol_export.h" + +#ifndef ENABLE_STATIC +WS_DLL_PUBLIC_NOEXTERN const gchar version[] = VERSION; + +/* Start the functions we need for the plugin stuff */ + +WS_DLL_PUBLIC_NOEXTERN void +plugin_register (void) +{ +EOF +# +# Build code to call all the protocol registration routines. +# +for f in "$@" +do + if [ -f $f ] + then + srcfile=$f + else + srcfile=$srcdir/$f + fi + grep '^proto_register_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' +done | sed -e 's/^.*://' -e 's/^\([a-z_0-9A-Z]*\).*/ {extern void \1 (void); \1 ();}/' >>${outfile}-tmp +for f in "$@" +do + if [ -f $f ] + then + srcfile=$f + else + srcfile=$srcdir/$f + fi + grep '^void proto_register_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' +done | sed -e 's/^.*://' -e 's/^void \([a-z_0-9A-Z]*\).*/ {extern void \1 (void); \1 ();}/' >>${outfile}-tmp +else + cat <<"EOF" >>${outfile}-tmp +#include "register.h" +void +register_all_protocols(register_cb cb, gpointer client_data) +{ +EOF +# +# Build code to call all the protocol registration routines. +# +for f in "$@" +do + if [ -f $f ] + then + srcfile=$f + else + srcfile=$srcdir/$f + fi + grep '^proto_register_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' +done | sed -e 's/^.*://' -e 's/^\([a-z_0-9A-Z]*\).*/ {extern void \1 (void); if(cb) (*cb)(RA_REGISTER, \"\1\", client_data); \1 ();}/' >>${outfile}-tmp +for f in "$@" +do + if [ -f $f ] + then + srcfile=$f + else + srcfile=$srcdir/$f + fi + grep '^void proto_register_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' +done | sed -e 's/^.*://' -e 's/^void \([a-z_0-9A-Z]*\).*/ {extern void \1 (void); if(cb) (*cb)(RA_REGISTER, \"\1\", client_data); \1 ();}/' >>${outfile}-tmp + +fi +echo '}' >>${outfile}-tmp + + +# +# Build code to call all the protocol handoff registration routines. +# +if [ "$registertype" = plugin ] +then + cat <<"EOF" >>${outfile}-tmp +WS_DLL_PUBLIC_NOEXTERN void +plugin_reg_handoff(void) +{ +EOF +for f in "$@" +do + if [ -f $f ] + then + srcfile=$f + else + srcfile=$srcdir/$f + fi + grep '^proto_reg_handoff_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' +done | sed -e 's/^.*://' -e 's/^\([a-z_0-9A-Z]*\).*/ {extern void \1 (void); \1 ();}/' >>${outfile}-tmp +for f in "$@" +do + if [ -f $f ] + then + srcfile=$f + else + srcfile=$srcdir/$f + fi + grep '^void proto_reg_handoff_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' +done | sed -e 's/^.*://' -e 's/^void \([a-z_0-9A-Z]*\).*/ {extern void \1 (void); \1 ();}/' >>${outfile}-tmp +else + cat <<"EOF" >>${outfile}-tmp +void +register_all_protocol_handoffs(register_cb cb, gpointer client_data) +{ +EOF +for f in "$@" +do + if [ -f $f ] + then + srcfile=$f + else + srcfile=$srcdir/$f + fi + grep '^proto_reg_handoff_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' +done | sed -e 's/^.*://' -e 's/^\([a-z_0-9A-Z]*\).*/ {extern void \1 (void); if(cb) (*cb)(RA_HANDOFF, \"\1\", client_data); \1 ();}/' >>${outfile}-tmp +for f in "$@" +do + if [ -f $f ] + then + srcfile=$f + else + srcfile=$srcdir/$f + fi + grep '^void proto_reg_handoff_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' +done | sed -e 's/^.*://' -e 's/^void \([a-z_0-9A-Z]*\).*/ {extern void \1 (void); if(cb) (*cb)(RA_HANDOFF, \"\1\", client_data); \1 ();}/' >>${outfile}-tmp +fi +echo '}' >>${outfile}-tmp +if [ "$registertype" = plugin ] +then + echo '#endif' >>${outfile}-tmp +else + cat <<"EOF" >>${outfile}-tmp +gulong register_count(void) +{ +EOF + proto_regs=`grep RA_REGISTER ${outfile}-tmp | wc -l` + handoff_regs=`grep RA_HANDOFF ${outfile}-tmp | wc -l` + echo " return $proto_regs + $handoff_regs;" >>${outfile}-tmp + echo '}' >>${outfile}-tmp +fi + +# Only overwrite outfile if it differs from newly generated file +diff ${outfile}-tmp ${outfile} >/dev/null || mv ${outfile}-tmp ${outfile} -- 1.8.1.5

On Thu, Sep 19, 2013 at 11:26:08PM +0900, Yuto KAWAMURA(kawamuray) wrote:
diff --git a/tools/wireshark/src/moduleinfo.h b/tools/wireshark/src/moduleinfo.h new file mode 100644 index 0000000..9ab642c --- /dev/null +++ b/tools/wireshark/src/moduleinfo.h @@ -0,0 +1,37 @@ +/* moduleinfo.h --- Define constants about wireshark plugin module ... + +/* Included *after* config.h, in order to re-define these macros */ + +#ifdef PACKAGE +# undef PACKAGE +#endif + +/* Name of package */ +#define PACKAGE "libvirt"
Huh ? "PACKAGE" will already be defined to 'libvirt' so why are you redefining it.
+ + +#ifdef VERSION +# undef VERSION +#endif + +/* Version number of package */ +#define VERSION "0.0.1"
This means the wireshark plugin will have a fixed version, even when libvirt protocol changes in new releases. This seems bogus. Again I think we should just use the existing defined "VERSION". I think this whole file can just go away completely
diff --git a/tools/wireshark/src/packet-libvirt.c b/tools/wireshark/src/packet-libvirt.c new file mode 100644 index 0000000..cd3e6ce --- /dev/null +++ b/tools/wireshark/src/packet-libvirt.c +static gboolean +dissect_xdr_bytes(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, + guint32 maxlen) +{ + goffset start; + guint8 *val = NULL; + guint32 length; + + start = xdr_getpos(xdrs); + if (xdr_bytes(xdrs, (char **)&val, &length, maxlen)) { + proto_tree_add_bytes_format_value(tree, hf, tvb, start, xdr_getpos(xdrs) - start, + NULL, "%s", format_xdr_bytes(val, length)); + /* Seems I can't call xdr_free() for this case. + It will raises SEGV by referencing out of bounds argument stack */ + xdrs->x_op = XDR_FREE; + xdr_bytes(xdrs, (char **)&val, &length, maxlen); + xdrs->x_op = XDR_DECODE;
Is accessing the internals of the 'XDR' struct really portable ? I think it would be desirable to solve the xdr_free problem rather than accessing struct internals Regards, 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 :|

2013/9/20 Daniel P. Berrange <berrange@redhat.com>:
On Thu, Sep 19, 2013 at 11:26:08PM +0900, Yuto KAWAMURA(kawamuray) wrote:
diff --git a/tools/wireshark/src/moduleinfo.h b/tools/wireshark/src/moduleinfo.h new file mode 100644 index 0000000..9ab642c --- /dev/null +++ b/tools/wireshark/src/moduleinfo.h @@ -0,0 +1,37 @@ +/* moduleinfo.h --- Define constants about wireshark plugin module ... + +/* Included *after* config.h, in order to re-define these macros */ + +#ifdef PACKAGE +# undef PACKAGE +#endif + +/* Name of package */ +#define PACKAGE "libvirt"
Huh ? "PACKAGE" will already be defined to 'libvirt' so why are you redefining it.
+ + +#ifdef VERSION +# undef VERSION +#endif + +/* Version number of package */ +#define VERSION "0.0.1"
This means the wireshark plugin will have a fixed version, even when libvirt protocol changes in new releases. This seems bogus. Again I think we should just use the existing defined "VERSION".
I think this whole file can just go away completely
Right. I'll remove whole moduleinfo.h.
diff --git a/tools/wireshark/src/packet-libvirt.c b/tools/wireshark/src/packet-libvirt.c new file mode 100644 index 0000000..cd3e6ce --- /dev/null +++ b/tools/wireshark/src/packet-libvirt.c +static gboolean +dissect_xdr_bytes(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, + guint32 maxlen) +{ + goffset start; + guint8 *val = NULL; + guint32 length; + + start = xdr_getpos(xdrs); + if (xdr_bytes(xdrs, (char **)&val, &length, maxlen)) { + proto_tree_add_bytes_format_value(tree, hf, tvb, start, xdr_getpos(xdrs) - start, + NULL, "%s", format_xdr_bytes(val, length)); + /* Seems I can't call xdr_free() for this case. + It will raises SEGV by referencing out of bounds argument stack */ + xdrs->x_op = XDR_FREE; + xdr_bytes(xdrs, (char **)&val, &length, maxlen); + xdrs->x_op = XDR_DECODE;
Is accessing the internals of the 'XDR' struct really portable ? I think it would be desirable to solve the xdr_free problem rather than accessing struct internals
I'll change this to use free(), but let me explain this problem detail. xdr_bytes may raises SEGV when it called from xdr_free. This is caused by xdr_free is accessing it's third argument 'sizep' even if it was called from xdr_free(in other word, when xdrs->x_op is XDR_FREE). This problem can't be reproduced in 64bit architecture due to 64bit system's register usage (I'll explain about this later). Following is a small enough code to reproduce this issue: #include <stdio.h> #include <stdlib.h> #include <rpc/xdr.h> /* Contents of this buffer is not important to reproduce the issue */ static char xdr_buffer[] = { 0x00, 0x00, 0x00, 0x02, /* length is 2byte */ 'A', '\0', 0, 0 /* 2 byte data and 2 byte padding bytes */ }; /* Same as the prototype of xdr_bytes() */ bool_t my_xdr_bytes(XDR *xdrs, char **cpp, u_int *sizep) { return TRUE; } /* Same as the prototype of xdr_free() */ void my_xdr_free(xdrproc_t proc, char *objp) { XDR x; (*proc)(&x, objp, NULL/* This NULL stored at same pos of 'sizep' in xdr_bytes() */); } int main(void) { XDR xdrs; char *opaque = NULL; unsigned int size; xdrmem_create(&xdrs, xdr_buffer, sizeof(xdr_buffer), XDR_DECODE); if (!xdr_bytes(&xdrs, &opaque, &size, ~0)) { fprintf(stderr, "xdr_bytes() returns FALSE\n"); exit(1); } /* Reproduce same stack-upping as call of xdr_free(xdr_bytes, &opaque). This is needed to stack-up 0x0(invalid address) on position of 'sizsp' which is third argument of xdr_bytes(). */ my_xdr_free((xdrproc_t)my_xdr_bytes, (char *)&opaque); /* *** SEGV!! *** */ xdr_free((xdrproc_t)xdr_bytes, (char *)&opaque); /* ************** */ xdr_destroy(&xdrs); return 0; } There are useless calls of my_xdr_free and my_xdr_bytes. These calls are used to reproduce same stack-upping of that when we call xdr_free((xdrproc_t)xdr_bytes, (char *)&allocated_mem). To reproduce this issue, we need to store some invalid address in callstack where it will referenced from xdr_bytes via it's third argument 'sizep'. These useless call will store NULL into the position on callstack which will be correspond to the position of 'sizep' in xdr_bytes. In rpc/xdr.h, xdrproc_t was typedefed as following: typedef bool_t (*xdrproc_t) (XDR *, void *,...); xdr_free will call passed xdrproc as function which takes two args and return bool_t. So called function from xdr_free should not access after second argument. But xdr_bytes is accessing it's third argument 'sizep' whatever it was called from xdr_free. This is totally wrong because we can't tell what is stored at position of after second argument in call stack. This problem can only be reproduced in 32bit environment due to register usage on function call. Following explanation is regarding to my environment, so it may not be reproduced in your environment. Compiler and library info: - glibc : 2.15 installed from Portage - gdb : GNU gdb (Gentoo 7.5.1 p2) 7.5.1 * x86 32bit - uname -rmpio : 3.10.7-gentoo i686 Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz GenuineIntel GNU/Linux - gcc : gcc version 4.6.3 (Gentoo 4.6.3 p1.9, pie-0.5.2) * x86_64 64bit - uname -rmpio : 3.8.13-gentoo x86_64 Intel(R) Xeon(R) CPU E5310 @ 1.60GHz GenuineIntel GNU/Linux - gcc : gcc version 4.6.3 (Gentoo 4.6.3 p1.13, pie-0.5.2) On x86(32bit), xdr_free and xdr_bytes were compiled as followings: void xdr_free(xdrproc_t proc, char *objp): #0 __GI_xdr_free (proc=0x8048430 <xdr_bytes@plt>, objp=0xbffff4e4 "\b\260\004\b\001") at xdr.c:66 Dump of assembler code for function __GI_xdr_free: => 0xb7f35fd0 <+0>: sub $0x3c,%esp 0xb7f35fd3 <+3>: mov 0x44(%esp),%eax 0xb7f35fd7 <+7>: movl $0x2,0x18(%esp) 0xb7f35fdf <+15>: mov %eax,0x4(%esp) 0xb7f35fe3 <+19>: lea 0x18(%esp),%eax 0xb7f35fe7 <+23>: mov %eax,(%esp) 0xb7f35fea <+26>: call *0x40(%esp) 0xb7f35fee <+30>: add $0x3c,%esp 0xb7f35ff1 <+33>: ret bool_t xdr_bytes(XDR *xdrs, char **cpp, u_int *sizep, u_int maxsize): #0 __GI_xdr_bytes (xdrs=0xbffff4a8, cpp=0xbffff4e4, sizep=0x0, maxsize=3085225635) at xdr.c:608 Dump of assembler code for function __GI_xdr_bytes: 0xb7f365b0 <+0>: sub $0x3c,%esp 0xb7f365b3 <+3>: mov %ebp,0x38(%esp) 0xb7f365b7 <+7>: mov 0x4c(%esp),%edx 0xb7f365bb <+11>: mov 0x44(%esp),%ebp 0xb7f365bf <+15>: mov %ebx,0x2c(%esp) 0xb7f365c3 <+19>: call 0xb7f417e3 <__i686.get_pc_thunk.bx> 0xb7f365c8 <+24>: add $0x82a38,%ebx 0xb7f365ce <+30>: mov %esi,0x30(%esp) 0xb7f365d2 <+34>: mov 0x40(%esp),%esi 0xb7f365d6 <+38>: mov %edi,0x34(%esp) 0xb7f365da <+42>: mov 0x48(%esp),%edi 0xb7f365de <+46>: mov %edx,0x18(%esp) 0xb7f365e2 <+50>: mov 0x0(%ebp),%edx 0xb7f365e5 <+53>: mov %esi,(%esp) 0xb7f365e8 <+56>: mov %edi,0x4(%esp) 0xb7f365ec <+60>: mov %edx,0x1c(%esp) 0xb7f365f0 <+64>: call 0xb7f36070 <__GI_xdr_u_long> 0xb7f365f5 <+69>: test %eax,%eax 0xb7f365f7 <+71>: je 0xb7f36660 <__GI_xdr_bytes+176> => 0xb7f365f9 <+73>: mov (%edi),%edi On x86_64(64bit), xdr_free and xdr_bytes were compiled as followings: void xdr_free(xdrproc_t proc, char *objp): #0 __GI_xdr_free (proc=0x400590 <xdr_bytes@plt>, objp=0x7fffffffe248 "\020 `") at xdr.c:66 Dump of assembler code for function __GI_xdr_free: => 0x00007ffff7b4b880 <+0>: sub $0x38,%rsp 0x00007ffff7b4b884 <+4>: mov %rdi,%rdx 0x00007ffff7b4b887 <+7>: xor %eax,%eax 0x00007ffff7b4b889 <+9>: movl $0x2,(%rsp) 0x00007ffff7b4b890 <+16>: mov %rsp,%rdi 0x00007ffff7b4b893 <+19>: callq *%rdx 0x00007ffff7b4b895 <+21>: add $0x38,%rsp 0x00007ffff7b4b899 <+25>: retq bool_t xdr_bytes(XDR *xdrs, char **cpp, u_int *sizep, u_int maxsize): #0 __GI_xdr_bytes (xdrs=0x7fffffffe200, cpp=0x7fffffffe248, sizep=0x400590 <xdr_bytes@plt>, maxsize=4294959688) at xdr.c:608 Dump of assembler code for function __GI_xdr_bytes: 0x00007ffff7b4bf00 <+0>: mov %rbx,-0x28(%rsp) 0x00007ffff7b4bf05 <+5>: mov %rdi,%rbx 0x00007ffff7b4bf08 <+8>: mov %rbp,-0x20(%rsp) 0x00007ffff7b4bf0d <+13>: mov %rsi,%rbp 0x00007ffff7b4bf10 <+16>: mov %r12,-0x18(%rsp) 0x00007ffff7b4bf15 <+21>: mov %rdx,%r12 0x00007ffff7b4bf18 <+24>: mov %r14,-0x8(%rsp) 0x00007ffff7b4bf1d <+29>: mov %ecx,%r14d 0x00007ffff7b4bf20 <+32>: mov %r13,-0x10(%rsp) 0x00007ffff7b4bf25 <+37>: sub $0x38,%rsp 0x00007ffff7b4bf29 <+41>: mov (%rsi),%r13 0x00007ffff7b4bf2c <+44>: mov %rdx,%rsi 0x00007ffff7b4bf2f <+47>: callq 0x7ffff7b4b920 <xdr_u_int> 0x00007ffff7b4bf34 <+52>: test %eax,%eax 0x00007ffff7b4bf36 <+54>: je 0x7ffff7b4bfb0 <__GI_xdr_bytes+176> => 0x00007ffff7b4bf38 <+56>: mov (%r12),%edx So these differences are why my code will raises SEGV only on x86 environment. In x86_64(64bit), gcc does positively uses register(s) instead of callstack to pass arguments to called function. As you can see in above assembly, 'sizep' of xdr_bytes is referencing register %rdx as location of it's value should sotred. But xdr_free does not store value in %rdx as an argument for xdr_bytes. It does only uses %rdx as a temporary location for previous processing. xdr_bytes is referencing %rdx for it's third argument. As you can see at 0x00007ffff7b4b884, it does only uses %rdx as temporary register. So it still keeps value 0x400590(address of xdr_bytes) and it wouldn't be a problem even if xdr_bytes will try to dereference it(because it's not a invalid address!). This is why my code doesn't raises SEGV in x86_64 environment. Anyway, xdr_bytes is accessing 'sizep' even if it was called from xdr_free. It is totally wrong and should be fixed. Appendix - output of valgrind on my 32bit system $ valgrind ./xdr_free_segv_reproduce ==2778== Memcheck, a memory error detector ==2778== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al. ==2778== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==2778== Command: ./xdr_free_segv_reproduce ==2778== ==2778== Use of uninitialised value of size 4 ==2778== at 0x416D5F9: xdr_bytes (xdr.c:608) ==2778== ==2778== Invalid read of size 4 ==2778== at 0x416D5F9: xdr_bytes (xdr.c:608) ==2778== Address 0x0 is not stack'd, malloc'd or (recently) free'd ==2778== ==2778== ==2778== Process terminating with default action of signal 11 (SIGSEGV) ==2778== Access not within mapped region at address 0x0 ==2778== at 0x416D5F9: xdr_bytes (xdr.c:608) ==2778== If you believe this happened as a result of a stack ==2778== overflow in your program's main thread (unlikely but ==2778== possible), you can try to increase the size of the ==2778== main thread stack using the --main-stacksize= flag. ==2778== The main thread stack size used in this run was 8388608. ==2778== ==2778== HEAP SUMMARY: ==2778== in use at exit: 2 bytes in 1 blocks ==2778== total heap usage: 1 allocs, 0 frees, 2 bytes allocated ==2778== ==2778== LEAK SUMMARY: ==2778== definitely lost: 0 bytes in 0 blocks ==2778== indirectly lost: 0 bytes in 0 blocks ==2778== possibly lost: 0 bytes in 0 blocks ==2778== still reachable: 2 bytes in 1 blocks ==2778== suppressed: 0 bytes in 0 blocks ==2778== Rerun with --leak-check=full to see details of leaked memory ==2778== ==2778== For counts of detected and suppressed errors, rerun with: -v ==2778== Use --track-origins=yes to see where uninitialised values come from ==2778== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0) zsh: segmentation fault valgrind ./xdr_free_segv_reproduce

On Mon, Sep 30, 2013 at 09:12:35PM +0900, Yuto KAWAMURA wrote:
2013/9/20 Daniel P. Berrange <berrange@redhat.com>:
On Thu, Sep 19, 2013 at 11:26:08PM +0900, Yuto KAWAMURA(kawamuray) wrote:
diff --git a/tools/wireshark/src/moduleinfo.h b/tools/wireshark/src/moduleinfo.h new file mode 100644 index 0000000..9ab642c --- /dev/null +++ b/tools/wireshark/src/moduleinfo.h @@ -0,0 +1,37 @@ +/* moduleinfo.h --- Define constants about wireshark plugin module ... + +/* Included *after* config.h, in order to re-define these macros */ + +#ifdef PACKAGE +# undef PACKAGE +#endif + +/* Name of package */ +#define PACKAGE "libvirt"
Huh ? "PACKAGE" will already be defined to 'libvirt' so why are you redefining it.
+ + +#ifdef VERSION +# undef VERSION +#endif + +/* Version number of package */ +#define VERSION "0.0.1"
This means the wireshark plugin will have a fixed version, even when libvirt protocol changes in new releases. This seems bogus. Again I think we should just use the existing defined "VERSION".
I think this whole file can just go away completely
Right. I'll remove whole moduleinfo.h.
diff --git a/tools/wireshark/src/packet-libvirt.c b/tools/wireshark/src/packet-libvirt.c new file mode 100644 index 0000000..cd3e6ce --- /dev/null +++ b/tools/wireshark/src/packet-libvirt.c +static gboolean +dissect_xdr_bytes(tvbuff_t *tvb, proto_tree *tree, XDR *xdrs, int hf, + guint32 maxlen) +{ + goffset start; + guint8 *val = NULL; + guint32 length; + + start = xdr_getpos(xdrs); + if (xdr_bytes(xdrs, (char **)&val, &length, maxlen)) { + proto_tree_add_bytes_format_value(tree, hf, tvb, start, xdr_getpos(xdrs) - start, + NULL, "%s", format_xdr_bytes(val, length)); + /* Seems I can't call xdr_free() for this case. + It will raises SEGV by referencing out of bounds argument stack */ + xdrs->x_op = XDR_FREE; + xdr_bytes(xdrs, (char **)&val, &length, maxlen); + xdrs->x_op = XDR_DECODE;
Is accessing the internals of the 'XDR' struct really portable ? I think it would be desirable to solve the xdr_free problem rather than accessing struct internals
I'll change this to use free(), but let me explain this problem detail.
xdr_bytes may raises SEGV when it called from xdr_free. This is caused by xdr_free is accessing it's third argument 'sizep' even if it was called from xdr_free(in other word, when xdrs->x_op is XDR_FREE). This problem can't be reproduced in 64bit architecture due to 64bit system's register usage (I'll explain about this later).
Following is a small enough code to reproduce this issue:
#include <stdio.h> #include <stdlib.h> #include <rpc/xdr.h>
/* Contents of this buffer is not important to reproduce the issue */ static char xdr_buffer[] = { 0x00, 0x00, 0x00, 0x02, /* length is 2byte */ 'A', '\0', 0, 0 /* 2 byte data and 2 byte padding bytes */ };
/* Same as the prototype of xdr_bytes() */ bool_t my_xdr_bytes(XDR *xdrs, char **cpp, u_int *sizep) { return TRUE; }
/* Same as the prototype of xdr_free() */ void my_xdr_free(xdrproc_t proc, char *objp) { XDR x; (*proc)(&x, objp, NULL/* This NULL stored at same pos of 'sizep' in xdr_bytes() */); }
int main(void) { XDR xdrs; char *opaque = NULL; unsigned int size;
xdrmem_create(&xdrs, xdr_buffer, sizeof(xdr_buffer), XDR_DECODE); if (!xdr_bytes(&xdrs, &opaque, &size, ~0)) { fprintf(stderr, "xdr_bytes() returns FALSE\n"); exit(1); }
/* Reproduce same stack-upping as call of xdr_free(xdr_bytes, &opaque). This is needed to stack-up 0x0(invalid address) on position of 'sizsp' which is third argument of xdr_bytes(). */ my_xdr_free((xdrproc_t)my_xdr_bytes, (char *)&opaque);
/* *** SEGV!! *** */ xdr_free((xdrproc_t)xdr_bytes, (char *)&opaque); /* ************** */
Ok, the scenario here is - 'xdr_bytes' takes 4 arguments - 'xdrproc_t' takes 2 mandatory args + var-args - 'xdr_free' calls the 'xdrproc_t' function with only 2 arguments - 'xdr_bytes' unconditionally accesses its 3rd argument So either - the cast from xdr_bytes -> xdrproc_t is invalid and thus xdr_bytes should not be used with xdr_free. or - xdr_bytes impl in glibc is buggy and shouldn't access the 3rd arg except when doing encode/decode operations. Regardless of which is right, we want our code to work on all xdr impls, so we must avoid problem 2. So I think we should just not use xdr_free here. Just do a plain 'free(opaque)' instead. 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 :|

From: "Yuto KAWAMURA(kawamuray)" <kawamuray.dadada@gmail.com> Add directory tools/wireshark/samples/ and libvirt-sample.pdml which is sample output of dissector. --- tools/wireshark/samples/libvirt-sample.pdml | 206 ++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 tools/wireshark/samples/libvirt-sample.pdml diff --git a/tools/wireshark/samples/libvirt-sample.pdml b/tools/wireshark/samples/libvirt-sample.pdml new file mode 100644 index 0000000..f6a4c28 --- /dev/null +++ b/tools/wireshark/samples/libvirt-sample.pdml @@ -0,0 +1,206 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="pdml2html.xsl"?> +<!-- *** + This file has been reduced for ineffective packets. + Real output contains more and more elements, but structure + and hierarchy of XML is same as this exmaple. +*** --> +<!-- You can find pdml2html.xsl in /usr/share/wireshark or at http://anonsvn.wireshark.org/trunk/wireshark/pdml2html.xsl. --> +<pdml version="0" creator="wireshark/1.10.2" time="Thu Sep 19 18:09:24 2013" capture_file=""> +<!-- Program = REMOTE, Procedure = AUTH_LIST --> +<packet> + <proto name="libvirt" showname="Libvirt" size="28" pos="66"> + <field name="libvirt.length" showname="length: 28" size="4" pos="66" show="28" value="0000001c"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: AUTH_LIST (66)" size="4" pos="78" show="66" value="00000042"/> + <field name="libvirt.type" showname="type: CALL (0)" size="4" pos="82" show="0" value="00000000"/> + <field name="libvirt.serial" showname="serial: 0" size="4" pos="86" show="0" value="00000000"/> + <field name="libvirt.status" showname="status: OK (0)" size="4" pos="90" show="0" value="00000000"/> + </proto> +</packet> +<packet> + <proto name="libvirt" showname="Libvirt" size="36" pos="66"> + <field name="libvirt.length" showname="length: 36" size="4" pos="66" show="36" value="00000024"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: AUTH_LIST (66)" size="4" pos="78" show="66" value="00000042"/> + <field name="libvirt.type" showname="type: REPLY (1)" size="4" pos="82" show="1" value="00000001"/> + <field name="libvirt.serial" showname="serial: 0" size="4" pos="86" show="0" value="00000000"/> + <field name="libvirt.status" showname="status: OK (0)" size="4" pos="90" show="0" value="00000000"/> + <field name="libvirt.remote_auth_list_ret" showname="remote_auth_list_ret" size="8" pos="94" show="" value=""> + <field name="libvirt.remote_auth_list_ret.types" showname="types :: remote_auth_type<1>" size="8" pos="94" show="" value=""> + <field name="libvirt.remote_auth_list_ret.types.types" showname="types: REMOTE_AUTH_NONE(0)" size="4" pos="98" show="0" value="00000000"/> + </field> + </field> + </proto> +</packet> + +<!-- Program = REMOTE, Procedure = CONNECT_OPEN --> +<packet> + <proto name="libvirt" showname="Libvirt" size="56" pos="66"> + <field name="libvirt.length" showname="length: 56" size="4" pos="66" show="56" value="00000038"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: CONNECT_OPEN (1)" size="4" pos="78" show="1" value="00000001"/> + <field name="libvirt.type" showname="type: CALL (0)" size="4" pos="82" show="0" value="00000000"/> + <field name="libvirt.serial" showname="serial: 2" size="4" pos="86" show="2" value="00000002"/> + <field name="libvirt.status" showname="status: OK (0)" size="4" pos="90" show="0" value="00000000"/> + <field name="libvirt.remote_connect_open_args" showname="remote_connect_open_args" size="8" pos="94" show="" value=""> + <field name="libvirt.remote_connect_open_args.name" showname="name: (null)" size="4" pos="94" show="" value=""/> + <field name="libvirt.remote_connect_open_args.flags" showname="flags: 15" size="4" pos="98" show="15" value="0000000f"/> + </field> + </proto> +</packet> +<packet> + <proto name="libvirt" showname="Libvirt" size="28" pos="66"> + <field name="libvirt.length" showname="length: 28" size="4" pos="66" show="28" value="0000001c"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: CONNECT_OPEN (1)" size="4" pos="78" show="1" value="00000001"/> + <field name="libvirt.type" showname="type: REPLY (1)" size="4" pos="82" show="1" value="00000001"/> + <field name="libvirt.serial" showname="serial: 2" size="4" pos="86" show="2" value="00000002"/> + <field name="libvirt.status" showname="status: OK (0)" size="4" pos="90" show="0" value="00000000"/> + </proto> +</packet> + +<!-- Program = REMOTE, Procedure = DOMAIN_LOOKUP_BY_NAME --> +<packet> + <proto name="libvirt" showname="Libvirt" size="40" pos="66"> + <field name="libvirt.length" showname="length: 40" size="4" pos="66" show="40" value="00000028"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: DOMAIN_LOOKUP_BY_NAME (23)" size="4" pos="78" show="23" value="00000017"/> + <field name="libvirt.type" showname="type: CALL (0)" size="4" pos="82" show="0" value="00000000"/> + <field name="libvirt.serial" showname="serial: 4" size="4" pos="86" show="4" value="00000004"/> + <field name="libvirt.status" showname="status: OK (0)" size="4" pos="90" show="0" value="00000000"/> + <field name="libvirt.remote_domain_lookup_by_name_args" showname="remote_domain_lookup_by_name_args" size="12" pos="94" show="" value=""> + <field name="libvirt.remote_domain_lookup_by_name_args.name" showname="name: domain1" size="12" pos="94" show="domain1" value="00000007646f6d61696e3100"/> + </field> + </proto> +</packet> +<packet> + <proto name="libvirt" showname="Libvirt" size="60" pos="66"> + <field name="libvirt.length" showname="length: 60" size="4" pos="66" show="60" value="0000003c"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: DOMAIN_LOOKUP_BY_NAME (23)" size="4" pos="78" show="23" value="00000017"/> + <field name="libvirt.type" showname="type: REPLY (1)" size="4" pos="82" show="1" value="00000001"/> + <field name="libvirt.serial" showname="serial: 4" size="4" pos="86" show="4" value="00000004"/> + <field name="libvirt.status" showname="status: OK (0)" size="4" pos="90" show="0" value="00000000"/> + <field name="libvirt.remote_domain_lookup_by_name_ret" showname="remote_domain_lookup_by_name_ret" size="32" pos="94" show="" value=""> + <field name="libvirt.remote_domain_lookup_by_name_ret.dom" showname="dom :: remote_nonnull_domain" size="32" pos="94" show="" value=""> + <field name="libvirt.remote_nonnull_domain.name" showname="name: domain1" size="12" pos="94" show="domain1" value="00000007646f6d61696e3100"/> + <field name="libvirt.remote_nonnull_domain.uuid" showname="uuid: 4c8b6b6d0a2907334b8398a02c3a4710" size="16" pos="106" show="4c:8b:6b:6d:0a:29:07:33:4b:83:98:a0:2c:3a:47:10" value="4c8b6b6d0a2907334b8398a02c3a4710"/> + <field name="libvirt.remote_nonnull_domain.id" showname="id: -1" size="4" pos="122" show="-1" value="ffffffff"/> + </field> + </field> + </proto> +</packet> + +<!-- Program = REMOTE, Procedure = NODE_GET_CPU_MAP --> +<packet> + <proto name="libvirt" showname="Libvirt" size="40" pos="66"> + <field name="libvirt.length" showname="length: 40" size="4" pos="66" show="40" value="00000028"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: NODE_GET_CPU_MAP (293)" size="4" pos="78" show="293" value="00000125"/> + <field name="libvirt.type" showname="type: CALL (0)" size="4" pos="82" show="0" value="00000000"/> + <field name="libvirt.serial" showname="serial: 5" size="4" pos="86" show="5" value="00000005"/> + <field name="libvirt.status" showname="status: OK (0)" size="4" pos="90" show="0" value="00000000"/> + <field name="libvirt.remote_node_get_cpu_map_args" showname="remote_node_get_cpu_map_args" size="12" pos="94" show="" value=""> + <field name="libvirt.remote_node_get_cpu_map_args.need_map" showname="need_map: 0" size="4" pos="94" show="0" value="00000000"/> + <field name="libvirt.remote_node_get_cpu_map_args.need_online" showname="need_online: 0" size="4" pos="98" show="0" value="00000000"/> + <field name="libvirt.remote_node_get_cpu_map_args.flags" showname="flags: 0" size="4" pos="102" show="0" value="00000000"/> + </field> + </proto> +</packet> +<packet> + <proto name="libvirt" showname="Libvirt" size="40" pos="66"> + <field name="libvirt.length" showname="length: 40" size="4" pos="66" show="40" value="00000028"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: NODE_GET_CPU_MAP (293)" size="4" pos="78" show="293" value="00000125"/> + <field name="libvirt.type" showname="type: REPLY (1)" size="4" pos="82" show="1" value="00000001"/> + <field name="libvirt.serial" showname="serial: 5" size="4" pos="86" show="5" value="00000005"/> + <field name="libvirt.status" showname="status: OK (0)" size="4" pos="90" show="0" value="00000000"/> + <field name="libvirt.remote_node_get_cpu_map_ret" showname="remote_node_get_cpu_map_ret" size="12" pos="94" show="" value=""> + <field name="libvirt.remote_node_get_cpu_map_ret.cpumap" showname="cpumap: " size="4" pos="94" show="00:00:00:00" value="00000000"/> + <field name="libvirt.remote_node_get_cpu_map_ret.online" showname="online: 0" size="4" pos="98" show="0" value="00000000"/> + <field name="libvirt.remote_node_get_cpu_map_ret.ret" showname="ret: 8" size="4" pos="102" show="8" value="00000008"/> + </field> + </proto> +</packet> + +<!-- Program = REMOTE, Procedure = DOMAIN_GET_BLKIO_PARAMETERS --> +<packet> + <proto name="libvirt" showname="Libvirt" size="68" pos="66"> + <field name="libvirt.length" showname="length: 68" size="4" pos="66" show="68" value="00000044"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: DOMAIN_GET_BLKIO_PARAMETERS (206)" size="4" pos="78" show="206" value="000000ce"/> + <field name="libvirt.type" showname="type: CALL (0)" size="4" pos="82" show="0" value="00000000"/> + <field name="libvirt.serial" showname="serial: 7" size="4" pos="86" show="7" value="00000007"/> + <field name="libvirt.status" showname="status: OK (0)" size="4" pos="90" show="0" value="00000000"/> + <field name="libvirt.remote_domain_get_blkio_parameters_args" showname="remote_domain_get_blkio_parameters_args" size="40" pos="94" show="" value=""> + <field name="libvirt.remote_domain_get_blkio_parameters_args.dom" showname="dom :: remote_nonnull_domain" size="32" pos="94" show="" value=""> + <field name="libvirt.remote_nonnull_domain.name" showname="name: domain1" size="12" pos="94" show="domain1" value="00000007646f6d61696e3100"/> + <field name="libvirt.remote_nonnull_domain.uuid" showname="uuid: 4c8b6b6d0a2907334b8398a02c3a4710" size="16" pos="106" show="4c:8b:6b:6d:0a:29:07:33:4b:83:98:a0:2c:3a:47:10" value="4c8b6b6d0a2907334b8398a02c3a4710"/> + <field name="libvirt.remote_nonnull_domain.id" showname="id: -1" size="4" pos="122" show="-1" value="ffffffff"/> + </field> + <field name="libvirt.remote_domain_get_blkio_parameters_args.nparams" showname="nparams: 2" size="4" pos="126" show="2" value="00000002"/> + <field name="libvirt.remote_domain_get_blkio_parameters_args.flags" showname="flags: 4" size="4" pos="130" show="4" value="00000004"/> + </field> + </proto> +</packet> +<packet> + <proto name="libvirt" showname="Libvirt" size="84" pos="66"> + <field name="libvirt.length" showname="length: 84" size="4" pos="66" show="84" value="00000054"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: DOMAIN_GET_BLKIO_PARAMETERS (206)" size="4" pos="78" show="206" value="000000ce"/> + <field name="libvirt.type" showname="type: REPLY (1)" size="4" pos="82" show="1" value="00000001"/> + <field name="libvirt.serial" showname="serial: 7" size="4" pos="86" show="7" value="00000007"/> + <field name="libvirt.status" showname="status: OK (0)" size="4" pos="90" show="0" value="00000000"/> + <field name="libvirt.remote_domain_get_blkio_parameters_ret" showname="remote_domain_get_blkio_parameters_ret" size="56" pos="94" show="" value=""> + <field name="libvirt.remote_domain_get_blkio_parameters_ret.params" showname="params :: remote_typed_param<2>" size="52" pos="94" show="" value=""> + <field name="libvirt.remote_domain_get_blkio_parameters_ret.params.params" showname="params :: remote_typed_param" size="20" pos="98" show="" value=""> + <field name="libvirt.remote_typed_param.field" showname="field: weight" size="12" pos="98" show="weight" value="000000067765696768740000"/> + <field name="libvirt.remote_typed_param_value.ui" showname="ui: 0" size="4" pos="114" show="0" value="00000000"/> + </field> + <field name="libvirt.remote_domain_get_blkio_parameters_ret.params.params" showname="params :: remote_typed_param" size="28" pos="118" show="" value=""> + <field name="libvirt.remote_typed_param.field" showname="field: device_weight" size="20" pos="118" show="device_weight" value="0000000d6465766963655f776569676874000000"/> + <field name="libvirt.remote_typed_param_value.s" showname="s: " size="4" pos="142" show="" value="00000000"/> + </field> + </field> + <field name="libvirt.remote_domain_get_blkio_parameters_ret.nparams" showname="nparams: 0" size="4" pos="146" show="0" value="00000000"/> + </field> + </proto> +</packet> + +<!-- Error reply (struct remote_error) --> +<packet> + <proto name="libvirt" showname="Libvirt" size="360" pos="66"> + <field name="libvirt.length" showname="length: 360" size="4" pos="66" show="360" value="00000168"/> + <field name="libvirt.program" showname="program: REMOTE (0x20008086)" size="4" pos="70" show="0x20008086" value="20008086"/> + <field name="libvirt.version" showname="version: 1" size="4" pos="74" show="1" value="00000001"/> + <field name="libvirt.procedure" showname="procedure: DOMAIN_GET_VCPUS (20)" size="4" pos="78" show="20" value="00000014"/> + <field name="libvirt.type" showname="type: REPLY (1)" size="4" pos="82" show="1" value="00000001"/> + <field name="libvirt.serial" showname="serial: 7" size="4" pos="86" show="7" value="00000007"/> + <field name="libvirt.status" showname="status: ERROR (1)" size="4" pos="90" show="1" value="00000001"/> + <field name="libvirt.remote_error" showname="remote_error" size="44" pos="94" show="" value=""> + <field name="libvirt.remote_error.code" showname="code: 55" size="4" pos="94" show="55" value="00000037"/> + <field name="libvirt.remote_error.domain" showname="domain: 10" size="4" pos="98" show="10" value="0000000a"/> + <field name="libvirt.remote_error.message" showname="message: (null)" size="4" pos="102" show="" value=""/> + <field name="libvirt.remote_error.level" showname="level: 136" size="4" pos="106" show="136" value="00000088"/> + <field name="libvirt.remote_error.dom" showname="dom: (null)" size="4" pos="110" show="" value=""/> + <field name="libvirt.remote_error.str1" showname="str1: (null)" size="4" pos="114" show="" value=""/> + <field name="libvirt.remote_error.str2" showname="str2: (null)" size="4" pos="118" show="" value=""/> + <field name="libvirt.remote_error.str3" showname="str3: (null)" size="4" pos="122" show="" value=""/> + <field name="libvirt.remote_error.int1" showname="int1: -1819417411" size="4" pos="126" show="-1819417411" value="938de4bd"/> + <field name="libvirt.remote_error.int2" showname="int2: -1662811729" size="4" pos="130" show="-1662811729" value="9ce381af"/> + <field name="libvirt.remote_error.net" showname="net: (null)" size="4" pos="134" show="" value=""/> + </field> + </proto> +</packet> +</pdml> -- 1.8.1.5
participants (3)
-
Daniel P. Berrange
-
Yuto KAWAMURA
-
Yuto KAWAMURA(kawamuray)