pespin has uploaded this change for review. (
https://gerrit.osmocom.org/c/osmo-pcap/+/39287?usp=email )
Change subject: server: Support storing of pcapng format
......................................................................
server: Support storing of pcapng format
Improve existing logic doing validation on payload data and generalize
the file_hdr field to account for the possibily of receiving recordings
in pcapng format.
This requires, during LINK_HDR when file header is received, discering
whether the recording is being done in pcap vs pcapng, (and in pcapng
figuring out the endianness of the file) and then applying validation
based on that knowledge.
With that knowledge, osmo-pcap-server can now store the pcap(ng) file
using the proper suffix for the file.
Related: SYS#5822
Change-Id: I275d28ab418a1514fa9c5c7c20f3d831cc6af8bb
---
M include/osmo-pcap/osmo_pcap_file.h
M include/osmo-pcap/osmo_pcap_server.h
M src/Makefile.am
M src/osmo_pcap_file.c
M src/osmo_server_network.c
M tests/rotate_localtime/Makefile.am
6 files changed, 335 insertions(+), 48 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-pcap refs/changes/87/39287/1
diff --git a/include/osmo-pcap/osmo_pcap_file.h b/include/osmo-pcap/osmo_pcap_file.h
index a12df89..3daad99 100644
--- a/include/osmo-pcap/osmo_pcap_file.h
+++ b/include/osmo-pcap/osmo_pcap_file.h
@@ -29,6 +29,8 @@
OSMO_PCAP_FMT_PCAPNG,
};
+int osmo_pcap_file_discover_fmt(const uint8_t *data, size_t data_len, enum osmo_pcap_fmt
*result_fmt);
+
/***********************************************************
* Libpcap File Format (.pcap)
*
https://wiki.wireshark.org/Development/LibpcapFileFormat
@@ -123,6 +125,11 @@
/* ... Options ... */
} __attribute__((packed));
+int osmo_pcapng_file_is_swapped(const uint8_t *data, size_t data_len);
+uint16_t osmo_pcapng_file_read_uint16(const uint8_t *data, bool endian_swapped);
+uint32_t osmo_pcapng_file_read_uint32(const uint8_t *data, bool endian_swapped);
+uint64_t osmo_pcapng_file_read_uint64(const uint8_t *data, bool endian_swapped);
+
/* Helper APIs to encode blocks: */
struct osmo_pcapng_file_shb_pars {
diff --git a/include/osmo-pcap/osmo_pcap_server.h b/include/osmo-pcap/osmo_pcap_server.h
index da39d99..f44781a 100644
--- a/include/osmo-pcap/osmo_pcap_server.h
+++ b/include/osmo-pcap/osmo_pcap_server.h
@@ -35,6 +35,7 @@
#include <osmo-pcap/wireformat.h>
#include <osmo-pcap/osmo_tls.h>
+#include <osmo-pcap/osmo_pcap_file.h>
struct rate_ctr_group;
struct rate_ctr_group_desc;
@@ -88,7 +89,10 @@
char *curr_filename;
/* pcap stuff */
- struct pcap_file_header file_hdr;
+ enum osmo_pcap_fmt file_fmt;
+ bool pcapng_endian_swapped;
+ uint8_t *file_hdr;
+ uint32_t file_hdr_len;
/* last time */
struct tm last_write;
diff --git a/src/Makefile.am b/src/Makefile.am
index bb70d21..0fb16a2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -35,6 +35,7 @@
osmo_pcap_server_SOURCES = \
osmo_server_main.c \
osmo_common.c \
+ osmo_pcap_file.c \
osmo_server_vty.c \
osmo_server_network.c \
osmo_server_stats.c \
diff --git a/src/osmo_pcap_file.c b/src/osmo_pcap_file.c
index ddd32ed..8e59a37 100644
--- a/src/osmo_pcap_file.c
+++ b/src/osmo_pcap_file.c
@@ -29,6 +29,40 @@
#include <osmo-pcap/common.h>
#include <osmo-pcap/wireformat.h>
+/* Find out whether data contains a start of a .pcap or .pcapng file.
+ * Returns 0 on success, negative on error. */
+int osmo_pcap_file_discover_fmt(const uint8_t *data, size_t data_len, enum osmo_pcap_fmt
*result_fmt)
+{
+ const struct pcapng_block_header *bh;
+ const struct pcapng_section_header_block *shb;
+
+ /* Check for .pcap: */
+ if (data_len >= sizeof(struct pcap_file_header) &&
+ ((struct pcap_file_header *)data)->magic == OSMO_PCAP_FILE_MAGIC) {
+ *result_fmt = OSMO_PCAP_FMT_PCAP;
+ return 0;
+ }
+
+ /* Check for .pcapng: */
+ if (data_len < sizeof(struct pcapng_block_header) +
+ sizeof(struct pcapng_section_header_block) +
+ sizeof(uint32_t))
+ return -1;
+
+ bh = (const struct pcapng_block_header *)data;
+ /* BLOCK_TYPE_SHB has the same value regardless of byte order */
+ if (bh->block_type != BLOCK_TYPE_SHB)
+ return -1;
+
+ shb = (const struct pcapng_section_header_block *)&bh->block_body[0];
+ if (shb->magic != OSMO_PCAPNG_FILE_MAGIC &&
+ shb->magic != OSMO_PCAPNG_FILE_MAGIC_SWAPPED)
+ return -2;
+
+ *result_fmt = OSMO_PCAP_FMT_PCAPNG;
+ return 0;
+}
+
/***********************************************************
* Libpcap File Format (.pcap)
*
https://wiki.wireshark.org/Development/LibpcapFileFormat
@@ -179,6 +213,64 @@
return sizeof(*opth);
}
+static int osmo_pcapng_file_shb_is_swapped(const struct pcapng_section_header_block
*shb)
+{
+ if (shb->magic == OSMO_PCAPNG_FILE_MAGIC_SWAPPED)
+ return 1;
+ if (shb->magic == OSMO_PCAPNG_FILE_MAGIC)
+ return 0;
+ return -1;
+}
+
+/* 1: true, 0: false, negative: error */
+int osmo_pcapng_file_is_swapped(const uint8_t *data, size_t data_len)
+{
+ const struct pcapng_block_header *bh = (const struct pcapng_block_header *)data;
+ const struct pcapng_section_header_block *shb;
+
+ if (data_len < sizeof(struct pcapng_block_header) +
+ sizeof(struct pcapng_section_header_block) +
+ sizeof(uint32_t))
+ return -1;
+
+ /* BLOCK_TYPE_SHB has the same value regardless of byte order */
+ if (bh->block_type != BLOCK_TYPE_SHB)
+ return -1;
+
+ shb = (const struct pcapng_section_header_block *)&bh->block_body[0];
+ return osmo_pcapng_file_shb_is_swapped(shb);
+}
+
+uint16_t osmo_pcapng_file_read_uint16(const uint8_t *data, bool endian_swapped)
+{
+ uint16_t val;
+
+ memcpy(&val, data, sizeof(val));
+ if (!endian_swapped)
+ return val;
+ return (val >> 8) | (val << 8);
+}
+
+uint32_t osmo_pcapng_file_read_uint32(const uint8_t *data, bool endian_swapped)
+{
+ uint32_t val;
+
+ memcpy(&val, data, sizeof(val));
+ if (!endian_swapped)
+ return val;
+ return __builtin_bswap32(val);
+}
+
+uint64_t osmo_pcapng_file_read_uint64(const uint8_t *data, bool endian_swapped)
+{
+ uint64_t val;
+
+ memcpy(&val, data, sizeof(val));
+ if (!endian_swapped)
+ return val;
+ return __builtin_bswap64(val);
+}
+
/* Get required length to store a given record (packet) */
unsigned int osmo_pcapng_file_shb_size(const struct osmo_pcapng_file_shb_pars *pars)
{
diff --git a/src/osmo_server_network.c b/src/osmo_server_network.c
index 64d772a..0a0763b 100644
--- a/src/osmo_server_network.c
+++ b/src/osmo_server_network.c
@@ -108,7 +108,7 @@
talloc_free(event_name);
pcap_zmq_send(conn->server->zmq_publ,
- &conn->file_hdr, sizeof(conn->file_hdr),
+ conn->file_hdr, conn->file_hdr_len,
ZMQ_SNDMORE);
pcap_zmq_send(conn->server->zmq_publ,
&data->data[0], data->len,
@@ -268,10 +268,11 @@
conn->server->base_path, strerror(errno));
return;
}
- conn->curr_filename = talloc_asprintf(conn,
"%s/trace-%s-%d%.2d%.2d_%.2d%.2d%.2d.pcap",
+ conn->curr_filename = talloc_asprintf(conn,
"%s/trace-%s-%d%.2d%.2d_%.2d%.2d%.2d.%s",
real_base_path, conn->name,
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
- tm.tm_hour, tm.tm_min, tm.tm_sec);
+ tm.tm_hour, tm.tm_min, tm.tm_sec,
+ conn->file_fmt == OSMO_PCAP_FMT_PCAP ? "pcap" :
"pcapng");
free(real_base_path);
if (!conn->curr_filename) {
LOGP(DSERVER, LOGL_ERROR, "Failed to assemble filename for %s.\n",
conn->name);
@@ -285,8 +286,8 @@
return;
}
- rc = write(conn->local_fd, &conn->file_hdr, sizeof(conn->file_hdr));
- if (rc != sizeof(conn->file_hdr)) {
+ rc = write(conn->local_fd, conn->file_hdr, conn->file_hdr_len);
+ if (rc != conn->file_hdr_len) {
LOGP(DSERVER, LOGL_ERROR, "Failed to write the header: %d\n", errno);
close(conn->local_fd);
conn->local_fd = -1;
@@ -296,27 +297,116 @@
update_last_write(conn, now);
}
-/* returns >0 on success, <= 0 on failure (closes conn) */
-static int rx_link_hdr(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data)
+static int validate_link_hdr_pcap(const struct osmo_pcap_conn *conn, const struct
osmo_pcap_data *data)
{
struct pcap_file_header *hdr;
hdr = (struct pcap_file_header *) &data->data[0];
-
+ if (data->len < sizeof(struct pcap_file_header)) {
+ LOGP(DSERVER, LOGL_ERROR,
+ "Implausible llink_hdr length: %u < %zu\n",
+ data->len, sizeof(struct pcap_file_header));
+ return -1;
+ }
if (hdr->snaplen > conn->server->max_snaplen) {
LOGP(DSERVER, LOGL_ERROR,
- "The recvd pcap_file_header contains too big snaplen %zu > %zu\n",
- (size_t) hdr->snaplen, (size_t) conn->server->max_snaplen);
+ "The recvd pcap_file_header contains too big snaplen %zu > %zu\n",
+ (size_t) hdr->snaplen, (size_t) conn->server->max_snaplen);
+ return -1;
+ }
+ return 0;
+}
+
+static int validate_link_hdr_pcapng(const struct osmo_pcap_conn *conn, const struct
osmo_pcap_data *data)
+{
+
+ const struct pcapng_block_header *bh = (const struct pcapng_block_header
*)&data->data[0];
+ const size_t pcapnb_bh_min_len = sizeof(struct pcapng_block_header) + sizeof(uint32_t);
+ uint32_t block_total_length, block_type;
+
+ if (data->len < pcapnb_bh_min_len) {
+ LOGP(DSERVER, LOGL_ERROR, "Implausible data length: %u < %zu\n",
+ data->len, pcapnb_bh_min_len);
return -1;
}
+ block_total_length = osmo_pcapng_file_read_uint32((uint8_t
*)&bh->block_total_length, conn->pcapng_endian_swapped);
+ if (block_total_length & 0x00000003) {
+ LOGP(DSERVER, LOGL_ERROR, "Implausible pcapng block total length not multiple of
4: %u\n",
+ block_total_length);
+ return -1;
+ }
+
+ /* We have a SHB block + N IDB blocks here, so it most probably won't be equal
+ * since we are only checking the 1st SHB block here: */
+ if (block_total_length > data->len) {
+ LOGP(DSERVER, LOGL_ERROR, "Implausible pcapng block total length: %u >
%u\n",
+ block_total_length, data->len);
+ return -1;
+ }
+
+ block_type = osmo_pcapng_file_read_uint32((uint8_t *)&bh->block_type,
conn->pcapng_endian_swapped);
+ if (block_type != BLOCK_TYPE_SHB) {
+ LOGP(DSERVER, LOGL_DEBUG, "Implausible pcapng Block Header type %u vs exp
%u\n",
+ block_type, BLOCK_TYPE_SHB);
+ return -1;
+ }
+
+ /* TODO: validate each idb->snaplen in data->data to be <=
conn->server->max_snaplen */
+
+ return 0;
+}
+
+static int validate_link_hdr(const struct osmo_pcap_conn *conn, const struct
osmo_pcap_data *data)
+{
+
+ /* Validation checks: */
+ switch (conn->file_fmt) {
+ case OSMO_PCAP_FMT_PCAP:
+ return validate_link_hdr_pcap(conn, data);
+ case OSMO_PCAP_FMT_PCAPNG:
+ return validate_link_hdr_pcapng(conn, data);
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* returns >0 on success, <= 0 on failure (closes conn) */
+static int rx_link_hdr(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data)
+{
+ int rc;
+
+ rc = osmo_pcap_file_discover_fmt(data->data, data->len, &conn->file_fmt);
+ if (rc < 0)
+ return rc;
+
+ if (conn->file_fmt == OSMO_PCAP_FMT_PCAPNG) {
+ rc = osmo_pcapng_file_is_swapped(data->data, data->len);
+ if (rc < 0) {
+ LOGP(DSERVER, LOGL_ERROR, "Unable to figure out pcapng file endianness\n");
+ return rc;
+ }
+ conn->pcapng_endian_swapped = !!rc;
+ }
+
+ /* Validation checks: */
+ if ((rc = validate_link_hdr(conn, data)) < 0)
+ return rc;
+
if (conn->store && conn->local_fd < 0) {
/* First received link hdr in conn */
- conn->file_hdr = *hdr;
+ talloc_free(conn->file_hdr);
+ conn->file_hdr = talloc_size(conn, data->len);
+ memcpy(conn->file_hdr, data->data, data->len);
+ conn->file_hdr_len = data->len;
restart_pcap(conn);
- } else if (memcmp(&conn->file_hdr, hdr, sizeof(*hdr)) != 0) {
+ } else if (conn->file_hdr_len != data->len ||
+ memcmp(&conn->file_hdr, data->data, data->len) != 0) {
/* Client changed the link hdr in conn */
- conn->file_hdr = *hdr;
+ talloc_free(conn->file_hdr);
+ conn->file_hdr = talloc_size(conn, data->len);
+ memcpy(conn->file_hdr, data->data, data->len);
+ conn->file_hdr_len = data->len;
restart_pcap(conn);
}
@@ -460,12 +550,95 @@
return true;
}
+static int validate_link_data_pcap(const struct osmo_pcap_conn *conn, const struct
osmo_pcap_data *data)
+{
+ unsigned int min_len, max_len;
+
+ min_len = sizeof(struct osmo_pcap_pkthdr);
+ max_len = conn->server->max_snaplen + sizeof(struct osmo_pcap_pkthdr);
+ if (data->len < min_len || data->len > max_len) {
+ LOGP(DSERVER, LOGL_ERROR, "Implausible data length: %u < %u <= %u\n",
+ min_len, data->len, max_len);
+ return -1;
+ }
+ return 0;
+}
+
+/* pcapng: validate size of payload (pkt) in EPB doesn't go through snaplen. */
+static int validate_link_data_pcapng(const struct osmo_pcap_conn *conn, const struct
osmo_pcap_data *data)
+{
+
+ const struct pcapng_block_header *bh = (const struct pcapng_block_header
*)&data->data[0];
+ const struct pcapng_enhanced_packet_block *epb;
+ const size_t pcapnb_bh_min_len = sizeof(struct pcapng_block_header) + sizeof(uint32_t);
+ uint32_t block_total_length, block_type, captured_len;
+
+ if (data->len < pcapnb_bh_min_len) {
+ LOGP(DSERVER, LOGL_ERROR, "Implausible data length: %u < %zu\n",
+ data->len, pcapnb_bh_min_len);
+ return -1;
+ }
+
+ block_total_length = osmo_pcapng_file_read_uint32((uint8_t
*)&bh->block_total_length, conn->pcapng_endian_swapped);
+ if (block_total_length & 0x00000003) {
+ LOGP(DSERVER, LOGL_ERROR, "Implausible pcapng block total length not multiple of
4: %u\n",
+ block_total_length);
+ return -1;
+ }
+
+ if (block_total_length != data->len) {
+ LOGP(DSERVER, LOGL_ERROR, "Implausible pcapng block total length: %u !=
%u\n",
+ block_total_length, data->len);
+ return -1;
+ }
+
+ block_type = osmo_pcapng_file_read_uint32((uint8_t *)&bh->block_type,
conn->pcapng_endian_swapped);
+ switch (block_type) {
+ case BLOCK_TYPE_EPB:
+ if (data->len < pcapnb_bh_min_len + sizeof(struct pcapng_enhanced_packet_block))
{
+ LOGP(DSERVER, LOGL_ERROR, "Implausible data length: %u < %zu\n",
+ data->len, pcapnb_bh_min_len + sizeof(struct pcapng_enhanced_packet_block));
+ return -1;
+ }
+ epb = (struct pcapng_enhanced_packet_block *)&bh->block_body[0];
+ captured_len = osmo_pcapng_file_read_uint32((uint8_t *)&epb->captured_len,
conn->pcapng_endian_swapped);
+ if (captured_len > conn->server->max_snaplen) {
+ LOGP(DSERVER, LOGL_ERROR, "Implausible pcapng EPB captured length: %u >
%u\n",
+ captured_len, conn->server->max_snaplen);
+ return -1;
+ }
+ break;
+ default:
+ LOGP(DSERVER, LOGL_DEBUG, "Unexpected pcapng Block Header type %u\n",
block_type);
+ /* Other types exist which we don't specifically support right now, but which may
+ * be sent by newer osmo-pcap-client. Allow storing it without further checks. */
+ break;
+ }
+ return 0;
+}
+
+static int validate_link_data(const struct osmo_pcap_conn *conn, const struct
osmo_pcap_data *data)
+{
+ /* Validation checks: */
+ switch (conn->file_fmt) {
+ case OSMO_PCAP_FMT_PCAP:
+ return validate_link_data_pcap(conn, data);
+ case OSMO_PCAP_FMT_PCAPNG:
+ return validate_link_data_pcapng(conn, data);
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
/* returns >0 on success, <= 0 on failure (closes conn) */
static int rx_link_data(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data)
{
time_t now = time(NULL);
int rc;
+ if ((rc = validate_link_data(conn, data)) < 0)
+ return rc;
+
zmq_send_client_data(conn, data);
if (!conn->store) {
@@ -499,6 +672,42 @@
talloc_free(conn);
}
+static inline size_t calc_data_max_len(const struct osmo_pcap_server *server)
+{
+ size_t data_max_len;
+
+ /* Some safe value regarding variable size options in a given pcapng block... */
+ const size_t pcapng_max_len_opt = 4096;
+ const size_t pcapng_max_iface_len = 256;
+
+ /* Maximum of the 2 types of .pcap blocks: */
+ data_max_len = OSMO_MAX(sizeof(struct pcap_file_header),
+ sizeof(struct osmo_pcap_pkthdr) + server->max_snaplen);
+
+ /* pcapng SHB: */
+ const size_t pcapng_shb_max_len = sizeof(struct pcapng_block_header) +
+ sizeof(struct pcapng_section_header_block) +
+ pcapng_max_len_opt +
+ sizeof(uint32_t);
+ /* pcapng IDB: */
+ const size_t pcapng_idb_max_len = sizeof(struct pcapng_block_header) +
+ sizeof(struct pcapng_iface_descr_block) +
+ pcapng_max_len_opt +
+ sizeof(uint32_t);
+ /* hdr_link for pcapng (SHB + N*IDB) */
+ const size_t pcapg_hdr_link_max_len = pcapng_shb_max_len + // SHB
+ (pcapng_max_iface_len * pcapng_idb_max_len); // N * IDB
+ data_max_len = OSMO_MAX(data_max_len, pcapg_hdr_link_max_len);
+
+ /* pcapng EPB: */
+ const size_t pcapng_epb_max_len = sizeof(struct pcapng_block_header) +
+ sizeof(struct pcapng_enhanced_packet_block) +
+ pcapng_max_len_opt +
+ sizeof(uint32_t);
+ data_max_len = OSMO_MAX(data_max_len, pcapng_epb_max_len + server->max_snaplen);
+ return data_max_len;
+}
+
static struct osmo_pcap_conn *osmo_pcap_conn_alloc(struct osmo_pcap_server *server,
const char *name)
{
@@ -512,8 +721,7 @@
return NULL;
}
- conn->data_max_len = OSMO_MAX(sizeof(struct pcap_file_header),
- sizeof(struct osmo_pcap_pkthdr) + server->max_snaplen);
+ conn->data_max_len = calc_data_max_len(server);
conn->data = talloc_zero_size(conn, sizeof(struct osmo_pcap_data) +
conn->data_max_len);
/* a bit nasty. we do not work with ids but names */
desc = talloc_zero(conn, struct rate_ctr_group_desc);
@@ -586,36 +794,6 @@
return do_read_tls(conn, buf, size);
}
-static bool pcap_data_valid(struct osmo_pcap_conn *conn)
-{
- unsigned int min_len, max_len;
- switch ((enum OsmoPcapDataType) conn->data->type) {
- case PKT_LINK_HDR:
- if (conn->data->len != sizeof(struct pcap_file_header)) {
- LOGP(DSERVER, LOGL_ERROR,
- "Implausible llink_hdr length: %u != %zu\n",
- conn->data->len, sizeof(struct osmo_pcap_pkthdr));
- return false;
- }
- break;
- case PKT_LINK_DATA:
- min_len = sizeof(struct osmo_pcap_pkthdr);
- max_len = conn->server->max_snaplen + sizeof(struct osmo_pcap_pkthdr);
- if (conn->data->len < min_len || conn->data->len > max_len) {
- LOGP(DSERVER, LOGL_ERROR,
- "Implausible data length: %u < %u <= %u\n",
- min_len, conn->data->len, max_len);
- return false;
- }
- break;
- default:
- LOGP(DSERVER, LOGL_ERROR, "Unknown data type %" PRIx8 "\n",
- conn->data->type);
- return false;
- }
- return true;
-}
-
/* Read segment header, struct osmo_pcap_data (without payload)
* returns >0 on success, <= 0 on failure (closes conn) */
static int read_cb_initial(struct osmo_pcap_conn *conn)
@@ -640,8 +818,11 @@
conn->data->len = ntohs(conn->data->len);
- if (!pcap_data_valid(conn))
+ if (conn->data->len > conn->data_max_len) {
+ LOGP(DSERVER, LOGL_ERROR, "Implausible data length: %u > %zu (snaplen
%u)\n",
+ conn->data->len, conn->data_max_len, conn->server->max_snaplen);
return -1;
+ }
conn->state = STATE_DATA;
conn->pend = conn->data->len;
@@ -766,7 +947,8 @@
{
close_connection(client);
- memset(&client->file_hdr, 0, sizeof(client->file_hdr));
+ TALLOC_FREE(client->file_hdr);
+ client->file_hdr_len = 0;
client->rem_wq.bfd.fd = new_fd;
if (osmo_fd_register(&client->rem_wq.bfd) != 0) {
LOGP(DSERVER, LOGL_ERROR, "Failed to register fd.\n");
diff --git a/tests/rotate_localtime/Makefile.am b/tests/rotate_localtime/Makefile.am
index fca9ff8..265e4fe 100644
--- a/tests/rotate_localtime/Makefile.am
+++ b/tests/rotate_localtime/Makefile.am
@@ -28,6 +28,7 @@
$(NULL)
rotate_localtime_test_LDADD = \
+ $(top_builddir)/src/osmo_pcap_file.o \
$(top_builddir)/src/osmo_server_network.o \
$(top_builddir)/src/osmo_server_stats.o \
$(top_builddir)/src/osmo_tls.o \
--
To view, visit
https://gerrit.osmocom.org/c/osmo-pcap/+/39287?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmo-pcap
Gerrit-Branch: master
Gerrit-Change-Id: I275d28ab418a1514fa9c5c7c20f3d831cc6af8bb
Gerrit-Change-Number: 39287
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin(a)sysmocom.de>