fix(host): filter usb hub devices (#15413)

This commit is contained in:
Zexi Li
2022-11-21 17:21:28 +08:00
committed by GitHub
parent 15d7ecd330
commit d60ea269fc
4 changed files with 406 additions and 8 deletions

View File

@@ -350,7 +350,7 @@ func (d *PCIDevice) IsBootVGA() (bool, error) {
return false, err
}
}
paths := ParseOutput(output)
paths := ParseOutput(output, true)
for _, p := range paths {
if strings.Contains(p, addr) && !strings.Contains(p, "No such file or directory") {
if content, err := fileutils2.FileGetContents(p); err != nil {

View File

@@ -403,24 +403,36 @@ func (dev *sBaseDevice) DetectByAddr() error {
return nil
}
func ParseOutput(output []byte) []string {
func ParseOutput(output []byte, doTrim bool) []string {
lines := make([]string, 0)
for _, line := range strings.Split(string(output), "\n") {
lines = append(lines, strings.TrimSpace(line))
if doTrim {
lines = append(lines, strings.TrimSpace(line))
} else {
lines = append(lines, line)
}
}
return lines
}
func bashOutput(cmd string) ([]string, error) {
func bashCmdOutput(cmd string, doTrim bool) ([]string, error) {
args := []string{"-c", cmd}
output, err := procutils.NewRemoteCommandAsFarAsPossible("bash", args...).Output()
if err != nil {
return nil, err
} else {
return ParseOutput(output), nil
return ParseOutput(output, doTrim), nil
}
}
func bashOutput(cmd string) ([]string, error) {
return bashCmdOutput(cmd, true)
}
func bashRawOutput(cmd string) ([]string, error) {
return bashCmdOutput(cmd, false)
}
type QemuParams struct {
Cpu string
Vga string

View File

@@ -16,6 +16,7 @@ package isolated_device
import (
"fmt"
"sort"
"strconv"
"strings"
@@ -30,12 +31,14 @@ import (
type sUSBDevice struct {
*sBaseDevice
lsusbLine *sLsusbLine
}
// TODO: rename PCIDevice
func newUSBDevice(dev *PCIDevice) *sUSBDevice {
func newUSBDevice(dev *PCIDevice, lsusbLine *sLsusbLine) *sUSBDevice {
return &sUSBDevice{
sBaseDevice: newBaseDevice(dev, api.USB_TYPE),
lsusbLine: lsusbLine,
}
}
@@ -154,6 +157,15 @@ func getPassthroughUSBs() ([]*sUSBDevice, error) {
return nil, errors.Wrap(err, "parseLsusb")
}
treeRet, err := bashRawOutput("lsusb -t")
if err != nil {
return nil, errors.Wrap(err, "execute `lsusb -t`")
}
trees, err := parseLsusbTrees(treeRet)
if err != nil {
return nil, errors.Wrap(err, "parseLsusbTrees")
}
// fitler linux root hub
retDev := make([]*sUSBDevice, 0)
for _, dev := range devs {
@@ -161,6 +173,16 @@ func getPassthroughUSBs() ([]*sUSBDevice, error) {
if isUSBLinuxRootHub(dev.dev.VendorId, dev.dev.DeviceId) {
continue
}
// check by trees
isHubClass, err := isUSBHubClass(dev, trees)
if err != nil {
return nil, errors.Wrap(err, "check isUSBHubClass")
}
if isHubClass {
continue
}
retDev = append(retDev, dev)
}
return retDev, nil
@@ -173,6 +195,27 @@ func isUSBLinuxRootHub(vendorId string, deviceId string) bool {
return false
}
func isUSBHubClass(dev *sUSBDevice, trees *sLsusbTrees) (bool, error) {
busNum, err := dev.lsusbLine.GetBusNumber()
if err != nil {
return false, errors.Wrapf(err, "GetBusNumber of dev %#v", dev.lsusbLine)
}
devNum, err := dev.lsusbLine.GetDeviceNumber()
if err != nil {
return false, errors.Wrapf(err, "GetDeviceNumber of dev %#v", dev.lsusbLine)
}
tree, ok := trees.GetBus(busNum)
if !ok {
return false, errors.Errorf("not found dev %#v by bus %d", dev.lsusbLine, busNum)
}
treeDev := tree.GetDevice(devNum)
if treeDev == nil {
return false, errors.Errorf("not found dev %#v by bus %d, dev %d", dev.lsusbLine, busNum, devNum)
}
return utils.IsInStringArray(treeDev.Class, []string{"root_hub", "Hub"}), nil
}
func parseLsusb(lines []string) ([]*sUSBDevice, error) {
devs := make([]*sUSBDevice, 0)
for _, line := range lines {
@@ -183,7 +226,7 @@ func parseLsusb(lines []string) ([]*sUSBDevice, error) {
if err != nil {
return nil, errors.Wrapf(err, "parseLsusbLine %q", line)
}
usbDev := newUSBDevice(dev.ToPCIDevice())
usbDev := newUSBDevice(dev.ToPCIDevice(), dev)
devs = append(devs, usbDev)
}
return devs, nil
@@ -218,3 +261,270 @@ func (dev *sLsusbLine) ToPCIDevice() *PCIDevice {
ModelName: dev.Name,
}
}
func (dev *sLsusbLine) GetBusNumber() (int, error) {
return strconv.Atoi(dev.BusId)
}
func (dev *sLsusbLine) GetDeviceNumber() (int, error) {
return strconv.Atoi(dev.Device)
}
type sLsusbTrees struct {
Trees map[int]*sLsusbTree
sorted bool
sortedTrees sortLsusbTree
}
type sortLsusbTree []*sLsusbTree
func (t sortLsusbTree) Len() int {
return len(t)
}
func (t sortLsusbTree) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}
func (t sortLsusbTree) Less(i, j int) bool {
it := t[i]
jt := t[j]
return it.Bus < jt.Bus
}
func newLsusbTrees() *sLsusbTrees {
return &sLsusbTrees{
Trees: make(map[int]*sLsusbTree),
sorted: false,
sortedTrees: sortLsusbTree([]*sLsusbTree{}),
}
}
func (ts *sLsusbTrees) Add(bus int, tree *sLsusbTree) *sLsusbTrees {
ts.Trees[bus] = tree
return ts
}
func (ts *sLsusbTrees) sortTrees() *sLsusbTrees {
if ts.sorted {
return ts
}
for _, t := range ts.Trees {
ts.sortedTrees = append(ts.sortedTrees, t)
}
sort.Sort(ts.sortedTrees)
return ts
}
func (ts *sLsusbTrees) GetContent() string {
ts.sortTrees()
ret := []string{}
for _, t := range ts.sortedTrees {
ret = append(ret, t.GetContents()...)
}
return strings.Join(ret, "\n")
}
func (ts *sLsusbTrees) GetBus(bus int) (*sLsusbTree, bool) {
t, ok := ts.Trees[bus]
return t, ok
}
// parseLsusbTrees parses `lsusb -t` output
func parseLsusbTrees(lines []string) (*sLsusbTrees, error) {
return _parseLsusbTrees(lines)
}
func _parseLsusbTrees(lines []string) (*sLsusbTrees, error) {
trees := newLsusbTrees()
var (
prevTree *sLsusbTree
)
for idx, line := range lines {
if len(strings.TrimSpace(line)) == 0 {
continue
}
tree, err := newLsusbTreeByLine(line)
if err != nil {
return nil, errors.Wrapf(err, "by line %q, index %d", line, idx)
}
if tree.IsRootBus {
tree.parentNode = nil
trees.Add(tree.Bus, tree)
prevTree = tree
} else {
if len(tree.LinePrefix) > len(prevTree.LinePrefix) {
// child node should be added to previous node
tree.parentNode = prevTree
tree.parentNode.AddNode(tree)
} else if len(tree.LinePrefix) == len(prevTree.LinePrefix) {
// sibling node should be added to parent
prevTree.parentNode.AddNode(tree)
} else if len(tree.LinePrefix) < len(prevTree.LinePrefix) {
// find current node's sibling node and added to it's parent
parent := prevTree.FindParentByTree(tree)
if parent == nil {
return nil, errors.Errorf("can't found parent by tree %s, current line %q, prevTree %s", jsonutils.Marshal(tree), line, jsonutils.Marshal(prevTree))
}
parent.AddNode(tree)
}
prevTree = tree
}
}
return trees, nil
}
const (
busRootPrefix = "/: Bus"
)
var (
lsusbTreeRootBusRegex = `(?P<prefix>(.*))Bus (?P<bus_id>([0-9]{2}))\.`
lsusbTreeBusSuffixRegex = `Port (?P<port_id>([0-9]{1,2})): Dev (?P<device>([0-9]{1,2})), Class=(?P<class>(.*)), Driver=(?P<driver>(.*)),\s{0,1}(?P<speed>(.*))`
lsusbTreeSuffixRegex = `Port (?P<port_id>([0-9]{1,2})): Dev (?P<device>([0-9]{1,2})), If (?P<interface>([0-9]{1,2})), Class=(?P<class>(.*)), Driver=(?P<driver>(.*)),\s{0,1}(?P<speed>(.*))`
lsusbTreeRootBusLineRegex = lsusbTreeRootBusRegex + lsusbTreeBusSuffixRegex
lsusbTreeLineRegex = `(?P<prefix>(.*))` + lsusbTreeSuffixRegex
)
type sLsusbTree struct {
parentNode *sLsusbTree
IsRootBus bool `json:"is_root_bus"`
LinePrefix string `json:"line_prefix"`
Bus int `json:"bus"`
Port int `json:"port"`
Dev int `json:"dev"`
// If maybe nil
If int `json:"if"`
Class string `json:"class"`
Driver string `json:"driver"`
Content string `json:"content`
Nodes []*sLsusbTree `json:"nodes"`
}
func newLsusbTreeByLine(line string) (*sLsusbTree, error) {
var (
isRootBus = false
regExp = lsusbTreeLineRegex
)
if strings.HasPrefix(line, busRootPrefix) {
isRootBus = true
}
if isRootBus {
regExp = lsusbTreeRootBusLineRegex
}
ret := regutils2.SubGroupMatch(regExp, line)
linePrefix := ret["prefix"]
if linePrefix == "" {
return nil, errors.Errorf("not found prefix of line %q", line)
}
t := &sLsusbTree{
IsRootBus: isRootBus,
Content: line,
LinePrefix: linePrefix,
Nodes: make([]*sLsusbTree, 0),
}
if isRootBus {
// parse bus
busIdStr, ok := ret["bus_id"]
if !ok {
return nil, errors.Errorf("not found 'Bus' in %q", line)
}
busId, err := strconv.Atoi(busIdStr)
if err != nil {
return nil, errors.Errorf("invalid Bus string %q", busIdStr)
}
t.Bus = busId
}
// parse port
portIdStr, ok := ret["port_id"]
if !ok {
return nil, errors.Errorf("not found 'Port' in %q", line)
}
portId, err := strconv.Atoi(portIdStr)
if err != nil {
return nil, errors.Errorf("invalid Port string %q", portIdStr)
}
t.Port = portId
// parse dev
devStr, ok := ret["device"]
if !ok {
return nil, errors.Errorf("not found 'Dev' in %q", line)
}
dev, err := strconv.Atoi(devStr)
if err != nil {
return nil, errors.Errorf("invalid Dev string %q", devStr)
}
t.Dev = dev
// parse if when not root bus
if !isRootBus {
ifStr, ok := ret["interface"]
if !ok {
return nil, errors.Errorf("not found 'If' in %q", line)
}
ifN, err := strconv.Atoi(ifStr)
if err != nil {
return nil, errors.Errorf("invalid ifStr string %q", ifStr)
}
t.If = ifN
}
// parse class
class, ok := ret["class"]
if !ok {
return nil, errors.Errorf("not found 'Class' in %q", line)
}
t.Class = class
// parse driver
driver := ret["driver"]
t.Driver = driver
return t, nil
}
func (t *sLsusbTree) AddNode(child *sLsusbTree) *sLsusbTree {
child.Bus = t.Bus
child.parentNode = t
t.Nodes = append(t.Nodes, child)
return t
}
func (t *sLsusbTree) FindParentByTree(it *sLsusbTree) *sLsusbTree {
tl := len(t.LinePrefix)
itl := len(it.LinePrefix)
if tl == itl {
return t.parentNode
} else if tl > itl {
return t
} else {
return t.parentNode.FindParentByTree(it)
}
}
func (t *sLsusbTree) GetContents() []string {
ret := []string{t.Content}
for _, n := range t.Nodes {
ret = append(ret, n.GetContents()...)
}
return ret
}
func (t *sLsusbTree) GetDevice(devNum int) *sLsusbTree {
// should check self firstly
if t.Dev == devNum {
return t
}
// then check children
for _, node := range t.Nodes {
dev := node.GetDevice(devNum)
if dev != nil {
return dev
}
}
return nil
}

View File

@@ -16,7 +16,10 @@ package isolated_device
import (
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_parseLsusbLine(t *testing.T) {
@@ -80,7 +83,9 @@ func Test_parseLsusbLine(t *testing.T) {
t.Errorf("parseLsusb() error = %v, wantErr %v", err, tt.wantErr)
return
}
t.Logf("line result: %#v", got)
busNum, _ := got.GetBusNumber()
devNum, _ := got.GetDeviceNumber()
t.Logf("line result: %#v, busNum: %d, devNum: %d", got, busNum, devNum)
if !reflect.DeepEqual(got, tt.want[i]) {
t.Errorf("parseLsusb() = %v, want %v", got, tt.want[i])
@@ -179,3 +184,74 @@ func Test_getUSBDevQemuOptions(t *testing.T) {
})
}
}
func Test_newLsusbRootBusTreeByLine(t *testing.T) {
assert := assert.New(t)
tree, _ := newLsusbTreeByLine("/: Bus 04.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/2p, 10000M")
assert.Equal(4, tree.Bus)
assert.Equal(1, tree.Port)
assert.Equal(1, tree.Dev)
assert.Equal(true, tree.IsRootBus)
assert.Equal("xhci_hcd/2p", tree.Driver)
tree, _ = newLsusbTreeByLine(" |__ Port 3: Dev 2, If 0, Class=Vendor Specific Class, Driver=, 12M")
assert.Equal(false, tree.IsRootBus)
assert.Equal("", tree.Driver)
assert.Equal("Vendor Specific Class", tree.Class)
assert.Equal(3, tree.Port)
assert.Equal(2, tree.Dev)
assert.Equal(0, tree.If)
}
func Test_parseLsusbTrees(t *testing.T) {
assert := assert.New(t)
input := `
/: Bus 04.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/2p, 10000M
/: Bus 03.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 480M
|__ Port 3: Dev 2, If 0, Class=Vendor Specific Class, Driver=, 12M
|__ Port 4: Dev 3, If 0, Class=Wireless, Driver=btusb, 480M
|__ Port 4: Dev 3, If 1, Class=Wireless, Driver=btusb, 480M
|__ Port 4: Dev 3, If 2, Class=Wireless, Driver=, 480M
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/2p, 10000M
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 480M
|__ Port 3: Dev 2, If 0, Class=Human Interface Device, Driver=usbhid, 12M
|__ Port 3: Dev 2, If 1, Class=Human Interface Device, Driver=usbhid, 12M
|__ Port 3: Dev 2, If 2, Class=Human Interface Device, Driver=usbhid, 12M
|__ Port 4: Dev 3, If 0, Class=Human Interface Device, Driver=usbfs, 12M
`
ts, err := parseLsusbTrees(strings.Split(input, "\n"))
assert.Equal(nil, err)
assert.Equal(
`/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 480M
|__ Port 3: Dev 2, If 0, Class=Human Interface Device, Driver=usbhid, 12M
|__ Port 3: Dev 2, If 1, Class=Human Interface Device, Driver=usbhid, 12M
|__ Port 3: Dev 2, If 2, Class=Human Interface Device, Driver=usbhid, 12M
|__ Port 4: Dev 3, If 0, Class=Human Interface Device, Driver=usbfs, 12M
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/2p, 10000M
/: Bus 03.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 480M
|__ Port 3: Dev 2, If 0, Class=Vendor Specific Class, Driver=, 12M
|__ Port 4: Dev 3, If 0, Class=Wireless, Driver=btusb, 480M
|__ Port 4: Dev 3, If 1, Class=Wireless, Driver=btusb, 480M
|__ Port 4: Dev 3, If 2, Class=Wireless, Driver=, 480M
/: Bus 04.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/2p, 10000M`, ts.GetContent())
bus, ok := ts.GetBus(3)
assert.Equal(true, ok)
dt := bus.GetDevice(1)
assert.Equal("root_hub", dt.Class)
dt = bus.GetDevice(2)
assert.Equal("Vendor Specific Class", dt.Class)
input2 := `
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/10p, 5000M
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/16p, 480M
|__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/7p, 480M
|__ Port 10: Dev 6, If 0, Class=Human Interface Device, Driver=usbfs, 12M
`
ts, err = parseLsusbTrees(strings.Split(input2, "\n"))
assert.Equal(nil, err)
bus, ok = ts.GetBus(1)
assert.Equal(true, ok)
dt = bus.GetDevice(6)
assert.Equal("Human Interface Device", dt.Class)
}