USBLL: Reassemble transactions into transfers

Create pseudo URB and pass the reassembled data to USB URB dissector.
Reassembly for control transfers is not problematic as the transfer
length is known. For bulk transfers assume the transfer can span across
multiple transactions, however for periodic (interrupt and isochronous)
assume the transfer never spans across multiple transactions.

Rely on USB dissector to provide endpoint maximum packet size. Actual
interface/configuration handling in USB dissector needs to be reworked
as the code assumes that there is only one configuration and alternate
interface configurations have matching endpoints.

While the reassembly bulk transfers and never reassemble periodic
transfers result in pretty good dissection, the USB class dissectors
need a mechanism to provide transfer size hints to USBLL dissector.
Such hint is not needed for software USB capture as software sniffers
essentially capture URBs and every transfer is associated with one URB.
The problem can be seen for example in Mass Storage Class where it is
common for data transfers length to be multiple of endpoint maximum
packet size. Because USBLL dissector doesn't know expected transfer
size, it combines together data and status transport.

Related to #15908
This commit is contained in:
Tomasz Moń 2021-04-28 14:22:47 +02:00
parent da8e7086f6
commit 7370516d21
3 changed files with 734 additions and 16 deletions

View File

@ -1733,6 +1733,7 @@ get_usb_conv_info(conversation_t *conversation)
usb_conv_info->alt_settings = wmem_array_new(wmem_file_scope(), sizeof(usb_alt_setting_t));
usb_conv_info->transactions = wmem_tree_new(wmem_file_scope());
usb_conv_info->descriptor_transfer_type = URB_UNKNOWN;
usb_conv_info->max_packet_size = 0;
conversation_add_proto_data(conversation, proto_usb, usb_conv_info);
}
@ -1815,6 +1816,36 @@ get_usb_iface_conv_info(packet_info *pinfo, guint8 interface_num)
return get_usb_conv_info(conversation);
}
/* Fetch usb_conv_info for specified endpoint, return NULL if not found */
usb_conv_info_t *
get_existing_usb_ep_conv_info(packet_info* pinfo, guint16 bus_id, guint16 device_address, int endpoint)
{
usb_address_t *src_addr = wmem_new0(pinfo->pool, usb_address_t),
*dst_addr = wmem_new0(pinfo->pool, usb_address_t);
address src, dst;
conversation_t *conversation;
usb_conv_info_t *usb_conv_info = NULL;
src_addr->bus_id = GUINT16_TO_LE(bus_id);
src_addr->device = GUINT16_TO_LE(device_address);
src_addr->endpoint = GUINT32_TO_LE(endpoint);
dst_addr->bus_id = GUINT16_TO_LE(bus_id);
dst_addr->device = 0xffffffff;
dst_addr->endpoint = NO_ENDPOINT;
set_address(&src, usb_address_type, USB_ADDR_LEN, (char *)src_addr);
set_address(&dst, usb_address_type, USB_ADDR_LEN, (char *)dst_addr);
conversation = find_conversation(pinfo->num, &src, &dst,
conversation_pt_to_endpoint_type(PT_USB),
src_addr->endpoint, dst_addr->endpoint, 0);
if (conversation) {
usb_conv_info = (usb_conv_info_t *)conversation_get_proto_data(conversation, proto_usb);
}
return usb_conv_info;
}
static const char* usb_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
{
if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == usb_address_type))
@ -2464,6 +2495,7 @@ dissect_usb_endpoint_descriptor(packet_info *pinfo, proto_tree *parent_tree,
guint8 endpoint;
guint8 ep_type;
guint8 len;
guint32 max_packet_size;
usb_trans_info_t *usb_trans_info = NULL;
conversation_t *conversation = NULL;
@ -2526,6 +2558,15 @@ dissect_usb_endpoint_descriptor(packet_info *pinfo, proto_tree *parent_tree,
}
offset += 1;
/* wMaxPacketSize */
ep_pktsize_item = proto_tree_add_item(tree, hf_usb_wMaxPacketSize, tvb, offset, 2, ENC_LITTLE_ENDIAN);
ep_pktsize_tree = proto_item_add_subtree(ep_pktsize_item, ett_endpoint_wMaxPacketSize);
if ((ep_type == ENDPOINT_TYPE_INTERRUPT) || (ep_type == ENDPOINT_TYPE_ISOCHRONOUS)) {
proto_tree_add_item(ep_pktsize_tree, hf_usb_wMaxPacketSize_slots, tvb, offset, 2, ENC_LITTLE_ENDIAN);
}
proto_tree_add_item_ret_uint(ep_pktsize_tree, hf_usb_wMaxPacketSize_size, tvb, offset, 2, ENC_LITTLE_ENDIAN, &max_packet_size);
offset+=2;
if (conversation) {
usb_conv_info_t* endpoint_conv_info = get_usb_conv_info(conversation);
guint8 transfer_type;
@ -2548,17 +2589,9 @@ dissect_usb_endpoint_descriptor(packet_info *pinfo, proto_tree *parent_tree,
break;
}
endpoint_conv_info->descriptor_transfer_type = transfer_type;
endpoint_conv_info->max_packet_size = max_packet_size;
}
/* wMaxPacketSize */
ep_pktsize_item = proto_tree_add_item(tree, hf_usb_wMaxPacketSize, tvb, offset, 2, ENC_LITTLE_ENDIAN);
ep_pktsize_tree = proto_item_add_subtree(ep_pktsize_item, ett_endpoint_wMaxPacketSize);
if ((ep_type == ENDPOINT_TYPE_INTERRUPT) || (ep_type == ENDPOINT_TYPE_ISOCHRONOUS)) {
proto_tree_add_item(ep_pktsize_tree, hf_usb_wMaxPacketSize_slots, tvb, offset, 2, ENC_LITTLE_ENDIAN);
}
proto_tree_add_item(ep_pktsize_tree, hf_usb_wMaxPacketSize_size, tvb, offset, 2, ENC_LITTLE_ENDIAN);
offset+=2;
/* bInterval */
proto_tree_add_item(tree, hf_usb_bInterval, tvb, offset, 1, ENC_LITTLE_ENDIAN);
offset += 1;
@ -4984,6 +5017,7 @@ dissect_usb_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent,
guint64 usb_id;
struct mausb_header *ma_header = NULL;
struct usbip_header *ip_header = NULL;
usb_pseudo_urb_t *pseudo_urb = NULL;
/* the goal is to get the conversation struct as early as possible
and store all status values in this struct
@ -5036,6 +5070,14 @@ dissect_usb_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent,
bus_id = location >> 24;
break;
case USB_HEADER_PSEUDO_URB:
pseudo_urb = (usb_pseudo_urb_t *) extra_data;
urb_type = pseudo_urb->from_host ? URB_SUBMIT : URB_COMPLETE;
device_address = pseudo_urb->device_address;
endpoint = pseudo_urb->endpoint;
bus_id = pseudo_urb->bus_id;
break;
default:
return; /* invalid USB pseudo header */
}
@ -5097,6 +5139,14 @@ dissect_usb_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent,
proto_item_set_len(urb_tree_ti, offset);
break;
case USB_HEADER_PSEUDO_URB:
usb_conv_info->transfer_type = pseudo_urb->transfer_type;
usb_conv_info->direction = pseudo_urb->from_host ? P2P_DIR_SENT : P2P_DIR_RECV;
usb_conv_info->is_setup = pseudo_urb->from_host && (pseudo_urb->transfer_type == URB_CONTROL);
usb_conv_info->is_request = pseudo_urb->from_host;
usb_id = 0;
break;
default:
usb_id = 0;
break;
@ -5137,6 +5187,9 @@ dissect_usb_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent,
case USB_HEADER_DARWIN:
break;
case USB_HEADER_PSEUDO_URB:
break;
}
break;
@ -5199,6 +5252,9 @@ dissect_usb_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent,
case USB_HEADER_DARWIN:
break;
case USB_HEADER_PSEUDO_URB:
break;
}
}
} else {
@ -5241,6 +5297,9 @@ dissect_usb_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent,
case USB_HEADER_DARWIN:
break;
case USB_HEADER_PSEUDO_URB:
break;
}
offset = dissect_usb_setup_response(pinfo, tree, tvb, offset,
@ -5273,6 +5332,9 @@ dissect_usb_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent,
offset = dissect_darwin_usb_iso_transfer(pinfo, tree, header_type,
urb_type, tvb, offset, usb_conv_info);
break;
case USB_HEADER_PSEUDO_URB:
break;
}
break;
@ -5299,6 +5361,9 @@ dissect_usb_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent,
case USB_HEADER_DARWIN:
break;
case USB_HEADER_PSEUDO_URB:
break;
}
break;
}

View File

@ -36,12 +36,21 @@ typedef enum {
USB_HEADER_USBPCAP,
USB_HEADER_MAUSB,
USB_HEADER_USBIP,
USB_HEADER_DARWIN
USB_HEADER_DARWIN,
USB_HEADER_PSEUDO_URB,
} usb_header_t;
#define USB_HEADER_IS_LINUX(type) \
((type) == USB_HEADER_LINUX_48_BYTES || (type) == USB_HEADER_LINUX_64_BYTES)
typedef struct _usb_pseudo_urb_t {
gboolean from_host;
guint8 transfer_type;
guint8 device_address;
guint8 endpoint;
guint16 bus_id;
} usb_pseudo_urb_t;
/* there is one such structure for each request/response */
typedef struct _usb_trans_info_t {
guint32 request_in;
@ -89,7 +98,8 @@ struct _usb_conv_info_t {
guint8 endpoint;
gint direction;
guint8 transfer_type; /* transfer type from URB */
guint8 descriptor_transfer_type; /* transfer type lifted from the device descriptor */
guint8 descriptor_transfer_type; /* transfer type lifted from the configuration descriptor */
guint16 max_packet_size; /* max packet size from configuration descriptor */
guint32 device_protocol;
gboolean is_request;
gboolean is_setup;
@ -254,6 +264,8 @@ extern const true_false_string tfs_endpoint_direction;
extern value_string_ext usb_class_vals_ext;
usb_conv_info_t *get_usb_iface_conv_info(packet_info *pinfo, guint8 interface_num);
usb_conv_info_t *get_existing_usb_ep_conv_info(packet_info *pinfo, guint16 bus_id,
guint16 device_address, int endpoint);
proto_item * dissect_usb_descriptor_header(proto_tree *tree,
tvbuff_t *tvb, int offset,

View File

@ -21,6 +21,7 @@
#include <epan/address_types.h>
#include <epan/to_str.h>
#include <epan/proto_data.h>
#include <epan/reassemble.h>
#include "packet-usb.h"
void proto_register_usbll(void);
@ -51,9 +52,43 @@ static int hf_usbll_split_crc5_status = -1;
static int hf_usbll_src = -1;
static int hf_usbll_dst = -1;
static int hf_usbll_addr = -1;
static int hf_usbll_transfer_fragments = -1;
static int hf_usbll_transfer_fragment = -1;
static int hf_usbll_transfer_fragment_overlap = -1;
static int hf_usbll_transfer_fragment_overlap_conflicts = -1;
static int hf_usbll_transfer_fragment_multiple_tails = -1;
static int hf_usbll_transfer_fragment_too_long_fragment = -1;
static int hf_usbll_transfer_fragment_error = -1;
static int hf_usbll_transfer_fragment_count = -1;
static int hf_usbll_transfer_reassembled_in = -1;
static int hf_usbll_transfer_reassembled_length = -1;
static int ett_usbll = -1;
static int ett_usbll_transfer_fragment = -1;
static int ett_usbll_transfer_fragments = -1;
static const fragment_items usbll_frag_items = {
/* Fragment subtrees */
&ett_usbll_transfer_fragment,
&ett_usbll_transfer_fragments,
/* Fragment Fields */
&hf_usbll_transfer_fragments,
&hf_usbll_transfer_fragment,
&hf_usbll_transfer_fragment_overlap,
&hf_usbll_transfer_fragment_overlap_conflicts,
&hf_usbll_transfer_fragment_multiple_tails,
&hf_usbll_transfer_fragment_too_long_fragment,
&hf_usbll_transfer_fragment_error,
&hf_usbll_transfer_fragment_count,
/* Reassembled in field */
&hf_usbll_transfer_reassembled_in,
/* Reassembled length field */
&hf_usbll_transfer_reassembled_length,
/* Reassembled data field */
NULL,
/* Tag */
"USB transfer fragments"
};
static expert_field ei_invalid_pid = EI_INIT;
static expert_field ei_undecoded = EI_INIT;
@ -63,11 +98,16 @@ static expert_field ei_wrong_crc16 = EI_INIT;
static expert_field ei_invalid_s = EI_INIT;
static expert_field ei_invalid_e_u = EI_INIT;
static expert_field ei_invalid_pid_sequence = EI_INIT;
static expert_field ei_invalid_setup_data = EI_INIT;
static int usbll_address_type = -1;
static dissector_handle_t usbll_handle;
static reassembly_table usbll_reassembly_table;
static wmem_map_t *transfer_info;
/* USB packet ID is 4-bit. It is send in octet alongside complemented form.
* The list of PIDs is available in Universal Serial Bus Specification Revision 2.0,
* Table 8-1. PID Types
@ -282,6 +322,14 @@ typedef enum usbll_state {
STATE_CSPLIT_ISOCHRONOUS_IN_NYET,
} usbll_state_t;
typedef enum usbll_ep_type {
USBLL_EP_UNKNOWN,
USBLL_EP_CONTROL,
USBLL_EP_BULK,
USBLL_EP_INTERRUPT,
USBLL_EP_ISOCHRONOUS,
} usbll_ep_type_t;
/* usbll_address_t represents the address
* of Host, Hub and Devices.
*/
@ -304,6 +352,18 @@ typedef struct usbll_transaction_info {
struct usbll_transaction_info *split_complete;
} usbll_transaction_info_t;
typedef struct usbll_transfer_info {
/* First data packet number, used as reassembly key */
guint32 first_packet;
/* Offset this packet starts at */
guint32 offset;
usbll_ep_type_t type;
/* TRUE if data from host to device, FALSE when from device to host */
gboolean from_host;
/* FALSE if this is the last packet */
gboolean more_frags;
} usbll_transfer_info_t;
/* USB is a stateful protocol. The addresses of Data Packets
* and Handshake Packets depend on the packets before them.
*
@ -329,6 +389,61 @@ static usbll_data_t *usbll_data_ptr = NULL;
static usbll_transaction_info_t ***tt_non_periodic;
static usbll_transaction_info_t ***tt_periodic;
typedef enum usbll_transfer_data {
USBLL_TRANSFER_NORMAL,
USBLL_TRANSFER_GET_DEVICE_DESCRIPTOR,
} usbll_transfer_data_t;
typedef struct usbll_endpoint_info {
usbll_ep_type_t type;
usbll_transfer_data_t data;
/* Maximum packet size, 0 if not known */
guint16 max_packet_size;
/* DATA0/DATA1 tracking to detect retransmissions */
guint8 last_data_pid;
/* Current transfer key, 0 if no transfer in progress */
guint32 active_transfer_key;
/* Offset where next packet should start at */
guint32 transfer_offset;
/* Last data packet length that was part of transfer */
guint32 last_data_len;
/* Transfer length if known, 0 if unknown */
guint32 requested_transfer_length;
} usbll_endpoint_info_t;
/* Endpoint info arrays used only during first pass. */
static usbll_endpoint_info_t **ep_info_in;
static usbll_endpoint_info_t **ep_info_out;
static guint usbll_fragment_key_hash(gconstpointer k)
{
return GPOINTER_TO_UINT(k);
}
static gint usbll_fragment_key_equal(gconstpointer k1, gconstpointer k2)
{
return GPOINTER_TO_UINT(k1) == GPOINTER_TO_UINT(k2);
}
static gpointer usbll_fragment_key(const packet_info *pinfo _U_, const guint32 id, const void *data _U_)
{
return GUINT_TO_POINTER(id);
}
static void usbll_fragment_free_key(gpointer ptr _U_)
{
/* there's nothing to be freed */
}
static const reassembly_table_functions usbll_reassembly_table_functions = {
.hash_func = usbll_fragment_key_hash,
.equal_func = usbll_fragment_key_equal,
.temporary_key_func = usbll_fragment_key,
.persistent_key_func = usbll_fragment_key,
.free_temporary_key_func = usbll_fragment_free_key,
.free_persistent_key_func = usbll_fragment_free_key,
};
static usbll_state_t
usbll_next_state(usbll_state_t state, guint8 pid)
{
@ -622,6 +737,54 @@ static gboolean usbll_is_non_split_token(usbll_state_t state)
}
}
static gboolean usbll_is_setup_data(usbll_state_t state)
{
switch (state)
{
case STATE_SETUP_DATA0:
case STATE_SSPLIT_CONTROL_SETUP_DATA0:
return TRUE;
default:
return FALSE;
}
}
static gboolean usbll_is_data_from_host(usbll_state_t state)
{
switch (state)
{
case STATE_OUT_DATA0:
case STATE_OUT_DATA1:
case STATE_OUT_HS_ISOCHRONOUS_DATA2:
case STATE_OUT_HS_ISOCHRONOUS_MDATA:
case STATE_SETUP_DATA0:
case STATE_SSPLIT_CONTROL_SETUP_DATA0:
case STATE_SSPLIT_CONTROL_OUT_DATA0:
case STATE_SSPLIT_CONTROL_OUT_DATA1:
case STATE_SSPLIT_BULK_OUT_DATA0:
case STATE_SSPLIT_BULK_OUT_DATA1:
case STATE_SSPLIT_INTERRUPT_OUT_DATA0:
case STATE_SSPLIT_INTERRUPT_OUT_DATA1:
case STATE_SSPLIT_ISOCHRONOUS_OUT_DATA0:
return TRUE;
case STATE_IN_DATA0:
case STATE_IN_DATA1:
case STATE_IN_HS_ISOCHRONOUS_DATA2:
case STATE_CSPLIT_CONTROL_IN_DATA0:
case STATE_CSPLIT_CONTROL_IN_DATA1:
case STATE_CSPLIT_BULK_IN_DATA0:
case STATE_CSPLIT_BULK_IN_DATA1:
case STATE_CSPLIT_INTERRUPT_IN_MDATA:
case STATE_CSPLIT_INTERRUPT_IN_DATA0:
case STATE_CSPLIT_INTERRUPT_IN_DATA1:
case STATE_CSPLIT_ISOCHRONOUS_IN_DATA0:
case STATE_CSPLIT_ISOCHRONOUS_IN_MDATA:
return FALSE;
default:
DISSECTOR_ASSERT_NOT_REACHED();
}
}
static int usbll_addr_to_str(const address* addr, gchar *buf, int buf_len)
{
const usbll_address_t *addrp = (const usbll_address_t *)addr->data;
@ -943,6 +1106,116 @@ tt_store_transaction(packet_info *pinfo, usbll_state_t state, guint8 hub_address
}
}
static usbll_ep_type_t
usbll_ep_type_from_urb_type(guint8 urb_type)
{
switch (urb_type)
{
case URB_ISOCHRONOUS: return USBLL_EP_ISOCHRONOUS;
case URB_INTERRUPT: return USBLL_EP_INTERRUPT;
case URB_CONTROL: return USBLL_EP_CONTROL;
case URB_BULK: return USBLL_EP_BULK;
default: return USBLL_EP_UNKNOWN;
}
}
static void
usbll_reset_endpoint_info(usbll_endpoint_info_t *info, usbll_ep_type_t type, guint16 max_packet_size)
{
info->type = type;
info->data = USBLL_TRANSFER_NORMAL;
info->max_packet_size = max_packet_size;
info->last_data_pid = 0;
info->active_transfer_key = 0;
info->transfer_offset = 0;
info->last_data_len = 0;
info->requested_transfer_length = 0;
}
static void usbll_init_endpoint_tables(void)
{
/* Address is 7 bits (0 - 127), while endpoint is 4 bits (0 - 15) */
int addr;
ep_info_in = wmem_alloc_array(wmem_file_scope(), usbll_endpoint_info_t *, 128);
for (addr = 0; addr < 128; addr++)
{
ep_info_in[addr] = wmem_alloc_array(wmem_file_scope(), usbll_endpoint_info_t, 16);
}
ep_info_out = wmem_alloc_array(wmem_file_scope(), usbll_endpoint_info_t *, 128);
for (addr = 0; addr < 128; addr++)
{
ep_info_out[addr] = wmem_alloc_array(wmem_file_scope(), usbll_endpoint_info_t, 16);
}
for (addr = 0; addr < 128; addr++)
{
int ep;
/* Endpoint 0 is always control type */
usbll_reset_endpoint_info(&ep_info_in[addr][0], USBLL_EP_CONTROL, 0);
usbll_reset_endpoint_info(&ep_info_out[addr][0], USBLL_EP_CONTROL, 0);
for (ep = 1; ep < 16; ep++)
{
usbll_reset_endpoint_info(&ep_info_in[addr][ep], USBLL_EP_UNKNOWN, 0);
usbll_reset_endpoint_info(&ep_info_out[addr][ep], USBLL_EP_UNKNOWN, 0);
}
}
}
static usbll_endpoint_info_t *
usbll_get_endpoint_info(packet_info *pinfo, guint8 addr, guint8 ep, gboolean from_host)
{
usbll_endpoint_info_t *info;
DISSECTOR_ASSERT(!PINFO_FD_VISITED(pinfo));
DISSECTOR_ASSERT(addr <= 127);
DISSECTOR_ASSERT(ep <= 15);
if (!ep_info_in || !ep_info_out)
{
usbll_init_endpoint_tables();
DISSECTOR_ASSERT(ep_info_in != NULL);
DISSECTOR_ASSERT(ep_info_out != NULL);
}
if (from_host)
{
info = &ep_info_out[addr][ep];
}
else
{
info = &ep_info_in[addr][ep];
}
if (ep != 0)
{
/* Get endpoint type and max packet size from USB dissector
* USB dissector gets the information from CONFIGURATION descriptor
*
* TODO: Reorganize USB dissector to call us whenever selected
* configuration and/or interface changes. USB dissector
* currently assumes only one configuration and that all
* alternate interface settings have matching endpoint
* information. This should be fixed but is good for now
* as most devices fullfills this (wrong) assumption.
*/
usb_conv_info_t *usb_conv_info;
usbll_ep_type_t type = USBLL_EP_UNKNOWN;
guint16 max_packet_size = 0;
usb_conv_info = get_existing_usb_ep_conv_info(pinfo, 0, addr, ep);
if (usb_conv_info && usb_conv_info->max_packet_size)
{
type = usbll_ep_type_from_urb_type(usb_conv_info->descriptor_transfer_type);
max_packet_size = usb_conv_info->max_packet_size;
}
/* Reset endpoint info if endpoint parameters changed */
if ((info->type != type) || (info->max_packet_size != max_packet_size))
{
usbll_reset_endpoint_info(info, type, max_packet_size);
}
}
return info;
}
static gint
dissect_usbll_sof(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset)
{
@ -1045,22 +1318,84 @@ dissect_usbll_token(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint of
return offset;
}
static gboolean
packet_ends_transfer(usbll_endpoint_info_t *ep_info, guint32 offset, gint data_size)
{
DISSECTOR_ASSERT(ep_info->type != USBLL_EP_UNKNOWN);
if (ep_info->requested_transfer_length != 0)
{
/* We know requested transfer length */
if (offset + data_size >= ep_info->requested_transfer_length)
{
/* No more data needed */
return TRUE;
}
/* else check max packet size as transfer can end prematurely */
}
else
{
DISSECTOR_ASSERT(ep_info->type != USBLL_EP_CONTROL);
DISSECTOR_ASSERT(ep_info->max_packet_size != 0);
/* We don't know requested transfer length, for bulk transfers
* assume that transfer can be larger than max packet length,
* for periodic transfers assume transfer is not larger than
* max packet length.
*/
if (ep_info->type != USBLL_EP_BULK)
{
return TRUE;
}
}
if (ep_info->max_packet_size)
{
return data_size < ep_info->max_packet_size;
}
DISSECTOR_ASSERT(ep_info->type == USBLL_EP_CONTROL);
/* This code is valid only for high-speed control endpoints */
if (data_size < 64)
{
return TRUE;
}
return FALSE;
}
static gboolean is_get_device_descriptor(guint8 setup[8])
{
guint16 lang_id = setup[4] | (setup[5] << 8);
guint16 length = setup[6] | (setup[7] << 8);
return (setup[0] == USB_DIR_IN) &&
(setup[1] == USB_SETUP_GET_DESCRIPTOR) &&
(setup[2] == 0x00) && /* Descriptor Index */
(setup[3] == 0x01) && /* DEVICE descriptor */
(lang_id == 0x00) && /* no language specified */
(length >= 8); /* atleast 8 bytes needed to get bMaxPacketSize0 */
}
static gint
dissect_usbll_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
guint8 pid, usbll_data_t *data)
{
/* TODO: How to determine the expected DATA size? */
gint data_size = tvb_reported_length_remaining(tvb, offset) - 2;
guint16 computed_crc, actual_crc;
gint data_offset = offset;
gint data_size = tvb_reported_length_remaining(tvb, offset) - 2;
proto_item *data_item = NULL;
usbll_transfer_info_t *transfer = NULL;
if (data_size > 0) {
proto_tree_add_item(tree, hf_usbll_data, tvb, offset, data_size, ENC_NA);
data_item = proto_tree_add_item(tree, hf_usbll_data, tvb, offset, data_size, ENC_NA);
offset += data_size;
}
actual_crc = tvb_get_letohs(tvb, offset);
computed_crc = crc16_usb_tvb_offset(tvb, 1, offset - 1);
proto_tree_add_checksum(tree, tvb, offset,
hf_usbll_data_crc, hf_usbll_data_crc_status, &ei_wrong_crc16, pinfo,
crc16_usb_tvb_offset(tvb, 1, offset - 1),
ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY);
computed_crc, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY);
offset += 2;
if (!PINFO_FD_VISITED(pinfo))
@ -1077,6 +1412,272 @@ dissect_usbll_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offs
}
}
if (actual_crc != computed_crc)
{
/* Do not reassemble on CRC error */
return offset;
}
if (usbll_is_setup_data(data->transaction_state))
{
if (data_size != 8)
{
expert_add_info(pinfo, data_item, &ei_invalid_setup_data);
}
else if (!PINFO_FD_VISITED(pinfo))
{
usbll_endpoint_info_t *ep_out, *ep_in;
ep_out = usbll_get_endpoint_info(pinfo, data->transaction->address, data->transaction->endpoint, TRUE);
ep_in = usbll_get_endpoint_info(pinfo, data->transaction->address, data->transaction->endpoint, FALSE);
/* Check if SETUP data is indeed to control endpoint (discard if targtet endpoint is not control).
* Practically all control transfers are to endpoint 0 which is always control endpoint.
*/
if ((ep_out->type == USBLL_EP_CONTROL) && (ep_in->type == USBLL_EP_CONTROL))
{
guint8 setup[8];
gboolean data_stage_from_host;
guint16 requested_length;
tvb_memcpy(tvb, setup, data_offset, 8);
/* bmRequestType D7 0 = Host-to-device, 1 = Device-to-host */
data_stage_from_host = (setup[0] & 0x80) ? FALSE : TRUE;
/* wLength */
requested_length = setup[6] | (setup[7] << 8);
usbll_reset_endpoint_info(ep_out, USBLL_EP_CONTROL, ep_out->max_packet_size);
usbll_reset_endpoint_info(ep_in, USBLL_EP_CONTROL, ep_in->max_packet_size);
transfer = wmem_new0(wmem_file_scope(), usbll_transfer_info_t);
transfer->first_packet = pinfo->num;
transfer->offset = 0;
transfer->type = USBLL_EP_CONTROL;
transfer->from_host = TRUE; /* SETUP is always from host to sevice */
if (requested_length > 0)
{
if (data_stage_from_host)
{
/* Merge SETUP data with OUT Data to pass to USB dissector */
transfer->more_frags = TRUE;
ep_out->active_transfer_key = pinfo->num;
ep_out->requested_transfer_length = requested_length;
ep_out->transfer_offset = 8;
ep_out->last_data_pid = pid;
/* If SETUP is sent again, it always starts a new transfer.
* If we receive DATA0 next then it is really a host failure.
* Do not "overwrite" the 8 SETUP bytes in such case.
*/
ep_out->last_data_len = 0;
}
else
{
transfer->more_frags = FALSE;
/* Expect requested_length when reading from control endpoint.
* The data should start with DATA1. If we receive DATA0 then
* is is really device failure.
*/
ep_in->requested_transfer_length = requested_length;
ep_in->last_data_pid = pid;
ep_in->last_data_len = 0;
}
}
if (is_get_device_descriptor(setup))
{
ep_in->data = USBLL_TRANSFER_GET_DEVICE_DESCRIPTOR;
}
wmem_map_insert(transfer_info, GUINT_TO_POINTER(pinfo->num), transfer);
}
}
}
else if (!PINFO_FD_VISITED(pinfo))
{
usbll_endpoint_info_t *ep_info;
gboolean from_host;
from_host = usbll_is_data_from_host(data->transaction_state);
ep_info = usbll_get_endpoint_info(pinfo, data->transaction->address, data->transaction->endpoint, from_host);
if (ep_info->type == USBLL_EP_CONTROL)
{
if (ep_info->requested_transfer_length > 0)
{
if (pid == ep_info->last_data_pid)
{
if (ep_info->last_data_len == 0)
{
/* We received DATA0 immediately after SETUP (as response to OUT or IN)
* Do not reassemble the data, instead mark it as unexpected PID.
*/
data->transaction_state = STATE_INVALID;
}
else
{
/* Retransmission */
transfer = wmem_new0(wmem_file_scope(), usbll_transfer_info_t);
transfer->first_packet = ep_info->active_transfer_key;
transfer->offset = ep_info->transfer_offset - ep_info->last_data_len;
transfer->type = USBLL_EP_CONTROL;
transfer->from_host = from_host;
transfer->more_frags = !packet_ends_transfer(ep_info, transfer->offset, data_size);
wmem_map_insert(transfer_info, GUINT_TO_POINTER(pinfo->num), transfer);
/* Do not update endpoint info, previously transferred packet must have
* the same data length as retransmitted packet.
*/
}
}
else if ((pid == USB_PID_DATA_DATA0) || (pid == USB_PID_DATA_DATA1))
{
if (ep_info->active_transfer_key == 0)
{
/* This is allowed only when Data stage is from device to host */
DISSECTOR_ASSERT(!from_host);
DISSECTOR_ASSERT(ep_info->transfer_offset == 0);
DISSECTOR_ASSERT(ep_info->last_data_len == 0);
ep_info->active_transfer_key = pinfo->num;
if ((ep_info->data == USBLL_TRANSFER_GET_DEVICE_DESCRIPTOR) && (data_size >= 8))
{
usbll_endpoint_info_t *ep_out;
ep_out = usbll_get_endpoint_info(pinfo, data->transaction->address, data->transaction->endpoint, TRUE);
ep_info->max_packet_size = tvb_get_guint8(tvb, data_offset + 7);
ep_out->max_packet_size = ep_info->max_packet_size;
}
}
transfer = wmem_new0(wmem_file_scope(), usbll_transfer_info_t);
transfer->first_packet = ep_info->active_transfer_key;
transfer->offset = ep_info->transfer_offset;
transfer->type = USBLL_EP_CONTROL;
transfer->from_host = from_host;
transfer->more_frags = !packet_ends_transfer(ep_info, transfer->offset, data_size);
wmem_map_insert(transfer_info, GUINT_TO_POINTER(pinfo->num), transfer);
ep_info->last_data_pid = pid;
ep_info->transfer_offset += data_size;
ep_info->last_data_len = data_size;
}
else
{
/* Only DATA0 and DATA1 are allowed in Control transfers */
data->transaction_state = STATE_INVALID;
}
}
else
{
/* We don't know anything about the control transfer.
* Most likely the capture is incomplete, there's nothing to be done here.
*/
}
}
else if ((ep_info->type == USBLL_EP_BULK) ||
(ep_info->type == USBLL_EP_INTERRUPT) ||
(ep_info->type == USBLL_EP_ISOCHRONOUS))
{
if (pid == ep_info->last_data_pid)
{
/* Retransmission */
DISSECTOR_ASSERT(ep_info->active_transfer_key != 0);
transfer = wmem_new0(wmem_file_scope(), usbll_transfer_info_t);
transfer->first_packet = ep_info->active_transfer_key;
transfer->offset = ep_info->transfer_offset - ep_info->last_data_len;
transfer->type = ep_info->type;
transfer->from_host = from_host;
transfer->more_frags = !packet_ends_transfer(ep_info, transfer->offset, data_size);
wmem_map_insert(transfer_info, GUINT_TO_POINTER(pinfo->num), transfer);
/* Do not update endpoint info, previously transferred packet must have
* the same data length as retransmitted packet.
*/
}
else if ((ep_info->active_transfer_key == 0) ||
packet_ends_transfer(ep_info, ep_info->transfer_offset, ep_info->last_data_len))
{
/* Packet starts new transfer */
transfer = wmem_new0(wmem_file_scope(), usbll_transfer_info_t);
transfer->first_packet = pinfo->num;
transfer->offset = 0;
transfer->type = ep_info->type;
transfer->from_host = from_host;
transfer->more_frags = !packet_ends_transfer(ep_info, transfer->offset, data_size);
wmem_map_insert(transfer_info, GUINT_TO_POINTER(pinfo->num), transfer);
ep_info->last_data_pid = pid;
ep_info->active_transfer_key = pinfo->num;
ep_info->transfer_offset = data_size;
ep_info->last_data_len = data_size;
}
else
{
transfer = wmem_new0(wmem_file_scope(), usbll_transfer_info_t);
transfer->first_packet = ep_info->active_transfer_key;
transfer->offset = ep_info->transfer_offset;
transfer->type = ep_info->type;
transfer->from_host = from_host;
transfer->more_frags = !packet_ends_transfer(ep_info, transfer->offset, data_size);
wmem_map_insert(transfer_info, GUINT_TO_POINTER(pinfo->num), transfer);
ep_info->last_data_pid = pid;
ep_info->transfer_offset += data_size;
ep_info->last_data_len = data_size;
}
}
}
transfer = (usbll_transfer_info_t *)wmem_map_lookup(transfer_info, GUINT_TO_POINTER(pinfo->num));
if (transfer)
{
tvbuff_t *transfer_tvb;
if ((transfer->first_packet == pinfo->num) && (!transfer->more_frags))
{
/* No multi-packet reassembly needed, simply construct tvb */
transfer_tvb = tvb_new_subset_length(tvb, data_offset, data_size);
add_new_data_source(pinfo, transfer_tvb, "USB transfer");
}
else
{
fragment_head *head;
head = fragment_add_check(&usbll_reassembly_table, tvb, data_offset,
pinfo, transfer->first_packet, NULL,
transfer->offset, data_size, transfer->more_frags);
transfer_tvb = process_reassembled_data(tvb, data_offset, pinfo,
"USB transfer", head, &usbll_frag_items,
NULL, tree);
}
if (transfer_tvb != NULL)
{
usb_pseudo_urb_t pseudo_urb;
pseudo_urb.from_host = transfer->from_host;
switch (transfer->type)
{
case USBLL_EP_UNKNOWN:
pseudo_urb.transfer_type = URB_UNKNOWN;
break;
case USBLL_EP_CONTROL:
pseudo_urb.transfer_type = URB_CONTROL;
break;
case USBLL_EP_BULK:
pseudo_urb.transfer_type = URB_BULK;
break;
case USBLL_EP_INTERRUPT:
pseudo_urb.transfer_type = URB_INTERRUPT;
break;
case USBLL_EP_ISOCHRONOUS:
pseudo_urb.transfer_type = URB_ISOCHRONOUS;
break;
default:
DISSECTOR_ASSERT_NOT_REACHED();
}
pseudo_urb.device_address = data->transaction->address;
pseudo_urb.endpoint = data->transaction->endpoint;
pseudo_urb.bus_id = 0;
dissect_usb_common(transfer_tvb, pinfo, proto_tree_get_parent_tree(tree),
USB_HEADER_PSEUDO_URB, &pseudo_urb);
}
}
return offset;
}
@ -1246,6 +1847,8 @@ usbll_cleanup_data(void)
usbll_data_ptr = NULL;
tt_non_periodic = NULL;
tt_periodic = NULL;
ep_info_in = NULL;
ep_info_out = NULL;
}
static int
@ -1434,6 +2037,39 @@ proto_register_usbll(void)
{ "CRC5 Status", "usbll.split_crc5.status",
FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0,
NULL, HFILL }},
{ &hf_usbll_transfer_fragments,
{ "Transfer fragments", "usbll.fragments",
FT_NONE, BASE_NONE, NULL, 0x00,
NULL, HFILL }},
{ &hf_usbll_transfer_fragment,
{"Transfer fragment", "usbll.fragment",
FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL }},
{ &hf_usbll_transfer_fragment_overlap,
{"Transfer fragment overlap", "usbll.fragment.overlap",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL }},
{ &hf_usbll_transfer_fragment_overlap_conflicts,
{"Transfer fragment overlapping with conflicting data",
"usbll.fragment.overlap.conflicts",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL }},
{ &hf_usbll_transfer_fragment_multiple_tails,
{"Transfer has multiple tail fragments",
"usbll.fragment.multiple_tails",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL }},
{ &hf_usbll_transfer_fragment_too_long_fragment,
{"Transfer fragment too long", "usbll.fragment.too_long_fragment",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL }},
{ &hf_usbll_transfer_fragment_error,
{"Transfer defragmentation error", "usbll.fragment.error",
FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL }},
{ &hf_usbll_transfer_fragment_count,
{"Transfer fragment count", "usbll.fragment.count",
FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL }},
{ &hf_usbll_transfer_reassembled_in,
{"Reassembled in", "usbll.reassembled.in",
FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL }},
{ &hf_usbll_transfer_reassembled_length,
{"Reassembled length", "usbll.reassembled.length",
FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL }},
};
static ei_register_info ei[] = {
@ -1445,12 +2081,16 @@ proto_register_usbll(void)
{ &ei_invalid_s, { "usbll.invalid_s", PI_MALFORMED, PI_ERROR, "Invalid bit (Must be 0)", EXPFILL }},
{ &ei_invalid_e_u, { "usbll.invalid_e_u", PI_MALFORMED, PI_ERROR, "Invalid bit (Must be 0)", EXPFILL }},
{ &ei_invalid_pid_sequence, {"usbll.invalid_pid_sequence", PI_MALFORMED, PI_ERROR, "Invalid PID Sequence",EXPFILL }},
{ &ei_invalid_setup_data, {"usbll.invalid_setup_data", PI_MALFORMED, PI_ERROR, "Invalid data length (Must be 8 bytes)", EXPFILL }},
};
static gint *ett[] = {
&ett_usbll,
&ett_usbll_transfer_fragment,
&ett_usbll_transfer_fragments,
};
transfer_info = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), g_direct_hash, g_direct_equal);
proto_usbll = proto_register_protocol("USB Link Layer", "USBLL", "usbll");
proto_register_field_array(proto_usbll, hf, array_length(hf));
proto_register_subtree_array(ett, array_length(ett));
@ -1465,6 +2105,7 @@ proto_register_usbll(void)
usbll_addr_to_str, usbll_addr_str_len,
NULL, NULL, NULL, NULL, NULL);
reassembly_table_register(&usbll_reassembly_table, &usbll_reassembly_table_functions);
}
void