bus-dump: change capture output to use pcapng (#21738)

This patch changes busctl capture to generate pcapng format
instead of the legacy pcap format files. It includes basic
meta-data in the file and still uses microsecond time
resolution. In future, more things can be added such as
high resolution timestams, statistics, etc.

PCAP Next Generation capture file format is what tshark uses
and is in process of being standardized in IETF. It is also
readable with libpcap.

$ capinfos /tmp/new.pcapng
File name:           /tmp/new.pcapng
File type:           Wireshark/... - pcapng
File encapsulation:  D-Bus
File timestamp precision:  microseconds (6)
Packet size limit:   file hdr: (not set)
Packet size limit:   inferred: 4096 bytes
Number of packets:   22
File size:           21kB
Data size:           20kB
Capture duration:    0.005694 seconds
First packet time:   2021-12-11 11:57:42.788374
Last packet time:    2021-12-11 11:57:42.794068
Data byte rate:      3,671kBps
Data bit rate:       29Mbps
Average packet size: 950.27 bytes
Average packet rate: 3,863 packets/s
SHA256:              b85ed8b094af60c64aa6d9db4a91404e841736d36b9e662d707db9e4096148f1
RIPEMD160:           81f9bac7ec0ec5cd1d55ede136a5c90413894e3a
SHA1:                8400822ef724b934d6000f5b7604b9e6e91be011
Strict time order:   True
Capture oper-sys:    Linux 5.14.0-0.bpo.2-amd64
Capture application: systemd 250 (250-rc2-33-gdc79ae2+)
Number of interfaces in file: 1
Interface #0 info:
                     Encapsulation = D-Bus (146 - dbus)
                     Capture length = 4096
                     Time precision = microseconds (6)
                     Time ticks per second = 1000000
                     Number of stat entries = 0
                     Number of packets = 22
This commit is contained in:
Stephen Hemminger 2021-12-24 22:07:40 -08:00 committed by GitHub
parent 402b81ffd8
commit 7c4bd9ac98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 236 additions and 53 deletions

View file

@ -78,10 +78,10 @@
<term><command>capture</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></term>
<listitem><para>Similar to <command>monitor</command> but
writes the output in pcap format (for details, see the <ulink
url="https://wiki.wireshark.org/Development/LibpcapFileFormat">Libpcap
File Format</ulink> description). Make sure to redirect
standard output to a file. Tools like
writes the output in pcapng format (for details, see
<ulink url="https://github.com/pcapng/pcapng/">
PCAP Next Generation (pcapng) Capture File Format</ulink>).
Make sure to redirect standard output to a file or pipe. Tools like
<citerefentry project='die-net'><refentrytitle>wireshark</refentrytitle><manvolnum>1</manvolnum></citerefentry>
may be used to dissect and view the resulting
files.</para></listitem>

View file

@ -3267,6 +3267,7 @@ public_programs += executable(
busctl_sources,
include_directories : includes,
link_with : [libshared],
dependencies : [versiondep],
install_rpath : rootlibexecdir,
install : true)

View file

@ -25,7 +25,7 @@
"list:List bus names"
"status:Show bus service, process or bus owner credentials"
"monitor:Show bus traffic"
"capture:Capture bus traffix as pcap"
"capture:Capture bus traffic"
"tree:Show object tree of service"
"introspect:Introspect object"
"call:Call a method"

115
src/basic/pcapng.h Normal file
View file

@ -0,0 +1,115 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
/*
* For details about the file format see RFC:
* https://www.ietf.org/id/draft-tuexen-opsawg-pcapng-03.html
* and
* https://github.com/pcapng/pcapng/
*/
enum pcapng_block_types {
PCAPNG_INTERFACE_BLOCK = 1,
PCAPNG_PACKET_BLOCK, /* Obsolete */
PCAPNG_SIMPLE_PACKET_BLOCK,
PCAPNG_NAME_RESOLUTION_BLOCK,
PCAPNG_INTERFACE_STATS_BLOCK,
PCAPNG_ENHANCED_PACKET_BLOCK,
PCAPNG_SECTION_BLOCK = 0x0A0D0D0A,
};
struct pcapng_option {
uint16_t code;
uint16_t length;
uint8_t data[];
};
#define PCAPNG_BYTE_ORDER_MAGIC 0x1A2B3C4D
#define PCAPNG_MAJOR_VERS 1
#define PCAPNG_MINOR_VERS 0
enum pcapng_opt {
PCAPNG_OPT_END = 0,
PCAPNG_OPT_COMMENT = 1,
};
struct pcapng_section {
uint32_t block_type;
uint32_t block_length;
uint32_t byte_order_magic;
uint16_t major_version;
uint16_t minor_version;
uint64_t section_length;
};
enum pcapng_section_opt {
PCAPNG_SHB_HARDWARE = 2,
PCAPNG_SHB_OS = 3,
PCAPNG_SHB_USERAPPL = 4,
};
struct pcapng_interface_block {
uint32_t block_type; /* 1 */
uint32_t block_length;
uint16_t link_type;
uint16_t reserved;
uint32_t snap_len;
};
enum pcapng_interface_options {
PCAPNG_IFB_NAME = 2,
PCAPNG_IFB_DESCRIPTION,
PCAPNG_IFB_IPV4ADDR,
PCAPNG_IFB_IPV6ADDR,
PCAPNG_IFB_MACADDR,
PCAPNG_IFB_EUIADDR,
PCAPNG_IFB_SPEED,
PCAPNG_IFB_TSRESOL,
PCAPNG_IFB_TZONE,
PCAPNG_IFB_FILTER,
PCAPNG_IFB_OS,
PCAPNG_IFB_FCSLEN,
PCAPNG_IFB_TSOFFSET,
PCAPNG_IFB_HARDWARE,
};
struct pcapng_enhance_packet_block {
uint32_t block_type; /* 6 */
uint32_t block_length;
uint32_t interface_id;
uint32_t timestamp_hi;
uint32_t timestamp_lo;
uint32_t capture_length;
uint32_t original_length;
};
/* Flags values */
#define PCAPNG_IFB_INBOUND 0b01
#define PCAPNG_IFB_OUTBOUND 0b10
enum pcapng_epb_options {
PCAPNG_EPB_FLAGS = 2,
PCAPNG_EPB_HASH,
PCAPNG_EPB_DROPCOUNT,
PCAPNG_EPB_PACKETID,
PCAPNG_EPB_QUEUE,
PCAPNG_EPB_VERDICT,
};
struct pcapng_statistics_block {
uint32_t block_type; /* 5 */
uint32_t block_length;
uint32_t interface_id;
uint32_t timestamp_hi;
uint32_t timestamp_lo;
};
enum pcapng_isb_options {
PCAPNG_ISB_STARTTIME = 2,
PCAPNG_ISB_ENDTIME,
PCAPNG_ISB_IFRECV,
PCAPNG_ISB_IFDROP,
PCAPNG_ISB_FILTERACCEPT,
PCAPNG_ISB_OSDROP,
PCAPNG_ISB_USRDELIV,
};

View file

@ -20,6 +20,7 @@
#include "json.h"
#include "log.h"
#include "main-func.h"
#include "os-util.h"
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
@ -31,6 +32,7 @@
#include "terminal-util.h"
#include "user-util.h"
#include "verbs.h"
#include "version.h"
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
@ -1329,13 +1331,20 @@ static int verb_monitor(int argc, char **argv, void *userdata) {
}
static int verb_capture(int argc, char **argv, void *userdata) {
_cleanup_free_ char *osname = NULL;
static const char info[] =
"busctl (systemd) " STRINGIFY(PROJECT_VERSION) " (Git " GIT_VERSION ")";
int r;
if (isatty(fileno(stdout)) > 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Refusing to write message data to console, please redirect output to a file.");
bus_pcap_header(arg_snaplen, stdout);
r = parse_os_release(NULL, "PRETTY_NAME", &osname);
if (r < 0)
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_INFO, r,
"Failed to read os-release file, ignoring: %m");
bus_pcap_header(arg_snaplen, osname, info, stdout);
r = monitor(argc, argv, message_pcap);
if (r < 0)

View file

@ -13,6 +13,7 @@
#include "format-util.h"
#include "glyph-util.h"
#include "macro.h"
#include "pcapng.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
@ -502,57 +503,99 @@ int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse) {
return 0;
}
/*
* For details about the file format, see:
*
* http://wiki.wireshark.org/Development/LibpcapFileFormat
*/
static uint16_t pcapng_optlen(size_t len) {
return ALIGN4(len + sizeof(struct pcapng_option));
}
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 */
static void pcapng_putopt(FILE *f, uint16_t code, const void *data, size_t len) {
struct pcapng_option opt = {
.code = code,
.length = len,
};
if (!f)
f = stdout;
assert(f);
assert((uint16_t) len == len);
assert(data || len == 0);
fwrite(&opt, 1, sizeof(opt), f);
if (len > 0) {
size_t pad = ALIGN4(len) - len;
fwrite(data, 1, len, f);
assert(pad < sizeof(uint32_t));
while (pad-- > 0)
fputc('\0', f);
}
}
static void pcapng_section_header(FILE *f, const char *os, const char *app) {
uint32_t len;
assert(f);
/* determine length of section header and options */
len = sizeof(struct pcapng_section);
if (os)
len += pcapng_optlen(strlen(os));
if (app)
len += pcapng_optlen(strlen(app));
len += pcapng_optlen(0); /* OPT_END */
len += sizeof(uint32_t); /* trailer length */
struct pcapng_section hdr = {
.block_type = PCAPNG_SECTION_BLOCK,
.block_length = len,
.byte_order_magic = PCAPNG_BYTE_ORDER_MAGIC,
.major_version = PCAPNG_MAJOR_VERS,
.minor_version = PCAPNG_MINOR_VERS,
.section_length = UINT64_MAX,
};
fwrite(&hdr, 1, sizeof(hdr), f);
if (os)
pcapng_putopt(f, PCAPNG_SHB_OS, os, strlen(os));
if (app)
pcapng_putopt(f, PCAPNG_SHB_USERAPPL, app, strlen(app));
pcapng_putopt(f, PCAPNG_OPT_END, NULL, 0);
fwrite(&len, 1, sizeof(uint32_t), f);
}
/* Only have a single instance of dbus pseudo interface */
static void pcapng_interface_header(FILE *f, size_t snaplen) {
uint32_t len;
assert(f);
assert(snaplen > 0);
assert((size_t) (uint32_t) snaplen == snaplen);
hdr.snaplen = (uint32_t) snaplen;
/* no options (yet) */
len = sizeof(struct pcapng_interface_block) + sizeof(uint32_t);
struct pcapng_interface_block hdr = {
.block_type = PCAPNG_INTERFACE_BLOCK,
.block_length = len,
.link_type = 231, /* D-Bus */
.snap_len = snaplen,
};
fwrite(&hdr, 1, sizeof(hdr), f);
fwrite(&len, 1, sizeof(uint32_t), f);
}
int bus_pcap_header(size_t snaplen, const char *os, const char *info, FILE *f) {
if (!f)
f = stdout;
pcapng_section_header(f, os, info);
pcapng_interface_header(f, snaplen);
return fflush_and_check(f);
}
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;
size_t msglen, caplen, pad;
uint32_t length;
uint64_t ts;
unsigned i;
size_t w;
@ -563,18 +606,27 @@ int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f) {
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);
ts = m->realtime ?: now(CLOCK_REALTIME);
msglen = BUS_MESSAGE_SIZE(m);
caplen = MIN(msglen, snaplen);
pad = ALIGN4(caplen) - caplen;
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);
/* packet block has no options */
length = sizeof(struct pcapng_enhance_packet_block)
+ caplen + pad + sizeof(uint32_t);
/* write the pcap header */
fwrite(&hdr, 1, sizeof(hdr), f);
struct pcapng_enhance_packet_block epb = {
.block_type = PCAPNG_ENHANCED_PACKET_BLOCK,
.block_length = length,
.interface_id = 0,
.timestamp_hi = (uint32_t)(ts >> 32),
.timestamp_lo = (uint32_t)ts,
.original_length = msglen,
.capture_length = caplen,
};
/* write the pcapng enhanced packet block header */
fwrite(&epb, 1, sizeof(epb), f);
/* write the dbus header */
w = MIN(BUS_MESSAGE_BODY_BEGIN(m), snaplen);
@ -591,5 +643,11 @@ int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f) {
snaplen -= w;
}
while (pad-- > 0)
fputc('\0', f);
/* trailing block length */
fwrite(&length, 1, sizeof(uint32_t), f);
return fflush_and_check(f);
}

View file

@ -8,5 +8,5 @@
int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse);
int bus_pcap_header(size_t snaplen, FILE *f);
int bus_pcap_header(size_t snaplen, const char *os, const char *app, FILE *f);
int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f);