From 95efea8c5686f25e302102d64be8a57afde1a8de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Sat, 30 Dec 2023 00:44:38 +0100 Subject: [PATCH 1/6] lxd/device/pci: Detect USB bus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit c239189ef9a583c4e14bb7dcaef22babc6054612) Signed-off-by: Wesley Hershberger License: Apache-2.0 --- lxd/device/pci/pci.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lxd/device/pci/pci.go b/lxd/device/pci/pci.go index cdf419d4fcc7..e02edadc2c0f 100644 --- a/lxd/device/pci/pci.go +++ b/lxd/device/pci/pci.go @@ -13,6 +13,9 @@ import ( "github.com/canonical/lxd/shared/revert" ) +// ErrDeviceIsUSB is returned when dealing with a USB device. +var ErrDeviceIsUSB = fmt.Errorf("Device is USB instead of PCI") + // Device represents info about a PCI uevent device. type Device struct { ID string @@ -40,6 +43,8 @@ func ParseUeventFile(ueventFilePath string) (Device, error) { dev.SlotName = fields[1] } else if fields[0] == "PCI_ID" { dev.ID = fields[1] + } else if fields[0] == "DEVTYPE" && fields[1] == "usb_interface" { + return dev, ErrDeviceIsUSB } else if fields[0] == "DRIVER" { dev.Driver = fields[1] } From 3c590b626e8919e6a1bda7bbe6333b89d1478ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Sat, 30 Dec 2023 00:45:07 +0100 Subject: [PATCH 2/6] lxd/device/nic: Support USB parents for physical NICs in VMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/lxc/incus/issues/339 Signed-off-by: Stéphane Graber (cherry picked from commit 1e2843d53bccb8f4516e01c93fa584d841a12b21) Signed-off-by: Wesley Hershberger License: Apache-2.0 --- lxd/device/nic_physical.go | 88 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/lxd/device/nic_physical.go b/lxd/device/nic_physical.go index be5db45d14db..98804bf4c607 100644 --- a/lxd/device/nic_physical.go +++ b/lxd/device/nic_physical.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "strconv" + "strings" deviceConfig "github.com/canonical/lxd/lxd/device/config" pcidev "github.com/canonical/lxd/lxd/device/pci" @@ -11,6 +12,7 @@ import ( "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/ip" "github.com/canonical/lxd/lxd/network" + "github.com/canonical/lxd/lxd/resources" "github.com/canonical/lxd/lxd/util" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" @@ -198,10 +200,15 @@ func (d *nicPhysical) Start() (*deviceConfig.RunConfig, error) { } } } else if d.inst.Type() == instancetype.VM { - // Get PCI information about the network interface. + // Try to get PCI information about the network interface. ueventPath := fmt.Sprintf("/sys/class/net/%s/device/uevent", saveData["host_name"]) pciDev, err := pcidev.ParseUeventFile(ueventPath) if err != nil { + if err == pcidev.ErrDeviceIsUSB { + // Device is USB rather than PCI. + return d.startVMUSB(saveData["host_name"]) + } + return nil, fmt.Errorf("Failed to get PCI device info for %q: %w", saveData["host_name"], err) } @@ -245,14 +252,87 @@ func (d *nicPhysical) Start() (*deviceConfig.RunConfig, error) { return &runConf, nil } +func (d *nicPhysical) startVMUSB(name string) (*deviceConfig.RunConfig, error) { + // Get the list of network interfaces. + interfaces, err := resources.GetNetwork() + if err != nil { + return nil, err + } + + // Look for our USB device. + var addr string + for _, card := range interfaces.Cards { + for _, port := range card.Ports { + if port.ID == name { + addr = card.USBAddress + break + } + } + + if addr != "" { + break + } + } + + if addr == "" { + return nil, fmt.Errorf("Failed to get USB device info for %q", name) + } + + // Parse the USB address. + fields := strings.Split(addr, ":") + if len(fields) != 2 { + return nil, fmt.Errorf("Bad USB device info for %q", name) + } + + usbBus, err := strconv.Atoi(fields[0]) + if err != nil { + return nil, fmt.Errorf("Bad USB device info for %q: %w", name, err) + } + + usbDev, err := strconv.Atoi(fields[1]) + if err != nil { + return nil, fmt.Errorf("Bad USB device info for %q: %w", name, err) + } + + // Record the addresses. + saveData := map[string]string{} + saveData["last_state.usb.bus"] = fmt.Sprintf("%03d", usbBus) + saveData["last_state.usb.device"] = fmt.Sprintf("%03d", usbDev) + + err = d.volatileSet(saveData) + if err != nil { + return nil, err + } + + // Generate a config. + runConf := deviceConfig.RunConfig{} + runConf.USBDevice = append(runConf.USBDevice, deviceConfig.USBDeviceItem{ + DeviceName: fmt.Sprintf("%s-%03d-%03d", d.name, usbBus, usbDev), + HostDevicePath: fmt.Sprintf("/dev/bus/usb/%03d/%03d", usbBus, usbDev), + }) + + return &runConf, nil +} + // Stop is run when the device is removed from the instance. func (d *nicPhysical) Stop() (*deviceConfig.RunConfig, error) { v := d.volatileGet() + runConf := deviceConfig.RunConfig{ PostHooks: []func() error{d.postStop}, - NetworkInterface: []deviceConfig.RunConfigItem{ + } + + if v["last_state.usb.bus"] != "" && v["last_state.usb.device"] != "" { + // Handle USB NICs. + runConf.USBDevice = append(runConf.USBDevice, deviceConfig.USBDeviceItem{ + DeviceName: fmt.Sprintf("%s-%s-%s", d.name, v["last_state.usb.bus"], v["last_state.usb.device"]), + HostDevicePath: fmt.Sprintf("/dev/bus/usb/%s/%s", v["last_state.usb.bus"], v["last_state.usb.device"]), + }) + } else { + // Handle all other NICs. + runConf.NetworkInterface = []deviceConfig.RunConfigItem{ {Key: "link", Value: v["host_name"]}, - }, + } } return &runConf, nil @@ -268,6 +348,8 @@ func (d *nicPhysical) postStop() error { "last_state.created": "", "last_state.pci.slot.name": "", "last_state.pci.driver": "", + "last_state.usb.bus": "", + "last_state.usb.device": "", }) }() From 710a06cb7a489d5e4b7d230089f5df165c611ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Sat, 30 Dec 2023 00:48:31 +0100 Subject: [PATCH 3/6] lxd/instance: Add new volatile keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit f9c119dccd6804f134dd10269a87bb911b12a21b) Signed-off-by: Wesley Hershberger License: Apache-2.0 --- lxd/instance/instancetype/instance.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lxd/instance/instancetype/instance.go b/lxd/instance/instancetype/instance.go index c57075f5a398..1b267b83210e 100644 --- a/lxd/instance/instancetype/instance.go +++ b/lxd/instance/instancetype/instance.go @@ -1210,6 +1210,14 @@ func ConfigKeyChecker(key string, instanceType Type) (func(value string) error, return validate.IsAny, nil } + if strings.HasSuffix(key, ".bus") { + return validate.IsAny, nil + } + + if strings.HasSuffix(key, ".device") { + return validate.IsAny, nil + } + if strings.HasSuffix(key, ".uuid") { return validate.IsAny, nil } From 9481e1752b8551d884c6d9c378702df485aa59b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Sat, 30 Dec 2023 00:56:47 +0100 Subject: [PATCH 4/6] lxd/instance/qemu: Fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit 1252d03118bb7d8e72034593625a64f08210926f) Signed-off-by: Wesley Hershberger License: Apache-2.0 --- lxd/instance/drivers/driver_qemu.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index ae3192a9c33a..2b178b4ea744 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -2467,7 +2467,7 @@ func (d *qemu) deviceStop(dev device.Device, instanceRunning bool, _ string) err } } - // Detach USB drom running instance. + // Detach USB from running instance. if configCopy["type"] == "usb" && runConf != nil { for _, usbDev := range runConf.USBDevice { err = d.deviceDetachUSB(usbDev) From f76edd66501af29fbf9c9687687e73149c7a5b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Sat, 30 Dec 2023 00:56:57 +0100 Subject: [PATCH 5/6] lxd/instance/qemu: Handle USB NIC hotremove MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit 280e5c473ebc80d1dc5a8e25ff781b66fc7e4bae) Signed-off-by: Wesley Hershberger License: Apache-2.0 --- lxd/instance/drivers/driver_qemu.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 2b178b4ea744..efe8bf0adc72 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -2461,6 +2461,13 @@ func (d *qemu) deviceStop(dev device.Device, instanceRunning bool, _ string) err if instanceRunning { // Detach NIC from running instance. if configCopy["type"] == "nic" { + for _, usbDev := range runConf.USBDevice { + err = d.deviceDetachUSB(usbDev) + if err != nil { + return err + } + } + err = d.deviceDetachNIC(dev.Name()) if err != nil { return err From 3dd1227ac1b815a7be376b33292f798c2c3d74d5 Mon Sep 17 00:00:00 2001 From: Wesley Hershberger Date: Fri, 9 Aug 2024 16:55:00 -0500 Subject: [PATCH 6/6] lxd/instance: lxdmeta for volatile usb NIC keys Signed-off-by: Wesley Hershberger --- lxd/instance/instancetype/instance.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lxd/instance/instancetype/instance.go b/lxd/instance/instancetype/instance.go index 1b267b83210e..cdcfbd52990d 100644 --- a/lxd/instance/instancetype/instance.go +++ b/lxd/instance/instancetype/instance.go @@ -1210,10 +1210,20 @@ func ConfigKeyChecker(key string, instanceType Type) (func(value string) error, return validate.IsAny, nil } + // lxdmeta:generate(entities=network-physical; group=volatile; key=volatile.last_state.usb.bus) + // + // -- + // type: string + // shortdesc: USB Bus Number if strings.HasSuffix(key, ".bus") { return validate.IsAny, nil } + // lxdmeta:generate(entities=network-physical; group=volatile; key=volatile.last_state.usb.device) + // + // -- + // type: string + // shortdesc: USB Device Number if strings.HasSuffix(key, ".device") { return validate.IsAny, nil }