diff mbox series

[RFC] rteval: Replace python-ethtool with inline code

Message ID 20221010140725.125546-1-jkacur@redhat.com
State New
Headers show
Series [RFC] rteval: Replace python-ethtool with inline code | expand

Commit Message

John Kacur Oct. 10, 2022, 2:07 p.m. UTC
This patch adds the file newnet.py which to replace the use of python-ethtool
The information it generates will appear in the summary.xml
You can also test the functionality by running
python rteval/sysinfo/newnet.py

Signed-off-by: John Kacur <jkacur@redhat.com>
---
 rteval/sysinfo/__init__.py |   3 +-
 rteval/sysinfo/network.py  | 117 -------------------
 rteval/sysinfo/newnet.py   | 224 +++++++++++++++++++++++++++++++++++++
 3 files changed, 226 insertions(+), 118 deletions(-)
 delete mode 100644 rteval/sysinfo/network.py
 create mode 100644 rteval/sysinfo/newnet.py

Comments

Kate Stewart Oct. 10, 2022, 2:20 p.m. UTC | #1
On Mon, Oct 10, 2022 at 9:07 AM John Kacur <jkacur@redhat.com> wrote:
>
> This patch adds the file newnet.py which to replace the use of python-ethtool
> The information it generates will appear in the summary.xml
> You can also test the functionality by running
> python rteval/sysinfo/newnet.py
>
> Signed-off-by: John Kacur <jkacur@redhat.com>
> ---
>  rteval/sysinfo/__init__.py |   3 +-
>  rteval/sysinfo/network.py  | 117 -------------------
>  rteval/sysinfo/newnet.py   | 224 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 226 insertions(+), 118 deletions(-)
>  delete mode 100644 rteval/sysinfo/network.py
>  create mode 100644 rteval/sysinfo/newnet.py

> +++ b/rteval/sysinfo/newnet.py
> @@ -0,0 +1,224 @@
> +''' Module to obtain network information for the rteval report '''
> +#
> +#   Copyright 2022 John Kacur <jkacur@redhat.com
> +#
> +

Please add SPDX license identifier to be explicit about the file's licensing.

Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org>
diff mbox series

Patch

diff --git a/rteval/sysinfo/__init__.py b/rteval/sysinfo/__init__.py
index a4359382f006..bb1d00810856 100644
--- a/rteval/sysinfo/__init__.py
+++ b/rteval/sysinfo/__init__.py
@@ -32,7 +32,7 @@  from rteval.sysinfo.services import SystemServices
 from rteval.sysinfo.cputopology import CPUtopology
 from rteval.sysinfo.memory import MemoryInfo
 from rteval.sysinfo.osinfo import OSInfo
-from rteval.sysinfo.network import NetworkInfo
+from rteval.sysinfo.newnet import NetworkInfo
 from rteval.sysinfo.cmdline import cmdlineInfo
 from rteval.sysinfo import dmi
 
@@ -46,6 +46,7 @@  class SystemInfo(KernelInfo, SystemServices, dmi.DMIinfo, CPUtopology,
         CPUtopology.__init__(self)
         OSInfo.__init__(self, logger=logger)
         cmdlineInfo.__init__(self, logger=logger)
+        NetworkInfo.__init__(self, logger=logger)
 
         # Parse initial DMI decoding errors
         dmi.ProcessWarnings()
diff --git a/rteval/sysinfo/network.py b/rteval/sysinfo/network.py
deleted file mode 100644
index ce9989a1240b..000000000000
--- a/rteval/sysinfo/network.py
+++ /dev/null
@@ -1,117 +0,0 @@ 
-# -*- coding: utf-8 -*-
-#
-#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.com>
-#
-#   This program is free software; you can redistribute it and/or modify
-#   it under the terms of the GNU General Public License as published by
-#   the Free Software Foundation; either version 2 of the License, or
-#   (at your option) any later version.
-#
-#   This program is distributed in the hope that it will be useful,
-#   but WITHOUT ANY WARRANTY; without even the implied warranty of
-#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#   GNU General Public License for more details.
-#
-#   You should have received a copy of the GNU General Public License along
-#   with this program; if not, write to the Free Software Foundation, Inc.,
-#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-#   For the avoidance of doubt the "preferred form" of this code is one which
-#   is in an open unpatent encumbered format. Where cryptographic key signing
-#   forms part of the process of creating an executable the information
-#   including keys needed to generate an equivalently functional executable
-#   are deemed to be part of the source code.
-#
-
-import ethtool, libxml2
-
-class NetworkInfo(object):
-    def __init__(self):
-        pass
-
-    def net_GetDefaultGW(self):
-        # Get the interface name for the IPv4 default gw
-        route = open('/proc/net/route')
-        defgw4 = None
-        if route:
-            rl = route.readline()
-            while rl != '' :
-                rl = route.readline()
-                splt = rl.split("\t")
-                # Only catch default route
-                if len(splt) > 2 and splt[2] != '00000000' and splt[1] == '00000000':
-                    defgw4 = splt[0]
-                    break
-            route.close()
-        return (defgw4, None) # IPv6 gw not yet implemented
-
-    def MakeReport(self):
-        ncfg_n = libxml2.newNode("NetworkConfig")
-        (defgw4, defgw6) = self.net_GetDefaultGW()
-
-        # Make an interface tag for each device found
-        if hasattr(ethtool, 'get_interfaces_info'):
-            # Using the newer python-ethtool API (version >= 0.4)
-            for dev in ethtool.get_interfaces_info(ethtool.get_devices()):
-                if dev.device == 'lo':
-                    continue
-
-                intf_n = libxml2.newNode('interface')
-                intf_n.newProp('device', dev.device)
-                intf_n.newProp('hwaddr', dev.mac_address)
-                ncfg_n.addChild(intf_n)
-
-                # Protcol configurations
-                if dev.ipv4_address:
-                    ipv4_n = libxml2.newNode('IPv4')
-                    ipv4_n.newProp('ipaddr', dev.ipv4_address)
-                    ipv4_n.newProp('netmask', str(dev.ipv4_netmask))
-                    ipv4_n.newProp('broadcast', dev.ipv4_broadcast)
-                    ipv4_n.newProp('defaultgw', (defgw4 == dev.device) and '1' or '0')
-                    intf_n.addChild(ipv4_n)
-
-                for ip6 in dev.get_ipv6_addresses():
-                    ipv6_n = libxml2.newNode('IPv6')
-                    ipv6_n.newProp('ipaddr', ip6.address)
-                    ipv6_n.newProp('netmask', str(ip6.netmask))
-                    ipv6_n.newProp('scope', ip6.scope)
-                    intf_n.addChild(ipv6_n)
-
-        else: # Fall back to older python-ethtool API (version < 0.4)
-            ifdevs = ethtool.get_active_devices()
-            ifdevs.remove('lo')
-            ifdevs.sort()
-
-            for dev in ifdevs:
-                intf_n = libxml2.newNode('interface')
-                intf_n.newProp('device', dev.device)
-                intf_n.newProp('hwaddr', dev.mac_address)
-                ncfg_n.addChild(intf_n)
-
-                ipv4_n = libxml2.newNode('IPv4')
-                ipv4_n.newProp('ipaddr', ethtool.get_ipaddr(dev))
-                ipv4_n.newProp('netmask', str(ethtool.get_netmask(dev)))
-                ipv4_n.newProp('defaultgw', (defgw4 == dev) and '1' or '0')
-                intf_n.addChild(ipv4_n)
-
-        return ncfg_n
-
-
-def unit_test(rootdir):
-    import sys
-    try:
-        net = NetworkInfo()
-        doc = libxml2.newDoc('1.0')
-        cfg = net.MakeReport()
-        doc.setRootElement(cfg)
-        doc.saveFormatFileEnc('-', 'UTF-8', 1)
-
-    except Exception as e:
-        import traceback
-        traceback.print_exc(file=sys.stdout)
-        print("** EXCEPTION %s", str(e))
-        return 1
-
-if __name__ == '__main__':
-    unit_test(None)
-
diff --git a/rteval/sysinfo/newnet.py b/rteval/sysinfo/newnet.py
new file mode 100644
index 000000000000..c8763fb68957
--- /dev/null
+++ b/rteval/sysinfo/newnet.py
@@ -0,0 +1,224 @@ 
+''' Module to obtain network information for the rteval report '''
+#
+#   Copyright 2022 John Kacur <jkacur@redhat.com
+#
+
+import os
+import socket
+import ipaddress
+import sys
+import libxml2
+from rteval.Log import Log
+
+def get_active_devices():
+    ''' returns a list of active network devices, similar to ethtool '''
+    ret = []
+
+    for device in socket.if_nameindex():
+        ret.append(device[1])
+
+    return ret
+
+def compress_iv6(addr):
+    ''' inserts colons into an ipv6address and returns it in compressed form '''
+    retaddr = ''
+    # Insert colons into the number
+    for i in range(4,33,4):
+        if i == 32:
+            retaddr += addr[i-4:i]
+        else:
+            retaddr += addr[i-4:i] + ':'
+    addr = ipaddress.IPv6Network(retaddr)
+    retaddr = str(ipaddress.IPv6Address(retaddr))
+    return retaddr
+
+def get_defaultgw():
+    ''' return the ipv4address of the default gateway '''
+    defaultgw = None
+    with open('/proc/net/route') as f:
+        line = f.readline().strip()
+        while len(line) > 0:
+            (iface, dest, gateway, _, _, _, _, _, _, _, _) = line.split()
+            if iface == 'Iface':
+                line = f.readline().strip()
+                continue
+            if dest == '00000000' and gateway != '00000000':
+                addr = int(gateway, base=16)
+                defaultgw = str(ipaddress.IPv4Address(socket.ntohl(addr)))
+                return defaultgw
+            line = f.readline().strip()
+    return defaultgw
+
+class IPv6Addresses():
+    ''' Obtains a list of IPv6 addresses from the proc file system '''
+
+    def __init__(self):
+        self.data = {}
+        IPv6Addresses.load(self)
+
+    def __contains__(self, dev):
+        return dev in self.data
+
+    def __getitem__(self, dev):
+        return self.data.get(dev, None)
+
+    def __iter__(self):
+        return iter(self.data)
+
+    def load(self):
+        '''
+            Called by init to load the self.data dictionary with device keys
+            and a list of ipv6addresses
+        '''
+        MYP = '/proc/net/if_inet6'
+        with open(MYP, 'r') as f:
+            mystr = f.readline().strip()
+            while len(mystr) > 0:
+                ipv6addr , _, _, _, _, intf = mystr.split()
+                ipv6addr = compress_iv6(ipv6addr)
+                if intf == 'lo':
+                    mystr = f.readline().strip()
+                    continue
+                if intf not in self.data:
+                    self.data[intf] = [ipv6addr]
+                else:
+                    self.data[intf].append(ipv6addr)
+                mystr = f.readline().strip()
+
+class IPv4Addresses():
+    ''' Obtains a list of IPv4 addresses from the proc file system '''
+
+    def __init__(self):
+        self.data = {}
+        IPv4Addresses.load(self)
+
+    def __contains__(self, dev):
+        return dev in self.data
+
+    def __getitem__(self, dev):
+        return self.data[dev]
+
+    def __iter__(self):
+        return iter(self.data)
+
+    def load(self):
+        '''
+            Called by init to load the self.data dictionary with
+            device keys, and value consisting of a list of
+            ipv4address, netmask and broadcast address
+        '''
+        MYP = '/proc/net/route'
+        with open(MYP, 'r') as f:
+            mystr = f.readline().strip()
+            while len(mystr) > 0:
+                intf, dest, _, _, _, _, _, mask, _, _, _ = mystr.split()
+                # Skip over the head of the table an the gateway line
+                if intf == "Iface" or dest == '00000000':
+                    mystr = f.readline().strip()
+                    continue
+                d1 = int(dest, base=16)
+                m1 = int(mask, base=16)
+                addr = str(ipaddress.IPv4Address(socket.ntohl(d1)))
+                netmask = str(ipaddress.IPv4Address(socket.ntohl(m1)))
+                addr_with_mask = ipaddress.ip_network(addr + '/' + netmask)
+                broadcast = str(addr_with_mask.broadcast_address)
+                if intf not in self.data:
+                    self.data[intf] = [(addr, netmask, broadcast)]
+                else:
+                    self.data[intf].append((addr, netmask, broadcast))
+                mystr = f.readline().strip()
+
+
+class MacAddresses():
+    ''' Obtains a list of hardware addresses of network devices '''
+
+    def __init__(self):
+        self.mac_address = {}
+        self.path = None
+        MacAddresses.load(self)
+
+    def load(self):
+        '''
+            called by init to load self.mac_address as a dictionary of
+            device keys, and mac or hardware addresses as values
+        '''
+        nics = get_active_devices()
+        for nic in nics:
+            self.path = f'/sys/class/net/{nic}'
+            hwaddr = MacAddresses.set_val(self, 'address')
+            self.mac_address[nic] = hwaddr
+
+    def set_val(self, val):
+        ''' Return the result of reading self.path/val '''
+        val_path = f'{self.path}/{val}'
+        if os.path.exists(val_path):
+            with open(val_path, 'r') as f:
+                return f.readline().strip()
+        return None
+
+    def __contains__(self, dev):
+        return dev in self.mac_address
+
+    def __getitem__(self, dev):
+        return self.mac_address[dev]
+
+    def __iter__(self):
+        return iter(self.mac_address)
+
+class NetworkInfo():
+    ''' Creates an xml report of the network for rteval '''
+
+    def __init__(self, logger):
+        self.defgw4 = get_defaultgw()
+        self.__logger = logger
+
+    def MakeReport(self):
+        ''' Make an xml report for rteval '''
+        ncfg_n = libxml2.newNode("NetworkConfig")
+        defgw4 = self.defgw4
+
+        mads = MacAddresses()
+        for device in mads:
+            if device == 'lo':
+                continue
+            intf_n = libxml2.newNode('interface')
+            intf_n.newProp('device', device)
+            intf_n.newProp('hwaddr', mads[device])
+            ncfg_n.addChild(intf_n)
+
+            ipv4ads = IPv4Addresses()
+            ipv6ads = IPv6Addresses()
+            for dev in ipv4ads:
+                if dev != device:
+                    continue
+                for lelem in ipv4ads[dev]:
+                    ipv4_n = libxml2.newNode('IPv4')
+                    (ipaddr, netmask, broadcast) = lelem
+                    ipv4_n.newProp('ipaddr', ipaddr)
+                    ipv4_n.newProp('netmask', netmask)
+                    ipv4_n.newProp('broadcast', broadcast)
+                    ipv4_n.newProp('defaultgw', (defgw4 == ipaddr) and '1' or '0')
+                    intf_n.addChild(ipv4_n)
+                if ipv6ads[dev]:
+                    for lelem in ipv6ads[dev]:
+                        ipv6_n = libxml2.newNode('IPv6')
+                        ipaddr = lelem
+                        ipv6_n.newProp('ipaddr', ipaddr)
+                        intf_n.addChild(ipv6_n)
+        return ncfg_n
+
+if __name__ == "__main__":
+
+    try:
+        log = Log()
+        log.SetLogVerbosity(Log.DEBUG|Log.INFO)
+        net = NetworkInfo(logger=log)
+        doc = libxml2.newDoc('1.0')
+        cfg = net.MakeReport()
+        doc.setRootElement(cfg)
+        doc.saveFormatFileEnc('-', 'UTF-8', 1)
+
+    except Exception as e:
+        import traceback
+        traceback.print_exc(file=sys.stdout)
+        print(f"** EXCEPTION {str(e)}")