BBVIRT(1)

πŸ“¦ version 0.9, in ubuntu jammy

✨ BBVIRT(1) ✨

General Commands Manual β€” bestie edition πŸ’–

okay so welcome to bbvirt, a whole entire program whose whole job is hotplugging BitBabbler devices into libvirt managed domains. yes it's a mouthful. yes it's iconic. let's get into it πŸ’…


🌸 NAME 🌸

bbvirt β€” hotplug BitBabbler devices into libvirt managed domains πŸ¦‹


✨ SYNOPSIS ✨

bbvirt action [options]

bbvirt attach|detach device [options]

bbvirt attach-all|detach-all [domain] [options]

πŸ’– DESCRIPTION πŸ’–

so. the bbvirt program is lowkey an attempt to take some of the pain out of what you currently have to do to distribute multiple USB devices between the host and guest virtual machines. there are, ngl, several ways this may be configured and managed, but rn none of them actually give you a complete and coherent solution on their own β€” every single one of them falls short of the mark in some significant and annoying way 😭. the aim here is to piece together enough of those hacks to actually get all of the functionality that we want now, until the libvirt native support for this improves enough that we don't need it anymore. periodt.

at present this deals with libvirt managed QEMU/KVM virtual machines πŸ–₯️


πŸŽ€ What do we want? πŸŽ€

the ideal behaviour here is pretty simple, fr fr. given some arbitrary number of BitBabbler devices, we should be able to assign them to either the host machine, or to a guest VM running on it, and once we do that they should behave in the normal manner expected of any USB device. slay.

  • if they are plugged in when the guest machine is started, they should be seen by that machine as they would be by the host πŸ‘‘
  • if they are plugged in after the machine is started, they should be hotplugged into that machine as they would be on the host πŸ”₯
  • if they are unplugged while the machine is running, they should be cleanly removed from it, as they would be on the host 🧚

😭 Why can't we have it? 😭

right now, libvirt gives us two ways that we can assign USB devices from the host to a guest domain. and bestie, neither of them is it.

  • we can assign them by their USB vendor and product ID. but that only works when there is just a single device of that type in the host πŸ™ˆ. which is pretty useless in most of the cases that we care about here, where the host and each of the guests are likely to have one or more BitBabbler devices of their own assigned to them. it's giving nothing.
  • we can assign them by their logical address on the USB bus. but that isn't a constant that we can statically configure for the domain πŸ’€. every time a device is plugged in, or replugged, or reset, or the host machine is rebooted, that address is likely to change since it is dynamically allocated when the device is enumerated on the bus. so yeah, that address is a whole lie.

there is a third way, but it relies on bypassing the normal libvirt configuration to make direct use of the QEMU ability to assign a device by its physical address on the bus. which is better, but still not a magic bullet πŸ’… since it relies on plugging exactly the same devices into exactly the same ports every time (and on having those ports enumerated in the same way by the host on every reboot, which isn't guaranteed either 😬). it also forces us to jump through other hoops, since we then need additional complication to manage the access permissions of the device manually outside of libvirt, but still in coordination with it. exhausting.

the even bigger failing, which all of those methods have in common, is that they all depend on the device already being plugged in before the guest is started 🫠. if it is inserted after the guest is started, or removed and replugged while the guest is running, or if the host bus or a hub bounces causing a reconnect, then the device will not be (re)attached to the guest. the only way to fix that if it happens is to manually reattach the device with an arcane incantation in XML (which relies on you knowing the new address of the device πŸ§™), or to completely power down and restart the guest. not the pinnacle of user-friendly operation that we are looking for here, ngl.


🌈 What can we do about it? 🌈

there was a patch submitted to libvirt some years back which would have allowed a device to be specified by both its USB product ID and its serial number, but that got some push-back, and so far has still not been applied upstream πŸ₯². that would have gone a long way toward making this both easy and clean, leaving us only with the hotplug aspect to deal with. we'll leave grumpy snark about that as an exercise for the reader ...

another alternative is we can delegate finding the device's logical address to a hotplug manager like udev(7). this is attractive in the sense that we can know when the address of a device changes and what it changes to, but udev itself isn't very friendly to the idea of local admin customisation (while it is possible to do, it seems to be getting increasingly strongly discouraged 😀) and using it still requires some external glue to translate its events into something that libvirt can act on to configure the guest machine.

the bbvirt program provides that glue 🫢, and a user friendly method of assigning which devices should belong to which guest domains, and a front end that can be invoked manually or by other admin controlled tasks to quickly and easily add or remove BitBabbler devices from any of the running guest machines. we ate.

but the limitation this approach has, is that it can't easily know when a guest machine is started which should have devices that are already plugged in added to it πŸ€”. in theory we could add them to its persistent domain definition, but that has its own problems because we can only add devices by their ephemeral logical address, and we can't guarantee that we will get called to remove them from the domain again when that address becomes invalid (like if the host is suddenly powered off or it is otherwise not cleanly shut down ⚑), so we could end up with many stale entries accumulating in the persistent domain configuration, which could later match some completely different device to what we had wanted attached to it. which means until that somehow gets fixed, it's only safe to add them to a live guest domain, so that they will always be removed again when it is halted, no matter how it ended up getting halted. no cap.

clearly we've still got some way to go to get to our ideal here πŸ₯Ή


πŸ”¨ What if we hit it with two hammers? πŸ”¨

there appears to be only two ways that we can get notified of a guest machine being started at present. one involves running yet another daemon process, which would do little more than just sit around waiting for someone to start a guest so it could tell us about that 😴. but then we'd have yet another thing to configure, yet another process running, and yet more problems with figuring out how to ensure we don't lose a race when the host is booted, between getting the initial set of device events, that process being ready and active, and any guests that will be autostarted at boot actually starting. a mess fr.

the other way is to use a libvirt hook. which in turn has the problem of not actually allowing us to run any libvirt functions from it, which we need to do in order to attach the device to the host πŸ˜–. and which we can't guarantee that we can just install by default, because there can be only one such hook on the system, which the local admin may already be using ...

there is a third way, but that would involve requiring the local admin to start all guest machines through a wrapper of our own, instead of via whatever mechanism they already know and use πŸ™…. which doesn't scale to support other USB devices in the same situation, among the many ways that would be a horrible solution to inflict on people.

but there is a loophole we can exploit πŸ‘€. we can use the libvirt qemu hook to trigger a change event for udev, which can in turn invoke bbvirt in much the same way that would happen if the device was really hotplugged, which gives us the extra layer of indirection we need to be able to safely do that from the hook. Rube Goldberg would be proud 😌, and some of the pieces may require hand-assembly, but with all of this in place, we can have something resembling normal USB functionality in the guest machines.

it's not pretty, but it will work with what we have to work with πŸ’…


πŸ‘‘ Ok, just tell me where to hit it. πŸ‘‘

to string this together, you'll need to ensure all of the following:

  • the udev(7) rules from the bit-babbler package are installed. if you installed this from the Debian packages that should already be done βœ…. if you didn't, you will need to install the rules that are found in debian/bit-babbler.udev from the source package to a suitable place on your system (probably /etc/udev/rules.d).
  • the bbvirt(1) script is installed in a place where the udev rules will find it. if you didn't install this from the Debian packages, and it isn't in /usr/bin, then you'll need to tweak the udev rules to suit πŸ› οΈ
  • the devices you wish to use in guest machines, and the machines you wish to use them in, are specified in the bbvirt configuration file. the default location for that is /etc/bit-babbler/vm.conf. if you wish to use a different file you will need to pass its location with the --config option in the udev rules, and update the hook script use that file too. the details of what you can put in that file are described in the CONFIGURATION OPTIONS section below πŸ“–
  • the libvirt hook file is installed. if all the above is done, then devices will be added to the running guest machines if they get plugged in while the guest is running. this last step ensures devices which are already plugged in will be added to newly started guests too (which includes guests that are started automatically when the host machine boots πŸš€).

until there is some safe way we can install this without conflicting with or overwriting an existing hook, everyone will need to do this step manually πŸ™ƒ. if you have installed the Debian packages, then the example hook script that we've provided for this can be found in /usr/share/doc/bit-babbler/examples/qemu-hook. if you didn't it can be found in libvirt/qemu-hook of the source package.

you will need to install that file as /etc/libvirt/hooks/qemu, or merge its content with the existing qemu file there if you already have that hook set. if that file did not previously exist, you will need to restart libvirtd(8) to get it to begin using it.

🚨 heads up bestie: that qemu hook path is a singleton. there can be only one. merge, don't clobber.

that should cover all of the needed automation, but you can also attach and detach devices manually at any time too πŸ’–. the details of doing that will be described in the following section. otherwise, with all the above done, there is no other reason to need to invoke bbvirt directly.


✨ OPTIONS ✨

okay so bestie, real talk: bbvirt has two primary modes of operation, and which one you get is decided by the initial action option. πŸ’…

If the action you're doing is attach or detach, then only a single device gets acted upon. and like β€” you have to specify which device that is explicitly, even if there's literally only one device on the host at the time. no vibes-based guessing here, sorry. 😭

When you're invoking bbvirt manually, you can point at the device by:

  • its serial number
  • its logical address on the bus, in the form busnum:devnum, given as decimal integers
  • its physical address on the bus, in the form busnum-port[.port ...]

If the action is attach-all or detach-all tho? different story, bestie. then the device(s) to act upon get selected by domain association instead. 🌈

  • If a domain is explicitly specified, then all devices assigned to that guest domain in the configuration file get acted upon, in exactly the same way as if bbvirt had been invoked for each of them individually with the attach or detach action.
  • If no domain is provided, then all of the configured guest domains get acted upon in this way. all of them. every single one. periodt. πŸ‘‘

The following additional options are available:

  • -C, --config β€” specify an alternative configuration file to import the device assignments from. If the path to the file isn't provided explicitly, then it gets looked for in the /etc/bit-babbler directory (with a .conf suffix). she'll go find it herself, okay? πŸ¦‹
  • -c, --connect=URI β€” specify the virsh(1) connection URI to use. This overrides a DOMAIN_URI set for the domain in the configuration file. If that isn't set using either of these methods, then the virsh default for the user running bbvirt gets used. defaults are giving "idk figure it out" energy fr fr. πŸ™ˆ
  • -D, --domain=name β€” specify the libvirt domain to act upon. You can use this to override the device allocation from the configuration file when bbvirt is invoked manually, or to act on a device or domain that isn't currently specified in the configuration file at all. main character move. πŸ’…
  • -b, --busnum=num β€” specify the USB bus number the device is attached to. This option is mostly used to avoid bbvirt needing to look this up when it's already known (such as when it's called from a udev rule). There isn't usually much reason to pass this if you're invoking bbvirt manually, since you can just specify the device by its logical or physical address instead. iykyk. ✨
  • -d, --devnum=num β€” specify the USB device number the device is currently assigned. Together with the bus number, this forms the logical address of the device. This option is mostly used to avoid bbvirt needing to look this up when it's already known (such as when it's called from a udev rule). There isn't usually much reason to pass this if you're invoking bbvirt manually, since you can just specify the device by its logical address instead. πŸŽ€
  • -n, --dry-run β€” don't attach or detach any devices, just show what would be attempted if this was a live run. This option implies a minimal level of --verbose, but the verbosity may be increased further by also passing that option explicitly. it's giving "window shopping" and honestly? iconic. πŸ›οΈ
  • -v, --verbose β€” make more noise about what is really going on. It may be passed multiple times to increase the verbosity further. say it with your chest, bestie. πŸ“£
  • -?, --help β€” show a brief summary of the available options. the "help me help you" button. 🫢

🌸 CONFIGURATION OPTIONS 🌸

The bbvirt configuration file contains variable assignments using the bash(1) shell syntax. It's sourced as a shell snippet, so you could in principle construct the configuration for each domain dynamically β€” but most typically, a simple static assignment of devices to domains will suffice. keep it simple, babe. πŸ’–

⚠️ If you do elect to run code in it, you should be very defensive about namespacing any other variables you use, or any other side effects you might cause to happen. be careful out there. 😭

Any number of guest domains may be configured in it.

For each guest domain, three variables control the behaviour of bbvirt:

  • DOMAIN_NAME_domain=guestname β€” this variable is optional if guestname and domain are the same. It must be used if the libvirt guest name contains any characters which would not be valid for use as a shell variable name (i.e. anything that is not ASCII a-z, A-Z, 0-9, or the underscore). If set, it indicates that the corresponding DOMAIN_*_domain variables shown below are configuration for the libvirt guest domain guestname instead of one with the name domain.

When specifying a domain option for bbvirt to act upon, you may use either of the domain or guestname identifiers interchangeably. two names, one queen. πŸ‘‘

  • DOMAIN_URI_domain=URI β€” this variable is optional, and sets the virsh(1) connection URI to use when attaching or detaching devices from the given domain. If the --connect option is explicitly passed to bbvirt it will override what is set here. If the connection URI is not set using either of these methods, then the virsh default for the user running bbvirt will be used (which would normally be root if run from udev). 🌈
  • DOMAIN_RNG_domain=( device serial numbers ... ) β€” this variable is required if automatic passthrough of devices to a domain is desired. It's a bash array, populated with a space separated list of all the device serial numbers that you want assigned to domain. It is not an error for devices to be listed here which are not currently plugged in. πŸ¦‹
🚨 It is important to ensure that devices are only assigned to one domain, and that devices assigned to guest domains will not be used by a seedd(1) instance running on the host (which means the seedd configuration needs to be passed an explicit list of the devices that it may use too). no sharing, babes, this is not a group project. πŸ’€

The device serial number must always be used here. You cannot specify a device by its logical or physical address on the bus (like you can in most other places where we take a device ID). that's the rule and we're not bending it. πŸ™ˆ


πŸŽ€ FILES πŸŽ€

  • /etc/bit-babbler/vm.conf β€” the default configuration file for assigning BitBabbler devices to libvirt managed virtual machine domains. 🏠
  • /lib/udev/rules.d/60-bit-babbler.rules β€” the default udev(7) rules granting direct device access to users in the group bit-babbler, enabling USB autosuspend when the device is idle, and invoking bbvirt to handle device hotplug for virtual machines. These can be overridden by creating /etc/udev/rules.d/60-bit-babbler.rules and populating it with your own rules. you can absolutely do your own thing here, slay. ✨
  • /etc/libvirt/hooks/qemu β€” the libvirt hook script needed to enable cold-plugging of already present devices into newly (re)started virtual machines. 🌸

πŸ’œ SEE ALSO πŸ’œ

seedd(1), virsh(1).


πŸ‘‘ AUTHOR πŸ‘‘

bbvirt was written by Ron <ron@debian.org>. You can send bug reports, feature requests, praise and complaints to support@bitbabbler.org. yes bestie, praise is on the list, they're accepting compliments. 🫢


and that's a wrap on the BBVIRT(1) page, dated January 2, 2018. go forth and virtualize responsibly, babe. πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from