Skip to content

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.

pip install usbip          # the pure-Python library, no C dependency

From a checkout instead (the examples come with it):

git clone https://github.com/jabezwinston/usbip-python
cd usbip-python
pip install -e .

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:

git clone https://github.com/jabezwinston/usbip-c
make -C usbip-c            # examples + both role libraries

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().

examples/doc/boot_keyboard.py
#!/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):

python3 examples/doc/boot_keyboard.py       # serves 1209:0011 on TCP :3240

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:

transport = usbip.USBIP("0.0.0.0", 3240)
dev.plug(via=transport)                               # serve on TCP :3240

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:

usbip.exe attach -r 127.0.0.1 -b 1-1

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.)

  • Device - a recipe per device class, plus vendor/WebUSB and composite devices
  • Host - drive a device from your own code, pyusb, or a stock binary
  • Platforms - importing the device on Linux, Windows, macOS or embedded hardware