Getting Started¶
Everything you need to run your first virtual USB device and plug it into a real operating system: install the library, write the device, import it with a USB/IP client.
From a checkout instead (the examples come with it):
Python 3.8 or newer. Check the installed version with
python -c "import usbip; print(usbip.__version__)".
The only prebuilt package is for Windows - headers, both role libraries in 32-
and 64-bit, the examples as .exe, attached to every
release. Everywhere else,
build from source:
On Windows, build natively from cmd with mingw32-make -C usbip-c (mingw-w64
on PATH), or cross-build from Linux with make -C usbip-c OS=Windows_NT.
See the C library reference for the gcc/clang link lines and the C walkthrough.
USB/IP inverts the everyday words: the side that provides the device is the USB/IP server, and the side that uses it is the USB/IP client.
| Your side | USB role | USB/IP role | Socket |
|---|---|---|---|
| a program on the device API (this library) | device | server | listens on TCP 3240 |
| a USB/IP client (the OS's own importer) | host | client | connects |
| a program on the host API | host | client | connects |
So dev.plug() does not plug into anything - it starts serving the device and
returns. Nothing enumerates it until a client imports it.
A USB Boot-protocol HID keyboard written as an Interface of your own:
hand-authored descriptors and one control handler, with no ready-made device class
involved. This is the whole surface a simple device needs - USBDevice, an
Interface with an endpoint and a control handler, plug().
#!/usr/bin/env python3
"""
The documentation's first example: a USB Boot-protocol HID keyboard served over
USB/IP, written as an INTERFACE OF ITS OWN (usbip.Interface) - every descriptor
authored by hand, every class request answered in one method, no ready-made device
class. Mirrors the C library's doc/examples/boot_keyboard.c.
python3 boot_keyboard.py # serve 1209:0011 on :3240
python3 boot_keyboard.py --port 4000 --type world
Roles are inverted from USB's: in USB terms this program is the DEVICE, but in
USB/IP terms it is the SERVER - plug() listens on TCP 3240 and waits. The USB
host end is the USB/IP CLIENT, and it connects.
"""
import argparse
import logging
import struct
import sys
import threading
import time
import usbip
from usbip import In, Interface, Stall, USBDevice
from usbip.core import CLASS, STANDARD
log = logging.getLogger("kbd")
# HID 1.11 - the handful of constants this example needs
HID_DT_HID, HID_DT_REPORT = 0x21, 0x22 # class descriptor types
HID_GET_REPORT, HID_GET_IDLE, HID_GET_PROTOCOL = 0x01, 0x02, 0x03
HID_SET_REPORT, HID_SET_IDLE, HID_SET_PROTOCOL = 0x09, 0x0A, 0x0B
USB_CLASS_HID = 0x03
HID_SUBCLASS_BOOT = 0x01 # bInterfaceSubClass: boot interface
HID_PROTOCOL_KEYBOARD = 0x01 # bInterfaceProtocol: keyboard
KEY_REPORT_LEN = 8 # modifiers, reserved, 6 key codes
# The standard boot-keyboard Report descriptor: an 8-byte Input report plus a
# 1-byte LED Output report, raw bytes in wire order.
# fmt: off
KEYBOARD_REPORT_DESC = bytes([
0x05, 0x01, # Usage Page (Generic Desktop)
0x09, 0x06, # Usage (Keyboard)
0xA1, 0x01, # Collection (Application)
0x05, 0x07, # Usage Page (Keyboard/Keypad)
0x19, 0xE0, # Usage Minimum (Left Control)
0x29, 0xE7, # Usage Maximum (Right GUI)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x75, 0x01, # Report Size (1)
0x95, 0x08, # Report Count (8)
0x81, 0x02, # Input (Data,Var,Abs) - modifiers
0x95, 0x01, # Report Count (1)
0x75, 0x08, # Report Size (8)
0x81, 0x03, # Input (Const) - reserved byte
0x95, 0x05, # Report Count (5)
0x75, 0x01, # Report Size (1)
0x05, 0x08, # Usage Page (LEDs)
0x19, 0x01, # Usage Minimum (Num Lock)
0x29, 0x05, # Usage Maximum (Kana)
0x91, 0x02, # Output (Data,Var,Abs) - LEDs
0x95, 0x01, # Report Count (1)
0x75, 0x03, # Report Size (3)
0x91, 0x03, # Output (Const) - padding
0x95, 0x06, # Report Count (6)
0x75, 0x08, # Report Size (8)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x65, # Logical Maximum (101)
0x05, 0x07, # Usage Page (Keyboard/Keypad)
0x19, 0x00, # Usage Minimum (0)
0x29, 0x65, # Usage Maximum (101)
0x81, 0x00, # Input (Data,Array) - 6 key codes
0xC0, # End Collection
])
# fmt: on
class BootKeyboard(Interface):
"""One USB interface, written from scratch: subclass Interface, then add it."""
# the interface descriptor's own fields, spelled as on the wire
bInterfaceClass = USB_CLASS_HID
bInterfaceSubClass = HID_SUBCLASS_BOOT
bInterfaceProtocol = HID_PROTOCOL_KEYBOARD
# device -> host: the 8-byte key reports, and the pipe to write them to
reports = In(0x81, "interrupt", mps=KEY_REPORT_LEN, interval=10)
def extra_descriptors(self) -> bytes:
"""Appended after the interface descriptor: the HID class descriptor (HID 1.11 6.2.1)."""
return struct.pack(
"<BBHBBBH",
9,
HID_DT_HID,
0x0111, # bcdHID 1.11
0, # bCountryCode: not localized
1, # bNumDescriptors
HID_DT_REPORT,
len(KEYBOARD_REPORT_DESC),
)
def on_control(self, s, data=b""):
"""The requests the core cannot answer for us. Raising Stall says "no"."""
if s.type == STANDARD: # GET_DESCRIPTOR(Report)
if s.bRequest == 0x06 and (s.wValue >> 8) == HID_DT_REPORT:
return KEYBOARD_REPORT_DESC
raise Stall
if s.type != CLASS:
raise Stall
if s.bRequest == HID_GET_REPORT:
return bytes(KEY_REPORT_LEN) # no key is held right now
if s.bRequest == HID_SET_REPORT: # the LED report, sent on endpoint 0
leds = data[0] if data else 0
num_lock = "on" if leds & 0x01 else "off"
caps_lock = "on" if leds & 0x02 else "off"
scroll_lock = "on" if leds & 0x04 else "off"
log.info("LEDs: NumLock=%s CapsLock=%s ScrollLock=%s", num_lock, caps_lock, scroll_lock)
return b""
if s.bRequest in (HID_SET_IDLE, HID_SET_PROTOCOL):
return b""
if s.bRequest == HID_GET_IDLE:
return b"\x00"
if s.bRequest == HID_GET_PROTOCOL:
return b"\x01" # report protocol
raise Stall
def type_string(self, text):
"""One key press is two reports on the interrupt IN endpoint: down, then up."""
for ch in text:
key = key_for(ch)
if key is None:
continue
mod, code = key
self.reports.write(bytes([mod, 0, code, 0, 0, 0, 0, 0])) # key down
time.sleep(0.02)
self.reports.write(bytes(KEY_REPORT_LEN)) # key up
time.sleep(0.02)
log.info("typed %r", text)
def build_keyboard(vid, pid):
"""Put the interface on a device; returns (device, keyboard)."""
dev = USBDevice(
vid, pid, manufacturer="USB over IP", product="USBIP Boot Keyboard", serial="0011"
)
# add() numbers the interface, routes its endpoint and registers its handlers
return dev, dev.add(BootKeyboard())
def key_for(ch):
"""Minimal ASCII -> (modifier, HID usage) on the Keyboard/Keypad page."""
if "a" <= ch <= "z":
return 0, 0x04 + ord(ch) - ord("a")
if "A" <= ch <= "Z":
return 0x02, 0x04 + ord(ch) - ord("A") # 0x02 = Left Shift
if "1" <= ch <= "9":
return 0, 0x1E + ord(ch) - ord("1")
if ch == "0":
return 0, 0x27
if ch == " ":
return 0, 0x2C
if ch in "\r\n":
return 0, 0x28
return None
def main():
ap = argparse.ArgumentParser(description="a HID boot keyboard written as an Interface")
ap.add_argument("--vid", type=lambda s: int(s, 0), default=0x1209)
ap.add_argument("--pid", type=lambda s: int(s, 0), default=0x0011)
ap.add_argument("--host", default="0.0.0.0", help="bind address (default: every interface)")
ap.add_argument("--port", type=int, default=3240, help="TCP port to serve on")
ap.add_argument("--type", default="hello", help="text to type once a host attaches")
args = ap.parse_args()
logging.basicConfig(level=logging.INFO, format="[%(name)s] %(message)s", stream=sys.stderr)
dev, kbd = build_keyboard(args.vid, args.pid)
# type on every (re)attach, off the serving thread so the delays don't stall it
def on_attach():
threading.Thread(target=kbd.type_string, args=(args.type,), daemon=True).start()
dev.add_reset_hook(on_attach)
# plug() starts SERVING: nothing enumerates until a USB/IP client imports it
transport = usbip.USBIP(args.host, args.port)
dev.plug(via=transport)
log.info("serving %04x:%04x on %s:%d", args.vid, args.pid, args.host, args.port)
log.info("attach it: sudo usbip attach -r 127.0.0.1 -b 1-1")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
dev.unplug()
return 0
if __name__ == "__main__":
sys.exit(main())
Run it (python examples\doc\boot_keyboard.py on Windows):
Nothing happens yet - the program is now the USB/IP server, waiting for a client.
The keyboard above is deliberately raw - it shows the whole core API. In practice a
ready-made device class authors the descriptors and runs the class
protocol for you, and the same keyboard shrinks to the snippet on the
introduction page: one dev.add(HID(...)) call replaces
all the descriptors and the control handler. To serve on the network instead of the
context-manager form, pass a transport:
See Device for one recipe per class.
Nothing enumerates until a USB/IP client imports what you are serving. Every example page in these docs assumes this step; how you take it depends on the OS.
The client is built into the kernel - the vhci-hcd virtual host controller:
sudo modprobe vhci-hcd # once per boot
sudo usbip attach -r 127.0.0.1 -b 1-1 # import the served device
Detach with sudo usbip detach -p 00. See
Platforms → Linux.
There is no in-box client - install one (usbip-win2 for Windows 10/11, or the
older usbip-win), then:
Windows binds its own in-box driver afterwards. See Platforms → Windows.
macOS has no in-box client, and the one experimental third-party client
(usbip-macos) only runs with System Integrity Protection disabled. The practical
way to get the keyboard into a Mac's USB stack is a
hardware client - a board that imports the device and
re-presents it on its own USB port, so the Mac sees plain USB. A Mac can also
serve a device for another machine to import, and drive one with the
host API. See Platforms → macOS.
Once imported, the keyboard types its text into whatever window has focus - the host binds its ordinary HID driver, because as far as it is concerned this is a real keyboard.
Embedded importers and the no-client options are covered under Platforms.
No root, no kernel
For tests you don't need vhci at all: drive your virtual device with a USBIP
host in the same or another process - see
Host driver.
Two environment variables turn on diagnostics for any program on either library:
USBIP_PCAPNG=file.pcapng records every transfer for Wireshark, and USBIP_DEBUG=1
logs every control request and data transfer. The full table - including the
USBIP_HOST/USBIP_PORT pair the C wrappers read, and how to set a variable in cmd
and PowerShell - is under Environment variables; the capture workflow is
under Capturing traffic.
The repository ships runnable device and host examples for every class - see the Examples page for the full list and commands.
python3 examples/hid_device.py --profile keyboard --type "hello"
# or the C build - `make` in the C library, then run hid_device from its build directory:
hid_device
(On Windows: python examples\hid_device.py …, and the C build is hid_device.exe.)