From nobody Mon Feb 9 21:12:09 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1611338182; cv=none; d=zohomail.com; s=zohoarc; b=YqXudUSJ5MJne4f4j2HqStp4nBZFn6N6mGHeaAOSallP31p8ZktBmsE1CWiMDKoFCbPWGlo6eYdS77AZV//+v7oAbhTY0rgkOmq5muOwM8dxDG2SYamHIW3D1uk9v2ZFYrlOX1cu2OTDojQZ34b9i3sgyioTctAh6YoUCL/sPWY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1611338182; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=MU9L3KBjHy7E33bhB7dlw3wAVpvK58mBVxYhFviXHW8=; b=WIc1qvkwU0cOd6WgQ6LvR94tanPvPMlf0x3loXtAkN/BBsJebeaeoU+uUlNqczju3L/1f83ur6XDWE47fASvY1evdR93dOjLMXwMxDftNgI/0ajWU6ql5Bk0ti4yceJteiMy2IKPWt6ufyHMIJI0ZxxJZ2sy8NMc4LI0CR5faa0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail header.from= (p=none dis=none) header.from= Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1611338182950681.5882832487887; Fri, 22 Jan 2021 09:56:22 -0800 (PST) Received: from localhost ([::1]:35850 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1l30fV-0003at-So for importer@patchew.org; Fri, 22 Jan 2021 12:56:21 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:51560) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1l30YY-0003dE-GI for qemu-devel@nongnu.org; Fri, 22 Jan 2021 12:49:10 -0500 Received: from us-smtp-delivery-124.mimecast.com ([63.128.21.124]:58086) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.90_1) (envelope-from ) id 1l30YS-0004ho-Mg for qemu-devel@nongnu.org; Fri, 22 Jan 2021 12:49:10 -0500 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-424-wi8mv1fTM-66HPV2L1oH8g-1; Fri, 22 Jan 2021 12:49:01 -0500 Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 10EF480666F; Fri, 22 Jan 2021 17:49:00 +0000 (UTC) Received: from sirius.home.kraxel.org (ovpn-112-182.ams2.redhat.com [10.36.112.182]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 4BC9D60C13; Fri, 22 Jan 2021 17:48:56 +0000 (UTC) Received: by sirius.home.kraxel.org (Postfix, from userid 1000) id 56903180062D; Fri, 22 Jan 2021 18:48:50 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1611337744; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=MU9L3KBjHy7E33bhB7dlw3wAVpvK58mBVxYhFviXHW8=; b=FJThOM+uGpbWLT/rItUp+V6wn86/SqQNbXoOlk4a53x1WBntLYyOn7tw2v9AWE3+/9VoiT pDbcSnO+aXHMpfODIvXsvssBv7cdD0nErJLvd2gxCGu/IGGvzJJZcGhRgF1oxYD4dD5qVe /OkSaiB8N/a03Rj0k2nieSScwURVMu4= X-MC-Unique: wi8mv1fTM-66HPV2L1oH8g-1 From: Gerd Hoffmann To: qemu-devel@nongnu.org Subject: [PULL 7/8] usb: add pcap support. Date: Fri, 22 Jan 2021 18:48:48 +0100 Message-Id: <20210122174849.3936119-8-kraxel@redhat.com> In-Reply-To: <20210122174849.3936119-1-kraxel@redhat.com> References: <20210122174849.3936119-1-kraxel@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=kraxel@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=63.128.21.124; envelope-from=kraxel@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -29 X-Spam_score: -3.0 X-Spam_bar: --- X-Spam_report: (-3.0 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.182, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Fam Zheng , Paolo Bonzini , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Gerd Hoffmann , qemu-block@nongnu.org Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) Log all traffic of a specific usb device to a pcap file for later inspection. File format is compatible with linux usb monitor. Usage: qemu -device usb-${somedevice},pcap=3Dfile.pcap wireshark file.pcap Signed-off-by: Gerd Hoffmann Reviewed-by: Philippe Mathieu-Daud=C3=A9 Message-Id: <20210119194452.2148048-1-kraxel@redhat.com> Signed-off-by: Gerd Hoffmann --- include/hw/usb.h | 8 ++ hw/usb/bus.c | 16 +++ hw/usb/core.c | 17 +++ hw/usb/pcap.c | 251 +++++++++++++++++++++++++++++++++++++++++++++ hw/usb/meson.build | 1 + 5 files changed, 293 insertions(+) create mode 100644 hw/usb/pcap.c diff --git a/include/hw/usb.h b/include/hw/usb.h index a70a72e9177a..abfbfc5284c2 100644 --- a/include/hw/usb.h +++ b/include/hw/usb.h @@ -231,6 +231,9 @@ struct USBDevice { void *opaque; uint32_t flags; =20 + char *pcap_filename; + FILE *pcap; + /* Actual connected speed */ int speed; /* Supported speeds, not in info because it may be variable (hostdevs)= */ @@ -570,4 +573,9 @@ int usb_get_quirks(uint16_t vendor_id, uint16_t product= _id, uint8_t interface_class, uint8_t interface_subclass, uint8_t interface_protocol); =20 +/* pcap.c */ +void usb_pcap_init(FILE *fp); +void usb_pcap_ctrl(USBPacket *p, bool setup); +void usb_pcap_data(USBPacket *p, bool setup); + #endif diff --git a/hw/usb/bus.c b/hw/usb/bus.c index 2b1104145157..064f94e9c3cc 100644 --- a/hw/usb/bus.c +++ b/hw/usb/bus.c @@ -23,6 +23,7 @@ static Property usb_props[] =3D { USB_DEV_FLAG_FULL_PATH, true), DEFINE_PROP_BIT("msos-desc", USBDevice, flags, USB_DEV_FLAG_MSOS_DESC_ENABLE, true), + DEFINE_PROP_STRING("pcap", USBDevice, pcap_filename), DEFINE_PROP_END_OF_LIST() }; =20 @@ -270,6 +271,17 @@ static void usb_qdev_realize(DeviceState *qdev, Error = **errp) return; } } + + if (dev->pcap_filename) { + int fd =3D qemu_open_old(dev->pcap_filename, O_CREAT | O_WRONLY | = O_TRUNC, 0666); + if (fd < 0) { + error_setg(errp, "open %s failed", dev->pcap_filename); + usb_qdev_unrealize(qdev); + return; + } + dev->pcap =3D fdopen(fd, "w"); + usb_pcap_init(dev->pcap); + } } =20 static void usb_qdev_unrealize(DeviceState *qdev) @@ -283,6 +295,10 @@ static void usb_qdev_unrealize(DeviceState *qdev) g_free(s); } =20 + if (dev->pcap) { + fclose(dev->pcap); + } + if (dev->attached) { usb_device_detach(dev); } diff --git a/hw/usb/core.c b/hw/usb/core.c index c895522a1d5d..975f76250a1a 100644 --- a/hw/usb/core.c +++ b/hw/usb/core.c @@ -154,6 +154,7 @@ static void do_token_setup(USBDevice *s, USBPacket *p) index =3D (s->setup_buf[5] << 8) | s->setup_buf[4]; =20 if (s->setup_buf[0] & USB_DIR_IN) { + usb_pcap_ctrl(p, true); usb_device_handle_control(s, p, request, value, index, s->setup_len, s->data_buf); if (p->status =3D=3D USB_RET_ASYNC) { @@ -190,6 +191,7 @@ static void do_token_in(USBDevice *s, USBPacket *p) switch(s->setup_state) { case SETUP_STATE_ACK: if (!(s->setup_buf[0] & USB_DIR_IN)) { + usb_pcap_ctrl(p, true); usb_device_handle_control(s, p, request, value, index, s->setup_len, s->data_buf); if (p->status =3D=3D USB_RET_ASYNC) { @@ -197,6 +199,7 @@ static void do_token_in(USBDevice *s, USBPacket *p) } s->setup_state =3D SETUP_STATE_IDLE; p->actual_length =3D 0; + usb_pcap_ctrl(p, false); } break; =20 @@ -215,6 +218,7 @@ static void do_token_in(USBDevice *s, USBPacket *p) } s->setup_state =3D SETUP_STATE_IDLE; p->status =3D USB_RET_STALL; + usb_pcap_ctrl(p, false); break; =20 default: @@ -230,6 +234,7 @@ static void do_token_out(USBDevice *s, USBPacket *p) case SETUP_STATE_ACK: if (s->setup_buf[0] & USB_DIR_IN) { s->setup_state =3D SETUP_STATE_IDLE; + usb_pcap_ctrl(p, false); /* transfer OK */ } else { /* ignore additional output */ @@ -251,6 +256,7 @@ static void do_token_out(USBDevice *s, USBPacket *p) } s->setup_state =3D SETUP_STATE_IDLE; p->status =3D USB_RET_STALL; + usb_pcap_ctrl(p, false); break; =20 default: @@ -288,6 +294,7 @@ static void do_parameter(USBDevice *s, USBPacket *p) usb_packet_copy(p, s->data_buf, s->setup_len); } =20 + usb_pcap_ctrl(p, true); usb_device_handle_control(s, p, request, value, index, s->setup_len, s->data_buf); if (p->status =3D=3D USB_RET_ASYNC) { @@ -301,6 +308,7 @@ static void do_parameter(USBDevice *s, USBPacket *p) p->actual_length =3D 0; usb_packet_copy(p, s->data_buf, s->setup_len); } + usb_pcap_ctrl(p, false); } =20 /* ctrl complete function for devices which use usb_generic_handle_packet = and @@ -311,6 +319,7 @@ void usb_generic_async_ctrl_complete(USBDevice *s, USBP= acket *p) { if (p->status < 0) { s->setup_state =3D SETUP_STATE_IDLE; + usb_pcap_ctrl(p, false); } =20 switch (s->setup_state) { @@ -325,6 +334,7 @@ void usb_generic_async_ctrl_complete(USBDevice *s, USBP= acket *p) case SETUP_STATE_ACK: s->setup_state =3D SETUP_STATE_IDLE; p->actual_length =3D 0; + usb_pcap_ctrl(p, false); break; =20 case SETUP_STATE_PARAM: @@ -359,12 +369,14 @@ USBDevice *usb_find_device(USBPort *port, uint8_t add= r) static void usb_process_one(USBPacket *p) { USBDevice *dev =3D p->ep->dev; + bool nak; =20 /* * Handlers expect status to be initialized to USB_RET_SUCCESS, but it * can be USB_RET_NAK here from a previous usb_process_one() call, * or USB_RET_ASYNC from going through usb_queue_one(). */ + nak =3D (p->status =3D=3D USB_RET_NAK); p->status =3D USB_RET_SUCCESS; =20 if (p->ep->nr =3D=3D 0) { @@ -388,6 +400,9 @@ static void usb_process_one(USBPacket *p) } } else { /* data pipe */ + if (!nak) { + usb_pcap_data(p, true); + } usb_device_handle_data(dev, p); } } @@ -439,6 +454,7 @@ void usb_handle_packet(USBDevice *dev, USBPacket *p) assert(p->stream || !p->ep->pipeline || QTAILQ_EMPTY(&p->ep->queue)); if (p->status !=3D USB_RET_NAK) { + usb_pcap_data(p, false); usb_packet_set_state(p, USB_PACKET_COMPLETE); } } @@ -458,6 +474,7 @@ void usb_packet_complete_one(USBDevice *dev, USBPacket = *p) (p->short_not_ok && (p->actual_length < p->iov.size))) { ep->halted =3D true; } + usb_pcap_data(p, false); usb_packet_set_state(p, USB_PACKET_COMPLETE); QTAILQ_REMOVE(&ep->queue, p, queue); dev->port->ops->complete(dev->port, p); diff --git a/hw/usb/pcap.c b/hw/usb/pcap.c new file mode 100644 index 000000000000..4350989d3a71 --- /dev/null +++ b/hw/usb/pcap.c @@ -0,0 +1,251 @@ +/* + * usb packet capture + * + * Copyright (c) 2021 Gerd Hoffmann + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "hw/usb.h" + +#define PCAP_MAGIC 0xa1b2c3d4 +#define PCAP_MAJOR 2 +#define PCAP_MINOR 4 + +/* https://wiki.wireshark.org/Development/LibpcapFileFormat */ + +struct pcap_hdr { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets = */ + uint32_t network; /* data link type */ +}; + +struct pcaprec_hdr { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +}; + +/* https://www.tcpdump.org/linktypes.html */ +/* linux: Documentation/usb/usbmon.rst */ +/* linux: drivers/usb/mon/mon_bin.c */ + +#define LINKTYPE_USB_LINUX 189 /* first 48 bytes only */ +#define LINKTYPE_USB_LINUX_MMAPPED 220 /* full 64 byte header */ + +struct usbmon_packet { + uint64_t id; /* 0: URB ID - from submission to callback */ + unsigned char type; /* 8: Same as text; extensible. */ + unsigned char xfer_type; /* ISO (0), Intr, Control, Bulk (3) */ + unsigned char epnum; /* Endpoint number and transfer direction= */ + unsigned char devnum; /* Device address */ + uint16_t busnum; /* 12: Bus number */ + char flag_setup; /* 14: Same as text */ + char flag_data; /* 15: Same as text; Binary zero is OK. */ + int64_t ts_sec; /* 16: gettimeofday */ + int32_t ts_usec; /* 24: gettimeofday */ + int32_t status; /* 28: */ + unsigned int length; /* 32: Length of data (submitted or actual) */ + unsigned int len_cap; /* 36: Delivered length */ + union { /* 40: */ + unsigned char setup[8]; /* Only for Control S-type */ + struct iso_rec { /* Only for ISO */ + int32_t error_count; + int32_t numdesc; + } iso; + } s; + int32_t interval; /* 48: Only for Interrupt and ISO */ + int32_t start_frame; /* 52: For ISO */ + uint32_t xfer_flags; /* 56: copy of URB's transfer_flags */ + uint32_t ndesc; /* 60: Actual number of ISO descriptors */ +}; /* 64 total length */ + +/* -----------------------------------------------------------------------= - */ + +#define CTRL_LEN 4096 +#define DATA_LEN 256 + +static int usbmon_status(USBPacket *p) +{ + switch (p->status) { + case USB_RET_SUCCESS: + return 0; + case USB_RET_NODEV: + return -19; /* -ENODEV */ + default: + return -121; /* -EREMOTEIO */ + } +} + +static unsigned int usbmon_epnum(USBPacket *p) +{ + unsigned epnum =3D 0; + + epnum |=3D p->ep->nr; + epnum |=3D (p->pid =3D=3D USB_TOKEN_IN) ? 0x80 : 0; + return epnum; +} + +static unsigned char usbmon_xfer_type[] =3D { + [USB_ENDPOINT_XFER_CONTROL] =3D 2, + [USB_ENDPOINT_XFER_ISOC] =3D 0, + [USB_ENDPOINT_XFER_BULK] =3D 3, + [USB_ENDPOINT_XFER_INT] =3D 1, +}; + +static void do_usb_pcap_header(FILE *fp, struct usbmon_packet *packet) +{ + struct pcaprec_hdr header; + struct timeval tv; + + gettimeofday(&tv, NULL); + packet->ts_sec =3D tv.tv_sec; + packet->ts_usec =3D tv.tv_usec; + + header.ts_sec =3D packet->ts_sec; + header.ts_usec =3D packet->ts_usec; + header.incl_len =3D packet->len_cap; + header.orig_len =3D packet->length + sizeof(*packet); + fwrite(&header, sizeof(header), 1, fp); + fwrite(packet, sizeof(*packet), 1, fp); +} + +static void do_usb_pcap_ctrl(FILE *fp, USBPacket *p, bool setup) +{ + USBDevice *dev =3D p->ep->dev; + bool in =3D dev->setup_buf[0] & USB_DIR_IN; + struct usbmon_packet packet =3D { + .id =3D 0, + .type =3D setup ? 'S' : 'C', + .xfer_type =3D usbmon_xfer_type[USB_ENDPOINT_XFER_CONTROL], + .epnum =3D in ? 0x80 : 0, + .devnum =3D dev->addr, + .flag_data =3D '=3D', + .length =3D dev->setup_len, + }; + int data_len =3D dev->setup_len; + + if (data_len > CTRL_LEN) { + data_len =3D CTRL_LEN; + } + if (setup) { + memcpy(packet.s.setup, dev->setup_buf, 8); + } else { + packet.status =3D usbmon_status(p); + } + + if (in && setup) { + packet.flag_data =3D '<'; + packet.length =3D 0; + data_len =3D 0; + } + if (!in && !setup) { + packet.flag_data =3D '>'; + packet.length =3D 0; + data_len =3D 0; + } + + packet.len_cap =3D data_len + sizeof(packet); + do_usb_pcap_header(fp, &packet); + if (data_len) { + fwrite(dev->data_buf, data_len, 1, fp); + } + + fflush(fp); +} + +static void do_usb_pcap_data(FILE *fp, USBPacket *p, bool setup) +{ + struct usbmon_packet packet =3D { + .id =3D p->id, + .type =3D setup ? 'S' : 'C', + .xfer_type =3D usbmon_xfer_type[p->ep->type], + .epnum =3D usbmon_epnum(p), + .devnum =3D p->ep->dev->addr, + .flag_data =3D '=3D', + .length =3D p->iov.size, + }; + int data_len =3D p->iov.size; + + if (p->ep->nr =3D=3D 0) { + /* ignore control pipe packets */ + return; + } + + if (data_len > DATA_LEN) { + data_len =3D DATA_LEN; + } + if (!setup) { + packet.status =3D usbmon_status(p); + if (packet.length > p->actual_length) { + packet.length =3D p->actual_length; + } + if (data_len > p->actual_length) { + data_len =3D p->actual_length; + } + } + + if (p->pid =3D=3D USB_TOKEN_IN && setup) { + packet.flag_data =3D '<'; + packet.length =3D 0; + data_len =3D 0; + } + if (p->pid =3D=3D USB_TOKEN_OUT && !setup) { + packet.flag_data =3D '>'; + packet.length =3D 0; + data_len =3D 0; + } + + packet.len_cap =3D data_len + sizeof(packet); + do_usb_pcap_header(fp, &packet); + if (data_len) { + void *buf =3D g_malloc(data_len); + iov_to_buf(p->iov.iov, p->iov.niov, 0, buf, data_len); + fwrite(buf, data_len, 1, fp); + g_free(buf); + } + + fflush(fp); +} + +void usb_pcap_init(FILE *fp) +{ + struct pcap_hdr header =3D { + .magic_number =3D PCAP_MAGIC, + .version_major =3D 2, + .version_minor =3D 4, + .snaplen =3D MAX(CTRL_LEN, DATA_LEN) + sizeof(struct usbmon_= packet), + .network =3D LINKTYPE_USB_LINUX_MMAPPED, + }; + + fwrite(&header, sizeof(header), 1, fp); +} + +void usb_pcap_ctrl(USBPacket *p, bool setup) +{ + FILE *fp =3D p->ep->dev->pcap; + + if (!fp) { + return; + } + + do_usb_pcap_ctrl(fp, p, setup); +} + +void usb_pcap_data(USBPacket *p, bool setup) +{ + FILE *fp =3D p->ep->dev->pcap; + + if (!fp) { + return; + } + + do_usb_pcap_data(fp, p, setup); +} diff --git a/hw/usb/meson.build b/hw/usb/meson.build index f46c6b665535..653192cff6fa 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -5,6 +5,7 @@ softmmu_ss.add(files( 'bus.c', 'combined-packet.c', 'core.c', + 'pcap.c', 'libhw.c' )) =20 --=20 2.29.2