f-stack/dpdk/lib/pcapng/rte_pcapng.c

722 lines
19 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright(c) 2019 Microsoft Corporation
*/
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifndef RTE_EXEC_ENV_WINDOWS
#include <net/if.h>
#include <sys/uio.h>
#endif
#include <bus_driver.h>
#include <rte_common.h>
#include <rte_cycles.h>
#include <dev_driver.h>
#include <rte_errno.h>
#include <rte_ethdev.h>
#include <rte_ether.h>
#include <rte_mbuf.h>
#include <rte_os_shim.h>
#include <rte_pcapng.h>
#include <rte_reciprocal.h>
#include <rte_time.h>
#include "pcapng_proto.h"
/* conversion from DPDK speed to PCAPNG */
#define PCAPNG_MBPS_SPEED 1000000ull
/* upper bound for section, stats and interface blocks (in uint32_t) */
#define PCAPNG_BLKSIZ (2048 / sizeof(uint32_t))
/* Format of the capture file handle */
struct rte_pcapng {
int outfd; /* output file */
unsigned int ports; /* number of interfaces added */
uint64_t offset_ns; /* ns since 1/1/1970 when initialized */
uint64_t tsc_base; /* TSC when started */
/* DPDK port id to interface index in file */
uint32_t port_index[RTE_MAX_ETHPORTS];
};
#ifdef RTE_EXEC_ENV_WINDOWS
/*
* Windows does not have writev() call.
* Emulate this by copying to a new buffer.
* The copy is necessary since pcapng needs to be thread-safe
* and do atomic write operations.
*/
#define IOV_MAX 128
struct iovec {
void *iov_base;
size_t iov_len;
};
static ssize_t writev(int fd, const struct iovec *iov, int iovcnt)
{
size_t bytes = 0;
uint8_t *ptr;
void *tmp_buf;
ssize_t ret;
int i;
for (i = 0; i < iovcnt; i++)
bytes += iov[i].iov_len;
if (unlikely(bytes == 0))
return 0;
tmp_buf = malloc(bytes);
if (unlikely(tmp_buf == NULL)) {
errno = ENOMEM;
return -1;
}
ptr = tmp_buf;
for (i = 0; i < iovcnt; i++) {
rte_memcpy(ptr, iov[i].iov_base, iov[i].iov_len);
ptr += iov[i].iov_len;
}
ret = write(fd, tmp_buf, bytes);
free(tmp_buf);
return ret;
}
#define IF_NAMESIZE 16
/* compatibility wrapper because name is optional */
#define if_indextoname(ifindex, ifname) NULL
#endif
/* Convert from TSC (CPU cycles) to nanoseconds */
static uint64_t
pcapng_timestamp(const rte_pcapng_t *self, uint64_t cycles)
{
uint64_t delta, rem, secs, ns;
const uint64_t hz = rte_get_tsc_hz();
delta = cycles - self->tsc_base;
/* Avoid numeric wraparound by computing seconds first */
secs = delta / hz;
rem = delta % hz;
ns = (rem * NS_PER_S) / hz;
return secs * NS_PER_S + ns + self->offset_ns;
}
/* length of option including padding */
static uint16_t pcapng_optlen(uint16_t len)
{
return RTE_ALIGN(sizeof(struct pcapng_option) + len,
sizeof(uint32_t));
}
/* build TLV option and return location of next */
static struct pcapng_option *
pcapng_add_option(struct pcapng_option *popt, uint16_t code,
const void *data, uint16_t len)
{
popt->code = code;
popt->length = len;
if (len > 0)
memcpy(popt->data, data, len);
return (struct pcapng_option *)((uint8_t *)popt + pcapng_optlen(len));
}
/*
* Write required initial section header describing the capture
*/
static int
pcapng_section_block(rte_pcapng_t *self,
const char *os, const char *hw,
const char *app, const char *comment)
{
struct pcapng_section_header *hdr;
struct pcapng_option *opt;
uint32_t buf[PCAPNG_BLKSIZ];
uint32_t len;
len = sizeof(*hdr);
if (hw)
len += pcapng_optlen(strlen(hw));
if (os)
len += pcapng_optlen(strlen(os));
if (app)
len += pcapng_optlen(strlen(app));
if (comment)
len += pcapng_optlen(strlen(comment));
/* reserve space for OPT_END */
len += pcapng_optlen(0);
len += sizeof(uint32_t);
if (len > sizeof(buf))
return -1;
hdr = (struct pcapng_section_header *)buf;
*hdr = (struct pcapng_section_header) {
.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,
};
/* After the section header insert variable length options. */
opt = (struct pcapng_option *)(hdr + 1);
if (comment)
opt = pcapng_add_option(opt, PCAPNG_OPT_COMMENT,
comment, strlen(comment));
if (hw)
opt = pcapng_add_option(opt, PCAPNG_SHB_HARDWARE,
hw, strlen(hw));
if (os)
opt = pcapng_add_option(opt, PCAPNG_SHB_OS,
os, strlen(os));
if (app)
opt = pcapng_add_option(opt, PCAPNG_SHB_USERAPPL,
app, strlen(app));
/* The standard requires last option to be OPT_END */
opt = pcapng_add_option(opt, PCAPNG_OPT_END, NULL, 0);
/* clone block_length after option */
memcpy(opt, &hdr->block_length, sizeof(uint32_t));
return write(self->outfd, buf, len);
}
/* Write an interface block for a DPDK port */
int
rte_pcapng_add_interface(rte_pcapng_t *self, uint16_t port,
const char *ifname, const char *ifdescr,
const char *filter)
{
struct pcapng_interface_block *hdr;
struct rte_eth_dev_info dev_info;
struct rte_ether_addr *ea, macaddr;
const struct rte_device *dev;
struct rte_eth_link link;
struct pcapng_option *opt;
const uint8_t tsresol = 9; /* nanosecond resolution */
uint32_t len;
uint32_t buf[PCAPNG_BLKSIZ];
char ifname_buf[IF_NAMESIZE];
char ifhw[256];
uint64_t speed = 0;
if (rte_eth_dev_info_get(port, &dev_info) < 0)
return -1;
/* make something like an interface name */
if (ifname == NULL) {
/* Use kernel name if available */
ifname = if_indextoname(dev_info.if_index, ifname_buf);
if (ifname == NULL) {
snprintf(ifname_buf, IF_NAMESIZE, "dpdk:%u", port);
ifname = ifname_buf;
}
}
/* make a useful device hardware string */
dev = dev_info.device;
if (dev)
snprintf(ifhw, sizeof(ifhw),
"%s-%s", dev->bus->name, dev->name);
/* DPDK reports in units of Mbps */
if (rte_eth_link_get(port, &link) == 0 &&
link.link_status == RTE_ETH_LINK_UP)
speed = link.link_speed * PCAPNG_MBPS_SPEED;
if (rte_eth_macaddr_get(port, &macaddr) < 0)
ea = NULL;
else
ea = &macaddr;
/* Compute length of interface block options */
len = sizeof(*hdr);
len += pcapng_optlen(sizeof(tsresol)); /* timestamp */
len += pcapng_optlen(strlen(ifname)); /* ifname */
if (ifdescr)
len += pcapng_optlen(strlen(ifdescr));
if (ea)
len += pcapng_optlen(RTE_ETHER_ADDR_LEN); /* macaddr */
if (speed != 0)
len += pcapng_optlen(sizeof(uint64_t));
if (filter)
len += pcapng_optlen(strlen(filter) + 1);
if (dev)
len += pcapng_optlen(strlen(ifhw));
len += pcapng_optlen(0);
len += sizeof(uint32_t);
if (len > sizeof(buf))
return -1;
hdr = (struct pcapng_interface_block *)buf;
*hdr = (struct pcapng_interface_block) {
.block_type = PCAPNG_INTERFACE_BLOCK,
.link_type = 1, /* DLT_EN10MB - Ethernet */
.block_length = len,
};
opt = (struct pcapng_option *)(hdr + 1);
opt = pcapng_add_option(opt, PCAPNG_IFB_TSRESOL,
&tsresol, sizeof(tsresol));
opt = pcapng_add_option(opt, PCAPNG_IFB_NAME,
ifname, strlen(ifname));
if (ifdescr)
opt = pcapng_add_option(opt, PCAPNG_IFB_DESCRIPTION,
ifdescr, strlen(ifdescr));
if (ea)
opt = pcapng_add_option(opt, PCAPNG_IFB_MACADDR,
ea, RTE_ETHER_ADDR_LEN);
if (speed != 0)
opt = pcapng_add_option(opt, PCAPNG_IFB_SPEED,
&speed, sizeof(uint64_t));
if (dev)
opt = pcapng_add_option(opt, PCAPNG_IFB_HARDWARE,
ifhw, strlen(ifhw));
if (filter) {
size_t len;
len = strlen(filter) + 1;
opt->code = PCAPNG_IFB_FILTER;
opt->length = len;
/* Encoding is that the first octet indicates string vs BPF */
opt->data[0] = 0;
memcpy(opt->data + 1, filter, strlen(filter));
opt = (struct pcapng_option *)((uint8_t *)opt + pcapng_optlen(len));
}
opt = pcapng_add_option(opt, PCAPNG_OPT_END, NULL, 0);
/* clone block_length after optionsa */
memcpy(opt, &hdr->block_length, sizeof(uint32_t));
/* remember the file index */
self->port_index[port] = self->ports++;
return write(self->outfd, buf, len);
}
/*
* Write an Interface statistics block at the end of capture.
*/
ssize_t
rte_pcapng_write_stats(rte_pcapng_t *self, uint16_t port_id,
uint64_t ifrecv, uint64_t ifdrop,
const char *comment)
{
struct pcapng_statistics *hdr;
struct pcapng_option *opt;
uint64_t start_time = self->offset_ns;
uint64_t sample_time;
uint32_t optlen, len;
uint32_t buf[PCAPNG_BLKSIZ];
RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -EINVAL);
optlen = 0;
if (ifrecv != UINT64_MAX)
optlen += pcapng_optlen(sizeof(ifrecv));
if (ifdrop != UINT64_MAX)
optlen += pcapng_optlen(sizeof(ifdrop));
if (start_time != 0)
optlen += pcapng_optlen(sizeof(start_time));
if (comment)
optlen += pcapng_optlen(strlen(comment));
if (optlen != 0)
optlen += pcapng_optlen(0);
len = sizeof(*hdr) + optlen + sizeof(uint32_t);
if (len > sizeof(buf))
return -1;
hdr = (struct pcapng_statistics *)buf;
opt = (struct pcapng_option *)(hdr + 1);
if (comment)
opt = pcapng_add_option(opt, PCAPNG_OPT_COMMENT,
comment, strlen(comment));
if (start_time != 0)
opt = pcapng_add_option(opt, PCAPNG_ISB_STARTTIME,
&start_time, sizeof(start_time));
if (ifrecv != UINT64_MAX)
opt = pcapng_add_option(opt, PCAPNG_ISB_IFRECV,
&ifrecv, sizeof(ifrecv));
if (ifdrop != UINT64_MAX)
opt = pcapng_add_option(opt, PCAPNG_ISB_IFDROP,
&ifdrop, sizeof(ifdrop));
if (optlen != 0)
opt = pcapng_add_option(opt, PCAPNG_OPT_END, NULL, 0);
hdr->block_type = PCAPNG_INTERFACE_STATS_BLOCK;
hdr->block_length = len;
hdr->interface_id = self->port_index[port_id];
sample_time = pcapng_timestamp(self, rte_get_tsc_cycles());
hdr->timestamp_hi = sample_time >> 32;
hdr->timestamp_lo = (uint32_t)sample_time;
/* clone block_length after option */
memcpy(opt, &len, sizeof(uint32_t));
return write(self->outfd, buf, len);
}
uint32_t
rte_pcapng_mbuf_size(uint32_t length)
{
/* The VLAN and EPB header must fit in the mbuf headroom. */
RTE_ASSERT(sizeof(struct pcapng_enhance_packet_block) +
sizeof(struct rte_vlan_hdr) <= RTE_PKTMBUF_HEADROOM);
/* The flags and queue information are added at the end. */
return sizeof(struct rte_mbuf)
+ RTE_ALIGN(length, sizeof(uint32_t))
+ pcapng_optlen(sizeof(uint32_t)) /* flag option */
+ pcapng_optlen(sizeof(uint32_t)) /* queue option */
+ sizeof(uint32_t); /* length */
}
/* More generalized version rte_vlan_insert() */
static int
pcapng_vlan_insert(struct rte_mbuf *m, uint16_t ether_type, uint16_t tci)
{
struct rte_ether_hdr *nh, *oh;
struct rte_vlan_hdr *vh;
if (!RTE_MBUF_DIRECT(m) || rte_mbuf_refcnt_read(m) > 1)
return -EINVAL;
if (rte_pktmbuf_data_len(m) < sizeof(*oh))
return -EINVAL;
oh = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);
nh = (struct rte_ether_hdr *)
rte_pktmbuf_prepend(m, sizeof(struct rte_vlan_hdr));
if (nh == NULL)
return -ENOSPC;
memmove(nh, oh, 2 * RTE_ETHER_ADDR_LEN);
nh->ether_type = rte_cpu_to_be_16(ether_type);
vh = (struct rte_vlan_hdr *) (nh + 1);
vh->vlan_tci = rte_cpu_to_be_16(tci);
return 0;
}
/*
* The mbufs created use the Pcapng standard enhanced packet block.
*
* 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 0 | Block Type = 0x00000006 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 4 | Block Total Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 8 | Interface ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 12 | Timestamp (High) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 16 | Timestamp (Low) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 20 | Captured Packet Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 24 | Original Packet Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 28 / /
* / Packet Data /
* / variable length, padded to 32 bits /
* / /
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Option Code = 0x0002 | Option Length = 0x004 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Flags (direction) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Option Code = 0x0006 | Option Length = 0x002 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Queue id |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Block Total Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
/* Make a copy of original mbuf with pcapng header and options */
struct rte_mbuf *
rte_pcapng_copy(uint16_t port_id, uint32_t queue,
const struct rte_mbuf *md,
struct rte_mempool *mp,
uint32_t length,
enum rte_pcapng_direction direction,
const char *comment)
{
struct pcapng_enhance_packet_block *epb;
uint32_t orig_len, pkt_len, padding, flags;
struct pcapng_option *opt;
uint64_t timestamp;
uint16_t optlen;
struct rte_mbuf *mc;
bool rss_hash;
#ifdef RTE_LIBRTE_ETHDEV_DEBUG
RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, NULL);
#endif
orig_len = rte_pktmbuf_pkt_len(md);
/* Take snapshot of the data */
mc = rte_pktmbuf_copy(md, mp, 0, length);
if (unlikely(mc == NULL))
return NULL;
/* Expand any offloaded VLAN information */
if ((direction == RTE_PCAPNG_DIRECTION_IN &&
(md->ol_flags & RTE_MBUF_F_RX_VLAN_STRIPPED)) ||
(direction == RTE_PCAPNG_DIRECTION_OUT &&
(md->ol_flags & RTE_MBUF_F_TX_VLAN))) {
if (pcapng_vlan_insert(mc, RTE_ETHER_TYPE_VLAN,
md->vlan_tci) != 0)
goto fail;
}
if ((direction == RTE_PCAPNG_DIRECTION_IN &&
(md->ol_flags & RTE_MBUF_F_RX_QINQ_STRIPPED)) ||
(direction == RTE_PCAPNG_DIRECTION_OUT &&
(md->ol_flags & RTE_MBUF_F_TX_QINQ))) {
if (pcapng_vlan_insert(mc, RTE_ETHER_TYPE_QINQ,
md->vlan_tci_outer) != 0)
goto fail;
}
/* record HASH on incoming packets */
rss_hash = (direction == RTE_PCAPNG_DIRECTION_IN &&
(md->ol_flags & RTE_MBUF_F_RX_RSS_HASH));
/* pad the packet to 32 bit boundary */
pkt_len = rte_pktmbuf_pkt_len(mc);
padding = RTE_ALIGN(pkt_len, sizeof(uint32_t)) - pkt_len;
if (padding > 0) {
void *tail = rte_pktmbuf_append(mc, padding);
if (tail == NULL)
goto fail;
memset(tail, 0, padding);
}
optlen = pcapng_optlen(sizeof(flags));
optlen += pcapng_optlen(sizeof(queue));
if (rss_hash)
optlen += pcapng_optlen(sizeof(uint8_t) + sizeof(uint32_t));
if (comment)
optlen += pcapng_optlen(strlen(comment));
/* reserve trailing options and block length */
opt = (struct pcapng_option *)
rte_pktmbuf_append(mc, optlen + sizeof(uint32_t));
if (unlikely(opt == NULL))
goto fail;
switch (direction) {
case RTE_PCAPNG_DIRECTION_IN:
flags = PCAPNG_IFB_INBOUND;
break;
case RTE_PCAPNG_DIRECTION_OUT:
flags = PCAPNG_IFB_OUTBOUND;
break;
default:
flags = 0;
}
opt = pcapng_add_option(opt, PCAPNG_EPB_FLAGS,
&flags, sizeof(flags));
opt = pcapng_add_option(opt, PCAPNG_EPB_QUEUE,
&queue, sizeof(queue));
if (rss_hash) {
uint8_t hash_opt[5];
/* The algorithm could be something else if
* using rte_flow_action_rss; but the current API does not
* have a way for ethdev to report this on a per-packet basis.
*/
hash_opt[0] = PCAPNG_HASH_TOEPLITZ;
memcpy(&hash_opt[1], &md->hash.rss, sizeof(uint32_t));
opt = pcapng_add_option(opt, PCAPNG_EPB_HASH,
&hash_opt, sizeof(hash_opt));
}
if (comment)
opt = pcapng_add_option(opt, PCAPNG_OPT_COMMENT, comment,
strlen(comment));
/* Note: END_OPT necessary here. Wireshark doesn't do it. */
/* Add PCAPNG packet header */
epb = (struct pcapng_enhance_packet_block *)
rte_pktmbuf_prepend(mc, sizeof(*epb));
if (unlikely(epb == NULL))
goto fail;
epb->block_type = PCAPNG_ENHANCED_PACKET_BLOCK;
epb->block_length = rte_pktmbuf_pkt_len(mc);
/* Interface index is filled in later during write */
mc->port = port_id;
/* Put timestamp in cycles here - adjust in packet write */
timestamp = rte_get_tsc_cycles();
epb->timestamp_hi = timestamp >> 32;
epb->timestamp_lo = (uint32_t)timestamp;
epb->capture_length = pkt_len;
epb->original_length = orig_len;
/* set trailer of block length */
*(uint32_t *)opt = epb->block_length;
return mc;
fail:
rte_pktmbuf_free(mc);
return NULL;
}
/* Write pre-formatted packets to file. */
ssize_t
rte_pcapng_write_packets(rte_pcapng_t *self,
struct rte_mbuf *pkts[], uint16_t nb_pkts)
{
struct iovec iov[IOV_MAX];
unsigned int i, cnt = 0;
ssize_t ret, total = 0;
for (i = 0; i < nb_pkts; i++) {
struct rte_mbuf *m = pkts[i];
struct pcapng_enhance_packet_block *epb;
uint64_t cycles, timestamp;
/* sanity check that is really a pcapng mbuf */
epb = rte_pktmbuf_mtod(m, struct pcapng_enhance_packet_block *);
if (unlikely(epb->block_type != PCAPNG_ENHANCED_PACKET_BLOCK ||
epb->block_length != rte_pktmbuf_pkt_len(m))) {
rte_errno = EINVAL;
return -1;
}
/* check that this interface was added. */
epb->interface_id = self->port_index[m->port];
if (unlikely(epb->interface_id > RTE_MAX_ETHPORTS)) {
rte_errno = EINVAL;
return -1;
}
/* adjust timestamp recorded in packet */
cycles = (uint64_t)epb->timestamp_hi << 32;
cycles += epb->timestamp_lo;
timestamp = pcapng_timestamp(self, cycles);
epb->timestamp_hi = timestamp >> 32;
epb->timestamp_lo = (uint32_t)timestamp;
/*
* Handle case of highly fragmented and large burst size
* Note: this assumes that max segments per mbuf < IOV_MAX
*/
if (unlikely(cnt + m->nb_segs >= IOV_MAX)) {
ret = writev(self->outfd, iov, cnt);
if (unlikely(ret < 0)) {
rte_errno = errno;
return -1;
}
total += ret;
cnt = 0;
}
/*
* The DPDK port is recorded during pcapng_copy.
* Map that to PCAPNG interface in file.
*/
do {
iov[cnt].iov_base = rte_pktmbuf_mtod(m, void *);
iov[cnt].iov_len = rte_pktmbuf_data_len(m);
++cnt;
} while ((m = m->next));
}
ret = writev(self->outfd, iov, cnt);
if (unlikely(ret < 0)) {
rte_errno = errno;
return -1;
}
return total + ret;
}
/* Create new pcapng writer handle */
rte_pcapng_t *
rte_pcapng_fdopen(int fd,
const char *osname, const char *hardware,
const char *appname, const char *comment)
{
unsigned int i;
rte_pcapng_t *self;
struct timespec ts;
uint64_t cycles;
self = malloc(sizeof(*self));
if (!self) {
rte_errno = ENOMEM;
return NULL;
}
self->outfd = fd;
self->ports = 0;
/* record start time in ns since 1/1/1970 */
cycles = rte_get_tsc_cycles();
clock_gettime(CLOCK_REALTIME, &ts);
self->tsc_base = (cycles + rte_get_tsc_cycles()) / 2;
self->offset_ns = rte_timespec_to_ns(&ts);
for (i = 0; i < RTE_MAX_ETHPORTS; i++)
self->port_index[i] = UINT32_MAX;
if (pcapng_section_block(self, osname, hardware, appname, comment) < 0)
goto fail;
return self;
fail:
free(self);
return NULL;
}
void
rte_pcapng_close(rte_pcapng_t *self)
{
close(self->outfd);
free(self);
}