Skip to content

Commit 58e1038

Browse files
committed
Added support of usbmon.
1 parent 9f58cd3 commit 58e1038

File tree

5 files changed

+222
-14
lines changed

5 files changed

+222
-14
lines changed

scapy/data.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
DLT_BLUETOOTH_HCI_H4_WITH_PHDR = 201
116116
DLT_AX25_KISS = 202
117117
DLT_PPP_WITH_DIR = 204
118+
DLT_USB_LINUX_MMAPPED = 220
118119
DLT_FC_2 = 224
119120
DLT_CAN_SOCKETCAN = 227
120121
if OPENBSD:

scapy/layers/usb.py

Lines changed: 213 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@
88
Default USB frames & Basic implementation
99
"""
1010

11-
# TODO: support USB headers for Linux and Darwin (usbmon/netmon)
11+
# TODO: support USB headers for Darwin (netmon)
1212
# https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-usb.c # noqa: E501
1313

1414
import re
1515
import subprocess
16+
from enum import Enum, IntEnum
1617

1718
from scapy.config import conf
1819
from scapy.consts import WINDOWS
1920
from scapy.compat import chb, plain_str
20-
from scapy.data import MTU, DLT_USBPCAP
21+
from scapy.data import DLT_USB_LINUX, DLT_USB_LINUX_MMAPPED, DLT_USBPCAP, MTU
2122
from scapy.error import warning
22-
from scapy.fields import ByteField, XByteField, ByteEnumField, LEShortField, \
23-
LEShortEnumField, LEIntField, LEIntEnumField, XLELongField, \
24-
LenField
23+
from scapy.fields import ByteEnumField, ByteField, CharEnumField, \
24+
ConditionalField, EnumField, LEIntEnumField, LEIntField, LELongField, \
25+
LenField, LEShortEnumField, LEShortField, MultipleTypeField, \
26+
PacketLenField, StrLenField, XByteField, XLELongField
2527
from scapy.interfaces import NetworkInterface, InterfaceProvider, \
2628
network_name, IFACES
29+
30+
from scapy.interfaces import IFACES, InterfaceProvider, NetworkInterface, network_name
2731
from scapy.packet import Packet, bind_top_down
2832
from scapy.supersocket import SuperSocket
2933
from scapy.utils import PcapReader
@@ -89,15 +93,15 @@
8993
class USBpcap(Packet):
9094
name = "USBpcap URB"
9195
fields_desc = [ByteField("headerLen", None),
92-
ByteField("res", 0),
93-
XLELongField("irpId", 0),
94-
LEIntEnumField("usbd_status", 0x0, _usbd_status_codes),
95-
LEShortEnumField("function", 0, _urb_functions),
96-
XByteField("info", 0),
97-
LEShortField("bus", 0),
98-
LEShortField("device", 0),
99-
XByteField("endpoint", 0),
100-
ByteEnumField("transfer", 0, _transfer_types),
96+
ByteField("res", 0),
97+
XLELongField("irpId", 0),
98+
LEIntEnumField("usbd_status", 0x0, _usbd_status_codes),
99+
LEShortEnumField("function", 0, _urb_functions),
100+
XByteField("info", 0),
101+
LEShortField("bus", 0),
102+
LEShortField("device", 0),
103+
XByteField("endpoint", 0),
104+
ByteEnumField("transfer", 0, _transfer_types),
101105
LenField("dataLength", None, fmt="<I")]
102106

103107
def post_build(self, p, pay):
@@ -284,3 +288,198 @@ def close(self):
284288
self.usbpcap_proc.kill()
285289

286290
conf.USBsocket = USBpcapSocket
291+
292+
293+
class EndpointNumber(ByteField):
294+
def any2i(self, pkt, x):
295+
if isinstance(x, tuple):
296+
return self.h2i(pkt, x)
297+
298+
if isinstance(x, int):
299+
return x
300+
301+
return super(EndpointNumber, self).any2i(pkt, x)
302+
303+
def h2i(self, pkt, x):
304+
is_input, endpoint = x
305+
return endpoint | (int(is_input) << 7)
306+
307+
def i2h(self, pkt, x):
308+
return bool(x >> 7), x & 0x7F
309+
310+
def i2repr(self, pkt, val):
311+
is_input, endpoint = self.i2h(pkt, val)
312+
return "%s 0x%x" % (u"\u2190" if is_input else u"\u2192", endpoint)
313+
314+
315+
# we have to do it since when using `PacketLenField`:
316+
# `.parent` is not populated, so we cannot use `MultipleTypeField`
317+
# alternatives are always evaluated no matter what
318+
# no way to provide parameters even with severe perversions
319+
setup_common_fields = [
320+
LEIntField("interval", None),
321+
LEIntField("start_frame", None),
322+
LEIntField("copy_of_urb_transfer_flags", None),
323+
LEIntField("iso_descriptors_count", None),
324+
]
325+
326+
327+
class SetupSetup(Packet):
328+
name = "Setup"
329+
330+
class PcapUsbSetup(Packet):
331+
"""USB setup header as defined in USB specification.
332+
Appears at the front of each Control S-type packet in DLT_USB captures.
333+
"""
334+
335+
name = "Setup"
336+
fields_desc = [
337+
XByteField("request_type", None), # 1
338+
XByteField("request", None), # 1
339+
LEShortField("value", None), # 2
340+
LEShortField("index", None), # 2
341+
LEShortField("length", None), # 2
342+
]
343+
344+
fields_desc = [
345+
PacketLenField("s", None, PcapUsbSetup, length_from=lambda pkt: 8),
346+
] + setup_common_fields
347+
348+
349+
class SetupIsocr(Packet):
350+
name = "Setup"
351+
352+
class IsoRec(Packet):
353+
"""Information from the URB for Isochronous transfers.
354+
355+
.. seealso::
356+
Source - https://github.com/the-tcpdump-group/libpcap/blob/ba0ef0353ed9f9f49a1edcfb49fefaf12dec54de/pcap/usb.h#L70
357+
"""
358+
359+
name = "IsoRec"
360+
fields_desc = [
361+
LEIntField("error_count", None), # 4
362+
LEIntField("descriptors_count", None), # 4
363+
]
364+
365+
fields_desc = [
366+
PacketLenField("s", None, IsoRec, length_from=lambda pkt: 8),
367+
] + setup_common_fields
368+
369+
370+
class USBMon(Packet):
371+
"""A native pcap header of `usbmon <https://www.kernel.org/doc/Documentation/usb/usbmon.txt>`__ part of libpcap and Linux kernel.
372+
373+
.. seealso::
374+
Source - https://github.com/the-tcpdump-group/libpcap/blob/ba0ef0353ed9f9f49a1edcfb49fefaf12dec54de/pcap/usb.h#L94
375+
376+
377+
.. seealso::
378+
Source - https://www.kernel.org/doc/Documentation/usb/usbmon.txt
379+
380+
381+
.. seealso::
382+
Source - https://www.kernel.org/doc/html/latest/driver-api/usb/URB.html
383+
384+
385+
.. seealso::
386+
Source - https://wiki.wireshark.org/USB
387+
"""
388+
389+
HEADER_SIZE = None
390+
name = "USBMonHeader"
391+
392+
class EventType(Enum):
393+
completion = b"C"
394+
error = b"E"
395+
submit = b"S"
396+
397+
class TransferType(IntEnum):
398+
isochronous = 0
399+
interrupt = 1
400+
control = 2
401+
bulk = 3
402+
403+
class SetupFlag(Enum):
404+
relevant = b"\0"
405+
irrelevant = b"-"
406+
407+
class DataFlag(Enum):
408+
urb = b"\0"
409+
incoming = b"<"
410+
outgoing = b">"
411+
error = b"E"
412+
413+
class TimeStamp(Packet):
414+
name = "TimeStamp"
415+
416+
@staticmethod
417+
def _getSize(*_args):
418+
return 12
419+
420+
fields_desc = [
421+
LELongField("seconds", None), # 8
422+
LEIntField("microseconds", None), # 4
423+
]
424+
425+
@property
426+
def needs_setup(self):
427+
SetupFlag = self.__class__.SetupFlag
428+
return SetupFlag(self.setup_flag) == SetupFlag.relevant
429+
430+
@property
431+
def is_isochr(self):
432+
TransferType = self.__class__.TransferType
433+
return TransferType(self.transfer_type) == TransferType.isochronous
434+
435+
HEADER_STATIC_PART_SIZE = (
436+
8 + 1 + 1 + 1 + 1 + 2 + 1 + 1
437+
+ TimeStamp._getSize() + # pylint:disable=protected-access
438+
4 + 4 + 4
439+
)
440+
441+
def _getOptionalPartSize(pkt=None):
442+
return 24
443+
444+
def _getPaddingSize(pkt):
445+
res = pkt.__class__.HEADER_SIZE - __class__._getOptionalPartSize() * int(pkt.needs_setup) - __class__.HEADER_STATIC_PART_SIZE # pylint:disable=protected-access
446+
return res
447+
448+
fields_desc = [
449+
LELongField("urb_id", None), # 8
450+
CharEnumField("event_type", b"\0", EventType), # 1
451+
EnumField("transfer_type", 0, TransferType, "<B"), # 1
452+
EndpointNumber("endpoint_number", 0), # 1
453+
XByteField("device_address", None), # 1
454+
LEShortField("bus_id", None), # 2
455+
CharEnumField("setup_flag", b"\0", SetupFlag), # 1
456+
CharEnumField("data_flag", b"\0", DataFlag), # 1
457+
# just PacketField doesn't work and breaks everything after it
458+
PacketLenField("timestamp", None, TimeStamp, length_from=TimeStamp._getSize), # 12, pylint:disable=protected-access
459+
LEIntEnumField("status", 0x0, _usbd_status_codes), # 4
460+
LEIntField("urb_size", 0), # 4
461+
LenField("data_size", 0, fmt="<i"), # 4
462+
ConditionalField(
463+
MultipleTypeField(
464+
[
465+
# just PacketField doesn't work and breaks everything after it
466+
(PacketLenField("setup", None, SetupIsocr, length_from=_getOptionalPartSize), lambda pkt: pkt.is_isochr,),
467+
],
468+
PacketLenField("setup", None, SetupSetup, length_from=_getOptionalPartSize),
469+
),
470+
lambda pkt: pkt.needs_setup,
471+
), # 24
472+
StrLenField("padding", None, length_from=_getPaddingSize),
473+
]
474+
475+
476+
class USBMonSimple(USBMon):
477+
HEADER_SIZE = 48
478+
479+
480+
class USBMonMMapped(USBMon):
481+
HEADER_SIZE = 64
482+
483+
484+
conf.l2types.register(DLT_USB_LINUX, USBMonSimple)
485+
conf.l2types.register(DLT_USB_LINUX_MMAPPED, USBMonMMapped)

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ def process_ignore_tags(buffer):
5959
'sphinx>=3.0.0',
6060
'sphinx_rtd_theme>=0.4.3',
6161
'tox>=3.0.0'
62+
],
63+
'usbmon': [
64+
'enum34 ; python_version < "3.5"'
6265
]
6366
},
6467
# We use __file__ in scapy/__init__.py, therefore Scapy isn't zip safe

test/pcaps/usbmon.pcap

1.88 KB
Binary file not shown.

test/pcaps/usbmon.pcap.license

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
SPDX-FileCopyrightText: 2019 The usbmon-tools Authors
2+
3+
SPDX-License-Identifier: Unlicense
4+
5+
Source: https://github.com/Flameeyes/usbmon-tools/blob/main/testdata/test1.pcap

0 commit comments

Comments
 (0)