summaryrefslogtreecommitdiffstats
path: root/staslib/udev.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--staslib/udev.py199
1 files changed, 184 insertions, 15 deletions
diff --git a/staslib/udev.py b/staslib/udev.py
index 12ef61b..80555dd 100644
--- a/staslib/udev.py
+++ b/staslib/udev.py
@@ -153,6 +153,169 @@ class Udev:
return False
+ @staticmethod
+ def _cid_matches_tcp_tid_legacy(tid, cid): # pylint: disable=too-many-return-statements,too-many-branches
+ '''On kernels older than 6.1, the src_addr parameter is not available
+ from the sysfs. Therefore, we need to infer a match based on other
+ parameters. And there are a few cases where we're simply not sure
+ whether an existing connection (cid) matches the candidate
+ connection (tid).
+ '''
+ cid_host_iface = cid['host-iface']
+ cid_host_traddr = iputil.get_ipaddress_obj(cid['host-traddr'], ipv4_mapped_convert=True)
+
+ if not cid_host_iface: # cid.host_iface is undefined
+ if not cid_host_traddr: # cid.host_traddr is undefined
+ # When the existing cid.src_addr, cid.host_traddr, and cid.host_iface
+ # are all undefined (which can only happen on kernels prior to 6.1),
+ # we can't know for sure on which interface an existing connection
+ # was made. In this case, we can only declare a match if both
+ # tid.host_iface and tid.host_traddr are undefined as well.
+ logging.debug(
+ 'Udev._cid_matches_tcp_tid_legacy() - cid=%s, tid=%s - Not enough info. Assume "match" but this could be wrong.',
+ cid,
+ tid,
+ )
+ return True
+
+ # cid.host_traddr is defined. If tid.host_traddr is also
+ # defined, then it must match the existing cid.host_traddr.
+ if tid.host_traddr:
+ tid_host_traddr = iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True)
+ if tid_host_traddr != cid_host_traddr:
+ return False
+
+ # If tid.host_iface is defined, then the interface where
+ # the connection is located must match. If tid.host_iface
+ # is not defined, then we don't really care on which
+ # interface the connection was made and we can skip this test.
+ if tid.host_iface:
+ # With the existing cid.host_traddr, we can find the
+ # interface of the exisiting connection.
+ connection_iface = iputil.get_interface(str(cid_host_traddr))
+ if tid.host_iface != connection_iface:
+ return False
+
+ return True
+
+ # cid.host_iface is defined
+ if not cid_host_traddr: # cid.host_traddr is undefined
+ if tid.host_iface and tid.host_iface != cid_host_iface:
+ return False
+
+ if tid.host_traddr:
+ # It's impossible to tell the existing connection source
+ # address. So, we can't tell if it matches tid.host_traddr.
+ # However, if the existing host_iface has only one source
+ # address assigned to it, we can assume that the source
+ # address used for the existing connection is that address.
+ if_addrs = iputil.net_if_addrs().get(cid_host_iface, {4: [], 6: []})
+ tid_host_traddr = iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True)
+ source_addrs = if_addrs[tid_host_traddr.version]
+ if len(source_addrs) != 1:
+ return False
+
+ src_addr0 = iputil.get_ipaddress_obj(source_addrs[0], ipv4_mapped_convert=True)
+ if src_addr0 != tid_host_traddr:
+ return False
+
+ return True
+
+ # cid.host_traddr is defined
+ if tid.host_iface and tid.host_iface != cid_host_iface:
+ return False
+
+ if tid.host_traddr:
+ tid_host_traddr = iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True)
+ if tid_host_traddr != cid_host_traddr:
+ return False
+
+ return True
+
+ @staticmethod
+ def _cid_matches_tid(tid, cid): # pylint: disable=too-many-return-statements,too-many-branches
+ '''Check if existing controller's cid matches candidate controller's tid.
+ @param cid: The Connection ID of an existing controller (from the sysfs).
+ @param tid: The Transport ID of a candidate controller.
+
+ We're trying to find if an existing connection (specified by cid) can
+ be re-used for the candidate controller (specified by tid).
+
+ We do not have a match if the candidate's tid.transport, tid.traddr,
+ tid.trsvcid, and tid.subsysnqn are not identical to those of the cid.
+ These 4 parameters are mandatory for a match.
+
+ The tid.host_traddr and tid.host_iface depend on the transport type.
+ These parameters may not apply or have a different syntax/meaning
+ depending on the transport type.
+
+ For TCP only:
+ With regards to the candidate's tid.host_traddr and tid.host_iface,
+ if those are defined but do not match the existing cid.host_traddr
+ and cid.host_iface, we may still be able to find a match by taking
+ the existing cid.src_addr into consideration since that parameter
+ identifies the actual source address of the connection and therefore
+ can be used to infer the interface of the connection. However, the
+ cid.src_addr can only be read from the sysfs starting with kernel
+ 6.1.
+ '''
+ # 'transport', 'traddr', 'trsvcid', and 'subsysnqn' must exactly match.
+ if cid['transport'] != tid.transport or cid['trsvcid'] != tid.trsvcid or cid['subsysnqn'] != tid.subsysnqn:
+ return False
+
+ if tid.transport in ('tcp', 'rdma'):
+ # Need to convert to ipaddress objects to properly
+ # handle all variations of IPv6 addresses.
+ tid_traddr = iputil.get_ipaddress_obj(tid.traddr, ipv4_mapped_convert=True)
+ cid_traddr = iputil.get_ipaddress_obj(cid['traddr'], ipv4_mapped_convert=True)
+ else:
+ cid_traddr = cid['traddr']
+ tid_traddr = tid.traddr
+
+ if cid_traddr != tid_traddr:
+ return False
+
+ # We need to know the type of transport to compare 'host-traddr' and
+ # 'host-iface'. These parameters don't apply to all transport types
+ # and may have a different meaning/syntax.
+ if tid.transport == 'tcp':
+ if tid.host_traddr or tid.host_iface:
+ src_addr = iputil.get_ipaddress_obj(cid['src-addr'], ipv4_mapped_convert=True)
+ if not src_addr:
+ # For legacy kernels (i.e. older than 6.1), the existing cid.src_addr
+ # is always undefined. We need to use advanced logic to determine
+ # whether cid and tid match.
+ return Udev._cid_matches_tcp_tid_legacy(tid, cid)
+
+ # The existing controller's cid.src_addr is always defined for kernel
+ # 6.1 and later. We can use the existing controller's cid.src_addr to
+ # find the interface on which the connection was made and therefore
+ # match it to the candidate's tid.host_iface. And the cid.src_addr
+ # can also be used to match the candidate's tid.host_traddr.
+ if tid.host_traddr:
+ tid_host_traddr = iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True)
+ if tid_host_traddr != src_addr:
+ return False
+
+ # host-iface is an optional tcp-only parameter.
+ if tid.host_iface and tid.host_iface != iputil.get_interface(str(src_addr)):
+ return False
+
+ elif tid.transport == 'fc':
+ # host-traddr is mandatory for FC.
+ if tid.host_traddr != cid['host-traddr']:
+ return False
+
+ elif tid.transport == 'rdma':
+ # host-traddr is optional for RDMA and is expressed as an IP address.
+ if tid.host_traddr:
+ tid_host_traddr = iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True)
+ cid_host_traddr = iputil.get_ipaddress_obj(cid['host-traddr'], ipv4_mapped_convert=True)
+ if tid_host_traddr != cid_host_traddr:
+ return False
+
+ return True
+
def find_nvme_dc_device(self, tid):
'''@brief Find the nvme device associated with the specified
Discovery Controller.
@@ -164,7 +327,8 @@ class Udev:
if not self.is_dc_device(device):
continue
- if self.get_tid(device) != tid:
+ cid = self.get_cid(device)
+ if not self._cid_matches_tid(tid, cid):
continue
return device
@@ -182,7 +346,8 @@ class Udev:
if not self.is_ioc_device(device):
continue
- if self.get_tid(device) != tid:
+ cid = self.get_cid(device)
+ if not self._cid_matches_tid(tid, cid):
continue
return device
@@ -300,28 +465,32 @@ class Udev:
return attr_str[start:end]
@staticmethod
- def _get_host_iface(device):
- host_iface = Udev._get_property(device, 'NVME_HOST_IFACE')
- if not host_iface:
- # We'll try to find the interface from the source address on
- # the connection. Only available if kernel exposes the source
- # address (src_addr) in the "address" attribute.
- src_addr = Udev.get_key_from_attr(device, 'address', 'src_addr=')
- host_iface = iputil.get_interface(src_addr)
- return host_iface
-
- @staticmethod
def get_tid(device):
'''@brief return the Transport ID associated with a udev device'''
+ cid = Udev.get_cid(device)
+ if cid['transport'] == 'tcp':
+ src_addr = cid['src-addr']
+ if not cid['host-iface'] and src_addr:
+ # We'll try to find the interface from the source address on
+ # the connection. Only available if kernel exposes the source
+ # address (src_addr) in the "address" attribute.
+ cid['host-iface'] = iputil.get_interface(src_addr)
+
+ return trid.TID(cid)
+
+ @staticmethod
+ def get_cid(device):
+ '''@brief return the Connection ID associated with a udev device'''
cid = {
'transport': Udev._get_property(device, 'NVME_TRTYPE'),
'traddr': Udev._get_property(device, 'NVME_TRADDR'),
'trsvcid': Udev._get_property(device, 'NVME_TRSVCID'),
'host-traddr': Udev._get_property(device, 'NVME_HOST_TRADDR'),
- 'host-iface': Udev._get_host_iface(device),
+ 'host-iface': Udev._get_property(device, 'NVME_HOST_IFACE'),
'subsysnqn': Udev._get_attribute(device, 'subsysnqn'),
+ 'src-addr': Udev.get_key_from_attr(device, 'address', 'src_addr='),
}
- return trid.TID(cid)
+ return cid
UDEV = Udev() # Singleton