libpcap/0036-Use-the-snapshot-length-to-set-the-buffer-size-and-s.patch
2019-09-30 10:57:32 -04:00

292 lines
9.8 KiB
Diff

From 4ccdc8a3cbf5195a7c089002136e53db541b36ba Mon Sep 17 00:00:00 2001
From: Guy Harris <guy@alum.mit.edu>
Date: Tue, 19 Mar 2019 11:03:40 -0700
Subject: [PATCH 36/83] Use the snapshot length to set the buffer size, and set
the len field properly.
The USB buffer size limits the number of bytes worth of data that is
saved; set the buffer size based on the snapshot length. Limit the
buffer size values to the range of values the kernel supports, and
adjust the reported snapshot length if we can't set the buffer size
large enough for the requested snapshot length.
When reading packets, for packets with URB data, set the len field based
on the urb_len field rather than the data_len field, so it reflects how
much data was transferred in the transaction rather than how much of
that data was actually provided by the USB monitor mechanism.
Fix usb_read_linux_mmap() to use sizeof(pcap_usb_header_mmapped) rather
than sizeof(pcap_usb_header) everywhere, as pcap_usb_header_mmapped is
the structure being supplied as the metadata header.
This should fix GitHub issue #808.
---
pcap-usb-linux.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 178 insertions(+), 17 deletions(-)
diff --git a/pcap-usb-linux.c b/pcap-usb-linux.c
index 3e70a8d..f202cb6 100644
--- a/pcap-usb-linux.c
+++ b/pcap-usb-linux.c
@@ -381,18 +381,102 @@ usb_findalldevs(pcap_if_list_t *devlistp, char *err_str)
}
}
+/*
+ * Matches what's in mon_bin.c in the Linux kernel.
+ */
+#define MIN_RING_SIZE (8*1024)
+#define MAX_RING_SIZE (1200*1024)
+
+static int
+usb_set_ring_size(pcap_t* handle, size_t header_size)
+{
+ /*
+ * A packet from binary usbmon has:
+ *
+ * 1) a fixed-length header, of size header_size;
+ * 2) descriptors, for isochronous transfers;
+ * 3) the payload.
+ *
+ * The kernel buffer has a size, defaulting to 300KB, with a
+ * minimum of 8KB and a maximum of 1200KB. The size is set with
+ * the MON_IOCT_RING_SIZE ioctl; the size passed in is rounded up
+ * to a page size.
+ *
+ * No more than {buffer size}/5 bytes worth of payload is saved.
+ * Therefore, if we subtract the fixed-length size from the
+ * snapshot length, we have the biggest payload we want (we
+ * don't worry about the descriptors - if we have descriptors,
+ * we'll just discard the last bit of the payload to get it
+ * to fit). We multiply that result by 5 and set the buffer
+ * size to that value.
+ */
+ int ring_size;
+
+ if ((size_t)handle->snapshot < header_size)
+ handle->snapshot = header_size;
+ /* The maximum snapshot size is small enough that this won't overflow */
+ ring_size = (handle->snapshot - header_size) * 5;
+
+ /*
+ * Will this get an error?
+ * (There's no wqy to query the minimum or maximum, so we just
+ * copy the value from the kernel source. We don't round it
+ * up to a multiple of the page size.)
+ */
+ if (ring_size > MAX_RING_SIZE) {
+ /*
+ * Yes. Lower the ring size to the maximum, and set the
+ * snapshot length to the value that would give us a
+ * maximum-size ring.
+ */
+ ring_size = MAX_RING_SIZE;
+ handle->snapshot = header_size + (MAX_RING_SIZE/5);
+ } else if (ring_size < MIN_RING_SIZE) {
+ /*
+ * Yes. Raise the ring size to the minimum, but leave
+ * the snapshot length unchanged, so we show the
+ * callback no more data than specified by the
+ * snapshot length.
+ */
+ ring_size = MIN_RING_SIZE;
+ }
+
+ if (ioctl(handle->fd, MON_IOCT_RING_SIZE, ring_size) == -1) {
+ pcap_fmt_errmsg_for_errno(handle->errbuf, PCAP_ERRBUF_SIZE,
+ errno, "Can't set ring size from fd %d", handle->fd);
+ return -1;
+ }
+ return ring_size;
+}
+
static
int usb_mmap(pcap_t* handle)
{
struct pcap_usb_linux *handlep = handle->priv;
- int len = ioctl(handle->fd, MON_IOCQ_RING_SIZE);
- if (len < 0)
+ int len;
+
+ /*
+ * Attempt to set the ring size as appropriate for the snapshot
+ * length, reducing the snapshot length if that'd make the ring
+ * bigger than the kernel supports.
+ */
+ len = usb_set_ring_size(handle, sizeof(pcap_usb_header_mmapped));
+ if (len == -1) {
+ /* Failed. Fall back on non-memory-mapped access. */
return 0;
+ }
handlep->mmapbuflen = len;
handlep->mmapbuf = mmap(0, handlep->mmapbuflen, PROT_READ,
MAP_SHARED, handle->fd, 0);
- return handlep->mmapbuf != MAP_FAILED;
+ if (handlep->mmapbuf == MAP_FAILED) {
+ /*
+ * Failed. We don't treat that as a fatal error, we
+ * just try to fall back on non-memory-mapped access.
+ */
+ return 0;
+ }
+ return 1;
}
#ifdef HAVE_LINUX_USBDEVICE_FS_H
@@ -606,6 +690,7 @@ usb_activate(pcap_t* handle)
/* try to use fast mmap access */
if (usb_mmap(handle))
{
+ /* We succeeded. */
handle->linktype = DLT_USB_LINUX_MMAPPED;
handle->stats_op = usb_stats_linux_bin;
handle->read_op = usb_read_linux_mmap;
@@ -622,7 +707,19 @@ usb_activate(pcap_t* handle)
return 0;
}
- /* can't mmap, use plain binary interface access */
+ /*
+ * We failed; try plain binary interface access.
+ *
+ * Attempt to set the ring size as appropriate for
+ * the snapshot length, reducing the snapshot length
+ * if that'd make the ring bigger than the kernel
+ * supports.
+ */
+ if (usb_set_ring_size(handle, sizeof(pcap_usb_header)) == -1) {
+ /* Failed. */
+ close(handle->fd);
+ return PCAP_ERROR;
+ }
handle->stats_op = usb_stats_linux_bin;
handle->read_op = usb_read_linux_bin;
#ifdef HAVE_LINUX_USBDEVICE_FS_H
@@ -1075,13 +1172,44 @@ usb_read_linux_bin(pcap_t *handle, int max_packets _U_, pcap_handler callback, u
return -1;
}
- /* we can get less that than really captured from kernel, depending on
- * snaplen, so adjust header accordingly */
+ /*
+ * info.hdr->data_len is the number of bytes of isochronous
+ * descriptors (if any) plus the number of bytes of data
+ * provided. There are no isochronous descriptors here,
+ * because we're using the old 48-byte header.
+ *
+ * If info.hdr->data_flag is non-zero, there's no URB data;
+ * info.hdr->urb_len is the size of the buffer into which
+ * data is to be placed; it does not represent the amount
+ * of data transferred. If info.hdr->data_flag is zero,
+ * there is URB data, and info.hdr->urb_len is the number
+ * of bytes transmitted or received; it doesn't include
+ * isochronous descriptors.
+ *
+ * The kernel may give us more data than the snaplen; if it did,
+ * reduce the data length so that the total number of bytes we
+ * tell our client we have is not greater than the snaplen.
+ */
if (info.hdr->data_len < clen)
clen = info.hdr->data_len;
info.hdr->data_len = clen;
- pkth.caplen = clen + sizeof(pcap_usb_header);
- pkth.len = info.hdr->data_len + sizeof(pcap_usb_header);
+ pkth.caplen = sizeof(pcap_usb_header) + clen;
+ if (info.hdr->data_flag) {
+ /*
+ * No data; just base the on-the-wire length on
+ * info.hdr->data_len (so that it's >= the captured
+ * length).
+ */
+ pkth.len = sizeof(pcap_usb_header) + info.hdr->data_len;
+ } else {
+ /*
+ * We got data; base the on-the-wire length on
+ * info.hdr->urb_len, so that it includes data
+ * discarded by the USB monitor device due to
+ * its buffer being too small.
+ */
+ pkth.len = sizeof(pcap_usb_header) + info.hdr->urb_len;
+ }
pkth.ts.tv_sec = info.hdr->ts_sec;
pkth.ts.tv_usec = info.hdr->ts_usec;
@@ -1108,12 +1236,12 @@ usb_read_linux_mmap(pcap_t *handle, int max_packets, pcap_handler callback, u_ch
struct mon_bin_mfetch fetch;
int32_t vec[VEC_SIZE];
struct pcap_pkthdr pkth;
- pcap_usb_header* hdr;
+ pcap_usb_header_mmapped* hdr;
int nflush = 0;
int packets = 0;
u_int clen, max_clen;
- max_clen = handle->snapshot - sizeof(pcap_usb_header);
+ max_clen = handle->snapshot - sizeof(pcap_usb_header_mmapped);
for (;;) {
int i, ret;
@@ -1151,19 +1279,52 @@ usb_read_linux_mmap(pcap_t *handle, int max_packets, pcap_handler callback, u_ch
nflush = fetch.nfetch;
for (i=0; i<fetch.nfetch; ++i) {
/* discard filler */
- hdr = (pcap_usb_header*) &handlep->mmapbuf[vec[i]];
+ hdr = (pcap_usb_header_mmapped*) &handlep->mmapbuf[vec[i]];
if (hdr->event_type == '@')
continue;
- /* we can get less that than really captured from kernel, depending on
- * snaplen, so adjust header accordingly */
+ /*
+ * hdr->data_len is the number of bytes of
+ * isochronous descriptors (if any) plus the
+ * number of bytes of data provided.
+ *
+ * If hdr->data_flag is non-zero, there's no
+ * URB data; hdr->urb_len is the size of the
+ * buffer into which data is to be placed; it does
+ * not represent the amount of data transferred.
+ * If hdr->data_flag is zero, there is URB data,
+ * and hdr->urb_len is the number of bytes
+ * transmitted or received; it doesn't include
+ * isochronous descriptors.
+ *
+ * The kernel may give us more data than the
+ * snaplen; if it did, reduce the data length
+ * so that the total number of bytes we
+ * tell our client we have is not greater than
+ * the snaplen.
+ */
clen = max_clen;
if (hdr->data_len < clen)
clen = hdr->data_len;
-
- /* get packet info from header*/
- pkth.caplen = clen + sizeof(pcap_usb_header_mmapped);
- pkth.len = hdr->data_len + sizeof(pcap_usb_header_mmapped);
+ pkth.caplen = sizeof(pcap_usb_header_mmapped) + clen;
+ if (hdr->data_flag) {
+ /*
+ * No data; just base the on-the-wire length
+ * on hdr->data_len (so that it's >= the
+ * captured length).
+ */
+ pkth.len = sizeof(pcap_usb_header_mmapped) +
+ hdr->data_len;
+ } else {
+ /*
+ * We got data; base the on-the-wire length
+ * on hdr->urb_len, so that it includes
+ * data discarded by the USB monitor device
+ * due to its buffer being too small.
+ */
+ pkth.len = sizeof(pcap_usb_header_mmapped) +
+ (hdr->ndesc * sizeof (usb_isodesc)) + hdr->urb_len;
+ }
pkth.ts.tv_sec = hdr->ts_sec;
pkth.ts.tv_usec = hdr->ts_usec;
--
1.8.3.1