Forwarding USB to a virtual machine over a network using UsbRedir and QEMU

  • Tutorial


Today, there are quite a few ways to transfer a USB device to another computer or virtual machine over the network.
Of the most popular - hardware such as AnywhereUSB and purely software products, of those that I tried myself: USB Redirector and USB / IP.
I would like to tell you about another interesting method that works directly with the QEMU emulator.
It is also part of the spice project officially supported by RedHat.

UsbRedir is an open protocol for forwarding usb devices via tcp to a remote virtual server developed with RedHat support as part of the spice project. But as it turned out, it can be quite successfully used without spice. Usbredirserver acts as a server, which fumbles a usb device to a specific port, and QEMU itself as a client, which emulates the connection of an exported usb device to a specific usb controller of your virtual machine. Thanks to this approach, absolutely any OS can be used as a guest system, since it does not even know that the device is forwarded remotely, and all the logic rests on QEMU.

First, a few words about the above solutions


  • AnywhereUSB is a pretty good solution, but expensive and has unpleasant glitches, for example, if the shared flash drive falls off, then you can reconnect it only by physically removing and inserting it.
  • USB / IP - OpenSource project. It seems like it was abandoned. In fact, it's pretty buggy. When the connection is broken, the machine often goes into a complete freezee, and windows shows BSOD
  • USB Redirector - Great software. To share devices from linux to linux is free, in all other cases it already costs money, not as much as AnywhereUSB, but not for free as we would like :)

Apparently there is plenty to choose from, but let's finally try another way - UsbRedir?

Virtual machine setup



In order to connect exported devices to the virtual machine, you need to create the necessary usb controllers:
  • uhci - for USB1.0
  • ehci - for USB2.0
  • xhci - for USB3.0

For qemu (without libvirt)


Add options to the virtual machine start command:
-device ich9-usb-ehci1,id=ehci,addr=1d.7,multifunction=on
-device ich9-usb-uhci1,id=uhci-1,addr=1d.0,multifunction=on,masterbus=ehci.0,firstport=0
-device ich9-usb-uhci2,id=uhci-2,addr=1d.1,multifunction=on,masterbus=ehci.0,firstport=2
-device ich9-usb-uhci3,id=uhci-3,addr=1d.2,multifunction=on,masterbus=ehci.0,firstport=4


For libvirt

In the source virtual machine configuration file in the node we remove all USB controllers and add the following block:


By the way, if you use spice, then adding 3 more special devices to the controllers, it will be possible to forward usb devices from the spice client to the server.
Example under the spoiler
For qemu

We add the following options to the start command of the virtual machine, in addition to the controllers that we defined earlier:
-chardev spicevmc,name=usbredir,id=usbredirchardev1
-device usb-redir,chardev=usbredirchardev1,id=usbredirdev1,debug=3
-chardev spicevmc,name=usbredir,id=usbredirchardev2
-device usb-redir,chardev=usbredirchardev2,id=usbredirdev2,debug=3
-chardev spicevmc,name=usbredir,id=usbredirchardev3
-device usb-redir,chardev=usbredirchardev3,id=usbredirdev3,debug=3


For libvirt

In the source virtual machine configuration file in the node add the following options, in addition to the controllers defined by us earlier:



Now everything is ready for forwarding.

Server start


The usbredirserver package can be found in standard repositories in almost all popular linux distributions.

We insert the USB flash drive into the computer, look at the output of usb devices:
$ lsusb
...
Bus 003 Device 011: ID 125f:c82a A-DATA Technology Co., Ltd. 
...


We see that the vendorid: prodid pair is 125f: c82a, and the kernel has identified USB flash drive 003-001 usbbus-usbaddr, respectively.

Now let's share it on port 4000:

# Используя пару vendorid:prodid
$ usbredirserver -p 4000 125f:c82a
# Используя пару usbbus-usbaddr
$ usbredirserver -p 4000 003-011


Connect a device to a virtual machine



Via options when starting a VM



The device that you want to connect to the VM can be specified at startup by adding the following options to the startup command

For qemu

-chardev socket,id=usbredirchardev1,port=4000,host=192.168.1.123
-device usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=ehci.0,debug=4


For libvirt

This block is placed before the tag. , next to the controllers we defined earlier:
It can also be executed with the virsh attach-device command

Or via qemu-monitor


We go to the hypervisor and in qemu-monitor of our machine we execute the following commands:
# Добавляем наше устройство
chardev-add socket,id=usbredirchardev1,port=4000,host=192.168.1.123
# Подключем его в ehci контроллер (USB-2.0)
device_add usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=ehci.0,debug=4

To disable the flash drive, such a command is enough:
device_del usbredirdev1


That's all, after these steps, your VM will see your flash drive and will be able to work with it natively.

If there are many devices and they are all the same


Here an interesting problem appeared, how to forward several identical devices to different VMs?
In this case, it is worth noting that all devices have the same vendorid: prodid pair, and the usbbus-usbaddr pair is not at all constant, you just need to remove and insert the device, so it will immediately change its usbaddr.

I solved it with udev.
By the way, if you don’t quite understand how udev works, there is a cool article on udev on the Debian Wiki

So let's get started


First, we need to find out the serial number of our device, by which we will identify it in udev:

Run the udev monitor:
$ udevadm monitor --environment --udev

And insert our device, after that we will immediately see a list of variables of this device that udev kindly initialized for us:
...
UDEV  [189056.151508] add      /devices/virtual/bdi/8:16 (bdi)
ACTION=add
DEVPATH=/devices/virtual/bdi/8:16
ID_SERIAL_SHORT=11C130317234004B
SEQNUM=4352
SUBSYSTEM=bdi
USEC_INITIALIZED=189056149826
...

Information about the serial and other attributes can be obtained in another way, but it is worth considering that for writing the rules we will use the variables from the command above, and not the attributes from the command below. Otherwise, the remove trigger will not work when the device is turned off.
$ udevadm info -a -n /dev/bus/usb/003/011 | grep '{serial}'


Now create the file /etc/udev/rules.d/99-usb-serial.rules and write the following rules into it:
ACTION=="add", ENV{ID_SERIAL_SHORT}="11C130317234004B", RUN+="/usr/bin/usbredirserver -p 4000 $attr{busnum}-$attr{devnum}"
ACTION=="remove", ENV{ID_SERIAL_SHORT}="11C130317234004B", RUN+="/usr/bin/fuser -k 4000/tcp"


Reload udev rules:
$ udevadm control --reload-rules

Done, now when you connect our device, it will automatically fumble on the port we need, and when you disconnect usbredirserver will stop its work.
By analogy, we add the remaining devices.

That's all. Thank you for your interest :)

UPD: Those who are interested in what came of it in the end, you can look here



Sources:


umvirt.ru/node/82
opennebula.org/opennebula-for-virtual-desktops
opennet.ru/opennews/art.shtml?num=30773
lists.gnu.org/archive/html/qemu-devel/2013-07/msg05244 .html
askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters
bugzilla.redhat.com/show_bug.cgi?id=805172#c26

Also popular now: