diff --git a/defaults/main.yml b/defaults/main.yml index d3977cb..6341a24 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -34,7 +34,7 @@ _network_packages_default_gobject_packages: ["python{{ ansible_python['version']['major'] | replace('2', '')}}-gobject-base"] network_service_name_default_nm: NetworkManager -network_packages_default_nm: "{{ ['ethtool', 'NetworkManager'] +network_packages_default_nm: "{{['NetworkManager'] + _network_packages_default_gobject_packages|select()|list() + _network_packages_default_802_1x|select()|list()}}" @@ -56,8 +56,8 @@ if ansible_distribution in ['RedHat', 'CentOS', 'OracleLinux'] and # |select() filters the list to include only values that evaluate to true # (the empty string is false) # |list() converts the generator that |select() creates to a list -network_packages_default_initscripts: "{{ ['ethtool'] -+ _network_packages_default_initscripts_bridge|select()|list() +network_packages_default_initscripts: "{{ +_network_packages_default_initscripts_bridge|select()|list() + _network_packages_default_initscripts_network_scripts|select()|list() }}" diff --git a/library/network_connections.py b/library/network_connections.py index af36df4..2ff210a 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -13,19 +13,21 @@ import traceback # pylint: disable=import-error, no-name-in-module from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network_lsr import ethtool from ansible.module_utils.network_lsr import MyError -# pylint: disable=import-error from ansible.module_utils.network_lsr.argument_validator import ( ArgUtil, ArgValidator_ListConnections, ValidationError, ) -# pylint: disable=import-error from ansible.module_utils.network_lsr.utils import Util from ansible.module_utils.network_lsr import nm_provider +# pylint: enable=import-error, no-name-in-module + + DOCUMENTATION = """ --- module: network_connections @@ -107,15 +109,7 @@ class SysUtil: @staticmethod def _link_read_permaddress(ifname): - try: - out = Util.check_output(["ethtool", "-P", ifname]) - except MyError: - return None - - m = re.match("^Permanent address: ([0-9A-Fa-f:]*)\n$", out) - if not m: - return None - return Util.mac_norm(m.group(1)) + return ethtool.get_perm_addr(ifname) @staticmethod def _link_infos_fetch(): diff --git a/module_utils/network_lsr/ethtool.py b/module_utils/network_lsr/ethtool.py new file mode 100644 index 0000000..21e2152 --- /dev/null +++ b/module_utils/network_lsr/ethtool.py @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: BSD-3-Clause + +import array +import struct +import fcntl +import socket + +from .utils import Util + +ETHTOOL_GPERMADDR = 0x00000020 +SIOCETHTOOL = 0x8946 +MAX_ADDR_LEN = 32 +IFNAMESIZ = 16 + + +def get_perm_addr(ifname): + """ + Return the Permanent address value for the specified interface using the + ETHTOOL_GPERMADDR ioctl command. + + Please for further documentation, see: + https://github.com/torvalds/linux/blob/master/include/uapi/linux/ethtool.h#L734 + https://github.com/torvalds/linux/blob/master/include/uapi/linux/ethtool.h#L1388 + https://git.kernel.org/pub/scm/network/ethtool/ethtool.git/tree/ethtool.c#n4172 + """ + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sockfd = sock.fileno() + ifname = ifname.encode("utf-8") + if len(ifname) > IFNAMESIZ: + return None + + ecmd = array.array( + "B", + struct.pack( + "II%is" % MAX_ADDR_LEN, + ETHTOOL_GPERMADDR, + MAX_ADDR_LEN, + b"\x00" * MAX_ADDR_LEN, + ), + ) + ifreq = struct.pack("%isP" % IFNAMESIZ, ifname, ecmd.buffer_info()[0]) + + fcntl.ioctl(sockfd, SIOCETHTOOL, ifreq) + try: + res = ecmd.tobytes() + except AttributeError: # tobytes() is not available in python2 + res = ecmd.tostring() + _, size, perm_addr = struct.unpack("II%is" % MAX_ADDR_LEN, res) + perm_addr = Util.mac_ntoa(perm_addr[:size]) + except IOError: + perm_addr = None + finally: + sock.close() + + return perm_addr diff --git a/module_utils/network_lsr/utils.py b/module_utils/network_lsr/utils.py index 0bdbfd6..10e8380 100644 --- a/module_utils/network_lsr/utils.py +++ b/module_utils/network_lsr/utils.py @@ -2,9 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause # vim: fileencoding=utf8 -import os import socket -import subprocess import sys import uuid @@ -25,19 +23,6 @@ class Util: return v return default - @staticmethod - def check_output(argv): - # subprocess.check_output is python 2.7. - with open("/dev/null", "wb") as DEVNULL: - env = os.environ.copy() - env["LANG"] = "C" - p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=DEVNULL, env=env) - # FIXME: Can we assume this to always be UTF-8? - out = p.communicate()[0].decode("UTF-8") - if p.returncode != 0: - raise MyError("failure calling %s: exit with %s" % (argv, p.returncode)) - return out - @staticmethod def path_to_glib_bytes(path): """ @@ -275,7 +260,8 @@ class Util: def mac_ntoa(mac): if mac is None: return None - return ":".join(["%02x" % c for c in mac]) + # bytearray() is needed for python2 compatibility + return ":".join(["%02x" % c for c in bytearray(mac)]) @staticmethod def mac_norm(mac_str, force_len=None): diff --git a/tests/unit/helpers/ethtool b/tests/unit/helpers/ethtool deleted file mode 100755 index 874561f..0000000 --- a/tests/unit/helpers/ethtool +++ /dev/null @@ -1,6 +0,0 @@ -#! /bin/bash - -if [ "${1}" == "-P" ] && [ "${2}" != "" ] -then - echo "Permanent address: 23:00:00:00:00:00" -fi diff --git a/tests/unit/test_network_connections.py b/tests/unit/test_network_connections.py index b735ad1..939f4f2 100755 --- a/tests/unit/test_network_connections.py +++ b/tests/unit/test_network_connections.py @@ -2503,10 +2503,9 @@ class TestNM(unittest.TestCase): class TestUtils(unittest.TestCase): - def test_check_output(self): - res = Util.check_output(["echo", "test"]) - self.assertEqual(res, "test\n") - self.assertRaises(n.MyError, Util.check_output, ["false"]) + def test_mac_ntoa(self): + mac_bytes = b"\xaa\xbb\xcc\xdd\xee\xff" + self.assertEqual(Util.mac_ntoa(mac_bytes), "aa:bb:cc:dd:ee:ff") def test_convert_passwd_flags_nm(self): test_cases = [ @@ -2528,10 +2527,9 @@ class TestUtils(unittest.TestCase): class TestSysUtils(unittest.TestCase): def test_link_read_permaddress(self): - # Manipulate PATH to use ethtool mock script to avoid hard dependency on - # ethtool - os.environ["PATH"] = TESTS_BASEDIR + "/helpers:" + os.environ["PATH"] - self.assertEqual(SysUtil._link_read_permaddress("lo"), "23:00:00:00:00:00") + self.assertEqual(SysUtil._link_read_permaddress("lo"), "00:00:00:00:00:00") + self.assertEqual(SysUtil._link_read_permaddress("fakeiface"), None) + self.assertEqual(SysUtil._link_read_permaddress("morethansixteenchars"), None) if __name__ == "__main__":