diff options
Diffstat (limited to 'contrib/libpcap/pcap-util.c')
| -rw-r--r-- | contrib/libpcap/pcap-util.c | 627 |
1 files changed, 627 insertions, 0 deletions
diff --git a/contrib/libpcap/pcap-util.c b/contrib/libpcap/pcap-util.c new file mode 100644 index 000000000000..1aa8e13e323e --- /dev/null +++ b/contrib/libpcap/pcap-util.c @@ -0,0 +1,627 @@ +/* + * Copyright (c) 1993, 1994, 1995, 1996, 1997 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that: (1) source code distributions + * retain the above copyright notice and this paragraph in its entirety, (2) + * distributions including binary code include the above copyright notice and + * this paragraph in its entirety in the documentation or other materials + * provided with the distribution, and (3) all advertising materials mentioning + * features or use of this software display the following acknowledgement: + * ``This product includes software developed by the University of California, + * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of + * the University nor the names of its contributors may be used to endorse + * or promote products derived from this software without specific prior + * written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * pcap-util.c - common code for various files + */ + +#include <config.h> + +#include <pcap-types.h> + +#include "pcap/can_socketcan.h" +#include "pcap/sll.h" +#include "pcap/usb.h" +#include "pcap/nflog.h" + +#include "pcap-int.h" +#include "extract.h" +#include "pcap-usb-linux-common.h" + +#include "pcap-util.h" +#include "pflog.h" + +/* + * Most versions of the DLT_PFLOG pseudo-header have UID and PID fields + * that are saved in host byte order. + * + * When reading a DLT_PFLOG packet, we need to convert those fields from + * the byte order of the host that wrote the file to this host's byte + * order. + */ +static void +swap_pflog_header(const struct pcap_pkthdr *hdr, u_char *buf) +{ + u_int caplen = hdr->caplen; + u_int length = hdr->len; + u_int pfloghdr_length; + struct pfloghdr *pflhdr = (struct pfloghdr *)buf; + + if (caplen < (u_int) (offsetof(struct pfloghdr, uid) + sizeof pflhdr->uid) || + length < (u_int) (offsetof(struct pfloghdr, uid) + sizeof pflhdr->uid)) { + /* Not enough data to have the uid field */ + return; + } + + pfloghdr_length = pflhdr->length; + + if (pfloghdr_length < (u_int) (offsetof(struct pfloghdr, uid) + sizeof pflhdr->uid)) { + /* Header doesn't include uid field */ + return; + } + pflhdr->uid = SWAPLONG(pflhdr->uid); + + if (caplen < (u_int) (offsetof(struct pfloghdr, pid) + sizeof pflhdr->pid) || + length < (u_int) (offsetof(struct pfloghdr, pid) + sizeof pflhdr->pid)) { + /* Not enough data to have the pid field */ + return; + } + if (pfloghdr_length < (u_int) (offsetof(struct pfloghdr, pid) + sizeof pflhdr->pid)) { + /* Header doesn't include pid field */ + return; + } + pflhdr->pid = SWAPLONG(pflhdr->pid); + + if (caplen < (u_int) (offsetof(struct pfloghdr, rule_uid) + sizeof pflhdr->rule_uid) || + length < (u_int) (offsetof(struct pfloghdr, rule_uid) + sizeof pflhdr->rule_uid)) { + /* Not enough data to have the rule_uid field */ + return; + } + if (pfloghdr_length < (u_int) (offsetof(struct pfloghdr, rule_uid) + sizeof pflhdr->rule_uid)) { + /* Header doesn't include rule_uid field */ + return; + } + pflhdr->rule_uid = SWAPLONG(pflhdr->rule_uid); + + if (caplen < (u_int) (offsetof(struct pfloghdr, rule_pid) + sizeof pflhdr->rule_pid) || + length < (u_int) (offsetof(struct pfloghdr, rule_pid) + sizeof pflhdr->rule_pid)) { + /* Not enough data to have the rule_pid field */ + return; + } + if (pfloghdr_length < (u_int) (offsetof(struct pfloghdr, rule_pid) + sizeof pflhdr->rule_pid)) { + /* Header doesn't include rule_pid field */ + return; + } + pflhdr->rule_pid = SWAPLONG(pflhdr->rule_pid); +} + +/* + * Linux cooked capture packets with a protocol type of LINUX_SLL_P_CAN or + * LINUX_SLL_P_CANFD have SocketCAN CAN classic/CAN FD headers in front + * of the payload,with the CAN ID being in the byte order of the host + * that wrote the packet, and Linux cooked capture packets with a protocol + * type of LINUX_SLL_P_CANXL have SocketCAN CAN XL headers in front of the + * payload with the protocol/VCID field, the payload length, and the + * acceptance field in the byte order of the host that wrote the packet. + * + * When reading a Linux cooked capture packet, we need to check for those + * packets and, if the byte order host that wrote the packet, as + * indicated by the byte order of the pcap file or pcapng section + * containing the packet, is the opposite of our byte order, convert + * the header files to our byte order by byte-swapping them. + */ +static void +swap_socketcan_header(uint16_t protocol, u_int caplen, u_int length, + u_char *buf) +{ + pcap_can_socketcan_hdr *hdrp; + pcap_can_socketcan_xl_hdr *xl_hdrp; + + switch (protocol) { + + case LINUX_SLL_P_CAN: + case LINUX_SLL_P_CANFD: + /* + * CAN classic/CAN FD packet; fix up the packet's header + * by byte-swapping the CAN ID field. + */ + hdrp = (pcap_can_socketcan_hdr *)buf; + if (caplen < (u_int) (offsetof(pcap_can_socketcan_hdr, can_id) + sizeof hdrp->can_id) || + length < (u_int) (offsetof(pcap_can_socketcan_hdr, can_id) + sizeof hdrp->can_id)) { + /* Not enough data to have the can_id field */ + return; + } + hdrp->can_id = SWAPLONG(hdrp->can_id); + break; + + case LINUX_SLL_P_CANXL: + /* + * CAN XL packet; fix up the packet's header by + * byte-swapping the priority/VCID field, the + * payload length, and the acceptance field. + */ + xl_hdrp = (pcap_can_socketcan_xl_hdr *)buf; + if (caplen < (u_int) (offsetof(pcap_can_socketcan_xl_hdr, priority_vcid) + sizeof xl_hdrp->priority_vcid) || + length < (u_int) (offsetof(pcap_can_socketcan_xl_hdr, priority_vcid) + sizeof xl_hdrp->priority_vcid)) { + /* Not enough data to have the priority_vcid field */ + return; + } + xl_hdrp->priority_vcid = SWAPLONG(xl_hdrp->priority_vcid); + if (caplen < (u_int) (offsetof(pcap_can_socketcan_xl_hdr, payload_length) + sizeof xl_hdrp->payload_length) || + length < (u_int) (offsetof(pcap_can_socketcan_xl_hdr, payload_length) + sizeof xl_hdrp->payload_length)) { + /* Not enough data to have the payload_length field */ + return; + } + xl_hdrp->payload_length = SWAPSHORT(xl_hdrp->payload_length); + if (caplen < (u_int) (offsetof(pcap_can_socketcan_xl_hdr, acceptance_field) + sizeof xl_hdrp->acceptance_field) || + length < (u_int) (offsetof(pcap_can_socketcan_xl_hdr, acceptance_field) + sizeof xl_hdrp->acceptance_field)) { + /* Not enough data to have the acceptance_field field */ + return; + } + xl_hdrp->acceptance_field = SWAPLONG(xl_hdrp->acceptance_field); + break; + + default: + /* + * Not a CAN packet; nothing to do. + */ + break; + } +} + +/* + * DLT_LINUX_SLL packets with a protocol type of LINUX_SLL_P_CAN or + * LINUX_SLL_P_CANFD have SocketCAN headers in front of the payload, + * with the CAN ID being in host byte order. + * + * When reading a DLT_LINUX_SLL packet, we need to check for those + * packets and convert the CAN ID from the byte order of the host that + * wrote the file to this host's byte order. + */ +static void +swap_linux_sll_socketcan_header(const struct pcap_pkthdr *hdr, u_char *buf) +{ + u_int caplen = hdr->caplen; + u_int length = hdr->len; + struct sll_header *shdr = (struct sll_header *)buf; + + if (caplen < (u_int) sizeof(struct sll_header) || + length < (u_int) sizeof(struct sll_header)) { + /* Not enough data to have the protocol field */ + return; + } + + /* + * Byte-swap what needs to be byte-swapped. + */ + swap_socketcan_header(EXTRACT_BE_U_2(&shdr->sll_protocol), + caplen - (u_int) sizeof(struct sll_header), + length - (u_int) sizeof(struct sll_header), + buf + sizeof(struct sll_header)); +} + +/* + * The same applies for DLT_LINUX_SLL2. + */ +static void +swap_linux_sll2_socketcan_header(const struct pcap_pkthdr *hdr, u_char *buf) +{ + u_int caplen = hdr->caplen; + u_int length = hdr->len; + struct sll2_header *shdr = (struct sll2_header *)buf; + + if (caplen < (u_int) sizeof(struct sll2_header) || + length < (u_int) sizeof(struct sll2_header)) { + /* Not enough data to have the protocol field */ + return; + } + + /* + * Byte-swap what needs to be byte-swapped. + */ + swap_socketcan_header(EXTRACT_BE_U_2(&shdr->sll2_protocol), + caplen - (u_int) sizeof(struct sll2_header), + length - (u_int) sizeof(struct sll2_header), + buf + sizeof(struct sll2_header)); +} + +/* + * The DLT_USB_LINUX and DLT_USB_LINUX_MMAPPED headers are in host + * byte order when capturing (it's supplied directly from a + * memory-mapped buffer shared by the kernel). + * + * When reading a DLT_USB_LINUX or DLT_USB_LINUX_MMAPPED packet, we + * need to convert it from the byte order of the host that wrote the + * file to this host's byte order. + */ +static void +swap_linux_usb_header(const struct pcap_pkthdr *hdr, u_char *buf, + int header_len_64_bytes) +{ + pcap_usb_header_mmapped *uhdr = (pcap_usb_header_mmapped *)buf; + bpf_u_int32 offset = 0; + + /* + * "offset" is the offset *past* the field we're swapping; + * we skip the field *before* checking to make sure + * the captured data length includes the entire field. + */ + + /* + * The URB id is a totally opaque value; do we really need to + * convert it to the reading host's byte order??? + */ + offset += 8; /* skip past id */ + if (hdr->caplen < offset) + return; + uhdr->id = SWAPLL(uhdr->id); + + offset += 4; /* skip past various 1-byte fields */ + + offset += 2; /* skip past bus_id */ + if (hdr->caplen < offset) + return; + uhdr->bus_id = SWAPSHORT(uhdr->bus_id); + + offset += 2; /* skip past various 1-byte fields */ + + offset += 8; /* skip past ts_sec */ + if (hdr->caplen < offset) + return; + uhdr->ts_sec = SWAPLL(uhdr->ts_sec); + + offset += 4; /* skip past ts_usec */ + if (hdr->caplen < offset) + return; + uhdr->ts_usec = SWAPLONG(uhdr->ts_usec); + + offset += 4; /* skip past status */ + if (hdr->caplen < offset) + return; + uhdr->status = SWAPLONG(uhdr->status); + + offset += 4; /* skip past urb_len */ + if (hdr->caplen < offset) + return; + uhdr->urb_len = SWAPLONG(uhdr->urb_len); + + offset += 4; /* skip past data_len */ + if (hdr->caplen < offset) + return; + uhdr->data_len = SWAPLONG(uhdr->data_len); + + if (uhdr->transfer_type == URB_ISOCHRONOUS) { + offset += 4; /* skip past s.iso.error_count */ + if (hdr->caplen < offset) + return; + uhdr->s.iso.error_count = SWAPLONG(uhdr->s.iso.error_count); + + offset += 4; /* skip past s.iso.numdesc */ + if (hdr->caplen < offset) + return; + uhdr->s.iso.numdesc = SWAPLONG(uhdr->s.iso.numdesc); + } else + offset += 8; /* skip USB setup header */ + + /* + * With the old header, there are no isochronous descriptors + * after the header. + * + * With the new header, the actual number of descriptors in + * the header is not s.iso.numdesc, it's ndesc - only the + * first N descriptors, for some value of N, are put into + * the header, and ndesc is set to the actual number copied. + * In addition, if s.iso.numdesc is negative, no descriptors + * are captured, and ndesc is set to 0. + */ + if (header_len_64_bytes) { + /* + * This is either the "version 1" header, with + * 16 bytes of additional fields at the end, or + * a "version 0" header from a memory-mapped + * capture, with 16 bytes of zeroed-out padding + * at the end. Byte swap them as if this were + * a "version 1" header. + */ + offset += 4; /* skip past interval */ + if (hdr->caplen < offset) + return; + uhdr->interval = SWAPLONG(uhdr->interval); + + offset += 4; /* skip past start_frame */ + if (hdr->caplen < offset) + return; + uhdr->start_frame = SWAPLONG(uhdr->start_frame); + + offset += 4; /* skip past xfer_flags */ + if (hdr->caplen < offset) + return; + uhdr->xfer_flags = SWAPLONG(uhdr->xfer_flags); + + offset += 4; /* skip past ndesc */ + if (hdr->caplen < offset) + return; + uhdr->ndesc = SWAPLONG(uhdr->ndesc); + + if (uhdr->transfer_type == URB_ISOCHRONOUS) { + /* swap the values in struct linux_usb_isodesc */ + usb_isodesc *pisodesc; + uint32_t i; + + pisodesc = (usb_isodesc *)(void *)(buf+offset); + for (i = 0; i < uhdr->ndesc; i++) { + offset += 4; /* skip past status */ + if (hdr->caplen < offset) + return; + pisodesc->status = SWAPLONG(pisodesc->status); + + offset += 4; /* skip past offset */ + if (hdr->caplen < offset) + return; + pisodesc->offset = SWAPLONG(pisodesc->offset); + + offset += 4; /* skip past len */ + if (hdr->caplen < offset) + return; + pisodesc->len = SWAPLONG(pisodesc->len); + + offset += 4; /* skip past padding */ + + pisodesc++; + } + } + } +} + +/* + * The DLT_NFLOG "packets" have a mixture of big-endian and host-byte-order + * data. They begin with a fixed-length header with big-endian fields, + * followed by a set of TLVs, where the type and length are in host + * byte order but the values are either big-endian or are a raw byte + * sequence that's the same regardless of the host's byte order. + * + * When reading a DLT_NFLOG packet, we need to convert the type and + * length values from the byte order of the host that wrote the file + * to the byte order of this host. + */ +static void +swap_nflog_header(const struct pcap_pkthdr *hdr, u_char *buf) +{ + u_char *p = buf; + nflog_hdr_t *nfhdr = (nflog_hdr_t *)buf; + nflog_tlv_t *tlv; + u_int caplen = hdr->caplen; + u_int length = hdr->len; + u_int size; + + if (caplen < (u_int) sizeof(nflog_hdr_t) || + length < (u_int) sizeof(nflog_hdr_t)) { + /* Not enough data to have any TLVs. */ + return; + } + + if (nfhdr->nflog_version != 0) { + /* Unknown NFLOG version */ + return; + } + + length -= sizeof(nflog_hdr_t); + caplen -= sizeof(nflog_hdr_t); + p += sizeof(nflog_hdr_t); + + while (caplen >= sizeof(nflog_tlv_t)) { + tlv = (nflog_tlv_t *) p; + + /* Swap the type and length. */ + tlv->tlv_type = SWAPSHORT(tlv->tlv_type); + tlv->tlv_length = SWAPSHORT(tlv->tlv_length); + + /* Get the length of the TLV. */ + size = tlv->tlv_length; + if (size % 4 != 0) + size += 4 - size % 4; + + /* Is the TLV's length less than the minimum? */ + if (size < sizeof(nflog_tlv_t)) { + /* Yes. Give up now. */ + return; + } + + /* Do we have enough data for the full TLV? */ + if (caplen < size || length < size) { + /* No. */ + return; + } + + /* Skip over the TLV. */ + length -= size; + caplen -= size; + p += size; + } +} + +static void +swap_pseudo_headers(int linktype, struct pcap_pkthdr *hdr, u_char *data) +{ + /* + * Convert pseudo-headers from the byte order of + * the host on which the file was saved to our + * byte order, as necessary. + */ + switch (linktype) { + + case DLT_PFLOG: + swap_pflog_header(hdr, data); + break; + + case DLT_LINUX_SLL: + swap_linux_sll_socketcan_header(hdr, data); + break; + + case DLT_LINUX_SLL2: + swap_linux_sll2_socketcan_header(hdr, data); + break; + + case DLT_USB_LINUX: + swap_linux_usb_header(hdr, data, 0); + break; + + case DLT_USB_LINUX_MMAPPED: + swap_linux_usb_header(hdr, data, 1); + break; + + case DLT_NFLOG: + swap_nflog_header(hdr, data); + break; + } +} + +static inline int +packet_length_might_be_wrong(struct pcap_pkthdr *hdr, + const pcap_usb_header_mmapped *usb_hdr) +{ + uint32_t old_style_packet_length; + + /* + * Calculate the packet length the old way. + * We know that the multiplication won't overflow, but + * we don't know that the additions won't. Calculate + * it with no overflow checks, as that's how it + * would have been calculated when it was captured. + */ + old_style_packet_length = iso_pseudo_header_len(usb_hdr) + + usb_hdr->urb_len; + return (hdr->len == old_style_packet_length); +} + +void +pcapint_post_process(int linktype, int swapped, struct pcap_pkthdr *hdr, + u_char *data) +{ + if (swapped) + swap_pseudo_headers(linktype, hdr, data); + + /* + * Is this a memory-mapped Linux USB capture? + */ + if (linktype == DLT_USB_LINUX_MMAPPED) { + /* + * Yes. + * + * In older versions of libpcap, in memory-mapped Linux + * USB captures, the original length of 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), it's a completion event + * for an incoming isochronous transfer, and the + * transfer length appears to have been calculated + * from the raw URB length, fix it. + * + * We only do this if we have the full USB pseudo-header, + * because we will have to look at that header and at + * all of the isochronous descriptors. + */ + if (hdr->caplen < sizeof (pcap_usb_header_mmapped)) { + /* + * We don't have the full pseudo-header. + */ + return; + } + + const pcap_usb_header_mmapped *usb_hdr = + (const pcap_usb_header_mmapped *) data; + + /* + * Make sure the number of descriptors is sane. + * + * The Linux binary USB monitor code limits the number of + * isochronous descriptors to 128; if the number in the file + * is larger than that, either 1) the file's been damaged + * or 2) the file was produced after the number was raised + * in the kernel. + * + * In case 1), the number can't be trusted, so don't rely on + * it to attempt to fix the original length field in the pcap + * or pcapng header. + * + * In case 2), the system was probably running a version of + * libpcap that didn't miscalculate the original length, so + * it probably doesn't need to be fixed. + * + * This avoids the possibility of the product of the number of + * descriptors and the size of descriptors won't overflow an + * unsigned 32-bit integer. + */ + if (usb_hdr->ndesc > USB_MAXDESC) + return; + + if (!usb_hdr->data_flag && + is_isochronous_transfer_completion(usb_hdr) && + packet_length_might_be_wrong(hdr, usb_hdr)) { + u_int len; + + /* + * Make sure we have all of the descriptors, + * as we will have to look at all of them. + * + * If not, we don't bother trying to fix + * anything. + */ + if (hdr->caplen < iso_pseudo_header_len(usb_hdr)) + return; + + /* + * Calculate what the length should have been. + */ + len = incoming_isochronous_transfer_completed_len(hdr, + data); + + /* + * len is the smaller of UINT_MAX and the total + * header plus data length. That's guaranteed + * to fit in a UINT_MAX. + * + * Don't reduce the original length to a value + * below the captured length, however, as that + * is bogus. + */ + if (len >= hdr->caplen) + hdr->len = 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 + * original length is always >= the captured length. + */ + if (hdr->caplen > hdr->len) + hdr->len = hdr->caplen; + } + } +} |
