pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-pcap/+/39265?usp=email )
Change subject: WIP: Support generation and storing of pcapng format ......................................................................
WIP: Support generation and storing of pcapng format
TODO: * client: timestamp in EPB * server: detect pcap vs pcapng using magic field in link_hdr ** server: Do size checks based on pcap/pcapng knowledge ** server: Change saved file suffix based on pcap/pcapng knowledge.
Change-Id: I3c80518a1e53a1f77e1aca8dfa83f683f9516ad6 --- M include/osmo-pcap/osmo_pcap_client.h M include/osmo-pcap/osmo_pcap_server.h M src/Makefile.am M src/osmo_client_core.c M src/osmo_client_network.c A src/osmo_client_pcap.c M src/osmo_client_vty.c M src/osmo_server_network.c 8 files changed, 704 insertions(+), 86 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-pcap refs/changes/65/39265/1
diff --git a/include/osmo-pcap/osmo_pcap_client.h b/include/osmo-pcap/osmo_pcap_client.h index 250a41f..83621bc 100644 --- a/include/osmo-pcap/osmo_pcap_client.h +++ b/include/osmo-pcap/osmo_pcap_client.h @@ -61,6 +61,12 @@ PROTOCOL_IPIP, };
+enum osmo_pcap_fmt { + OSMO_PCAP_FMT_PCAP = 0, + OSMO_PCAP_FMT_PCAPNG, +}; + + struct osmo_pcap_client_conn { struct llist_head entry; const char *name; @@ -114,6 +120,7 @@ int filter_itself; int gprs_filtering; int snaplen; + enum osmo_pcap_fmt pcap_fmt;
struct osmo_pcap_client_conn *conn; struct llist_head conns; @@ -138,7 +145,9 @@ struct osmo_pcap_client_conn *osmo_client_conn_alloc(struct osmo_pcap_client *client, const char *name); void osmo_client_conn_free(struct osmo_pcap_client_conn *conn); void osmo_client_conn_send_data(struct osmo_pcap_client_conn *conn, - struct pcap_pkthdr *hdr, const uint8_t *data); + const struct osmo_pcap_handle *ph, + const struct pcap_pkthdr *pkthdr, + const uint8_t *data); void osmo_client_conn_send_link(struct osmo_pcap_client_conn *conn); void osmo_client_conn_connect(struct osmo_pcap_client_conn *conn); void osmo_client_conn_disconnect(struct osmo_pcap_client_conn *conn); @@ -149,6 +158,47 @@ void osmo_pcap_handle_free(struct osmo_pcap_handle *ph); int osmo_pcap_handle_start_capture(struct osmo_pcap_handle *ph);
+unsigned int osmo_pcap_file_global_header_size(void); +int osmo_pcap_file_msgb_append_global_header(struct msgb *msg, uint32_t snaplen, uint32_t linktype); +unsigned int osmo_pcap_file_record_size(const struct pcap_pkthdr *in_hdr); +int osmo_pcap_file_msgb_append_record(struct msgb *msg, const struct pcap_pkthdr *in_hdr, const uint8_t *data); + +struct osmo_pcapng_file_shb_pars { + const char *hardware; + const char *os; + const char *userappl; +}; +unsigned int osmo_pcapng_file_shb_size(const struct osmo_pcapng_file_shb_pars *pars); +int osmo_pcapng_file_msgb_append_shb(struct msgb *msg, const struct osmo_pcapng_file_shb_pars *pars); + +struct osmo_pcapng_file_idb_pars { + //char *comment; + const char *name; + //char *descr; + //char *filter; + //char *os; + //char *hardware; + int link_type; + int snap_len; + //uint64_t *bytes_written; + //uint64_t if_speed; + //uint8_t tsresol; +}; +unsigned int osmo_pcapng_file_idb_size(const struct osmo_pcapng_file_idb_pars *pars); +int osmo_pcapng_file_msgb_append_idb(struct msgb *msg, const struct osmo_pcapng_file_idb_pars *pars); + +struct osmo_pcapng_file_epb_pars { + uint32_t interface_id; + uint32_t timestamp_high; + uint32_t timestamp_low; + const uint8_t *captured_data; + uint32_t captured_len; + uint32_t packet_len; +}; +unsigned int osmo_pcapng_file_epb_size(const struct osmo_pcapng_file_epb_pars *pars); +int osmo_pcapng_file_msgb_append_epb(struct msgb *msg, const struct osmo_pcapng_file_epb_pars *pars); + + #define LOGCONN(conn, lvl, fmt, args...) \ LOGP(DCLIENT, lvl, "CONN(%s,%s:%d) " fmt, (conn)->name, (conn)->srv_ip, (conn)->srv_port, ## args)
diff --git a/include/osmo-pcap/osmo_pcap_server.h b/include/osmo-pcap/osmo_pcap_server.h index 614ceca..b3188a8 100644 --- a/include/osmo-pcap/osmo_pcap_server.h +++ b/include/osmo-pcap/osmo_pcap_server.h @@ -30,6 +30,7 @@ #include <osmocom/core/linuxlist.h> #include <osmocom/core/write_queue.h>
+#include <stdint.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> @@ -91,7 +92,8 @@ char *curr_filename;
/* pcap stuff */ - struct pcap_file_header file_hdr; + 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 b35227c..47f7795 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -17,9 +17,10 @@ osmo_client_main.c \ osmo_common.c \ osmo_client_core.c \ - osmo_client_vty.c \ + osmo_client_pcap.c \ osmo_client_network.c \ osmo_client_stats.c \ + osmo_client_vty.c \ osmo_tls.c \ $(NULL)
diff --git a/src/osmo_client_core.c b/src/osmo_client_core.c index a2ab894..dd16878 100644 --- a/src/osmo_client_core.c +++ b/src/osmo_client_core.c @@ -172,7 +172,7 @@ return 0;
llist_for_each_entry(conn, &client->conns, entry) - osmo_client_conn_send_data(conn, &hdr, data); + osmo_client_conn_send_data(conn, ph, &hdr, data); return 0; }
@@ -303,6 +303,7 @@ return NULL;
client->snaplen = DEFAULT_SNAPLEN; + client->pcap_fmt = OSMO_PCAP_FMT_PCAP; INIT_LLIST_HEAD(&client->handles); INIT_LLIST_HEAD(&client->conns);
diff --git a/src/osmo_client_network.c b/src/osmo_client_network.c index 70fe740..a9282e4 100644 --- a/src/osmo_client_network.c +++ b/src/osmo_client_network.c @@ -1,6 +1,7 @@ /* * osmo-pcap-client code * + * (C) 2025 by sysmocom - s.f.m.c. GmbH info@sysmocom.de * (C) 2011-2016 by Holger Hans Peter Freyther holger@moiji-mobile.com * (C) 2011 by On-Waves * All Rights Reserved @@ -24,6 +25,7 @@ #include <osmo-pcap/common.h> #include <osmo-pcap/wireformat.h>
+#include <osmocom/core/utils.h> #include <osmocom/core/msgb.h> #include <osmocom/core/rate_ctr.h> #include <osmocom/core/select.h> @@ -160,112 +162,287 @@ } }
-void osmo_client_conn_send_data(struct osmo_pcap_client_conn *conn, - struct pcap_pkthdr *in_hdr, const uint8_t *data) +static struct msgb *osmo_client_conn_prepare_msg_data_pcap(struct osmo_pcap_client_conn *conn, + const struct osmo_pcap_handle *ph, + const struct pcap_pkthdr *pkthdr, + const uint8_t *data) { struct osmo_pcap_data *om_hdr; - struct osmo_pcap_pkthdr *hdr; struct msgb *msg; - int offset, ip_len; - struct osmo_pcap_handle *ph; + unsigned int record_size = osmo_pcap_file_record_size(pkthdr);
- if (in_hdr->len > in_hdr->caplen) { - LOGCONN(conn, LOGL_ERROR, "Recording truncated packet, len %zu > snaplen %zu\n", - (size_t) in_hdr->len, (size_t) in_hdr->caplen); - rate_ctr_inc2(conn->client->ctrg, CLIENT_CTR_2BIG); - } - - msg = msgb_alloc(in_hdr->caplen + sizeof(*om_hdr) + sizeof(*hdr), "data-data"); + msg = msgb_alloc(sizeof(*om_hdr) + record_size, "pcap-data"); if (!msg) { LOGCONN(conn, LOGL_ERROR, "Failed to allocate\n"); rate_ctr_inc2(conn->client->ctrg, CLIENT_CTR_NOMEM); - return; + return NULL; + } + + om_hdr = (struct osmo_pcap_data *) msgb_put(msg, sizeof(*om_hdr)); + om_hdr->type = PKT_LINK_DATA; + om_hdr->len = htons(record_size); + osmo_pcap_file_msgb_append_record(msg, pkthdr, data); + + return msg; +} + +static struct msgb *osmo_client_conn_prepare_msg_data_pcapng(struct osmo_pcap_client_conn *conn, + const struct osmo_pcap_handle *ph, + const struct pcap_pkthdr *pkthdr, + const uint8_t *data) +{ + struct osmo_pcap_data *om_hdr; + struct msgb *msg; + struct osmo_pcapng_file_epb_pars epb_pars; + unsigned int record_size; + int rc; + + epb_pars = (struct osmo_pcapng_file_epb_pars){ + .interface_id = ph->idx, + .timestamp_high = 0, /* TODO: fill this properly */ + .timestamp_low = 0, /* TODO: fill this properly */ + .captured_data = data, + .captured_len = pkthdr->caplen, + .packet_len = pkthdr->len, + }; + + record_size = osmo_pcapng_file_epb_size(&epb_pars); + + msg = msgb_alloc(sizeof(*om_hdr) + record_size, "pcap-data"); + if (!msg) { + LOGCONN(conn, LOGL_ERROR, "Failed to allocate\n"); + rate_ctr_inc2(conn->client->ctrg, CLIENT_CTR_NOMEM); + return NULL; + } + + om_hdr = (struct osmo_pcap_data *) msgb_put(msg, sizeof(*om_hdr)); + om_hdr->type = PKT_LINK_DATA; + om_hdr->len = htons(record_size); + rc = osmo_pcapng_file_msgb_append_epb(msg, &epb_pars); + if (rc < 0) { + msgb_free(msg); + return NULL; + } + + return msg; +} + +static struct msgb *osmo_client_conn_prepare_msg_ipip(struct osmo_pcap_client_conn *conn, + const struct osmo_pcap_handle *ph, + const struct pcap_pkthdr *pkthdr, + const uint8_t *data) +{ + struct msgb *msg; + int offset, ip_len; + + offset = get_iphdr_offset(pcap_datalink(ph->handle)); + if (offset < 0) + return NULL; + + ip_len = pkthdr->caplen - offset; + if (ip_len < 0) + return NULL; + + + msg = msgb_alloc(ip_len, "ipip_msg"); + if (!msg) { + LOGP(DCLIENT, LOGL_ERROR, "Failed to allocate data.\n"); + rate_ctr_inc2(conn->client->ctrg, CLIENT_CTR_NOMEM); + return NULL; + } + msg->l2h = msgb_put(msg, ip_len); + memcpy(msg->l2h, data+offset, ip_len); + return msg; +} + +void osmo_client_conn_send_data(struct osmo_pcap_client_conn *conn, + const struct osmo_pcap_handle *ph, + const struct pcap_pkthdr *pkthdr, + const uint8_t *data) +{ + struct osmo_pcap_client *client = conn->client; + struct msgb *msg; + + if (pkthdr->len > pkthdr->caplen) { + LOGCONN(conn, LOGL_ERROR, "Recording truncated packet, len %zu > snaplen %zu\n", + (size_t) pkthdr->len, (size_t) pkthdr->caplen); + rate_ctr_inc2(client->ctrg, CLIENT_CTR_2BIG); }
switch (conn->protocol) { case PROTOCOL_OSMOPCAP: - om_hdr = (struct osmo_pcap_data *) msgb_put(msg, sizeof(*om_hdr)); - om_hdr->type = PKT_LINK_DATA; + switch (client->pcap_fmt) { + case OSMO_PCAP_FMT_PCAP: + msg = osmo_client_conn_prepare_msg_data_pcap(conn, ph, pkthdr, data); + break; + case OSMO_PCAP_FMT_PCAPNG: + msg = osmo_client_conn_prepare_msg_data_pcapng(conn, ph, pkthdr, data); + break; + default: + OSMO_ASSERT(0); + }
- msg->l2h = msgb_put(msg, sizeof(*hdr)); - hdr = (struct osmo_pcap_pkthdr *) msg->l2h; - hdr->ts_sec = in_hdr->ts.tv_sec; - hdr->ts_usec = in_hdr->ts.tv_usec; - hdr->caplen = in_hdr->caplen; - hdr->len = in_hdr->len; - - msg->l3h = msgb_put(msg, in_hdr->caplen); - memcpy(msg->l3h, data, in_hdr->caplen); - - om_hdr->len = htons(msgb_l2len(msg)); - rate_ctr_add2(conn->client->ctrg, CLIENT_CTR_BYTES, hdr->caplen); - rate_ctr_inc2(conn->client->ctrg, CLIENT_CTR_PKTS); break; case PROTOCOL_IPIP: - /* TODO: support capturing from multiple interfaces here: */ - ph = llist_first_entry_or_null(&conn->client->handles, struct osmo_pcap_handle, entry); - if (!ph) { - msgb_free(msg); - return; - } - offset = get_iphdr_offset(pcap_datalink(ph->handle)); - if (offset < 0) { - msgb_free(msg); - return; - } - ip_len = in_hdr->caplen - offset; - if (ip_len < 0) { - msgb_free(msg); - return; - } - msg->l2h = msgb_put(msg, ip_len); - memcpy(msg->l2h, data+offset, ip_len); + msg = osmo_client_conn_prepare_msg_ipip(conn, ph, pkthdr, data); break; default: OSMO_ASSERT(0); }
+ if (!msg) + return; + + rate_ctr_add2(conn->client->ctrg, CLIENT_CTR_BYTES, pkthdr->caplen); + rate_ctr_inc2(conn->client->ctrg, CLIENT_CTR_PKTS); + write_data(conn, msg); }
+static struct msgb *osmo_client_conn_prepare_msg_link_pcap(struct osmo_pcap_client_conn *conn) +{ + struct osmo_pcap_data *om_hdr; + struct msgb *msg; + struct osmo_pcap_handle *ph; + int rc; + int linktype; + + ph = llist_first_entry_or_null(&conn->client->handles, struct osmo_pcap_handle, entry); + if (!ph || !ph->handle) { + LOGCONN(conn, LOGL_ERROR, "No pcap_handle not sending link info\n"); + return NULL; + } + linktype = pcap_datalink(ph->handle); + + /* Make sure others have same linktype, .pcap doesn't support different + * linktypes since traffic from all ifaces goes mixed together. */ + llist_for_each_entry(ph, &conn->client->handles, entry) { + if (linktype != pcap_datalink(ph->handle)) { + LOGCONN(conn, LOGL_ERROR, + "File format 'pcap' doesn't support recording from multiple ifaces " + "with different link types! Use VTY config 'pcap file-format pcapng'.\n"); + return NULL; + } + } + + msg = msgb_alloc(sizeof(*om_hdr) + osmo_pcap_file_global_header_size(), "link-data"); + if (!msg) { + LOGP(DCLIENT, LOGL_ERROR, "Failed to allocate data.\n"); + return NULL; + } + + + om_hdr = (struct osmo_pcap_data *)msgb_put(msg, sizeof(*om_hdr)); + om_hdr->type = PKT_LINK_HDR; + + rc = osmo_pcap_file_msgb_append_global_header(msg, conn->client->snaplen, pcap_datalink(ph->handle)); + if (rc < 0) { + LOGP(DCLIENT, LOGL_ERROR, "Failed to create pcap file global header.\n"); + msgb_free(msg); + return NULL; + } + + /* Update payload length: */ + om_hdr->len = htons(rc); + return msg; +} + +struct osmo_pcapng_file_shb_pars shb_pars = { + .hardware = "osmo-pcap hw", + .os = "osmo-pcap os", + .userappl = "osmo-pcap userappl", +}; + +static int pcap_handle_prepare_pcapng_idb_pars(const struct osmo_pcap_handle *ph, + struct osmo_pcapng_file_idb_pars *pars) +{ + struct osmo_pcap_client *client = ph->client; + memset(pars, 0, sizeof(*pars)); + + pars->name = ph->devname; + //pars->filter = client->filter_string; + pars->link_type = pcap_datalink(ph->handle); + pars->snap_len = client->snaplen; + return 0; +} + +static struct msgb *osmo_client_conn_prepare_msg_link_pcapng(struct osmo_pcap_client_conn *conn) +{ + struct osmo_pcap_data *om_hdr; + struct msgb *msg; + struct osmo_pcap_handle *ph; + int rc; + uint32_t file_hdr_size = osmo_pcapng_file_shb_size(&shb_pars); + + /* Calculate size: */ + llist_for_each_entry(ph, &conn->client->handles, entry) { + struct osmo_pcapng_file_idb_pars idb_pars; + if (pcap_handle_prepare_pcapng_idb_pars(ph, &idb_pars) < 0) { + LOGPH(ph, LOGL_ERROR, "Failed preparing pcapng IDB from handle\n"); + return NULL; + } + file_hdr_size += osmo_pcapng_file_idb_size(&idb_pars); + } + + msg = msgb_alloc(sizeof(*om_hdr) + file_hdr_size, "link-data"); + if (!msg) { + LOGCONN(conn, LOGL_ERROR, "Failed to allocate data.\n"); + return NULL; + } + + om_hdr = (struct osmo_pcap_data *)msgb_put(msg, sizeof(*om_hdr)); + om_hdr->type = PKT_LINK_HDR; + + rc = osmo_pcapng_file_msgb_append_shb(msg, &shb_pars); + if (rc < 0) { + LOGCONN(conn, LOGL_ERROR, "Failed to create pcapng SHB\n"); + msgb_free(msg); + return NULL; + } + om_hdr->len = rc; + + llist_for_each_entry(ph, &conn->client->handles, entry) { + struct osmo_pcapng_file_idb_pars idb_pars; + if (pcap_handle_prepare_pcapng_idb_pars(ph, &idb_pars) < 0) { + LOGPH(ph, LOGL_ERROR, "Failed preparing pcapng IDB from handle\n"); + msgb_free(msg); + return NULL; + } + rc = osmo_pcapng_file_msgb_append_idb(msg, &idb_pars); + if (rc < 0) { + LOGPH(ph, LOGL_ERROR, "Failed to append pcapng IDB to msgb\n"); + msgb_free(msg); + return NULL; + } + om_hdr->len += rc; + } + + OSMO_ASSERT(om_hdr->len == file_hdr_size); + om_hdr->len = htons(om_hdr->len); + return msg; +} + void osmo_client_conn_send_link(struct osmo_pcap_client_conn *conn) { - struct osmo_pcap_handle *ph; - struct pcap_file_header *hdr; - struct osmo_pcap_data *om_hdr; struct msgb *msg;
/* IPIP encapsulation has no linktype header */ if (conn->protocol == PROTOCOL_IPIP) return;
- /* TODO: support capturing from multiple interfaces here: */ - ph = llist_first_entry_or_null(&conn->client->handles, struct osmo_pcap_handle, entry); - if (!ph || !ph->handle) { - LOGCONN(conn, LOGL_ERROR, "No pcap_handle not sending link info\n"); - return; + switch (conn->client->pcap_fmt) { + case OSMO_PCAP_FMT_PCAP: + msg = osmo_client_conn_prepare_msg_link_pcap(conn); + break; + case OSMO_PCAP_FMT_PCAPNG: + msg = osmo_client_conn_prepare_msg_link_pcapng(conn); + break; + default: + OSMO_ASSERT(0); }
- msg = msgb_alloc(sizeof(*om_hdr) + sizeof(*hdr), "link-data"); - if (!msg) { - LOGP(DCLIENT, LOGL_ERROR, "Failed to allocate data.\n"); + if (!msg) return; - } - - - om_hdr = (struct osmo_pcap_data *) msgb_put(msg, sizeof(*om_hdr)); - om_hdr->type = PKT_LINK_HDR; - om_hdr->len = htons(sizeof(*hdr)); - - hdr = (struct pcap_file_header *) msgb_put(msg, sizeof(*hdr)); - hdr->magic = 0xa1b2c3d4; - hdr->version_major = 2; - hdr->version_minor = 4; - hdr->thiszone = 0; - hdr->sigfigs = 0; - hdr->snaplen = conn->client->snaplen; - hdr->linktype = pcap_datalink(ph->handle); - write_data(conn, msg); }
diff --git a/src/osmo_client_pcap.c b/src/osmo_client_pcap.c new file mode 100644 index 0000000..e7b987e --- /dev/null +++ b/src/osmo_client_pcap.c @@ -0,0 +1,355 @@ +/* + * osmo-pcap-client code + * + * (C) 2025 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * (C) 2011-2016 by Holger Hans Peter Freyther holger@moiji-mobile.com + * (C) 2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <pcap/pcap.h> + +#include <osmocom/core/msgb.h> + +#include <osmo-pcap/osmo_pcap_client.h> +#include <osmo-pcap/common.h> +#include <osmo-pcap/wireformat.h> + +/*********************************************************** + * Libpcap File Format (.pcap) + * https://wiki.wireshark.org/Development/LibpcapFileFormat + ***********************************************************/ + +/* Get required length to store a Global Header */ +unsigned int osmo_pcap_file_global_header_size(void) +{ + return sizeof(struct pcap_file_header); +} + +/* Appends a Global Header to msg + * returns number of bytes appended on success, negative on error */ +int osmo_pcap_file_msgb_append_global_header(struct msgb *msg, uint32_t snaplen, uint32_t linktype) +{ + struct pcap_file_header *hdr; + + hdr = (struct pcap_file_header *) msgb_put(msg, sizeof(*hdr)); + hdr->magic = 0xa1b2c3d4; + hdr->version_major = 2; + hdr->version_minor = 4; + hdr->thiszone = 0; + hdr->sigfigs = 0; + hdr->snaplen = snaplen; + hdr->linktype = linktype; + + return sizeof(*hdr); +} + +/* Get required length to store a given record (packet) */ +unsigned int osmo_pcap_file_record_size(const struct pcap_pkthdr *in_hdr) +{ + return sizeof(struct osmo_pcap_pkthdr) + in_hdr->caplen; +} + +/* Appends a Record (Packet) Header to msg + * returns number of bytes appended on success, negative on error */ +int osmo_pcap_file_msgb_append_record(struct msgb *msg, const struct pcap_pkthdr *in_hdr, const uint8_t *data) +{ + struct osmo_pcap_pkthdr *hdr; + uint8_t *pkt_payload; + + hdr = (struct osmo_pcap_pkthdr *) msgb_put(msg, sizeof(*hdr)); + hdr->ts_sec = in_hdr->ts.tv_sec; + hdr->ts_usec = in_hdr->ts.tv_usec; + hdr->caplen = in_hdr->caplen; + hdr->len = in_hdr->len; + + pkt_payload = msgb_put(msg, in_hdr->caplen); + memcpy(pkt_payload, data, in_hdr->caplen); + + return osmo_pcap_file_record_size(in_hdr); +} + + +/*********************************************************** + * PCAP Next Generation (pcapng) Capture File Format + * https://wiki.wireshark.org/Development/PcapNg + * https://ietf-opsawg-wg.github.io/draft-ietf-opsawg-pcap/draft-ietf-opsawg-pc... + * wireshark.git: wiretap/pcapng.h, wiretap/pcapng.c, wiretap/pcapng_module.h + ***********************************************************/ + +#define BLOCK_TYPE_SHB 0x0A0D0D0A /* Section Header Block */ +#define BLOCK_TYPE_IDB 0x00000001 /* Interface Description Block */ +#define BLOCK_TYPE_PB 0x00000002 /* Packet Block (obsolete) */ +#define BLOCK_TYPE_SPB 0x00000003 /* Simple Packet Block */ +#define BLOCK_TYPE_EPB 0x00000006 /* Enhanced Packet Block */ + +/* Options for all blocks */ +#define OPT_EOFOPT 0 +#define OPT_COMMENT 1 +/* Section Header block (SHB) */ +#define OPT_SHB_HARDWARE 2 +#define OPT_SHB_OS 3 +#define OPT_SHB_USERAPPL 4 + +/* Interface Description block (IDB) */ +#define OPT_IDB_NAME 2 +#define OPT_IDB_DESCRIPTION 3 +#define OPT_IDB_IP4ADDR 4 +#define OPT_IDB_IP6ADDR 5 +#define OPT_IDB_MACADDR 6 +#define OPT_IDB_FILTER 11 + +/* pcapng: common block header file encoding for every block type */ +struct pcapng_block_header { + uint32_t block_type; + uint32_t block_total_length; + /* x bytes block_body */ + /* uint32_t block_total_length */ +} __attribute__((packed)); + +struct pcapng_option_header { + uint16_t type; + uint16_t value_length; +} __attribute__((packed)); + +/* pcapng: section header block file encoding */ +struct pcapng_section_header_block { + /* pcapng_block_header_t */ + uint32_t magic; + uint16_t version_major; + uint16_t version_minor; + uint64_t section_length; /* might be -1 for unknown */ + /* ... Options ... */ +} __attribute__((packed)); + +/* pcapng: interface description block file encoding */ +struct pcapng_iface_descr_block { + uint16_t linktype; + uint16_t reserved; + uint32_t snaplen; + /* ... Options ... */ +} __attribute__((packed)); + +/* pcapng: enhanced packet block file encoding */ +struct pcapng_enhanced_packet_block { + uint32_t interface_id; + uint32_t timestamp_high; + uint32_t timestamp_low; + uint32_t captured_len; + uint32_t packet_len; + /* ... Packet Data ... */ + /* ... Padding ... */ + /* ... Options ... */ +} __attribute__((packed)); + +/* + * Minimum IDB size = minimum block size + size of fixed length portion of IDB. + */ +#define MIN_IDB_SIZE ((uint32_t)(MIN_BLOCK_SIZE + sizeof(pcapng_interface_description_block_t))) + +/* Get required length to store a given record (packet) */ +static unsigned int osmo_pcapng_file_opt_string_size(const char *str) +{ + size_t str_len = str ? strlen(str) : 0; + uint8_t pad = str_len % 4; + /* Each option is padded to 4 bytes: */ + if (pad) + pad = 4 - pad; + return sizeof(struct pcapng_option_header) + str_len + pad; +} + +static int osmo_pcapng_file_msgb_append_opt_string(struct msgb *msg, uint16_t type, const char *str) +{ + struct pcapng_option_header *opth; + size_t str_len = str ? strlen(str) : 0; + + opth = (struct pcapng_option_header *)msgb_put(msg, sizeof(*opth)); + opth->type = type; + opth->value_length = str_len; + if (str_len > 0) + memcpy(msgb_put(msg, str_len), str, str_len); + + /* Each option is padded to 4 bytes: */ + uint8_t pad = str_len % 4; + if (pad) { + pad = 4 - pad; + uint8_t *buf = (uint8_t *)msgb_put(msg, pad); + memset(buf, 0, pad); + } + return sizeof(*opth) + opth->value_length + pad; +} + +/* Get required length to store a given record (packet) */ +static unsigned int osmo_pcapng_file_opt_eofopt_size(void) +{ + return sizeof(struct pcapng_option_header); +} + +static int osmo_pcapng_file_msgb_append_opt_eofopt(struct msgb *msg) +{ + struct pcapng_option_header *opth; + + opth = (struct pcapng_option_header *)msgb_put(msg, sizeof(*opth)); + opth->type = OPT_EOFOPT; + opth->value_length = 0; + + return sizeof(*opth); +} + +/* Get required length to store a given record (packet) */ +unsigned int osmo_pcapng_file_shb_size(const struct osmo_pcapng_file_shb_pars *pars) +{ + uint32_t block_total_len = sizeof(struct pcapng_block_header) + + sizeof(struct pcapng_section_header_block) + + sizeof(uint32_t); + block_total_len += osmo_pcapng_file_opt_string_size(pars->hardware); + block_total_len += osmo_pcapng_file_opt_string_size(pars->os); + block_total_len += osmo_pcapng_file_opt_string_size(pars->userappl); + block_total_len += osmo_pcapng_file_opt_eofopt_size(); + return block_total_len; +} + +/* Appends a Section Header Block (SHB) to msg + * returns number of bytes appended on success, negative on error */ +int osmo_pcapng_file_msgb_append_shb(struct msgb *msg, const struct osmo_pcapng_file_shb_pars *pars) +{ + struct pcapng_block_header *bh; + struct pcapng_section_header_block *shb; + uint8_t *footer_len; + uint32_t block_total_len = osmo_pcapng_file_shb_size(pars); + + bh = (struct pcapng_block_header *)msgb_put(msg, sizeof(*bh)); + bh->block_type = BLOCK_TYPE_SHB; + bh->block_total_length = block_total_len; + + /* write block fixed content */ + shb = (struct pcapng_section_header_block *)msgb_put(msg, sizeof(*shb)); + shb->magic = 0x1A2B3C4D; + shb->version_major = 1; + shb->version_minor = 0; + shb->section_length = -1; + + /* Options (variable) */ + osmo_pcapng_file_msgb_append_opt_string(msg, OPT_SHB_HARDWARE, pars->hardware); + osmo_pcapng_file_msgb_append_opt_string(msg, OPT_SHB_OS, pars->os); + osmo_pcapng_file_msgb_append_opt_string(msg, OPT_SHB_USERAPPL, pars->userappl); + osmo_pcapng_file_msgb_append_opt_eofopt(msg); + + /* SHB Block Total Length */ + footer_len = (uint8_t *)msgb_put(msg, sizeof(uint32_t)); + memcpy(footer_len, &block_total_len, sizeof(uint32_t)); + + return block_total_len; +} + +unsigned int osmo_pcapng_file_idb_size(const struct osmo_pcapng_file_idb_pars *pars) +{ + uint32_t block_total_len = sizeof(struct pcapng_block_header) + + sizeof(struct pcapng_iface_descr_block) + + sizeof(uint32_t); + block_total_len += osmo_pcapng_file_opt_string_size(pars->name); + //block_total_len += osmo_pcapng_file_opt_string_size(pars->filter); + block_total_len += osmo_pcapng_file_opt_eofopt_size(); + return block_total_len; +} + +int osmo_pcapng_file_msgb_append_idb(struct msgb *msg, const struct osmo_pcapng_file_idb_pars *pars) +{ + struct pcapng_block_header *bh; + struct pcapng_iface_descr_block *idb; + uint8_t *footer_len; + uint32_t block_total_len = osmo_pcapng_file_idb_size(pars); + + bh = (struct pcapng_block_header *)msgb_put(msg, sizeof(*bh)); + bh->block_type = BLOCK_TYPE_IDB; + bh->block_total_length = block_total_len; + + /* write block fixed content */ + idb = (struct pcapng_iface_descr_block *)msgb_put(msg, sizeof(*idb)); + idb->linktype = pars->link_type; + idb->reserved = 0; + idb->snaplen = pars->snap_len; + + /* Options (variable) */ + osmo_pcapng_file_msgb_append_opt_string(msg, OPT_IDB_NAME, pars->name); + // TODO: filter is not a string opt, it has an extra prepended byte.... + //osmo_pcapng_file_msgb_append_opt_string(msg, OPT_IDB_FILTER, pars->filter); + osmo_pcapng_file_msgb_append_opt_eofopt(msg); + + /* IDB Block Total Length */ + footer_len = (uint8_t *)msgb_put(msg, sizeof(uint32_t)); + memcpy(footer_len, &block_total_len, sizeof(uint32_t)); + + return block_total_len; +} + +unsigned int osmo_pcapng_file_epb_size(const struct osmo_pcapng_file_epb_pars *pars) +{ + uint32_t block_total_len = sizeof(struct pcapng_block_header) + + sizeof(struct pcapng_enhanced_packet_block) + + pars->captured_len + + sizeof(uint32_t); + /* Packet data is padded to 4 bytes: */ + uint8_t pad = pars->captured_len % 4; + if (pad) + block_total_len += (4 - pad); + + /* TODO: other Options */ + block_total_len += osmo_pcapng_file_opt_eofopt_size(); + return block_total_len; +} + +int osmo_pcapng_file_msgb_append_epb(struct msgb *msg, const struct osmo_pcapng_file_epb_pars *pars) +{ + struct pcapng_block_header *bh; + struct pcapng_enhanced_packet_block *epb; + uint8_t *footer_len; + uint32_t block_total_len = osmo_pcapng_file_epb_size(pars); + + bh = (struct pcapng_block_header *)msgb_put(msg, sizeof(*bh)); + bh->block_type = BLOCK_TYPE_EPB; + bh->block_total_length = block_total_len; + + /* write block fixed content */ + epb = (struct pcapng_enhanced_packet_block *)msgb_put(msg, sizeof(*epb)); + epb->interface_id = pars->interface_id; + epb->timestamp_high = 0; /* TODO: apply... */ + epb->timestamp_low = 0; /* TODO: apply... */ + epb->captured_len = pars->captured_len; + epb->packet_len = pars->packet_len; + + /* Packet Data */ + if (pars->captured_len > 0) + memcpy(msgb_put(msg, pars->captured_len), pars->captured_data, pars->captured_len); + + /* Each option is padded to 4 bytes: */ + uint8_t pad = pars->captured_len % 4; + if (pad) { + pad = 4 - pad; + uint8_t *buf = (uint8_t *)msgb_put(msg, pad); + memset(buf, 0, pad); + } + + /* Options (variable) */ + osmo_pcapng_file_msgb_append_opt_eofopt(msg); + + /* EPB Block Total Length */ + footer_len = (uint8_t *)msgb_put(msg, sizeof(uint32_t)); + memcpy(footer_len, &block_total_len, sizeof(uint32_t)); + + return block_total_len; +} diff --git a/src/osmo_client_vty.c b/src/osmo_client_vty.c index 009c735..1824324 100644 --- a/src/osmo_client_vty.c +++ b/src/osmo_client_vty.c @@ -1,6 +1,7 @@ /* * osmo-pcap-client code * + * (C) 2025 by sysmocom - s.f.m.c. GmbH info@sysmocom.de * (C) 2011-2016 by Holger Hans Peter Freyther holger@moiji-mobile.com * (C) 2011 by On-Waves * All Rights Reserved @@ -130,6 +131,8 @@
vty_out(vty, "client%s", VTY_NEWLINE);
+ if (pcap_client->pcap_fmt != OSMO_PCAP_FMT_PCAP) + vty_out(vty, " pcap file-format pcapng%s", VTY_NEWLINE); llist_for_each_entry(ph, &pcap_client->handles, entry) { vty_out(vty, " pcap device %s%s", ph->devname, VTY_NEWLINE); @@ -150,6 +153,22 @@ return CMD_SUCCESS; }
+DEFUN(cfg_client_pcap_file_format, + cfg_client_pcap_file_format_cmd, + "pcap file-format (pcap|pcapng)", + PCAP_STRING "The pcap file format to use\n" + "Libpcap Capture File Format (.pcap)\n" + "PCAP Next Generation Capture File Format (.pcapng)\n") +{ + if (strcmp(argv[0], "pcap") == 0) + pcap_client->pcap_fmt = OSMO_PCAP_FMT_PCAP; + else if (strcmp(argv[0], "pcapng") == 0) + pcap_client->pcap_fmt = OSMO_PCAP_FMT_PCAPNG; + else + return CMD_WARNING; + return CMD_SUCCESS; +} + DEFUN(cfg_client_no_device, cfg_client_no_device_cmd, "no pcap device NAME", @@ -559,6 +578,7 @@
install_node(&server_node, config_write_server);
+ install_element(CLIENT_NODE, &cfg_client_pcap_file_format_cmd); install_element(CLIENT_NODE, &cfg_client_no_device_cmd); install_element(CLIENT_NODE, &cfg_client_device_cmd); install_element(CLIENT_NODE, &cfg_client_snaplen_cmd); diff --git a/src/osmo_server_network.c b/src/osmo_server_network.c index 9750170..86afc90 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, @@ -285,8 +285,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; @@ -298,6 +298,8 @@
static int link_data(struct osmo_pcap_conn *conn, struct osmo_pcap_data *data) { +#if 0 + /* TODO: discover pcap vs pcapng based on "magic" field. Check minimum size. */ struct pcap_file_header *hdr;
hdr = (struct pcap_file_header *) &data->data[0]; @@ -308,12 +310,20 @@ (size_t) hdr->snaplen, (size_t) conn->server->max_snaplen); return -1; } +#endif
if (!conn->no_store && conn->local_fd < 0) { - 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) { - conn->file_hdr = *hdr; + } else if (conn->file_hdr_len != data->len || + memcmp(&conn->file_hdr, data->data, data->len) != 0) { + 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); }
@@ -583,9 +593,10 @@ 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)) { + /* TODO: discover pcap vs pcapng based on "magic" field. */ + if (conn->data->len < sizeof(struct pcap_file_header)) { LOGP(DSERVER, LOGL_ERROR, - "Implausible llink_hdr length: %u != %zu\n", + "Implausible llink_hdr length: %u < %zu\n", conn->data->len, sizeof(struct osmo_pcap_pkthdr)); return false; } @@ -747,7 +758,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");