USB Passthrough with LXC

Just a short and simple post on how to passthrough a USB-device with LXC. For my environment specifically, I am using Proxmox VE 4.x, which uses LXC underneath (among other things). USB passthrough is a simple thing to do, but I had trouble finding good documentation on it so I want to share what I did here.

First we need to find the device we want to passthrough, and get the device ID's (both major and minor). For my scenario, I am referring to my Aeon Labs Z-Wave Z-Stick. When plugged in, it takes the name /dev/ttyACM0'. If I run als` for similar devices, I get this:

ls -la /dev/ttyACM*
crw-rw---- 1 root dialout 166, 0 Nov   7 13:18 /dev/ttyACM0

In the name, after the user and group and before the date, we find 166, 0 which is this devices major and minor device ID's. If I was to plug a second one in, I would see a new device, likely /dev/ttyACM1 with an ID of 166, 1. In the event of a reboot, it possible that this device could change names or minor ID so we want to create an alias before we passthrough this device to our LXC container.

So we use this command to create a /dev/zwave alias:

echo 'SUBSYSTEM=="tty", ATTRS{idVendor}=="0658", ATTRS{idProduct}=="0200", SYMLINK+="zwave"' > /etc/udev/rules.d/99-usb-serial.rules

After a reboot, we will still see /dev/ttyACM0 (or possibly /dev/ttyACM1 if it changed on us), but we will also see this:

ls -la /dev/zwave
lrwxrwxrwx 1 root root 7 Nov 19 23:49 /dev/zwave -> ttyACM0

Which is nothing more than a symlink. Now we need to configure our LXC container to allow passthrough, and to mount the new symlink for /dev/zwave. Find the configuration file for the LXC you want to passthrough to. For Proxmox, that should be in /etc/pve/lxc/<number>.conf.

I added the following 3 lines to my config (for my Home Assistant LXC):

lxc.apparmor.profile: unconfined
lxc.cgroup.devices.allow: c 166:* rwm
lxc.mount.entry: /dev/zwave dev/zwave none bind,optional,create=file

The first line, lxc.apparmor.profile is to not confine this container to any AppArmor profile. The default AppArmor profile will not allow mounted devices, and I prefer to just unconfine for simplicity. Obviously this isn't as secure as creating a proper profile, but I'm not going to cover that. Research into AppArmor policies if that tickles your fancy.
The second line, lxc.cgroup.devices.allow tells this container that they have access to any device with a major ID of 166 and a minor ID of *, or any device. We could set it to 166:0 rwm to only allow our one specific device, but if it was to change on a host reboot, our LXC would break as well. From my understanding, a major ID of 166 is for USB devices so I prefer to tell my LXC that it's allowed to access any USB device from the host. The rwm at the end stands for "read, write, mount" which are permissions that it can have. The third line, lxc.mount.entry is the actual mounting process. So even though the second line allows a (or multiple) devices to be accessed, we have to mount them in the LXC before that can happen. What we are doing with this third line is mounted the /dev/zwave device from the host to dev/zwave (with / as our working dir), and setting the options of none bind,optional,create=file.

Save this conf file, shutdown and power on the LXC and now we should see /dev/zwave inside our LXC. If the minor ID or original device name changes on our host, we shouldn't have to worry about it because of our symlink/alias. If you're not seeing the device, make sure that you have stopped the container and started it again; not just issued a restart from within the container.


Related Posts

Share on: Twitter | Facebook | Google+ | Email

comments powered by Disqus