summaryrefslogtreecommitdiffstats
path: root/lib/util/xpcapng.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/util/xpcapng.c635
1 files changed, 635 insertions, 0 deletions
diff --git a/lib/util/xpcapng.c b/lib/util/xpcapng.c
new file mode 100644
index 0000000..e453b88
--- /dev/null
+++ b/lib/util/xpcapng.c
@@ -0,0 +1,635 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Description:
+ * Simple PcapNG library developed from scratch as no library existed that
+ * met the requirements for xdpdump. It can also be used by other XDP
+ * applications that would like to capture packets for debugging purposes.
+ */
+
+/*****************************************************************************
+ * Include files
+ *****************************************************************************/
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/uio.h>
+
+#include "xpcapng.h"
+
+/*****************************************************************************
+ * Simple roundup() macro
+ *****************************************************************************/
+#ifndef roundup
+#define roundup(x, y) ( \
+{ \
+ typeof(y) __y = y; \
+ (((x) + (__y - 1)) / __y) * __y; \
+} \
+)
+#endif
+
+/*****************************************************************************
+ * pcapng_dumper structure
+ *****************************************************************************/
+struct xpcapng_dumper {
+ int pd_fd;
+ uint32_t pd_interfaces;
+};
+
+/*****************************************************************************
+ * general pcapng block and option definitions
+ *****************************************************************************/
+enum pcapng_block_types {
+ PCAPNG_SECTION_BLOCK = 0x0A0D0D0A,
+ PCAPNG_INTERFACE_BLOCK = 1,
+ PCAPNG_PACKET_BLOCK,
+ PCAPNG_SIMPLE_PACKET_BLOCK,
+ PCAPNG_NAME_RESOLUTION_BLOCK,
+ PCAPNG_INTERFACE_STATS_BLOCK,
+ PCAPNG_ENHANCED_PACKET_BLOCK
+};
+
+struct pcapng_option {
+ uint16_t po_type;
+ uint16_t po_length;
+ uint8_t po_data[];
+} __attribute__((__packed__));
+
+enum pcapng_opt {
+ PCAPNG_OPT_END = 0,
+ PCAPNG_OPT_COMMENT = 1,
+ PCAPNG_OPT_CUSTOME_2988 = 2988,
+ PCAPNG_OPT_CUSTOME_2989 = 2989,
+ PCAPNG_OPT_CUSTOME_19372 = 19372,
+ PCAPNG_OPT_CUSTOME_19373 = 29373
+};
+
+/*****************************************************************************
+ * pcapng section header block definitions
+ *****************************************************************************/
+struct pcapng_section_header_block {
+ uint32_t shb_block_type;
+ uint32_t shb_block_length;
+ uint32_t shb_byte_order_magic;
+ uint16_t shb_major_version;
+ uint16_t shb_minor_version;
+ uint64_t shb_section_length;
+ uint8_t shb_options[];
+ /* The options are followed by another:
+ * uint32_t shb_block_length;
+ */
+} __attribute__((__packed__));
+
+#define PCAPNG_BYTE_ORDER_MAGIC 0x1A2B3C4D
+#define PCAPNG_MAJOR_VERSION 1
+#define PCAPNG_MINOR_VERSION 0
+
+enum pcapng_opt_shb {
+ PCAPNG_OPT_SHB_HARDWARE = 2,
+ PCAPNG_OPT_SHB_OS,
+ PCAPNG_OPT_SHB_USERAPPL
+};
+
+/*****************************************************************************
+ * pcapng interface description block definitions
+ *****************************************************************************/
+struct pcapng_interface_description_block {
+ uint32_t idb_block_type;
+ uint32_t idb_block_length;
+ uint16_t idb_link_type;
+ uint16_t idb_reserved;
+ uint32_t idb_snap_len;
+ uint8_t idb_options[];
+ /* The options are followed by another:
+ * uint32_t idb_block_length;
+ */
+} __attribute__((__packed__));
+
+enum pcapng_opt_idb {
+ PCAPNG_OPT_IDB_IF_NAME = 2,
+ PCAPNG_OPT_IDB_IF_DESCRIPTION,
+ PCAPNG_OPT_IDB_IF_IPV4_ADDR,
+ PCAPNG_OPT_IDB_IF_IPV6_ADDR,
+ PCAPNG_OPT_IDB_IF_MAC_ADDR,
+ PCAPNG_OPT_IDB_IF_EUI_ADDR,
+ PCAPNG_OPT_IDB_IF_SPEED,
+ PCAPNG_OPT_IDB_IF_TSRESOL,
+ PCAPNG_OPT_IDB_IF_TZONE,
+ PCAPNG_OPT_IDB_IF_FILTER,
+ PCAPNG_OPT_IDB_IF_OS,
+ PCAPNG_OPT_IDB_IF_FCSLEN,
+ PCAPNG_OPT_IDB_IF_TOFFSET,
+ PCAPNG_OPT_IDB_IF_HARDWARE
+};
+
+/*****************************************************************************
+ * pcapng interface description block definitions
+ *****************************************************************************/
+struct pcapng_enhanced_packet_block {
+ uint32_t epb_block_type;
+ uint32_t epb_block_length;
+ uint32_t epb_interface_id;
+ uint32_t epb_timestamp_hi;
+ uint32_t epb_timestamp_low;
+ uint32_t epb_captured_length;
+ uint32_t epb_original_length;
+ uint8_t epb_packet_data[];
+ /* The packet data is followed by:
+ * uint8_t epb_options[];
+ * uint32_t epb_block_length;
+ */
+} __attribute__((__packed__));
+
+enum pcapng_opt_epb {
+ PCAPNG_OPT_EPB_FLAGS = 2,
+ PCAPNG_OPT_EPB_HASH,
+ PCAPNG_OPT_EPB_DROPCOUNT,
+ PCAPNG_OPT_EPB_PACKETID,
+ PCAPNG_OPT_EPB_QUEUE,
+ PCAPNG_OPT_EPB_VERDICT
+};
+
+enum pcapng_epb_vedict_type {
+ PCAPNG_EPB_VEDRICT_TYPE_HARDWARE = 0,
+ PCAPNG_EPB_VEDRICT_TYPE_EBPF_TC,
+ PCAPNG_EPB_VEDRICT_TYPE_EBPF_XDP
+};
+
+/*****************************************************************************
+ * pcapng_get_option_length()
+ *****************************************************************************/
+static size_t pcapng_get_option_length(size_t len)
+{
+ return roundup(sizeof(struct pcapng_option) + len, sizeof(uint32_t));
+}
+
+/*****************************************************************************
+ * pcapng_add_option()
+ *****************************************************************************/
+static struct pcapng_option *pcapng_add_option(struct pcapng_option *opt,
+ uint16_t type, uint16_t length,
+ const void *data)
+{
+ if (opt == NULL)
+ return NULL;
+
+ opt->po_type = type;
+ opt->po_length = length;
+ if (data)
+ memcpy(opt->po_data, data, length);
+
+ return (struct pcapng_option *)
+ ((uint8_t *)opt + pcapng_get_option_length(length));
+}
+
+/*****************************************************************************
+ * pcapng_write_shb()
+ *****************************************************************************/
+static bool pcapng_write_shb(struct xpcapng_dumper *pd, const char *comment,
+ const char *hardware, const char *os,
+ const char *user_application)
+{
+ int rc;
+ size_t shb_length;
+ struct pcapng_section_header_block *shb;
+ struct pcapng_option *opt;
+
+ if (pd == NULL) {
+ errno = EINVAL;
+ return false;
+ }
+
+ /* First calculate the total length of the SHB. */
+ shb_length = sizeof(*shb);
+
+ if (comment)
+ shb_length += pcapng_get_option_length(strlen(comment));
+
+ if (hardware)
+ shb_length += pcapng_get_option_length(strlen(hardware));
+
+ if (os)
+ shb_length += pcapng_get_option_length(strlen(os));
+
+ if (user_application)
+ shb_length += pcapng_get_option_length(
+ strlen(user_application));
+
+ shb_length += pcapng_get_option_length(0);
+ shb_length += sizeof(uint32_t);
+
+ /* Allocate the SHB and fill it. */
+ shb = calloc(shb_length, 1);
+ if (shb == NULL) {
+ errno = ENOMEM;
+ return false;
+ }
+
+ shb->shb_block_type = PCAPNG_SECTION_BLOCK;
+ shb->shb_block_length = shb_length;
+ shb->shb_byte_order_magic = PCAPNG_BYTE_ORDER_MAGIC;
+ shb->shb_major_version = PCAPNG_MAJOR_VERSION;
+ shb->shb_minor_version = PCAPNG_MINOR_VERSION;
+ shb->shb_section_length = UINT64_MAX;
+
+ /* Add the options and block_length value */
+ opt = (struct pcapng_option *) &shb->shb_options;
+
+ if (comment)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_COMMENT,
+ strlen(comment), comment);
+
+ if (hardware)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_SHB_HARDWARE,
+ strlen(hardware), hardware);
+
+ if (os)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_SHB_OS,
+ strlen(os), os);
+
+ if (user_application)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_SHB_USERAPPL,
+ strlen(user_application),
+ user_application);
+ /* WARNING: If a new option is added, make sure the length calculation
+ * above is also updated!
+ */
+
+ opt = pcapng_add_option(opt, PCAPNG_OPT_END, 0, NULL);
+ memcpy(opt, &shb->shb_block_length, sizeof(shb->shb_block_length));
+
+ /* Write the SHB, and free its memory. */
+ rc = write(pd->pd_fd, shb, shb_length);
+ free(shb);
+
+ if ((size_t)rc != shb_length)
+ return false;
+
+ return true;
+}
+
+/*****************************************************************************
+ * pcapng_write_idb()
+ *****************************************************************************/
+static bool pcapng_write_idb(struct xpcapng_dumper *pd, const char *name,
+ uint16_t snap_len, const char *description,
+ const uint8_t *mac, uint64_t speed,
+ uint8_t ts_resolution, const char *hardware)
+{
+ int rc;
+ size_t idb_length;
+ struct pcapng_interface_description_block *idb;
+ struct pcapng_option *opt;
+
+ if (pd == NULL) {
+ errno = EINVAL;
+ return false;
+ }
+
+ /* First calculate the total length of the IDB. */
+ idb_length = sizeof(*idb);
+
+ if (name)
+ idb_length += pcapng_get_option_length(strlen(name));
+
+ if (description)
+ idb_length += pcapng_get_option_length(strlen(description));
+
+ if (mac)
+ idb_length += pcapng_get_option_length(6);
+
+ if (speed)
+ idb_length += pcapng_get_option_length(sizeof(uint64_t));
+
+ if (ts_resolution != 6 && ts_resolution != 0)
+ idb_length += pcapng_get_option_length(1);
+
+ if (hardware)
+ idb_length += pcapng_get_option_length(strlen(hardware));
+
+ idb_length += pcapng_get_option_length(0);
+ idb_length += sizeof(uint32_t);
+
+ /* Allocate the IDB and fill it. */
+ idb = calloc(idb_length, 1);
+ if (idb == NULL) {
+ errno = ENOMEM;
+ return false;
+ }
+
+ idb->idb_block_type = PCAPNG_INTERFACE_BLOCK;
+ idb->idb_block_length = idb_length;
+ idb->idb_link_type = 1; /* Ethernet */
+ idb->idb_snap_len = snap_len;
+
+ /* Add the options and block_length value */
+ opt = (struct pcapng_option *) &idb->idb_options;
+
+ if (name)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_IDB_IF_NAME,
+ strlen(name), name);
+
+ if (description)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_IDB_IF_DESCRIPTION,
+ strlen(description), description);
+
+ if (mac)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_IDB_IF_MAC_ADDR, 6,
+ mac);
+
+ if (speed)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_IDB_IF_SPEED,
+ sizeof(uint64_t), &speed);
+
+ if (ts_resolution != 6 && ts_resolution != 0)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_IDB_IF_TSRESOL,
+ sizeof(uint8_t), &ts_resolution);
+
+ if (hardware)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_IDB_IF_HARDWARE,
+ strlen(hardware), hardware);
+ /* WARNING: If a new option is added, make sure the length calculation
+ * above is also updated!
+ */
+
+ opt = pcapng_add_option(opt, PCAPNG_OPT_END, 0, NULL);
+ memcpy(opt, &idb->idb_block_length, sizeof(idb->idb_block_length));
+
+ /* Write the IDB, and free it's memory. */
+ rc = write(pd->pd_fd, idb, idb_length);
+ free(idb);
+
+ if ((size_t)rc != idb_length)
+ return false;
+
+ return true;
+}
+
+/*****************************************************************************
+ * pcapng_write_epb()
+ *****************************************************************************/
+static bool pcapng_write_epb(struct xpcapng_dumper *pd, uint32_t ifid,
+ const uint8_t *pkt, uint32_t len,
+ uint32_t caplen, uint64_t timestamp,
+ struct xpcapng_epb_options_s *epb_options)
+{
+ int i = 0;
+ int rc;
+ size_t pad_length;
+ size_t com_length = 0;
+ size_t epb_length;
+ struct pcapng_enhanced_packet_block epb;
+ struct pcapng_option *opt;
+ struct iovec iov[7];
+ static uint8_t pad[4] = {0, 0, 0, 0};
+ uint8_t options[8 + 12 + 12 + 8 + 16 + 4 + 4];
+ /* PCAPNG_OPT_EPB_FLAGS[8] +
+ * PCAPNG_OPT_EPB_DROPCOUNT[12] +
+ * PCAPNG_OPT_EPB_PACKETID[12] +
+ * PCAPNG_OPT_EPB_QUEUE[8] +
+ * PCAPNG_OPT_EPB_VERDICT[16] +
+ * PCAPNG_OPT_END[4] +
+ * epb_block_length
+ */
+ static struct xdp_verdict {
+ uint8_t type;
+ int64_t verdict;
+ }__attribute__((__packed__)) verdict = {
+ PCAPNG_EPB_VEDRICT_TYPE_EBPF_XDP, 0 };
+
+ if (pd == NULL) {
+ errno = EINVAL;
+ return false;
+ }
+
+ /* First calculate the total length of the EPB. */
+ pad_length = roundup(caplen, sizeof(uint32_t)) - caplen;
+
+ epb_length = sizeof(epb);
+ epb_length += caplen + pad_length;
+
+ if (epb_options->flags)
+ epb_length += pcapng_get_option_length(sizeof(uint32_t));
+
+ if (epb_options->dropcount)
+ epb_length += pcapng_get_option_length(sizeof(uint64_t));
+
+ if (epb_options->packetid)
+ epb_length += pcapng_get_option_length(sizeof(uint64_t));
+
+ if (epb_options->queue)
+ epb_length += pcapng_get_option_length(sizeof(uint32_t));
+
+ if (epb_options->xdp_verdict)
+ epb_length += pcapng_get_option_length(sizeof(verdict));
+
+ if (epb_options->comment) {
+ com_length = strlen(epb_options->comment);
+ epb_length += pcapng_get_option_length(com_length);
+ }
+
+ epb_length += pcapng_get_option_length(0);
+ epb_length += sizeof(uint32_t);
+
+ /* Fill in the EPB. */
+ epb.epb_block_type = PCAPNG_ENHANCED_PACKET_BLOCK;
+ epb.epb_block_length = epb_length;
+ epb.epb_interface_id = ifid;
+ epb.epb_timestamp_hi = timestamp >> 32;
+ epb.epb_timestamp_low = (uint32_t) timestamp;
+ epb.epb_captured_length = caplen;
+ epb.epb_original_length = len;
+
+ /* Add the flag/end option and block_length value */
+ opt = (struct pcapng_option *) options;
+
+ if (epb_options->flags)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_EPB_FLAGS,
+ sizeof(uint32_t), &epb_options->flags);
+
+ if (epb_options->dropcount)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_EPB_DROPCOUNT,
+ sizeof(uint64_t),
+ &epb_options->dropcount);
+
+ if (epb_options->packetid)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_EPB_PACKETID,
+ sizeof(uint64_t),
+ epb_options->packetid);
+
+ if (epb_options->queue)
+ opt = pcapng_add_option(opt, PCAPNG_OPT_EPB_QUEUE,
+ sizeof(uint32_t), epb_options->queue);
+
+ if (epb_options->xdp_verdict) {
+ verdict.verdict = *epb_options->xdp_verdict;
+ opt = pcapng_add_option(opt, PCAPNG_OPT_EPB_VERDICT,
+ sizeof(verdict), &verdict);
+ }
+ /* WARNING: If a new option is added, make sure the length calculation
+ * and the options[] variable above are also updated!
+ */
+
+ opt = pcapng_add_option(opt, PCAPNG_OPT_END, 0, NULL);
+ memcpy(opt, &epb.epb_block_length, sizeof(epb.epb_block_length));
+
+ /* Write the EPB in parts, including the options, this looks not as
+ * straightforward as pcapng_write_idb() but here we would like to
+ * avoid as many memcopy's as possible.
+ */
+
+ /* Add base EPB structure. */
+ iov[i].iov_base = &epb;
+ iov[i++].iov_len = sizeof(epb);
+
+ /* Add Packet Data. */
+ iov[i].iov_base = (void *)pkt;
+ iov[i++].iov_len = caplen;
+
+ /* Add Packet Data padding if needed. */
+ if (pad_length > 0) {
+ iov[i].iov_base = pad;
+ iov[i++].iov_len = pad_length;
+ }
+
+ /* Add comment if supplied */
+ if (epb_options->comment) {
+ uint16_t opt[2] = {PCAPNG_OPT_COMMENT, com_length};
+ size_t opt_pad = roundup(com_length,
+ sizeof(uint32_t)) - com_length;
+ /* Add option header. */
+ iov[i].iov_base = opt;
+ iov[i++].iov_len = sizeof(opt);
+
+ /* Add actual comment string. */
+ iov[i].iov_base = (void *)epb_options->comment;
+ iov[i++].iov_len = com_length;
+
+ /* Add padding to uint32_t if needed. */
+ if (opt_pad) {
+ iov[i].iov_base = pad;
+ iov[i++].iov_len = opt_pad;
+ }
+ }
+
+ /* Write other options and final EPB size. */
+ iov[i].iov_base = options;
+ iov[i++].iov_len = 8 + (epb_options->flags ? 8 : 0) +
+ (epb_options->dropcount ? 12 : 0) +
+ (epb_options->packetid ? 12 : 0) +
+ (epb_options->queue ? 8 : 0) +
+ (epb_options->xdp_verdict ? 16 : 0);
+ rc = writev(pd->pd_fd, iov, i);
+ if ((size_t)rc != epb_length)
+ return false;
+
+ return true;
+}
+
+/*****************************************************************************
+ * xpcapng_dump_open()
+ *****************************************************************************/
+struct xpcapng_dumper *xpcapng_dump_open(const char *file,
+ const char *comment,
+ const char *hardware,
+ const char *os,
+ const char *user_application)
+{
+ struct xpcapng_dumper *pd = NULL;
+
+ if (file == NULL) {
+ errno = EINVAL;
+ goto error_exit;
+ }
+
+ pd = calloc(sizeof(*pd), 1);
+ if (pd == NULL) {
+ errno = ENOMEM;
+ goto error_exit;
+ }
+ pd->pd_fd = -1;
+
+ if (strcmp(file, "-") == 0) {
+ pd->pd_fd = STDOUT_FILENO;
+ } else {
+ pd->pd_fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (pd->pd_fd < 0)
+ goto error_exit;
+ }
+
+ if (!pcapng_write_shb(pd, comment, hardware, os, user_application))
+ goto error_exit;
+
+ return pd;
+
+error_exit:
+ if (pd) {
+ if (pd->pd_fd >= 0 && pd->pd_fd != STDOUT_FILENO)
+ close(pd->pd_fd);
+
+ free(pd);
+ }
+ return NULL;
+}
+
+/*****************************************************************************
+ * xpcapng_dump_close()
+ *****************************************************************************/
+void xpcapng_dump_close(struct xpcapng_dumper *pd)
+{
+ if (pd == NULL)
+ return;
+
+ if (pd->pd_fd < 0 && pd->pd_fd != STDOUT_FILENO)
+ close(pd->pd_fd);
+
+ free(pd);
+}
+
+/*****************************************************************************
+ * xpcapng_dump_flush()
+ *****************************************************************************/
+int xpcapng_dump_flush(struct xpcapng_dumper *pd)
+{
+ if (pd != NULL)
+ return fsync(pd->pd_fd);
+
+ errno = EINVAL;
+ return -1;
+}
+
+/*****************************************************************************
+ * pcapng_dump_add_interface()
+ *****************************************************************************/
+int xpcapng_dump_add_interface(struct xpcapng_dumper *pd, uint16_t snap_len,
+ const char *name, const char *description,
+ const uint8_t *mac, uint64_t speed,
+ uint8_t ts_resolution, const char *hardware)
+{
+ if (!pcapng_write_idb(pd, name, snap_len, description, mac, speed,
+ ts_resolution, hardware))
+ return -1;
+
+ return pd->pd_interfaces++;
+}
+
+/*****************************************************************************
+ * xpcapng_dump_enhanced_pkt()
+ *****************************************************************************/
+bool xpcapng_dump_enhanced_pkt(struct xpcapng_dumper *pd, uint32_t ifid,
+ const uint8_t *pkt, uint32_t len,
+ uint32_t caplen, uint64_t timestamp,
+ struct xpcapng_epb_options_s *options)
+{
+ struct xpcapng_epb_options_s default_options = {};
+
+ return pcapng_write_epb(pd, ifid, pkt, len, caplen, timestamp,
+ options ?: &default_options);
+}