diff --git a/nova/network/neutron.py b/nova/network/neutron.py
index eef2abe36414..e6d47c9a6b39 100644
--- a/nova/network/neutron.py
+++ b/nova/network/neutron.py
@@ -1601,6 +1601,13 @@ class API:
'pf_mac_address': pf_mac,
'vf_num': vf_num,
})
+
+ # Update port binding capabilities using PCI device's network
+ # capabilities if they exist.
+ pci_net_caps = pci_dev.network_caps
+ if pci_net_caps:
+ vf_profile.update({'capabilities': pci_net_caps})
+
return vf_profile
def _get_pci_device_profile(self, pci_dev):
diff --git a/nova/objects/pci_device.py b/nova/objects/pci_device.py
index 14f9cca14a97..ab775b4554ac 100644
--- a/nova/objects/pci_device.py
+++ b/nova/objects/pci_device.py
@@ -589,6 +589,13 @@ class PciDevice(base.NovaPersistentObject, base.NovaObject):
"""
return self.extra_info.get('mac_address')
+ @property
+ def network_caps(self):
+ """PCI device network capabilities or empty list if not available"""
+ caps_json = self.extra_info.get('capabilities', '{}')
+ caps = jsonutils.loads(caps_json)
+ return caps.get('network', [])
+
@base.NovaObjectRegistry.register
class PciDeviceList(base.ObjectListBase, base.NovaObject):
diff --git a/nova/tests/fixtures/libvirt_data.py b/nova/tests/fixtures/libvirt_data.py
index f022860f615d..f921a5e2df37 100644
--- a/nova/tests/fixtures/libvirt_data.py
+++ b/nova/tests/fixtures/libvirt_data.py
@@ -2182,6 +2182,7 @@ _fake_NodeDevXml = {
+
""", # noqa:E501
diff --git a/nova/tests/unit/network/test_neutron.py b/nova/tests/unit/network/test_neutron.py
index c551191e4cf8..0789022cfae1 100644
--- a/nova/tests/unit/network/test_neutron.py
+++ b/nova/tests/unit/network/test_neutron.py
@@ -8144,17 +8144,20 @@ class TestAPIPortbinding(TestAPIBase):
'pf_mac_address': '52:54:00:1e:59:c6',
'vf_num': 1,
},
+ 'network_caps': ['gso', 'sg', 'tso', 'tx'],
'dev_type': obj_fields.PciDeviceType.SRIOV_VF,
}
PciDevice = collections.namedtuple('PciDevice',
['vendor_id', 'product_id', 'address',
'card_serial_number', 'sriov_cap',
- 'dev_type', 'parent_addr'])
+ 'dev_type', 'parent_addr',
+ 'network_caps'])
mydev = PciDevice(**pci_dev)
self.assertEqual(self.api._get_vf_pci_device_profile(mydev),
{'pf_mac_address': '52:54:00:1e:59:c6',
'vf_num': 1,
- 'card_serial_number': 'MT2113X00000'})
+ 'card_serial_number': 'MT2113X00000',
+ 'capabilities': ['gso', 'sg', 'tso', 'tx']})
@mock.patch.object(
neutronapi.API, '_get_vf_pci_device_profile',
diff --git a/nova/tests/unit/objects/test_pci_device.py b/nova/tests/unit/objects/test_pci_device.py
index 1e971c5a2149..e0570b69a8ef 100644
--- a/nova/tests/unit/objects/test_pci_device.py
+++ b/nova/tests/unit/objects/test_pci_device.py
@@ -171,6 +171,16 @@ class _TestPciDeviceObject(object):
self.pci_device = pci_device.PciDevice.create(None, self.dev_dict)
self.assertEqual(self.pci_device.card_serial_number, '42')
+ def test_pci_device_extra_info_network_capabilities(self):
+ self.dev_dict = copy.copy(dev_dict)
+ self.pci_device = pci_device.PciDevice.create(None, self.dev_dict)
+ self.assertEqual(self.pci_device.network_caps, [])
+
+ self.dev_dict = copy.copy(dev_dict)
+ self.dev_dict['capabilities'] = {'network': ['sg', 'tso', 'tx']}
+ self.pci_device = pci_device.PciDevice.create(None, self.dev_dict)
+ self.assertEqual(self.pci_device.network_caps, ['sg', 'tso', 'tx'])
+
def test_update_device(self):
self.pci_device = pci_device.PciDevice.create(None, dev_dict)
self.pci_device.obj_reset_changes()
diff --git a/nova/tests/unit/virt/libvirt/test_host.py b/nova/tests/unit/virt/libvirt/test_host.py
index 9f82aefda027..a94dea48d3fc 100644
--- a/nova/tests/unit/virt/libvirt/test_host.py
+++ b/nova/tests/unit/virt/libvirt/test_host.py
@@ -1345,7 +1345,7 @@ Active: 8381604 kB
"parent_ifname": "ens1",
"capabilities": {
"network": ["rx", "tx", "sg", "tso", "gso", "gro", "rxvlan",
- "txvlan", "rxhash"],
+ "txvlan", "rxhash", "switchdev"],
"sriov": {"pf_mac_address": "52:54:00:1e:59:c6",
"vf_num": 1},
# Should be obtained from the parent PF in this case.
diff --git a/releasenotes/notes/translate_vf_network_capabilities_to_port_binding-48abbfe0ce2923cf.yaml b/releasenotes/notes/translate_vf_network_capabilities_to_port_binding-48abbfe0ce2923cf.yaml
new file mode 100644
index 000000000000..b5ee283c8c4c
--- /dev/null
+++ b/releasenotes/notes/translate_vf_network_capabilities_to_port_binding-48abbfe0ce2923cf.yaml
@@ -0,0 +1,16 @@
+---
+fixes:
+ - |
+ Previously ``switchdev`` capabilities should be configured manually by a
+ user with admin privileges using port's binding profile. This blocked
+ regular users from managing ports with Open vSwitch hardware offloading
+ as providing write access to a port's binding profile to non-admin users
+ introduces security risks. For example, a binding profile may contain a
+ ``pci_slot`` definition, which denotes the host PCI address of the
+ device attached to the VM. A malicious user can use this parameter to
+ passthrough any host device to a guest, so it is impossible to provide
+ write access to a binding profile to regular users in many scenarios.
+
+ This patch fixes this situation by translating VF capabilities reported
+ by Libvirt to Neutron port binding profiles. Other VF capabilities are
+ translated as well for possible future use.