From Iveze
< Linux‎ | Virtualization
Revision as of 15:18, 16 June 2015 by Admin (Talk | contribs) (auto.crypt)

Jump to: navigation, search

If a USB device is plugged in a host machine, it needs to know what to do with it. Should the host itself use it, or should it be forwarded to a guest, and if so to which guest?


Before making USB plug-and-play, we need to explore the options virsh offers us.

Forward as USB device

By vendor/product

A USB device can be forwarded to a guest by vendor id and product id. This works well if there is only one device of a certain model ever plugged into the host.

The vendor id and product id can be obtained with lsusb (if not available, install usbutils). There will be several lines like this:
Bus 002 Device 004: ID 051d:0002 American Power Conversion Uninterruptible Power Supply
In this line the vendor id is 051d and the product id is 0002. From the description you can make out which line you are looking for.

Now a sub-definition of the guest's definition must be written in a file, containing the vendor and product id.

<hostdev mode="subsystem" type="usb">
<source startupPolicy="optional">
<vendor id="0x051d"/>
<product id="0x0002"/>

Assuming the file is called ups.xml, this file can be used to attach or detach the usb device.

virsh attach-device guestname ups.xml


virsh detach-device guestname ups.xml

By USB port

One could choose to use specific USB ports for specific guests. Lsusb also shows bus and device. These numbers can be used in the address element of the hostdev. The file ups.xml would then look like this.

  <hostdev mode="subsystem" type="usb">
    <source startupPolicy="optional">
      <address bus="002" device="004"/>

We can not guarantee this to work well, because we have not tested it. We prefer to identify USB devices by attributes of themselves, so we stay free to use any USB port.

Forward as harddisk

If the USB device is an external harddisk it can be forwarded as such. USB is not always forwarded full speed. By leaving the USB part to the host, full speed is ensured. Another advantage is that no xml file is necessary.

Assuming the usb disk on the host is /dev/sdb and the guest has only /dev/vda, so vdb is free.

virsh attach-disk guestname /dev/sdb vdb


virsh detach-disk guestname vdb

Plug and Play

If a USB device is attached in one of the above ways, it will not be plug and play. Remove and plugin the USB device again, and you may have to type the attach statement again.


Udev gives access to the events of plugging in and pulling out devices. It comes pre-installed with the OS. Just create a rules file that redirects all usb events to a script.


SUBSYSTEMS=="usb", \

All usb events now go to the script usb-quest, where we are going to decide if anything needs to be done. Udev sets several environment variables for the script, enabling us to make decisions. These are the most interesting variables and values.

Variable Values Remark
SUBSYSTEM usb or block Block can be used to identify that it is an external disk
ACTION add or remove
ID_SERIAL Serial number A good match for symlinks in /dev/disk/by-id/
ID_VENDOR_ID Vendor id The same as in lsusb and the hostdev xml
ID_MODEL_ID Product id The same as in lsusb and the hostdev xml

Identifying USB devices

We want to get at a situation where we only have to maintain a list of USB device id's with the action to be taken. Such list may contain different types of id's and actions.

Plain USB forwarding

For plain USB forwarding we put in the list "usb-<vendor>:<product>" and the guest to forward it to.

usb-051d:0002 guestname

When the USB device 051d:0002 is added or removed, it must be attached to or detached from guestname. The hostdev xml file will be temporarily generated from these values.

Harddisk to guest

External disks we pass as harddisk. But not by /dev/sdb or so. There is no guarantee that the same disk always gets the same identifier this way. Probably disk label could work. But if disks are encrypted, the label is not accessible until decryption is on.

We use the serial number the disk identifies itself with. Often that number is based on the serial number written on the casing. When the external disk is plugged in the serial number can be found in
An example is
This symlink has a prefix "ata-". Others may have "usb-". We do not use that. So we write a line in the list <serial id without prefix> <guestname> <preferred virtual disk in the guest>.
WDC_WD20NMVW-11AV3S3_WD-WX91A848D75S guestname vdb

When the external harddisk wit serial id WDC_WD20NMVW-11AV3S3_WD-WX91A848D75S is added or removed, it must be attached to or detached from guestname as virtual disk vdb in the guest. First the disk is looked up in /dev/disk/by-id/ to get the full path /dev/disk/by-id/ata-WDC_WD20NMVW-11AV3S3_WD-WX91A848D75S. This is then attached to guestname as vdb.

Harddisk to host

We do not want the host to grab random external disks for itself. So the ones for the host itself must also be part of this system.

The host uses autofs for dynamic mounting of external disks. If there is a definition for mount "backup" in autofs, then if anyone touches /mnt/mydisks/backup, autofs will try to mount the external disk to /mnt/mydisks/backup. After a while of disuse it will be automatically unmounted.

In the list we need to write a line <serial id without prefix> <autofs mount point>.

WDC_WD20NMVW-11AV3S3_WD-WX91A848D76S backup

When anyone touches /mnt/mydisks/backup an autofs script will lookup "backup" in the list. If for any line with "backup" a symlink in /dev/disk/by-id can be found, then that will be mounted at /mnt/mydisks/backup.

The scripts

These scripts are assumed to be in /var/scripts. Idf they are anywhere else, the variable SCRIPTS inside the scripts must be adjusted accordingly.


This is the list of id's and actions, i.e.

usb-051d:0002 guest1
usb-045f:0008 guest2
WDC_WD20NMVW-11AV3S3_WD-WX91A848D75S guest2 vdb
WDC_WD20NMVW-11AV3S3_WD-WX91A848D76S guest2 vdc
WDC_WD20NMVW-11AV3S3_WD-WX91A848D77S backup
WDC_WD20NMVW-11AV3S3_WD-WX91A848D78S backup

In this little example guest1 gets a plain USB device, guest2 gets a plain USB device and can have 2 external disks at once, and the host may mount one of two disks at a time that are used for backup.


This helper script reads the file usb-serial and tries to find the device in /dev/disk/by-id/. It is used from the scripts usb-guest and auto.crypt.

It has 2 parameters.

  1. "serial" or "mount", which indicates whether the next parameter is a serial number or a mount point
  2. the serial number or autofs mount point
if [ "$1" == "mount" ]; then
DISKS=`grep "\s\$2\s*$" $SCRIPTS/usb-serial`
echo "$DISKS" | while read LINE ; do
DEVICE=`find /dev/disk/by-id -name "$PREFIX${WORDS[0]}$PART"`
if [ "$DEVICE" != "" ]; then
echo "$DEVICE"
elif [ "$1" == "serial" ]; then
LINE=`grep "^\s*\$2\s" $SCRIPTS/usb-serial`
DEVICE=`find /dev/disk/by-id -name "$PREFIX${WORDS[0]}"`
echo "$DEVICE ${WORDS[1]} ${WORDS[2]}"


This is the main script, called from the udev script when USB events occur. It uses the environment variables set by udev. It can also be used with parameters. Then the variables are set based on the parameters.

# # This script is runned by: /etc/udev/rules.d/95-libvirt-usb.rules #
# udev env variables used:
exec &>> /tmp/udev.log
# can run with params ACTION { ID_SERIAL | ID_VENDOR_ID ID_MODEL_ID }
if [ "$1" != "" ] && [ "$3" != "" ]; then
elif [ "$1" != "" ] && [ "$2" != "" ]; then
if [ "$SUBSYSTEM" == "block" ]; then
WORDS=( `$SCRIPTS/usb-read serial "$ID_SERIAL"` )
echo "$(date) block $ACTION $ID_SERIAL | $DEVICE $GUEST $VDISK"
if [ "$DEVICE" == "" ] || [ "$GUEST" == "" ] || [ "$VDISK" == "" ]; then
echo "Nothing done for block device"
if [ "$ACTION" == "add" ]; then
/usr/bin/virsh attach-disk "$GUEST" "$DEVICE" "$VDISK"
elif [ "$ACTION" == "remove" ]; then
/usr/bin/virsh detach-disk "$GUEST" "$VDISK"
elif [ "$SUBSYSTEM" == "usb" ]; then
# echo is for trim
GUEST=$(echo `$SCRIPTS/usb-read serial "usb-${ID_VENDOR_ID}:${ID_MODEL_ID}"`)
echo "$(date) usb $ACTION usb-${ID_VENDOR_ID}:${ID_MODEL_ID} | $GUEST"
if [ "$GUEST" == "" ]; then
echo "Nothing done for usb device"
echo "<hostdev mode=\"subsystem\" type=\"usb\">" > $TEMP
echo " <source startupPolicy=\"optional\">" >> $TEMP
echo " <vendor id=\"0x${ID_VENDOR_ID}\"/>" >> $TEMP
echo " <product id=\"0x${ID_MODEL_ID}\"/>" >> $TEMP
echo " </source>" >> $TEMP
echo "</hostdev>" >> $TEMP
if [ "$ACTION" == "add" ]; then
/usr/bin/virsh attach-device "$GUEST" "$TEMP"
elif [ "$ACTION" == "remove" ]; then
/usr/bin/virsh detach-device "$GUEST" "$TEMP"


This is the script used by autofs when one of it's mount points on the host is touched. We mostly use encrypted external disks, as they tend to leave the building. The script gets 1 parameter from autofs: the mount point being touched i.e. "backup". With that information it does a lookup with the script usb-read to see if one of the backup disks is attached.

DEVICE=`$SCRIPTS/disk-read mount "$1"`
if [ "${1}" == "" ]; then

if [ "${DEVICE}" == "" ]; then

ls "${KEY}" > /dev/null 2> /dev/null
if [ $? != 0 ]; then

ls "${CRYPT}" > /dev/null 2> /dev/null
if [ $? == 0 ]; then

if [ $ACTION == 1 ]; then
/sbin/cryptsetup -d "$KEY" luksOpen "${DEVICE}" "${1}"
echo -n "-fstype=ext4,acl :${CRYPT}"