summaryrefslogtreecommitdiffstats
path: root/staslib/iputil.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--staslib/iputil.py305
1 files changed, 222 insertions, 83 deletions
diff --git a/staslib/iputil.py b/staslib/iputil.py
index 9199a49..96c5d56 100644
--- a/staslib/iputil.py
+++ b/staslib/iputil.py
@@ -8,45 +8,126 @@
'''A collection of IP address and network interface utilities'''
+import struct
import socket
-import logging
import ipaddress
-from staslib import conf
+RTM_BASE = 16
+RTM_GETLINK = 18
RTM_NEWADDR = 20
RTM_GETADDR = 22
NLM_F_REQUEST = 0x01
NLM_F_ROOT = 0x100
NLMSG_DONE = 3
-IFLA_ADDRESS = 1
-NLMSGHDR_SZ = 16
+NLMSG_HDRLEN = 16
IFADDRMSG_SZ = 8
+IFINFOMSG_SZ = 16
+ARPHRD_ETHER = 1
+ARPHRD_LOOPBACK = 772
+NLMSG_LENGTH = lambda msg_len: msg_len + NLMSG_HDRLEN # pylint: disable=unnecessary-lambda-assignment
+
RTATTR_SZ = 4
+RTA_ALIGN = lambda length: ((length + 3) & ~3) # pylint: disable=unnecessary-lambda-assignment
+IFLA_ADDRESS = 1
+IFLA_IFNAME = 3
+
+
+def _nlmsghdr(nlmsg_type, nlmsg_flags, nlmsg_seq, nlmsg_pid, msg_len: int):
+ '''Implement this C struct:
+ struct nlmsghdr {
+ __u32 nlmsg_len; /* Length of message including header */
+ __u16 nlmsg_type; /* Message content */
+ __u16 nlmsg_flags; /* Additional flags */
+ __u32 nlmsg_seq; /* Sequence number */
+ __u32 nlmsg_pid; /* Sending process port ID */
+ };
+ '''
+ return struct.pack('<LHHLL', NLMSG_LENGTH(msg_len), nlmsg_type, nlmsg_flags, nlmsg_seq, nlmsg_pid)
+
+
+def _ifaddrmsg(family=0, prefixlen=0, flags=0, scope=0, index=0):
+ '''Implement this C struct:
+ struct ifaddrmsg {
+ __u8 ifa_family;
+ __u8 ifa_prefixlen; /* The prefix length */
+ __u8 ifa_flags; /* Flags */
+ __u8 ifa_scope; /* Address scope */
+ __u32 ifa_index; /* Link index */
+ };
+ '''
+ return struct.pack('<BBBBL', family, prefixlen, flags, scope, index)
+
+
+def _ifinfomsg(family=0, dev_type=0, index=0, flags=0, change=0):
+ '''Implement this C struct:
+ struct ifinfomsg {
+ unsigned char ifi_family; /* AF_UNSPEC */
+ unsigned char __ifi_pad;
+ unsigned short ifi_type; /* Device type: ARPHRD_* */
+ int ifi_index; /* Interface index */
+ unsigned int ifi_flags; /* Device flags: IFF_* */
+ unsigned int ifi_change; /* change mask: IFF_* */
+ };
+ '''
+ return struct.pack('<BBHiII', family, 0, dev_type, index, flags, change)
+
+
+def _nlmsg(nlmsg_type, nlmsg_flags, msg: bytes):
+ '''Build a Netlink message'''
+ return _nlmsghdr(nlmsg_type, nlmsg_flags, 0, 0, len(msg)) + msg
+
+
+# Netlink request (Get address command)
+GETADDRCMD = _nlmsg(RTM_GETADDR, NLM_F_REQUEST | NLM_F_ROOT, _ifaddrmsg())
# Netlink request (Get address command)
-GETADDRCMD = (
- # BEGIN: struct nlmsghdr
- b'\0' * 4 # nlmsg_len (placeholder - actual length calculated below)
- + (RTM_GETADDR).to_bytes(2, byteorder='little', signed=False) # nlmsg_type
- + (NLM_F_REQUEST | NLM_F_ROOT).to_bytes(2, byteorder='little', signed=False) # nlmsg_flags
- + b'\0' * 2 # nlmsg_seq
- + b'\0' * 2 # nlmsg_pid
- # END: struct nlmsghdr
- + b'\0' * 8 # struct ifaddrmsg
-)
-GETADDRCMD = len(GETADDRCMD).to_bytes(4, byteorder='little') + GETADDRCMD[4:] # nlmsg_len
+GETLINKCMD = _nlmsg(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ROOT, _ifinfomsg(family=socket.AF_UNSPEC, change=0xFFFFFFFF))
# ******************************************************************************
-def get_ipaddress_obj(ipaddr):
- '''@brief Return a IPv4Address or IPv6Address depending on whether @ipaddr
- is a valid IPv4 or IPv6 address. Return None otherwise.'''
- try:
- ip = ipaddress.ip_address(ipaddr)
- except ValueError:
- return None
+def _data_matches_mac(data, mac):
+ return mac.lower() == ':'.join([f'{x:02x}' for x in data[0:6]])
- return ip
+
+def mac2iface(mac: str): # pylint: disable=too-many-locals
+ '''@brief Find the interface that has @mac as its assigned MAC address.
+ @param mac: The MAC address to match
+ '''
+ with socket.socket(family=socket.AF_NETLINK, type=socket.SOCK_RAW, proto=socket.NETLINK_ROUTE) as sock:
+ sock.sendall(GETLINKCMD)
+ nlmsg = sock.recv(8192)
+ nlmsg_idx = 0
+ while True: # pylint: disable=too-many-nested-blocks
+ if nlmsg_idx >= len(nlmsg):
+ nlmsg += sock.recv(8192)
+
+ nlmsghdr = nlmsg[nlmsg_idx : nlmsg_idx + NLMSG_HDRLEN]
+ nlmsg_len, nlmsg_type, _, _, _ = struct.unpack('<LHHLL', nlmsghdr)
+
+ if nlmsg_type == NLMSG_DONE:
+ break
+
+ if nlmsg_type == RTM_BASE:
+ msg_indx = nlmsg_idx + NLMSG_HDRLEN
+ msg = nlmsg[msg_indx : msg_indx + IFINFOMSG_SZ] # ifinfomsg
+ _, _, ifi_type, ifi_index, _, _ = struct.unpack('<BBHiII', msg)
+
+ if ifi_type in (ARPHRD_LOOPBACK, ARPHRD_ETHER):
+ rtattr_indx = msg_indx + IFINFOMSG_SZ
+ while rtattr_indx < (nlmsg_idx + nlmsg_len):
+ rtattr = nlmsg[rtattr_indx : rtattr_indx + RTATTR_SZ]
+ rta_len, rta_type = struct.unpack('<HH', rtattr)
+ if rta_type == IFLA_ADDRESS:
+ data = nlmsg[rtattr_indx + RTATTR_SZ : rtattr_indx + rta_len]
+ if _data_matches_mac(data, mac):
+ return socket.if_indextoname(ifi_index)
+
+ rta_len = RTA_ALIGN(rta_len) # Round up to multiple of 4
+ rtattr_indx += rta_len # Move to next rtattr
+
+ nlmsg_idx += nlmsg_len # Move to next Netlink message
+
+ return ''
# ******************************************************************************
@@ -71,8 +152,7 @@ def _data_matches_ip(data_family, data, ip):
return other_ip == ip
-# ******************************************************************************
-def iface_of(src_addr):
+def _iface_of(src_addr): # pylint: disable=too-many-locals
'''@brief Find the interface that has src_addr as one of its assigned IP addresses.
@param src_addr: The IP address to match
@type src_addr: Instance of ipaddress.IPv4Address or ipaddress.IPv6Address
@@ -85,36 +165,133 @@ def iface_of(src_addr):
if nlmsg_idx >= len(nlmsg):
nlmsg += sock.recv(8192)
- nlmsg_type = int.from_bytes(nlmsg[nlmsg_idx + 4 : nlmsg_idx + 6], byteorder='little', signed=False)
+ nlmsghdr = nlmsg[nlmsg_idx : nlmsg_idx + NLMSG_HDRLEN]
+ nlmsg_len, nlmsg_type, _, _, _ = struct.unpack('<LHHLL', nlmsghdr)
+
if nlmsg_type == NLMSG_DONE:
break
- if nlmsg_type != RTM_NEWADDR:
- break
+ if nlmsg_type == RTM_NEWADDR:
+ msg_indx = nlmsg_idx + NLMSG_HDRLEN
+ msg = nlmsg[msg_indx : msg_indx + IFADDRMSG_SZ] # ifaddrmsg
+ ifa_family, _, _, _, ifa_index = struct.unpack('<BBBBL', msg)
+
+ rtattr_indx = msg_indx + IFADDRMSG_SZ
+ while rtattr_indx < (nlmsg_idx + nlmsg_len):
+ rtattr = nlmsg[rtattr_indx : rtattr_indx + RTATTR_SZ]
+ rta_len, rta_type = struct.unpack('<HH', rtattr)
+ if rta_type == IFLA_ADDRESS:
+ data = nlmsg[rtattr_indx + RTATTR_SZ : rtattr_indx + rta_len]
+ if _data_matches_ip(ifa_family, data, src_addr):
+ return socket.if_indextoname(ifa_index)
+
+ rta_len = RTA_ALIGN(rta_len) # Round up to multiple of 4
+ rtattr_indx += rta_len # Move to next rtattr
+
+ nlmsg_idx += nlmsg_len # Move to next Netlink message
+
+ return ''
+
+
+# ******************************************************************************
+def get_ipaddress_obj(ipaddr, ipv4_mapped_convert=False):
+ '''@brief Return a IPv4Address or IPv6Address depending on whether @ipaddr
+ is a valid IPv4 or IPv6 address. Return None otherwise.
+
+ If ipv4_mapped_resolve is set to True, IPv6 addresses that are IPv4-Mapped,
+ will be converted to their IPv4 equivalent.
+ '''
+ try:
+ ip = ipaddress.ip_address(ipaddr)
+ except ValueError:
+ return None
+
+ if ipv4_mapped_convert:
+ ipv4_mapped = getattr(ip, 'ipv4_mapped', None)
+ if ipv4_mapped is not None:
+ ip = ipv4_mapped
+
+ return ip
+
- nlmsg_len = int.from_bytes(nlmsg[nlmsg_idx : nlmsg_idx + 4], byteorder='little', signed=False)
- if nlmsg_len % 4: # Is msg length not a multiple of 4?
+# ******************************************************************************
+def net_if_addrs(): # pylint: disable=too-many-locals
+ '''@brief Return a dictionary listing every IP addresses for each interface.
+ The first IP address of a list is the primary address used as the default
+ source address.
+ @example: {
+ 'wlp0s20f3': {
+ 4: ['10.0.0.28'],
+ 6: [
+ 'fd5e:9a9e:c5bd:0:5509:890c:1848:3843',
+ 'fd5e:9a9e:c5bd:0:1fd5:e527:8df7:7912',
+ '2605:59c8:6128:fb00:c083:1b8:c467:81d2',
+ '2605:59c8:6128:fb00:e99d:1a02:38e0:ad52',
+ 'fe80::d71b:e807:d5ee:7614'
+ ],
+ },
+ 'lo': {
+ 4: ['127.0.0.1'],
+ 6: ['::1'],
+ },
+ 'docker0': {
+ 4: ['172.17.0.1'],
+ 6: []
+ },
+ }
+ '''
+ interfaces = {}
+ with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW) as sock:
+ sock.sendall(GETADDRCMD)
+ nlmsg = sock.recv(8192)
+ nlmsg_idx = 0
+ while True: # pylint: disable=too-many-nested-blocks
+ if nlmsg_idx >= len(nlmsg):
+ nlmsg += sock.recv(8192)
+
+ nlmsghdr = nlmsg[nlmsg_idx : nlmsg_idx + NLMSG_HDRLEN]
+ nlmsg_len, nlmsg_type, _, _, _ = struct.unpack('<LHHLL', nlmsghdr)
+
+ if nlmsg_type == NLMSG_DONE:
break
- ifaddrmsg_indx = nlmsg_idx + NLMSGHDR_SZ
- ifa_family = nlmsg[ifaddrmsg_indx]
- ifa_index = int.from_bytes(nlmsg[ifaddrmsg_indx + 4 : ifaddrmsg_indx + 8], byteorder='little', signed=False)
+ if nlmsg_type == RTM_NEWADDR:
+ msg_indx = nlmsg_idx + NLMSG_HDRLEN
+ msg = nlmsg[msg_indx : msg_indx + IFADDRMSG_SZ] # ifaddrmsg
+ ifa_family, _, _, _, ifa_index = struct.unpack('<BBBBL', msg)
+
+ if ifa_family in (socket.AF_INET, socket.AF_INET6):
+ interfaces.setdefault(ifa_index, {4: [], 6: []})
+
+ rtattr_indx = msg_indx + IFADDRMSG_SZ
+ while rtattr_indx < (nlmsg_idx + nlmsg_len):
+ rtattr = nlmsg[rtattr_indx : rtattr_indx + RTATTR_SZ]
+ rta_len, rta_type = struct.unpack('<HH', rtattr)
- rtattr_indx = ifaddrmsg_indx + IFADDRMSG_SZ
- while rtattr_indx < (nlmsg_idx + nlmsg_len):
- rta_len = int.from_bytes(nlmsg[rtattr_indx : rtattr_indx + 2], byteorder='little', signed=False)
- rta_type = int.from_bytes(nlmsg[rtattr_indx + 2 : rtattr_indx + 4], byteorder='little', signed=False)
- if rta_type == IFLA_ADDRESS:
- data = nlmsg[rtattr_indx + RTATTR_SZ : rtattr_indx + rta_len]
- if _data_matches_ip(ifa_family, data, src_addr):
- return socket.if_indextoname(ifa_index)
+ if rta_type == IFLA_IFNAME:
+ data = nlmsg[rtattr_indx + RTATTR_SZ : rtattr_indx + rta_len]
+ ifname = data.rstrip(b'\0').decode()
+ interfaces[ifa_index]['name'] = ifname
- rta_len = (rta_len + 3) & ~3 # Round up to multiple of 4
- rtattr_indx += rta_len # Move to next rtattr
+ elif rta_type == IFLA_ADDRESS:
+ data = nlmsg[rtattr_indx + RTATTR_SZ : rtattr_indx + rta_len]
+ ip = get_ipaddress_obj(data)
+ if ip:
+ family = 4 if ifa_family == socket.AF_INET else 6
+ interfaces[ifa_index][family].append(ip)
+
+ rta_len = RTA_ALIGN(rta_len) # Round up to multiple of 4
+ rtattr_indx += rta_len # Move to next rtattr
nlmsg_idx += nlmsg_len # Move to next Netlink message
- return ''
+ if_addrs = {}
+ for value in interfaces.values():
+ name = value.pop('name', None)
+ if name is not None:
+ if_addrs[name] = value
+
+ return if_addrs
# ******************************************************************************
@@ -128,42 +305,4 @@ def get_interface(src_addr):
src_addr = src_addr.split('%')[0] # remove scope-id (if any)
src_addr = get_ipaddress_obj(src_addr)
- return '' if src_addr is None else iface_of(src_addr)
-
-
-# ******************************************************************************
-def remove_invalid_addresses(controllers: list):
- '''@brief Remove controllers with invalid addresses from the list of controllers.
- @param controllers: List of TIDs
- '''
- service_conf = conf.SvcConf()
- valid_controllers = list()
- for controller in controllers:
- if controller.transport in ('tcp', 'rdma'):
- # Let's make sure that traddr is
- # syntactically a valid IPv4 or IPv6 address.
- ip = get_ipaddress_obj(controller.traddr)
- if ip is None:
- logging.warning('%s IP address is not valid', controller)
- continue
-
- # Let's make sure the address family is enabled.
- if ip.version not in service_conf.ip_family:
- logging.debug(
- '%s ignored because IPv%s is disabled in %s',
- controller,
- ip.version,
- service_conf.conf_file,
- )
- continue
-
- valid_controllers.append(controller)
-
- elif controller.transport in ('fc', 'loop'):
- # At some point, need to validate FC addresses as well...
- valid_controllers.append(controller)
-
- else:
- logging.warning('Invalid transport %s', controller.transport)
-
- return valid_controllers
+ return '' if src_addr is None else _iface_of(src_addr)