summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ifconfig/ifname_store.py60
1 files changed, 57 insertions, 3 deletions
diff --git a/python/vyos/ifconfig/ifname_store.py b/python/vyos/ifconfig/ifname_store.py
index b0ee3df35..e0a6b4cbe 100644
--- a/python/vyos/ifconfig/ifname_store.py
+++ b/python/vyos/ifconfig/ifname_store.py
@@ -54,6 +54,13 @@ _BUS_RANK = {'pci': 0, 'vmbus': 1, 'xen': 2, 'usb': 3}
_BUS_RANK_UNKNOWN = 90
_PCI_BDF = re.compile(r'([0-9a-f]{4}):([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]+)', re.I)
+# ESXi labels its adapters Ethernet0..EthernetN, in "Network adapter N" order
+_PORT_LABEL = re.compile(r'^Ethernet(\d+)$')
+# sorts after every real label, and must stay an int - canonical_sort_key()
+# compares tuples element-wise, so a None here would raise against an int
+_LABEL_UNKNOWN = 1 << 30
+_VMWARE_PCI_VENDOR = '0x15ad'
+_DMI_SYS_VENDOR = Path('/sys/class/dmi/id/sys_vendor')
_MAC_RE = re.compile(r'([0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5})')
@@ -91,18 +98,62 @@ def device_matches(properties: dict, key: str) -> bool:
def canonical_sort_key(device: dict) -> tuple:
- """Total order by bus class, physical position then MAC, so it never
- depends on probe order. Only used for hardware not yet in the store.
+ """Total order by bus class, the hypervisor's adapter order where it says
+ so, then physical position and MAC, so it never depends on probe order.
+ Only used for hardware not yet in the store.
"""
properties = device.get('properties') or {}
path = properties.get('ID_PATH', '')
rank = _BUS_RANK.get(properties.get('ID_BUS', ''), _BUS_RANK_UNKNOWN)
+ # a labelled adapter outranks the PCI address, which on ESXi does not
+ # follow the order the adapters were added in. Unlabelled hardware sorts
+ # after it: a passthrough NIC is not part of the hypervisor's own set and
+ # must not take the first name from it.
+ label = device.get('label_index', _LABEL_UNKNOWN)
+
# numeric, so 0000:09:00.0 sorts before 0000:10:00.0
segments = [tuple(int(part, 16) for part in m.groups())
for m in _PCI_BDF.finditer(path)]
- return (rank, segments, path, device.get('mac') or '')
+ return (rank, label, segments, path, device.get('mac') or '')
+
+
+def _sysfs(path: Path) -> str:
+ try:
+ return path.read_text().strip()
+ except OSError:
+ return ''
+
+
+def is_vmware_nic(ifname: str, sys_class_net: str = '/sys/class/net') -> bool:
+ """Whether a NIC is one ESXi presents - vmxnet3 carries VMware's PCI
+ vendor, an emulated e1000 carries it as the subsystem vendor.
+
+ The DMI fallback is not redundant: smbios.reflectHost makes a guest report
+ the physical host's vendor, and a NIC model whose IDs are not listed here
+ would otherwise go unrecognised.
+ """
+ device = Path(sys_class_net) / ifname / 'device'
+ if _VMWARE_PCI_VENDOR in (_sysfs(device / 'vendor'),
+ _sysfs(device / 'subsystem_vendor')):
+ return True
+ return _sysfs(_DMI_SYS_VENDOR) == 'VMware, Inc.'
+
+
+def port_label_index(ifname: str, sys_class_net: str = '/sys/class/net') -> int:
+ """The hypervisor's adapter number, from the firmware port label.
+
+ ESXi does not lay its adapters out in PCI address order - with four or more
+ it places the fourth on a root port which sorts ahead of the first three -
+ so the label is the only thing that gives their intended order.
+
+ Returns _LABEL_UNKNOWN when there is no such label. Physical servers label
+ ports too, e.g. "NIC1" or "Embedded LOM 1 Port 1", and must not be reordered
+ by this.
+ """
+ m = _PORT_LABEL.match(_sysfs(Path(sys_class_net) / ifname / 'device' / 'label'))
+ return int(m.group(1)) if m else _LABEL_UNKNOWN
def permanent_mac(ifname: str, sys_class_net: str = '/sys/class/net') -> str:
@@ -200,6 +251,9 @@ def discover_devices(sys_class_net: str = '/sys/class/net') -> list:
'mac': mac,
'wireless': is_wireless(entry.name, sys_class_net),
'wwan': is_wwan(entry.name, sys_class_net),
+ 'label_index': (port_label_index(entry.name, sys_class_net)
+ if is_vmware_nic(entry.name, sys_class_net)
+ else _LABEL_UNKNOWN),
'properties': read_properties(entry.name),
})