busctl: add new "capture" verb to record bus messages in libpcap compatible files, for dissection with wireshark

This commit is contained in:
Lennart Poettering 2014-10-30 01:13:11 +01:00
parent 1ab19cb167
commit 1f70b0876a
4 changed files with 186 additions and 7 deletions

View file

@ -133,6 +133,16 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
</listitem>
</varlistentry>
<varlistentry>
<term><option>--size=</option></term>
<listitem>
<para>When used with the <command>capture</command> command
specifies the maximum bus message size to capture
("snaplen"). Defaults to 4096 bytes.</para>
</listitem>
</varlistentry>
<xi:include href="user-system-options.xml" xpointer="user" />
<xi:include href="user-system-options.xml" xpointer="system" />
<xi:include href="user-system-options.xml" xpointer="host" />
@ -166,6 +176,19 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
bus.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>capture</command> <arg choice="opt" rep="repeat"><replaceable>NAME</replaceable></arg></term>
<listitem><para>Similar to <command>monitor</command> but
writes the output in pcap format (for details see the <ulink
url="http://wiki.wireshark.org/Development/LibpcapFileFormat">Libpcap
File Format</ulink> description. Make sure to redirect the
output to STDOUT to a file. Tools like
<citerefentry><refentrytitle>wireshark</refentrytitle><manvolnum>1</manvolnum></citerefentry>
may be used to dissect and view the generated
files.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>status</command> <arg choice="plain"><replaceable>NAME</replaceable></arg></term>
@ -191,7 +214,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
<citerefentry><refentrytitle>sd-bus</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-bus-proxyd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>wireshark</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View file

@ -23,6 +23,7 @@
#include "capability.h"
#include "strv.h"
#include "audit.h"
#include "macro.h"
#include "bus-message.h"
#include "bus-internal.h"
@ -424,3 +425,98 @@ int bus_creds_dump(sd_bus_creds *c, FILE *f) {
return 0;
}
/*
* For details about the file format, see:
*
* http://wiki.wireshark.org/Development/LibpcapFileFormat
*/
typedef struct _packed_ pcap_hdr_s {
uint32_t magic_number; /* magic number */
uint16_t version_major; /* major version number */
uint16_t version_minor; /* minor version number */
int32_t thiszone; /* GMT to local correction */
uint32_t sigfigs; /* accuracy of timestamps */
uint32_t snaplen; /* max length of captured packets, in octets */
uint32_t network; /* data link type */
} pcap_hdr_t ;
typedef struct _packed_ pcaprec_hdr_s {
uint32_t ts_sec; /* timestamp seconds */
uint32_t ts_usec; /* timestamp microseconds */
uint32_t incl_len; /* number of octets of packet saved in file */
uint32_t orig_len; /* actual length of packet */
} pcaprec_hdr_t;
int bus_pcap_header(size_t snaplen, FILE *f) {
pcap_hdr_t hdr = {
.magic_number = 0xa1b2c3d4U,
.version_major = 2,
.version_minor = 4,
.thiszone = 0, /* UTC */
.sigfigs = 0,
.network = 231, /* D-Bus */
};
if (!f)
f = stdout;
assert(snaplen > 0);
assert((size_t) (uint32_t) snaplen == snaplen);
hdr.snaplen = (uint32_t) snaplen;
fwrite(&hdr, 1, sizeof(hdr), f);
fflush(f);
return 0;
}
int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f) {
struct bus_body_part *part;
pcaprec_hdr_t hdr = {};
struct timeval tv;
unsigned i;
size_t w;
if (!f)
f = stdout;
assert(m);
assert(snaplen > 0);
assert((size_t) (uint32_t) snaplen == snaplen);
if (m->realtime != 0)
timeval_store(&tv, m->realtime);
else
assert_se(gettimeofday(&tv, NULL) >= 0);
hdr.ts_sec = tv.tv_sec;
hdr.ts_usec = tv.tv_usec;
hdr.orig_len = BUS_MESSAGE_SIZE(m);
hdr.incl_len = MIN(hdr.orig_len, snaplen);
/* write the pcap header */
fwrite(&hdr, 1, sizeof(hdr), f);
/* write the dbus header */
w = MIN(BUS_MESSAGE_BODY_BEGIN(m), snaplen);
fwrite(m->header, 1, w, f);
snaplen -= w;
/* write the dbus body */
MESSAGE_FOREACH_PART(part, i, m) {
if (snaplen <= 0)
break;
w = MIN(part->size, snaplen);
fwrite(part->data, 1, w, f);
snaplen -= w;
}
fflush(f);
return 0;
}

View file

@ -29,3 +29,6 @@
int bus_message_dump(sd_bus_message *m, FILE *f, bool with_header);
int bus_creds_dump(sd_bus_creds *c, FILE *f);
int bus_pcap_header(size_t snaplen, FILE *f);
int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f);

View file

@ -44,6 +44,7 @@ static char **arg_matches = NULL;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
static char *arg_host = NULL;
static bool arg_user = false;
static size_t arg_snaplen = 4096;
static void pager_open_if_enabled(void) {
@ -223,7 +224,15 @@ static int list_bus_names(sd_bus *bus, char **argv) {
return 0;
}
static int monitor(sd_bus *bus, char *argv[]) {
static int message_dump(sd_bus_message *m, FILE *f) {
return bus_message_dump(m, f, true);
}
static int message_pcap(sd_bus_message *m, FILE *f) {
return bus_message_pcap_frame(m, arg_snaplen, f);
}
static int monitor(sd_bus *bus, char *argv[], int (*dump)(sd_bus_message *m, FILE *f)) {
bool added_something = false;
char **i;
int r;
@ -277,7 +286,7 @@ static int monitor(sd_bus *bus, char *argv[]) {
}
if (m) {
bus_message_dump(m, stdout, true);
dump(m, stdout);
continue;
}
@ -292,6 +301,28 @@ static int monitor(sd_bus *bus, char *argv[]) {
}
}
static int capture(sd_bus *bus, char *argv[]) {
int r;
if (isatty(fileno(stdout)) > 0) {
log_error("Refusing to write message data to console, please redirect output to a file.");
return -EINVAL;
}
bus_pcap_header(arg_snaplen, stdout);
r = monitor(bus, argv, message_pcap);
if (r < 0)
return r;
if (ferror(stdout)) {
log_error("Couldn't write capture file.");
return -EIO;
}
return r;
}
static int status(sd_bus *bus, char *argv[]) {
_cleanup_bus_creds_unref_ sd_bus_creds *creds = NULL;
pid_t pid;
@ -339,6 +370,7 @@ static int help(void) {
"Commands:\n"
" list List bus names\n"
" monitor [SERVICE...] Show bus traffic\n"
" capture [SERVICE...] Capture bus traffic as pcap\n"
" status NAME Show name status\n"
" help Show this help\n"
, program_invocation_short_name);
@ -359,7 +391,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SHOW_MACHINE,
ARG_UNIQUE,
ARG_ACQUIRED,
ARG_ACTIVATABLE
ARG_ACTIVATABLE,
ARG_SIZE,
};
static const struct option options[] = {
@ -377,10 +410,11 @@ static int parse_argv(int argc, char *argv[]) {
{ "match", required_argument, NULL, ARG_MATCH },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "size", required_argument, NULL, ARG_SIZE },
{},
};
int c;
int c, r;
assert(argc >= 0);
assert(argv);
@ -438,6 +472,24 @@ static int parse_argv(int argc, char *argv[]) {
return log_oom();
break;
case ARG_SIZE: {
off_t o;
r = parse_size(optarg, 0, &o);
if (r < 0) {
log_error("Failed to parse size: %s", optarg);
return r;
}
if ((off_t) (size_t) o != o) {
log_error("Size out of range.");
return -E2BIG;
}
arg_snaplen = (size_t) o;
break;
}
case 'H':
arg_transport = BUS_TRANSPORT_REMOTE;
arg_host = optarg;
@ -469,7 +521,10 @@ static int busctl_main(sd_bus *bus, int argc, char *argv[]) {
return list_bus_names(bus, argv + optind);
if (streq(argv[optind], "monitor"))
return monitor(bus, argv + optind);
return monitor(bus, argv + optind, message_dump);
if (streq(argv[optind], "capture"))
return capture(bus, argv + optind);
if (streq(argv[optind], "status"))
return status(bus, argv + optind);
@ -498,7 +553,8 @@ int main(int argc, char *argv[]) {
goto finish;
}
if (streq_ptr(argv[optind], "monitor")) {
if (streq_ptr(argv[optind], "monitor") ||
streq_ptr(argv[optind], "capture")) {
r = sd_bus_set_monitor(bus, true);
if (r < 0) {