diff options
Diffstat (limited to 'c-sources/pcap-monitor.c')
-rw-r--r-- | c-sources/pcap-monitor.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/c-sources/pcap-monitor.c b/c-sources/pcap-monitor.c new file mode 100644 index 0000000..54b9e1e --- /dev/null +++ b/c-sources/pcap-monitor.c @@ -0,0 +1,534 @@ +/* + * pcap-monitor.c - monitors a bus and dumps messages to a pcap file + * Copyright ©2011–2012 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "pcap-monitor.h" + +#include <string.h> +#include <pcap/pcap.h> + +/* Not using GString because it holds signed chars but we both receive and + * provide unsigned chars. C! + */ +typedef struct { + struct timeval ts; + gsize size; + guchar *blob; +} Message; + +#define STOP ((Message *) 0x1) + +typedef struct { + pcap_dumper_t *dumper; + GAsyncQueue *message_queue; +} ThreadData; + +struct _BustlePcapMonitorPrivate { + GBusType bus_type; + GDBusConnection *connection; + GDBusCapabilityFlags caps; + + guint filter_id; + + gchar *filename; + pcap_t *p; + + GThread *thread; + ThreadData td; + + /* FIXME: this does not really belong here. main() should connect to + * ::message-logged when it provides enough details. */ + gboolean verbose; +}; + +enum { + PROP_BUS_TYPE = 1, + PROP_FILENAME, + PROP_VERBOSE, +}; + +enum { + SIG_MESSAGE_LOGGED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS]; + +static void initable_iface_init ( + gpointer g_class, + gpointer unused); + +G_DEFINE_TYPE_WITH_CODE (BustlePcapMonitor, bustle_pcap_monitor, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init); + ) + +static void +bustle_pcap_monitor_init (BustlePcapMonitor *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, BUSTLE_TYPE_PCAP_MONITOR, + BustlePcapMonitorPrivate); + self->priv->bus_type = G_BUS_TYPE_SESSION; + self->priv->td.message_queue = g_async_queue_new (); +} + +static void +bustle_pcap_monitor_get_property ( + GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); + BustlePcapMonitorPrivate *priv = self->priv; + + switch (property_id) + { + case PROP_BUS_TYPE: + g_value_set_enum (value, priv->bus_type); + break; + case PROP_FILENAME: + g_value_set_string (value, priv->filename); + break; + case PROP_VERBOSE: + g_value_set_boolean (value, priv->verbose); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +bustle_pcap_monitor_set_property ( + GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); + BustlePcapMonitorPrivate *priv = self->priv; + + switch (property_id) + { + case PROP_BUS_TYPE: + priv->bus_type = g_value_get_enum (value); + break; + case PROP_FILENAME: + priv->filename = g_value_dup_string (value); + break; + case PROP_VERBOSE: + priv->verbose = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +bustle_pcap_monitor_dispose (GObject *object) +{ + BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); + BustlePcapMonitorPrivate *priv = self->priv; + GObjectClass *parent_class = bustle_pcap_monitor_parent_class; + + if (parent_class->dispose != NULL) + parent_class->dispose (object); + + /* Make sure we're all closed up. */ + bustle_pcap_monitor_stop (self); + + g_clear_object (&priv->connection); +} + +static void +bustle_pcap_monitor_finalize (GObject *object) +{ + BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); + BustlePcapMonitorPrivate *priv = self->priv; + GObjectClass *parent_class = bustle_pcap_monitor_parent_class; + + if (parent_class->finalize != NULL) + parent_class->finalize (object); + + g_free (priv->filename); + priv->filename = NULL; + + g_async_queue_unref (priv->td.message_queue); + priv->td.message_queue = NULL; +} + +static void +bustle_pcap_monitor_class_init (BustlePcapMonitorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GParamSpec *param_spec; + + object_class->get_property = bustle_pcap_monitor_get_property; + object_class->set_property = bustle_pcap_monitor_set_property; + object_class->dispose = bustle_pcap_monitor_dispose; + object_class->finalize = bustle_pcap_monitor_finalize; + + g_type_class_add_private (klass, sizeof (BustlePcapMonitorPrivate)); + +#define THRICE(x) x, x, x + + param_spec = g_param_spec_enum (THRICE ("bus-type"), + G_TYPE_BUS_TYPE, G_BUS_TYPE_SESSION, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_BUS_TYPE, param_spec); + + param_spec = g_param_spec_string (THRICE ("filename"), NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_FILENAME, param_spec); + + param_spec = g_param_spec_boolean (THRICE ("verbose"), FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_VERBOSE, param_spec); + + signals[SIG_MESSAGE_LOGGED] = g_signal_new ("message-logged", + BUSTLE_TYPE_PCAP_MONITOR, G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + NULL, G_TYPE_NONE, 0); +} + +static gpointer +log_thread (gpointer data) +{ + ThreadData *td = data; + Message *message; + + while (STOP != (message = g_async_queue_pop (td->message_queue))) + { + struct pcap_pkthdr hdr; + hdr.ts = message->ts; + + hdr.caplen = message->size; + hdr.len = message->size; + + /* The cast is necessary because libpcap is weird. */ + pcap_dump ((u_char *) td->dumper, &hdr, message->blob); + g_free (message->blob); + g_slice_free (Message, message); + } + + return NULL; +} + +static gboolean +emit_me (gpointer data) +{ + BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (data); + + g_signal_emit (self, signals[SIG_MESSAGE_LOGGED], 0); + g_object_unref (self); + return FALSE; +} + +GDBusMessage * +filter ( + GDBusConnection *connection, + GDBusMessage *message, + gboolean is_incoming, + gpointer user_data) +{ + BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (user_data); + const gchar *sender, *dest; + Message m; + GError *error = NULL; + + gettimeofday (&m.ts, NULL); + m.blob = g_dbus_message_to_blob (message, &m.size, self->priv->caps, &error); + if (m.blob == NULL) + { + g_critical ("Couldn't marshal message: %s", error->message); + g_return_val_if_reached (NULL); + } + g_async_queue_push (self->priv->td.message_queue, g_slice_dup (Message, &m)); + + sender = g_dbus_message_get_sender (message); + dest = g_dbus_message_get_destination (message); + + if (self->priv->verbose) + g_print ("(%s) %s -> %s: %u %s\n", + is_incoming ? "incoming" : "outgoing", + sender, + dest, + g_dbus_message_get_message_type (message), + g_dbus_message_get_member (message)); + + if (!is_incoming || + g_strcmp0 (dest, g_dbus_connection_get_unique_name (connection)) == 0) + { + /* This message is either outgoing or actually for us, as opposed to + * being eavesdropped. */ + return message; + } + else + { + /* We get our own outgoing messages echoed back to us by the bus daemon. */ + if (g_strcmp0 (sender, g_dbus_connection_get_unique_name (connection)) != 0) + { + /* This is a message we've snooped on; signal its receipt in the UI + * thread. */ + g_idle_add (emit_me, g_object_ref (self)); + } + + /* We have to say we've handled the message, or else GDBus replies to other + * people's method calls and we all get really confused. + */ + g_object_unref (message); + return NULL; + } +} + +static gboolean +match_everything ( + GDBusProxy *bus, + GError **error) +{ + char *rules[] = { + "type='signal'", + "type='method_call'", + "type='method_return'", + "type='error'", + NULL + }; + char **r; + + for (r = rules; *r != NULL; r++) + { + GVariant *ret = g_dbus_proxy_call_sync ( + bus, + "AddMatch", + g_variant_new ("(s)", *r), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + error); + + if (ret == NULL) + { + g_prefix_error (error, "Couldn't AddMatch(%s): ", *r); + return FALSE; + } + else + { + g_variant_unref (ret); + } + } + + return TRUE; +} + +static gboolean +list_all_names ( + GDBusProxy *bus, + GError **error) +{ + GVariant *ret; + gchar **names; + + g_assert (G_IS_DBUS_PROXY (bus)); + + ret = g_dbus_proxy_call_sync (bus, "ListNames", NULL, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); + if (ret == NULL) + { + g_prefix_error (error, "Couldn't ListNames: "); + return FALSE; + } + + for (g_variant_get_child (ret, 0, "^a&s", &names); + *names != NULL; + names++) + { + gchar *name = *names; + + if (!g_dbus_is_unique_name (name) && + strcmp (name, "org.freedesktop.DBus") != 0) + { + GVariant *owner = g_dbus_proxy_call_sync (bus, "GetNameOwner", + g_variant_new ("(s)", name), + G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); + + if (owner != NULL) + g_variant_unref (owner); + /* else they were too quick for us! */ + } + } + + g_variant_unref (ret); + return TRUE; +} + +static gboolean +initable_init ( + GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (initable); + BustlePcapMonitorPrivate *priv = self->priv; + GDBusProxy *bus; + + if (priv->bus_type == G_BUS_TYPE_NONE) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Logging things other than message busses is not supported"); + return FALSE; + } + + if (priv->filename == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "You must specify a filename"); + return FALSE; + } + + /* FIXME: use DLT_DBUS when it makes it into libpcap. */ + priv->p = pcap_open_dead (DLT_NULL, 1 << 27); + if (priv->p == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "pcap_open_dead failed. wtf"); + return FALSE; + } + + priv->td.dumper = pcap_dump_open (priv->p, priv->filename); + if (priv->td.dumper == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Couldn't open target file %s", pcap_geterr (priv->p)); + return FALSE; + } + + priv->thread = g_thread_create (log_thread, &priv->td, TRUE, error); + if (priv->thread == NULL) + { + g_prefix_error (error, "Couldn't spawn logging thread: "); + return FALSE; + } + + priv->connection = g_bus_get_sync (priv->bus_type, NULL, error); + if (priv->connection == NULL) + { + g_prefix_error (error, "Couldn't connect to %s bus: ", + priv->bus_type == G_BUS_TYPE_SESSION ? "session" : "system"); + return FALSE; + } + + priv->caps = g_dbus_connection_get_capabilities (priv->connection); + + bus = g_dbus_proxy_new_sync (priv->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + NULL, + error); + if (bus == NULL) + { + g_prefix_error (error, "Couldn't construct bus proxy: "); + return FALSE; + } + + if (!match_everything (bus, error)) + return FALSE; + + priv->filter_id = g_dbus_connection_add_filter (priv->connection, filter, + g_object_ref (self), g_object_unref); + + { + /* FIXME: there's a race between listing all the names and binding to all + * signals (and hence getting NameOwnerChanged). Old bustle-dbus-monitor had + * it too. + */ + gboolean ret = list_all_names (bus, error); + g_object_unref (bus); + + return ret; + } +} + +/* FIXME: make this async? */ +void +bustle_pcap_monitor_stop ( + BustlePcapMonitor *self) +{ + BustlePcapMonitorPrivate *priv = self->priv; + + if (priv->filter_id != 0) + { + g_return_if_fail (priv->connection != NULL); + g_dbus_connection_remove_filter (priv->connection, priv->filter_id); + priv->filter_id = 0; + } + + if (priv->connection != NULL && + !g_dbus_connection_is_closed (priv->connection)) + { + g_dbus_connection_close_sync (priv->connection, NULL, NULL); + } + + if (priv->thread != NULL) + { + g_return_if_fail (priv->td.message_queue != NULL); + /* Wait for the writer thread to spit out all the messages, then close up. */ + g_async_queue_push (priv->td.message_queue, STOP); + g_thread_join (priv->thread); + priv->thread = NULL; + } + + if (priv->td.dumper != NULL) + { + pcap_dump_close (priv->td.dumper); + priv->td.dumper = NULL; + } + + if (priv->p != NULL) + { + pcap_close (priv->p); + priv->p = NULL; + } +} + +static void +initable_iface_init ( + gpointer g_class, + gpointer unused) +{ + GInitableIface *iface = g_class; + + iface->init = initable_init; +} + +BustlePcapMonitor * +bustle_pcap_monitor_new ( + GBusType bus_type, + const gchar *filename, + gboolean verbose, + GError **error) +{ + return g_initable_new ( + BUSTLE_TYPE_PCAP_MONITOR, NULL, error, + "bus-type", bus_type, + "filename", filename, + "verbose", verbose, + NULL); +} |