292 lines
9.8 KiB
Diff
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
|