# usbip is a Rust USB/IP server that can simulate a HID keyboard or a serial device

> This crate runs a USB/IP server in Rust, which means it can either share a physical USB device from the host to remote clients or simulate a virtual one, and the examples cover a HID keyboard, a CDC ACM serial port, a real host and both at once. A QEMU script in the repository boots a real Linux kernel and checks the simulated devices end to end.

**jiegec/usbip** — A Rust library to run a USB/IP server

- Repository: https://github.com/jiegec/usbip
- Stars: 543 · Forks: 52
- Language: Rust
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/jiegec-usbip

## What USB/IP is and what this crate adds to it

usbip is described as a Rust library to run a USB/IP server to simulate USB devices and share real USB devices over a network.

The protocol itself is simple to state. USB/IP is a network protocol that allows USB devices to be shared between computers over a network. The README lists what that enables: device simulation, which means creating virtual USB devices that can be accessed remotely, device sharing, which means sharing physical USB devices from one machine to another, and cross-platform support, since it works across different operating systems such as Linux.

What the crate contributes is the server side in Rust. The interesting consequence of that is the second half of the README's description: a USB/IP server that simulates devices is not just a network endpoint, it is a virtual USB device stack. The server receives USB request blocks from a remote client, decides what a device would do with them, and sends back completion blocks. To simulate a HID keyboard means answering the report descriptor request with a keyboard descriptor, then emitting input reports when the client asks for them. To simulate a CDC ACM serial port means answering the class-specific requests that configure the line coding and then forwarding bytes to whatever the server wants to send.

The dependency list shows how that is assembled. rusb 0.9.3 is the Rust binding to libusb, which is how the crate reaches physical devices for the sharing case, and nusb 0.2.1 is a native USB library in Rust, which serves the same purpose without the C dependency and is what the simulated side is built on. serde is an optional dependency behind a feature flag, and num-traits and num-derive are present for the numeric work at the protocol level. tokio provides the runtime with the rt, net, io-util and sync features.

The package is usbip at version 0.9.0, edition 2024, MIT licensed, authored by Jiajie Chen. The default branch is master, the last push was on 2026-09-03, and there are no GitHub releases, so there is nothing to pin to other than the crate version.

The repository topics are usbip, usb-sharing, usb-hid and usb-cdc, which is a precise summary of the two device classes this crate knows how to fake.

## The four examples, which are the documentation

The examples directory contains four example programs, and between them they cover both modes of the crate.

The hid_keyboard example simulates a HID keyboard that types something every second. That is the smallest useful thing the crate can do, and it is also the hardest part to get right, because a HID keyboard is defined by its report descriptor and the host driver parses that descriptor to build its input handling. A simulated keyboard has to present a descriptor the host believes, which is why the example exists at all.

The cdc_acm_serial example simulates a CDC ACM serial device that receives a character every second. CDC ACM is the class used by virtual serial ports and by countless debug adapters and microcontrollers, so this example stands in for a large family of real hardware.

The host example acts as a USB/IP server, sharing physical devices from the host machine to remote clients. This is the other half of the protocol and the reason the crate depends on both rusb and nusb: it has to enumerate real devices on one machine and answer the client's requests about them.

The demo example simulates a HID keyboard and a CDC ACM serial device together, and the README says it is used by the QEMU test. Combining them is not incidental: it is the case where a client attaches two devices to the same server, which exercises the server's per-device bookkeeping rather than a single device path.

Running one is a cargo run with the example name:

```bash
cargo run --example hid_keyboard
```

And on the client machine, which the README describes as a Linux system with USB/IP support, you list what the server is offering and then attach:

```bash
# List available devices
usbip list -r $remote_ip

# Attach to a device
usbip attach -r $remote_ip -b $bus_id
```

The two commands are the whole client-side story. The first asks the server what it has, the second claims one by bus id and the host kernel's vhci driver takes over.

## The feature flags and the two USB libraries behind the crate

The Cargo.toml is short and the details in it explain how the crate is put together.

The dependency list is five runtime crates and one optional. tokio 1.22.0 with the rt, net, io-util and sync features provides the asynchronous runtime and the TCP side of the USB/IP protocol, since the server is a network service that has to handle many clients at once. log 0.4.17 is the logging facade. num-traits and num-derive handle the numeric types the USB descriptors need, where field widths are fixed at one, two or four bytes and the offsets have to be computed rather than assumed.

Then there are the two USB libraries, and they exist for different reasons. rusb 0.9.3 is a binding to libusb, and it is what lets the host example reach real devices. nusb 0.2.1 is a native USB implementation in Rust, with no C dependency, and it is what the simulated-device side is built on. Having both is not redundancy: one covers the physical case and the other covers the virtual case, and a crate that does both needs both.

serde is optional, and the features section shows how it is wired:

```toml
[features]
default = []
serde = ["dep:serde", "rusb/serde"]
```

The default feature set is empty, which means a plain cargo build gives you the crate with no serialisation support at all, and opting in pulls serde in and enables the corresponding feature on rusb. The reason serde is optional rather than always present is the simulation use case: if you are only pretending to be a keyboard, you have no need to serialise device state, and the feature flag keeps that dependency out of the binary.

The dev-dependencies are the tokio full feature set and env_logger 0.11.7, which gives the examples a logger to write to. The package uses edition 2024, so it needs a recent Rust toolchain, and the author is Jiajie Chen.

## The QEMU test, and why the CI kernel matters

The end-to-end test is the strongest evidence in the repository, and the README is unusually specific about how it works.

The simulated devices can be verified against a real Linux kernel booted under QEMU. The test assembles a minimal initramfs, containing busybox, the usbip tool, the demo server and the kernel's usbip, vhci, cdc and hid modules, boots it, and then uses vhci-hcd to attach the simulated keyboard and serial device. Inside the guest it confirms that the serial port emits 'a' and that the keyboard generates a KEY_1 input event.

That is a proper integration test. It is not checking that the crate's structs serialise correctly; it is booting an operating system, attaching a virtual USB controller to it, and asserting that the guest's driver stack produces the right input events.

The details about the kernel are the part most CI setups get wrong, and the README explains them. In CI, using scripts/qemu and the workflow at .github/workflows/qemu.yml, the Ubuntu generic kernel is installed and booted. This matters because the GitHub runner's Azure kernel has CONFIG_USB_HID disabled, so it cannot enumerate a simulated USB keyboard. The generic kernel ships the hid, hid-generic and usbhid modules, which are loaded in the guest so both the serial device and the keyboard are verified.

That is a real trap and it is documented here: a simulated HID keyboard fails on a kernel built without USB HID support, and the failure looks like a bug in the crate rather than a missing kernel config option.

Running it locally needs qemu-system-x86, a static busybox, cpio and the usbip tool:

```bash
./scripts/qemu/run-qemu-test.sh --build --dump-log
```

It runs under KVM when available and falls back to QEMU TCG otherwise. The kernel, vmlinuz and module tree used can be overridden via the KERNEL, VMLINUZ and MODTREE environment variables, which the README says is useful to reproduce a CI kernel locally. That is the kind of escape hatch that turns a test you cannot run into one you can.

## Where usbip is the wrong tool

Three limits, and the first is about the platform asymmetry.

The first is that only the server side is Rust here. The client side in the README's instructions is usbip list and usbip attach, which are the Linux kernel's userspace tools. The client also needs the vhci_hcd or vhci-hcd virtual host controller driver in the kernel, and the repository's own test loads the kernel's usbip, vhci, cdc and hid modules in the guest to make it work. So a Linux client is assumed on both ends of the demonstration, and the cross-platform claim in the feature list means the protocol is portable rather than that the client side has been ported.

The second is that the simulated device classes are narrow. The crate knows how to be a HID keyboard and a CDC ACM serial port, because those are the two the examples implement and the two the topics name. Anything else, a mass storage device, a webcam, a printer, a USB hub, a security key, is not simulated by this crate. The protocol permits a wide range of device classes, and the work of implementing one is substantial: the class-specific request handling, the descriptor layout and the state machine for that device type all have to be written. So if your goal is to attach a device class the examples do not cover, this crate is a starting point rather than a solution.

The third is the scale question for the sharing case. The host example shares physical devices from one machine to remote clients, which is a data path that puts every USB transaction over TCP. USB is a latency-sensitive protocol with a polling model, and a network hop in the middle of it is exactly the situation the USB/IP protocol was designed for, with its trade-offs. The README says nothing about performance, about how many devices can be shared at once, or about what happens when the network stalls, and a shared physical device cannot be attached by two remote clients at the same time.

The alternative approach for the sharing case on Linux is the kernel's own usbip-host, which ships with the kernel and does the same job without a userspace server. The difference in approach is that the kernel implementation is older, less flexible and harder to extend, while this crate is Rust, embeddable in a larger application, and able to also simulate devices, which the kernel tool cannot do. For a Linux-only setup that just needs to share a printer over the network, the kernel tool is less to run. For anything involving virtual devices, or for a Rust application that wants to be both host and server, this crate is the thing to look at.

## Conclusion

Adopt usbip if you need a USB/IP server written in Rust, because the crate covers both halves of the protocol, sharing real devices and simulating virtual ones, and the QEMU test gives you a way to check the behaviour against a real Linux kernel rather than a mock. Do not adopt it if you are on Windows or macOS expecting a working host driver, because the README describes the client side as Linux with USB/IP support and the whole design assumes the Linux vhci and usbip modules. Verify first that your Rust toolchain builds edition 2024 code, that the kernel on the client machine has the usbip and vhci modules available, that you understand the default features are empty and serde is opt-in, and that the crate version you pin is 0.9.0, since there are no GitHub releases to fall back on.

## FAQ

### What does the usbip Rust crate do?

It is a library to run a USB/IP server, which means it can share real physical USB devices over a network and simulate virtual USB devices that remote clients attach to. It targets USB/IP, a network protocol for sharing USB devices between computers.

### How do I build usbip from source?

Install Rust first, then clone the repository with git clone https://github.com/jiegec/usbip.git, change into the usbip directory and run cargo build --release. The crate is at version 0.9.0, uses edition 2024 and is published on crates.io as usbip.

### Which example programs does usbip ship?

Four: hid_keyboard simulates a HID keyboard that types something every second, cdc_acm_serial simulates a CDC ACM serial device that receives a character every second, host acts as a USB/IP server sharing physical devices, and demo simulates a keyboard and a serial device together and is used by the QEMU test.

### How do I attach a simulated device from a client machine?

On a Linux client with USB/IP support, run usbip list -r $remote_ip to see the devices the server offers and usbip attach -r $remote_ip -b $bus_id to attach one by bus id. The host side needs the vhci virtual host controller driver.

### How does the usbip QEMU end-to-end test work?

It assembles a minimal initramfs with busybox, the usbip tool, the demo server and the kernel's usbip, vhci, cdc and hid modules, boots it under QEMU, then uses vhci-hcd to attach the simulated devices. Inside the guest it checks that the serial port emits 'a' and that the keyboard generates a KEY_1 event.

### Why does the usbip CI use the Ubuntu generic kernel?

Because the GitHub runner's Azure kernel has CONFIG_USB_HID disabled and therefore cannot enumerate a simulated USB keyboard. The generic kernel ships the hid, hid-generic and usbhid modules, which are loaded in the guest so both the serial device and the keyboard can be verified.

## Sources

- [Issues](https://github.com/jiegec/usbip/issues)
- [jiegec/usbip on GitHub](https://github.com/jiegec/usbip)
- [License: MIT](https://github.com/jiegec/usbip/blob/master/LICENSE)
- [README](https://github.com/jiegec/usbip/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jiegec-usbip
