From: Guy Harris Date: Thu, 16 Jun 2022 00:10:03 +0000 (-0700) Subject: Linux USB: make the capture file length fixup more conservative. X-Git-Url: https://git.tcpdump.org/libpcap/commitdiff_plain/b4eeafb93955807464d8bb9327772fb217fcdd9a Linux USB: make the capture file length fixup more conservative. When retroactively fixing up capture file on-the-network lengths for memory-apped Linux USB captures, only fix up the on-the-network length if it has the value that would be expected from the old capture code. In the fixup code, make sure it's both >= the proper length, as best we can calculate it, and the captured length. --- diff --git a/pcap-common.c b/pcap-common.c index 408717e1..de8b5981 100644 --- a/pcap-common.c +++ b/pcap-common.c @@ -1822,17 +1822,33 @@ swap_pseudo_headers(int linktype, struct pcap_pkthdr *hdr, u_char *data) } void -fixup_pcap_pkthdr(int linktype, struct pcap_pkthdr *hdr, u_char *data) +fixup_pcap_pkthdr(int linktype, struct pcap_pkthdr *hdr, const u_char *data) { + const pcap_usb_header_mmapped *usb_hdr; + + usb_hdr = (const pcap_usb_header_mmapped *) data; if (linktype == DLT_USB_LINUX_MMAPPED) { /* * In older versions of libpcap, in memory-mapped captures, - * the "on-the-bus length" for isochronous transfers was - * miscalculated; it needed to be calculated based on the - * offsets and lengths in the descriptors, not on the raw - * URB length, but it wasn't. Recalculate it from the - * packet data. + * the "on-the-bus length" for completion events for + * incoming isochronous transfers was miscalculated; it + * needed to be calculated based on the* offsets and lengths + * in the descriptors, not on the raw URB length, but it + * wasn't. + * + * If this packet contains transferred data (yes, data_flag + * is 0 if we *do* have data), and the total on-the-network + * length is equal to the value calculated from the raw URB + * length, then it might be one of those tranfers. */ - set_linux_usb_mmapped_length(hdr, data); + if (!usb_hdr->data_flag && + hdr->len == sizeof(pcap_usb_header_mmapped) + + (usb_hdr->ndesc * sizeof (usb_isodesc)) + usb_hdr->urb_len) { + /* + * It might leed fixing; fix it if it's a completion + * event for an incoming isochronous transfer. + */ + fix_linux_usb_mmapped_length(hdr, data); + } } } diff --git a/pcap-common.h b/pcap-common.h index a779afe9..cc944ef6 100644 --- a/pcap-common.h +++ b/pcap-common.h @@ -51,6 +51,6 @@ extern void swap_pseudo_headers(int linktype, struct pcap_pkthdr *hdr, u_char *data); extern void fixup_pcap_pkthdr(int linktype, struct pcap_pkthdr *hdr, - u_char *data); + const u_char *data); extern u_int max_snaplen_for_dlt(int dlt); diff --git a/pcap-usb-linux-common.c b/pcap-usb-linux-common.c index ef0bb462..2207f87e 100644 --- a/pcap-usb-linux-common.c +++ b/pcap-usb-linux-common.c @@ -36,7 +36,7 @@ * Set the "unsliced length" field of the packet header to that value. */ void -set_linux_usb_mmapped_length(struct pcap_pkthdr *pkth, const u_char *bp) +fix_linux_usb_mmapped_length(struct pcap_pkthdr *pkth, const u_char *bp) { const pcap_usb_header_mmapped *hdr; u_int bytes_left; @@ -51,58 +51,80 @@ set_linux_usb_mmapped_length(struct pcap_pkthdr *pkth, const u_char *bp) bytes_left -= sizeof (pcap_usb_header_mmapped); hdr = (const pcap_usb_header_mmapped *) bp; - if (hdr->data_flag) { - /* - * No data; just base the on-the-bus 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; calculate the on-the-bus length based on - * the data length prior to the USB monitor device discarding - * data due to its buffer being too small. - */ + if (!hdr->data_flag && hdr->transfer_type == URB_ISOCHRONOUS && + hdr->event_type == URB_COMPLETE && + (hdr->endpoint_number & URB_TRANSFER_IN) && + pkth->len == sizeof(pcap_usb_header_mmapped) + + (hdr->ndesc * sizeof (usb_isodesc)) + hdr->urb_len) { usb_isodesc *descs; - u_int pre_truncation_data_len; + u_int pre_truncation_data_len, pre_truncation_len; descs = (usb_isodesc *) (bp + sizeof(pcap_usb_header_mmapped)); /* - * For most transfers, urb_len is that amount. + * We have data (yes, data_flag is 0 if we *do* have data), + * and this is a "this is complete" incoming isochronous + * transfer event, and the length was calculated based + * on the URB length. + * + * That's not correct, because the data isn't contiguous, + * and the isochronous descriptos show how it's scattered. + * + * Find the end of the last chunk of data in the buffer + * referred to by the isochronous descriptors; that indicates + * how far into the buffer the data would have gone. + * + * Make sure we don't run past the end of the captured data + * while processing the isochronous descriptors. */ - pre_truncation_data_len = hdr->urb_len; - if (hdr->transfer_type == URB_ISOCHRONOUS && - hdr->event_type == URB_COMPLETE && - (hdr->endpoint_number & URB_TRANSFER_IN)) { - /* - * For "this is complete" incoming isochronous - * transfer events, however, the data isn't - * contiguous, and the isochronous descriptos - * show how it's scattered. - * - * Find the end of the last chunk of data in - * the buffer referred to by the isochronous - * descriptors; that indicates how far into - * the buffer the data would have gone. - * - * Make sure we don't run past the end of the - * captured data while processing the isochronous - * descriptors. - */ - pre_truncation_data_len = 0; - for (uint32_t desc = 0; - desc < hdr->ndesc && bytes_left >= sizeof (usb_isodesc); - desc++, bytes_left -= sizeof (usb_isodesc)) { - u_int desc_end; + pre_truncation_data_len = 0; + for (uint32_t desc = 0; + desc < hdr->ndesc && bytes_left >= sizeof (usb_isodesc); + desc++, bytes_left -= sizeof (usb_isodesc)) { + u_int desc_end; - desc_end = descs[desc].offset + descs[desc].len; - if (desc_end > pre_truncation_data_len) - pre_truncation_data_len = desc_end; - } + desc_end = descs[desc].offset + descs[desc].len; + if (desc_end > pre_truncation_data_len) + pre_truncation_data_len = desc_end; } - pkth->len = sizeof(pcap_usb_header_mmapped) + + + /* + * Now calculate the total length based on that data + * length. + */ + pre_truncation_len = sizeof(pcap_usb_header_mmapped) + (hdr->ndesc * sizeof (usb_isodesc)) + pre_truncation_data_len; + + /* + * If that's greater than or equal to the captured length, + * use that as the length. + */ + if (pre_truncation_len >= pkth->caplen) + pkth->len = pre_truncation_len; + + /* + * If the captured length is greater than the length, + * use the captured length. + * + * For completion events for incoming isochronous transfers, + * it's based on data_len, which is calculated the same way + * we calculated pre_truncation_data_len above, except that + * it has access to all the isochronous descriptors, not + * just the ones that the kernel were able to provide us or, + * for a capture file, that weren't sliced off by a snapshot + * length. + * + * However, it might have been reduced by the USB capture + * mechanism arbitrarily limiting the amount of data it + * provides to userland, or by the libpcap capture code + * limiting it to being no more than the snapshot, so + * we don't want to just use it all the time; we only + * do so to try to get a better estimate of the actual + * length - and to make sure the on-the-network length + * is always >= the captured length. + */ + if (pkth->caplen > pkth->len) + pkth->len = pkth->caplen; } } diff --git a/pcap-usb-linux-common.h b/pcap-usb-linux-common.h index 05fca6a3..8cff7ba1 100644 --- a/pcap-usb-linux-common.h +++ b/pcap-usb-linux-common.h @@ -22,5 +22,5 @@ * deal with Linux USB captures. */ -extern void set_linux_usb_mmapped_length(struct pcap_pkthdr *pkth, +extern void fix_linux_usb_mmapped_length(struct pcap_pkthdr *pkth, const u_char *bp); diff --git a/pcap-usb-linux.c b/pcap-usb-linux.c index ef495bd2..7020577c 100644 --- a/pcap-usb-linux.c +++ b/pcap-usb-linux.c @@ -875,7 +875,31 @@ usb_read_linux_mmap(pcap_t *handle, int max_packets, pcap_handler callback, u_ch if (hdr->data_len < clen) clen = hdr->data_len; pkth.caplen = sizeof(pcap_usb_header_mmapped) + clen; - set_linux_usb_mmapped_length(&pkth, bp); + 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; + + /* + * Now clean it up if it's a completion + * event for an incoming isochronous + * transfer. + */ + fix_linux_usb_mmapped_length(&pkth, bp); + } pkth.ts.tv_sec = (time_t)hdr->ts_sec; pkth.ts.tv_usec = hdr->ts_usec;