From 89d6cd1a3a4da117019e2ac617514cf940dd7528 Mon Sep 17 00:00:00 2001 From: twangboy Date: Mon, 28 Jan 2019 14:57:07 -0700 Subject: [PATCH 1/5] Use .NET to gather NIC information on newer systems --- salt/utils/network.py | 46 +----- salt/utils/win_network.py | 294 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+), 43 deletions(-) create mode 100644 salt/utils/win_network.py diff --git a/salt/utils/network.py b/salt/utils/network.py index d749339f8f8c..49218d2aa6ed 100644 --- a/salt/utils/network.py +++ b/salt/utils/network.py @@ -19,10 +19,9 @@ # Import 3rd-party libs from salt.ext import six from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin -# Attempt to import wmi +# Attempt to import win_network try: - import wmi - import salt.utils.winapi + import salt.utils.win_network except ImportError: pass @@ -1006,46 +1005,7 @@ def win_interfaces(): ''' Obtain interface information for Windows systems ''' - with salt.utils.winapi.Com(): - c = wmi.WMI() - ifaces = {} - for iface in c.Win32_NetworkAdapterConfiguration(IPEnabled=1): - ifaces[iface.Description] = dict() - if iface.MACAddress: - ifaces[iface.Description]['hwaddr'] = iface.MACAddress - if iface.IPEnabled: - ifaces[iface.Description]['up'] = True - for ip in iface.IPAddress: - if '.' in ip: - if 'inet' not in ifaces[iface.Description]: - ifaces[iface.Description]['inet'] = [] - item = {'address': ip, - 'label': iface.Description} - if iface.DefaultIPGateway: - broadcast = next((i for i in iface.DefaultIPGateway if '.' in i), '') - if broadcast: - item['broadcast'] = broadcast - if iface.IPSubnet: - netmask = next((i for i in iface.IPSubnet if '.' in i), '') - if netmask: - item['netmask'] = netmask - ifaces[iface.Description]['inet'].append(item) - if ':' in ip: - if 'inet6' not in ifaces[iface.Description]: - ifaces[iface.Description]['inet6'] = [] - item = {'address': ip} - if iface.DefaultIPGateway: - broadcast = next((i for i in iface.DefaultIPGateway if ':' in i), '') - if broadcast: - item['broadcast'] = broadcast - if iface.IPSubnet: - netmask = next((i for i in iface.IPSubnet if ':' in i), '') - if netmask: - item['netmask'] = netmask - ifaces[iface.Description]['inet6'].append(item) - else: - ifaces[iface.Description]['up'] = False - return ifaces + return salt.utils.win_network.get_interface_info() def interfaces(): diff --git a/salt/utils/win_network.py b/salt/utils/win_network.py new file mode 100644 index 000000000000..f907e4ebd780 --- /dev/null +++ b/salt/utils/win_network.py @@ -0,0 +1,294 @@ +''' +This salt util uses WMI to gather network information on Windows 7 and .NET 4.0+ +on newer systems. + +The reason for this is that calls to WMI tend to be slower. Especially if the +query has not been optimized. For example, timing to gather NIC info from WMI +and .NET were as follows in testing: + +WMI: 3.4169998168945312 seconds +NET: 1.0390000343322754 seconds + +Since this is used to generate grain information we want to avoid using WMI as +much as possible. + +There are 3 functions in this salt util. +- get_interface_info_dot_net +- get_interface_info_wmi +- get_interface_info + +The ``get_interface_info`` function will call one of the other two functions +depending on the the version of Windows this is run on. Once support for Windows +7 is dropped we can remove the WMI stuff and just use .NET. + +:depends: - pythonnet + - wmi +''' +# https://docs.microsoft.com/en-us/dotnet/api/system.net.networkinformation.networkinterface.getallnetworkinterfaces?view=netframework-4.7.2 +# Import python libs +from __future__ import absolute_import, unicode_literals, print_function +import ipaddress +import platform +from distutils.version import StrictVersion + +IS_WINDOWS = platform.system() == 'Windows' +USE_WMI = StrictVersion(platform.version()) < StrictVersion('6.2') + +__virtualname__ = 'win_network' + +if IS_WINDOWS: + if USE_WMI: + import wmi + import salt.utils.winapi + else: + import clr + from System.Net import NetworkInformation + + enum_adapter_types = { + 1: 'Unknown', + 6: 'Ethernet', + 9: 'TokenRing', + 15: 'FDDI', + 20: 'BasicISDN', + 21: 'PrimaryISDN', + 23: 'PPP', + 24: 'Loopback', + 26: 'Ethernet3Megabit', + 28: 'Slip', + 37: 'ATM', + 48: 'GenericModem', + 62: 'FastEthernetT', + 63: 'ISDN', + 69: 'FastEthernetFx', + 71: 'Wireless802.11', + 94: 'AsymmetricDSL', + 95: 'RateAdaptDSL', + 96: 'SymmetricDSL', + 97: 'VeryHighSpeedDSL', + 114: 'IPOverATM', + 117: 'GigabitEthernet', + 131: 'Tunnel', + 143: 'MultiRateSymmetricDSL', + 144: 'HighPerformanceSerialBus', + 237: 'WMAN', + 243: 'WWANPP', + 244: 'WWANPP2'} + + enum_operational_status = { + 1: 'Up', + 2: 'Down', + 3: 'Testing', + 4: 'Unknown', + 5: 'Dormant', + 6: 'NotPresent', + 7: 'LayerDown'} + + enum_prefix_suffix = { + 0: 'Other', + 1: 'Manual', + 2: 'WellKnown', + 3: 'DHCP', + 4: 'Router', + 5: 'Random'} + + af_inet = 2 + af_inet6 = 23 + + +def __virtual__(): + ''' + Only load if windows + ''' + if not IS_WINDOWS: + return False, 'This utility will only run on Windows' + + return __virtualname__ + + +def get_interface_info_dot_net(): + ''' + Uses .NET 4.0+ to gather Network Interface information. Should only run on + Windows systems newer than Windows 7/Server 2008R2 + + Returns: + dict: A dictionary of information about all interfaces on the system + ''' + clr.AddReference('System.Net') + interfaces = NetworkInformation.NetworkInterface.GetAllNetworkInterfaces() + + int_dict = {} + for i_face in interfaces: + raw_mac = i_face.GetPhysicalAddress().ToString() + int_dict[i_face.Name] = { + 'alias': i_face.Name, + 'description': i_face.Description, + 'id': i_face.Id, + 'receive_only': i_face.IsReceiveOnly, + 'type': enum_adapter_types[i_face.NetworkInterfaceType], + 'status': enum_operational_status[i_face.OperationalStatus], + 'physical_address': ':'.join(raw_mac[i:i+2] for i in range(0, 12, 2))} + + ip_properties = i_face.GetIPProperties() + int_dict[i_face.Name].update({ + 'dns_suffix': ip_properties.DnsSuffix, + 'dns_enabled': ip_properties.IsDnsEnabled, + 'dynamic_dns_enabled': ip_properties.IsDynamicDnsEnabled + }) + + if ip_properties.UnicastAddresses.Count > 0: + names = {af_inet: 'ip_addresses', + af_inet6: 'ipv6_addresses'} + for addrs in ip_properties.UnicastAddresses: + if addrs.Address.AddressFamily == af_inet: + ip = addrs.Address.ToString() + mask = addrs.IPv4Mask.ToString() + net = ipaddress.IPv4Network(ip + '/' + mask, False) + ip_info = { + 'address': ip, + 'netmask': mask, + 'broadcast': net.broadcast_address.compressed, + 'loopback': addrs.Address.Loopback.ToString()} + else: + ip_info = { + 'address': addrs.Address.ToString().split('%')[0], + # ScopeID is a suffix affixed to the end of an IPv6 + # address it denotes the adapter. This is different from + # ScopeLevel. Need to figure out how to get ScopeLevel + # for feature parity with Linux + 'interface_index': int(addrs.Address.ScopeId)} + ip_info.update({ + 'prefix_length': addrs.PrefixLength, + 'prefix_origin': enum_prefix_suffix[addrs.PrefixOrigin], + 'suffix_origin': enum_prefix_suffix[addrs.SuffixOrigin]}) + int_dict[i_face.Name].setdefault( + names[addrs.Address.AddressFamily], []).append(ip_info) + + if ip_properties.GatewayAddresses.Count > 0: + names = {af_inet: 'ip_gateways', + af_inet6: 'ipv6_gateways'} + for addrs in ip_properties.GatewayAddresses: + int_dict[i_face.Name].setdefault( + names[addrs.Address.AddressFamily], + []).append(addrs.Address.ToString()) + + if ip_properties.DnsAddresses.Count > 0: + names = {af_inet: 'ip_dns', + af_inet6: 'ipv6_dns'} + for addrs in ip_properties.DnsAddresses: + int_dict[i_face.Name].setdefault( + names[addrs.AddressFamily], []).append(addrs.ToString()) + + if ip_properties.MulticastAddresses.Count > 0: + names = {af_inet: 'ip_multicast', + af_inet6: 'ipv6_multicast'} + for addrs in ip_properties.MulticastAddresses: + int_dict[i_face.Name].setdefault( + names[addrs.Address.AddressFamily], + []).append(addrs.Address.ToString()) + + if ip_properties.AnycastAddresses.Count > 0: + names = {af_inet: 'ip_anycast', + af_inet6: 'ipv6_anycast'} + for addrs in ip_properties.AnycastAddresses: + int_dict[i_face.Name].setdefault( + names[addrs.Address.AddressFamily], + []).append(addrs.Address.ToString()) + + if ip_properties.WinsServersAddresses.Count > 0: + for addrs in ip_properties.WinsServersAddresses: + int_dict[i_face.Name].setdefault( + 'ip_wins', []).append(addrs.ToString()) + + return int_dict + + +def get_interface_info_wmi(): + ''' + Uses WMI to gather Network Interface information. Should only run on + Windows 7/2008 R2 and lower systems. This code was pulled from the + ``win_interfaces`` function in ``salt.utils.network`` unchanged. + + Returns: + dict: A dictionary of information about all interfaces on the system + ''' + with salt.utils.winapi.Com(): + c = wmi.WMI() + ifaces = {} + for iface in c.Win32_NetworkAdapterConfiguration(IPEnabled=1): + ifaces[iface.Description] = dict() + if iface.MACAddress: + ifaces[iface.Description]['hwaddr'] = iface.MACAddress + if iface.IPEnabled: + ifaces[iface.Description]['up'] = True + for ip in iface.IPAddress: + if '.' in ip: + if 'inet' not in ifaces[iface.Description]: + ifaces[iface.Description]['inet'] = [] + item = {'address': ip, + 'label': iface.Description} + if iface.DefaultIPGateway: + broadcast = next((i for i in iface.DefaultIPGateway if '.' in i), '') + if broadcast: + item['broadcast'] = broadcast + if iface.IPSubnet: + netmask = next((i for i in iface.IPSubnet if '.' in i), '') + if netmask: + item['netmask'] = netmask + ifaces[iface.Description]['inet'].append(item) + if ':' in ip: + if 'inet6' not in ifaces[iface.Description]: + ifaces[iface.Description]['inet6'] = [] + item = {'address': ip} + if iface.DefaultIPGateway: + broadcast = next((i for i in iface.DefaultIPGateway if ':' in i), '') + if broadcast: + item['broadcast'] = broadcast + if iface.IPSubnet: + netmask = next((i for i in iface.IPSubnet if ':' in i), '') + if netmask: + item['netmask'] = netmask + ifaces[iface.Description]['inet6'].append(item) + else: + ifaces[iface.Description]['up'] = False + return ifaces + + +def get_interface_info(): + ''' + This function will return network interface information for the system and + will use the best method to retrieve that information. Windows 7/2008R2 and + below will use WMI. Newer systems will use .NET. + + Returns: + dict: A dictionary of information about the Network interfaces + ''' + # On Windows 7 machines, use WMI as dotnet 4.0 is not available by default + if USE_WMI: + return get_interface_info_wmi() + + # Massage the data returned by dotnet to mirror that returned by wmi + interfaces = get_interface_info_dot_net() + ifaces = dict() + for iface in interfaces: + if interfaces[iface]['status'] == 'Up': + name = interfaces[iface]['description'] + ifaces.setdefault(name, {}).update({ + 'hwaddr': interfaces[iface]['physical_address'], + 'up': True + }) + for ip in interfaces[iface].get('ip_addresses', []): + ifaces[name].setdefault('inet', []).append({ + 'address': ip['address'], + 'broadcast': ip['broadcast'], + 'netmask': ip['netmask'], + 'gateway': interfaces[iface].get('ip_gateways', [''])[0], + 'label': name + }) + for ip in interfaces[iface].get('ipv6_addresses', []): + ifaces[name].setdefault('inet6', []).append({ + 'address': ip['address'], + 'gateway': interfaces[iface].get('ipv6_gateways', [''])[0], + # Add prefix length + }) + + return ifaces From 9600a1b3eb38043fae1b1f17c5247481a84a8489 Mon Sep 17 00:00:00 2001 From: twangboy Date: Tue, 29 Jan 2019 17:38:03 -0700 Subject: [PATCH 2/5] Break some things out to make it easier to test --- salt/utils/win_network.py | 305 +++++++++++++++++++++----------------- 1 file changed, 172 insertions(+), 133 deletions(-) diff --git a/salt/utils/win_network.py b/salt/utils/win_network.py index f907e4ebd780..eb3518d87309 100644 --- a/salt/utils/win_network.py +++ b/salt/utils/win_network.py @@ -44,55 +44,55 @@ import clr from System.Net import NetworkInformation - enum_adapter_types = { - 1: 'Unknown', - 6: 'Ethernet', - 9: 'TokenRing', - 15: 'FDDI', - 20: 'BasicISDN', - 21: 'PrimaryISDN', - 23: 'PPP', - 24: 'Loopback', - 26: 'Ethernet3Megabit', - 28: 'Slip', - 37: 'ATM', - 48: 'GenericModem', - 62: 'FastEthernetT', - 63: 'ISDN', - 69: 'FastEthernetFx', - 71: 'Wireless802.11', - 94: 'AsymmetricDSL', - 95: 'RateAdaptDSL', - 96: 'SymmetricDSL', - 97: 'VeryHighSpeedDSL', - 114: 'IPOverATM', - 117: 'GigabitEthernet', - 131: 'Tunnel', - 143: 'MultiRateSymmetricDSL', - 144: 'HighPerformanceSerialBus', - 237: 'WMAN', - 243: 'WWANPP', - 244: 'WWANPP2'} - - enum_operational_status = { - 1: 'Up', - 2: 'Down', - 3: 'Testing', - 4: 'Unknown', - 5: 'Dormant', - 6: 'NotPresent', - 7: 'LayerDown'} - - enum_prefix_suffix = { - 0: 'Other', - 1: 'Manual', - 2: 'WellKnown', - 3: 'DHCP', - 4: 'Router', - 5: 'Random'} - - af_inet = 2 - af_inet6 = 23 +enum_adapter_types = { + 1: 'Unknown', + 6: 'Ethernet', + 9: 'TokenRing', + 15: 'FDDI', + 20: 'BasicISDN', + 21: 'PrimaryISDN', + 23: 'PPP', + 24: 'Loopback', + 26: 'Ethernet3Megabit', + 28: 'Slip', + 37: 'ATM', + 48: 'GenericModem', + 62: 'FastEthernetT', + 63: 'ISDN', + 69: 'FastEthernetFx', + 71: 'Wireless802.11', + 94: 'AsymmetricDSL', + 95: 'RateAdaptDSL', + 96: 'SymmetricDSL', + 97: 'VeryHighSpeedDSL', + 114: 'IPOverATM', + 117: 'GigabitEthernet', + 131: 'Tunnel', + 143: 'MultiRateSymmetricDSL', + 144: 'HighPerformanceSerialBus', + 237: 'WMAN', + 243: 'WWANPP', + 244: 'WWANPP2'} + +enum_operational_status = { + 1: 'Up', + 2: 'Down', + 3: 'Testing', + 4: 'Unknown', + 5: 'Dormant', + 6: 'NotPresent', + 7: 'LayerDown'} + +enum_prefix_suffix = { + 0: 'Other', + 1: 'Manual', + 2: 'WellKnown', + 3: 'DHCP', + 4: 'Router', + 5: 'Random'} + +af_inet = 2 +af_inet6 = 23 def __virtual__(): @@ -105,6 +105,119 @@ def __virtual__(): return __virtualname__ +def _get_base_properties(i_face): + raw_mac = i_face.GetPhysicalAddress().ToString() + return { + 'alias': i_face.Name, + 'description': i_face.Description, + 'id': i_face.Id, + 'receive_only': i_face.IsReceiveOnly, + 'type': enum_adapter_types[i_face.NetworkInterfaceType], + 'status': enum_operational_status[i_face.OperationalStatus], + 'physical_address': ':'.join(raw_mac[i:i+2] for i in range(0, 12, 2))} + + +def _get_ip_base_properties(i_face): + ip_properties = i_face.GetIPProperties() + return {'dns_suffix': ip_properties.DnsSuffix, + 'dns_enabled': ip_properties.IsDnsEnabled, + 'dynamic_dns_enabled': ip_properties.IsDynamicDnsEnabled} + + +def _get_ip_unicast_info(i_face): + ip_properties = i_face.GetIPProperties() + int_dict = {} + if ip_properties.UnicastAddresses.Count > 0: + names = {af_inet: 'ip_addresses', + af_inet6: 'ipv6_addresses'} + for addrs in ip_properties.UnicastAddresses: + if addrs.Address.AddressFamily == af_inet: + ip = addrs.Address.ToString() + mask = addrs.IPv4Mask.ToString() + net = ipaddress.IPv4Network(ip + '/' + mask, False) + ip_info = { + 'address': ip, + 'netmask': mask, + 'broadcast': net.broadcast_address.compressed, + 'loopback': addrs.Address.Loopback.ToString()} + else: + ip_info = { + 'address': addrs.Address.ToString().split('%')[0], + # ScopeID is a suffix affixed to the end of an IPv6 + # address it denotes the adapter. This is different from + # ScopeLevel. Need to figure out how to get ScopeLevel + # for feature parity with Linux + 'interface_index': int(addrs.Address.ScopeId)} + ip_info.update({ + 'prefix_length': addrs.PrefixLength, + 'prefix_origin': enum_prefix_suffix[addrs.PrefixOrigin], + 'suffix_origin': enum_prefix_suffix[addrs.SuffixOrigin]}) + int_dict.setdefault( + names[addrs.Address.AddressFamily], []).append(ip_info) + return int_dict + + +def _get_ip_gateway_info(i_face): + ip_properties = i_face.GetIPProperties() + int_dict = {} + if ip_properties.GatewayAddresses.Count > 0: + names = {af_inet: 'ip_gateways', + af_inet6: 'ipv6_gateways'} + for addrs in ip_properties.GatewayAddresses: + int_dict.setdefault( + names[addrs.Address.AddressFamily], + []).append(addrs.Address.ToString()) + return int_dict + + +def _get_ip_dns_info(i_face): + ip_properties = i_face.GetIPProperties() + int_dict = {} + if ip_properties.DnsAddresses.Count > 0: + names = {af_inet: 'ip_dns', + af_inet6: 'ipv6_dns'} + for addrs in ip_properties.DnsAddresses: + int_dict.setdefault( + names[addrs.AddressFamily], []).append(addrs.ToString()) + return int_dict + + +def _get_ip_multicast_info(i_face): + ip_properties = i_face.GetIPProperties() + int_dict = {} + if ip_properties.MulticastAddresses.Count > 0: + names = {af_inet: 'ip_multicast', + af_inet6: 'ipv6_multicast'} + for addrs in ip_properties.MulticastAddresses: + int_dict.setdefault( + names[addrs.Address.AddressFamily], + []).append(addrs.Address.ToString()) + return int_dict + + +def _get_ip_anycast_info(i_face): + ip_properties = i_face.GetIPProperties() + int_dict = {} + if ip_properties.AnycastAddresses.Count > 0: + names = {af_inet: 'ip_anycast', + af_inet6: 'ipv6_anycast'} + for addrs in ip_properties.AnycastAddresses: + int_dict.setdefault( + names[addrs.Address.AddressFamily], + []).append(addrs.Address.ToString()) + return int_dict + + +def _get_ip_wins_info(i_face): + ip_properties = i_face.GetIPProperties() + int_dict = {} + if ip_properties.WinsServersAddresses.Count > 0: + for addrs in ip_properties.WinsServersAddresses: + int_dict.setdefault( + 'ip_wins', []).append(addrs.ToString()) + return int_dict + + def get_interface_info_dot_net(): ''' Uses .NET 4.0+ to gather Network Interface information. Should only run on @@ -118,86 +231,14 @@ def get_interface_info_dot_net(): int_dict = {} for i_face in interfaces: - raw_mac = i_face.GetPhysicalAddress().ToString() - int_dict[i_face.Name] = { - 'alias': i_face.Name, - 'description': i_face.Description, - 'id': i_face.Id, - 'receive_only': i_face.IsReceiveOnly, - 'type': enum_adapter_types[i_face.NetworkInterfaceType], - 'status': enum_operational_status[i_face.OperationalStatus], - 'physical_address': ':'.join(raw_mac[i:i+2] for i in range(0, 12, 2))} - - ip_properties = i_face.GetIPProperties() - int_dict[i_face.Name].update({ - 'dns_suffix': ip_properties.DnsSuffix, - 'dns_enabled': ip_properties.IsDnsEnabled, - 'dynamic_dns_enabled': ip_properties.IsDynamicDnsEnabled - }) - - if ip_properties.UnicastAddresses.Count > 0: - names = {af_inet: 'ip_addresses', - af_inet6: 'ipv6_addresses'} - for addrs in ip_properties.UnicastAddresses: - if addrs.Address.AddressFamily == af_inet: - ip = addrs.Address.ToString() - mask = addrs.IPv4Mask.ToString() - net = ipaddress.IPv4Network(ip + '/' + mask, False) - ip_info = { - 'address': ip, - 'netmask': mask, - 'broadcast': net.broadcast_address.compressed, - 'loopback': addrs.Address.Loopback.ToString()} - else: - ip_info = { - 'address': addrs.Address.ToString().split('%')[0], - # ScopeID is a suffix affixed to the end of an IPv6 - # address it denotes the adapter. This is different from - # ScopeLevel. Need to figure out how to get ScopeLevel - # for feature parity with Linux - 'interface_index': int(addrs.Address.ScopeId)} - ip_info.update({ - 'prefix_length': addrs.PrefixLength, - 'prefix_origin': enum_prefix_suffix[addrs.PrefixOrigin], - 'suffix_origin': enum_prefix_suffix[addrs.SuffixOrigin]}) - int_dict[i_face.Name].setdefault( - names[addrs.Address.AddressFamily], []).append(ip_info) - - if ip_properties.GatewayAddresses.Count > 0: - names = {af_inet: 'ip_gateways', - af_inet6: 'ipv6_gateways'} - for addrs in ip_properties.GatewayAddresses: - int_dict[i_face.Name].setdefault( - names[addrs.Address.AddressFamily], - []).append(addrs.Address.ToString()) - - if ip_properties.DnsAddresses.Count > 0: - names = {af_inet: 'ip_dns', - af_inet6: 'ipv6_dns'} - for addrs in ip_properties.DnsAddresses: - int_dict[i_face.Name].setdefault( - names[addrs.AddressFamily], []).append(addrs.ToString()) - - if ip_properties.MulticastAddresses.Count > 0: - names = {af_inet: 'ip_multicast', - af_inet6: 'ipv6_multicast'} - for addrs in ip_properties.MulticastAddresses: - int_dict[i_face.Name].setdefault( - names[addrs.Address.AddressFamily], - []).append(addrs.Address.ToString()) - - if ip_properties.AnycastAddresses.Count > 0: - names = {af_inet: 'ip_anycast', - af_inet6: 'ipv6_anycast'} - for addrs in ip_properties.AnycastAddresses: - int_dict[i_face.Name].setdefault( - names[addrs.Address.AddressFamily], - []).append(addrs.Address.ToString()) - - if ip_properties.WinsServersAddresses.Count > 0: - for addrs in ip_properties.WinsServersAddresses: - int_dict[i_face.Name].setdefault( - 'ip_wins', []).append(addrs.ToString()) + int_dict[i_face.Name] = _get_base_properties(i_face) + int_dict[i_face.Name].update(_get_ip_base_properties(i_face)) + int_dict[i_face.Name].update(_get_ip_unicast_info(i_face)) + int_dict[i_face.Name].update(_get_ip_gateway_info(i_face)) + int_dict[i_face.Name].update(_get_ip_dns_info(i_face)) + int_dict[i_face.Name].update(_get_ip_multicast_info(i_face)) + int_dict[i_face.Name].update(_get_ip_anycast_info(i_face)) + int_dict[i_face.Name].update(_get_ip_wins_info(i_face)) return int_dict @@ -274,16 +315,14 @@ def get_interface_info(): name = interfaces[iface]['description'] ifaces.setdefault(name, {}).update({ 'hwaddr': interfaces[iface]['physical_address'], - 'up': True - }) + 'up': True}) for ip in interfaces[iface].get('ip_addresses', []): ifaces[name].setdefault('inet', []).append({ 'address': ip['address'], 'broadcast': ip['broadcast'], 'netmask': ip['netmask'], 'gateway': interfaces[iface].get('ip_gateways', [''])[0], - 'label': name - }) + 'label': name}) for ip in interfaces[iface].get('ipv6_addresses', []): ifaces[name].setdefault('inet6', []).append({ 'address': ip['address'], From d4117549819535e22270f4be405dfce5a9d1402b Mon Sep 17 00:00:00 2001 From: twangboy Date: Wed, 30 Jan 2019 15:18:33 -0700 Subject: [PATCH 3/5] Add tests test_get_network_info tets_get_network_info_dot_net There never were tests for the WMI portion --- salt/utils/win_network.py | 15 ++- tests/unit/utils/test_win_network.py | 174 +++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 tests/unit/utils/test_win_network.py diff --git a/salt/utils/win_network.py b/salt/utils/win_network.py index eb3518d87309..87504d7ca147 100644 --- a/salt/utils/win_network.py +++ b/salt/utils/win_network.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ''' This salt util uses WMI to gather network information on Windows 7 and .NET 4.0+ on newer systems. @@ -166,7 +167,7 @@ def _get_ip_gateway_info(i_face): for addrs in ip_properties.GatewayAddresses: int_dict.setdefault( names[addrs.Address.AddressFamily], - []).append(addrs.Address.ToString()) + []).append(addrs.Address.ToString().split('%')[0]) return int_dict @@ -178,7 +179,8 @@ def _get_ip_dns_info(i_face): af_inet6: 'ipv6_dns'} for addrs in ip_properties.DnsAddresses: int_dict.setdefault( - names[addrs.AddressFamily], []).append(addrs.ToString()) + names[addrs.AddressFamily], + []).append(addrs.ToString().split('%')[0]) return int_dict @@ -191,7 +193,7 @@ def _get_ip_multicast_info(i_face): for addrs in ip_properties.MulticastAddresses: int_dict.setdefault( names[addrs.Address.AddressFamily], - []).append(addrs.Address.ToString()) + []).append(addrs.Address.ToString().split('%')[0]) return int_dict @@ -218,6 +220,11 @@ def _get_ip_wins_info(i_face): return int_dict +def _get_network_interfaces(): + clr.AddReference('System.Net') + return NetworkInformation.NetworkInterface.GetAllNetworkInterfaces() + + def get_interface_info_dot_net(): ''' Uses .NET 4.0+ to gather Network Interface information. Should only run on @@ -227,7 +234,7 @@ def get_interface_info_dot_net(): dict: A dictionary of information about all interfaces on the system ''' clr.AddReference('System.Net') - interfaces = NetworkInformation.NetworkInterface.GetAllNetworkInterfaces() + interfaces = _get_network_interfaces() int_dict = {} for i_face in interfaces: diff --git a/tests/unit/utils/test_win_network.py b/tests/unit/utils/test_win_network.py new file mode 100644 index 000000000000..236a3072c29b --- /dev/null +++ b/tests/unit/utils/test_win_network.py @@ -0,0 +1,174 @@ +# Import Python Libs +from __future__ import absolute_import, unicode_literals, print_function + +# Import Salt Testing Libs +from tests.support.mixins import LoaderModuleMockMixin +from tests.support.mock import NO_MOCK, NO_MOCK_REASON, patch, MagicMock +from tests.support.unit import TestCase, skipIf + +# Import Salt Libs +import salt.utils.platform +import salt.utils.win_network as win_network + +mock_base = MagicMock(return_value={ + 'alias': 'Ethernet', + 'description': 'Dell GigabitEthernet', + 'id': '{C5F468C0-DD5F-4C2B-939F-A411DCB5DE16}', + 'receive_only': False, + 'status': 'Up', + 'type': 'Ethernet', + 'physical_address': '02:D5:F1:DD:31:E0' +}) + +mock_ip_base = MagicMock(return_value={ + 'dns_enabled': False, + 'dns_suffix': '', + 'dynamic_dns_enabled': False, +}) + +mock_unicast = MagicMock(return_value={ + 'ip_addresses': [{ + 'address': '172.18.87.49', + 'broadcast': '172.18.87.63', + 'loopback': '127.0.0.1', + 'netmask': '255.255.255.240', + 'prefix_length': 28, + 'prefix_origin': 'Manual', + 'suffix_origin': 'Manual'}], + 'ipv6_addresses': [{ + 'address': 'fe80::e8a4:1224:5548:2b81', + 'interface_index': 32, + 'prefix_length': 64, + 'prefix_origin': 'WellKnown', + 'suffix_origin': 'Router'}], +}) + +mock_gateway = MagicMock(return_value={ + 'ip_gateways': ['192.168.0.1'], + 'ipv6_gateways': ['fe80::208:a2ff:fe0b:de70'] +}) + +mock_dns = MagicMock(return_value={ + 'ip_dns': ['10.4.0.1', '10.1.0.1', '8.8.8.8'], + 'ipv6_dns': ['2600:740a:1:304::1'] +}) + +mock_multicast = MagicMock(return_value={ + u'ip_multicast': ['224.0.0.1', + '224.0.0.251', + '224.0.0.252', + '230.230.230.230', + '239.0.0.250', + '239.255.255.250'], + 'ipv6_multicast': ['ff01::1', + 'ff02::1', + 'ff02::c', + 'ff02::fb', + 'ff02::1:3', + 'ff02::1:ff0f:4c48', + 'ff02::1:ffa6:f6e6'], +}) + +mock_anycast = MagicMock(return_value={'ip_anycast': [], + 'ipv6_anycast': []}) + +mock_wins = MagicMock(return_value={'ip_wins': []}) + + +class Interface(object): + Name = 'Ethernet' + + +mock_int = MagicMock(return_value=[Interface()]) + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +@skipIf(not salt.utils.platform.is_windows(), 'System is not Windows') +class WinNetworkTestCase(TestCase): + def test_get_interface_info_dot_net(self): + expected ={ + 'Ethernet': { + 'alias': 'Ethernet', + 'description': 'Dell GigabitEthernet', + 'dns_enabled': False, + 'dns_suffix': '', + 'dynamic_dns_enabled': False, + 'id': '{C5F468C0-DD5F-4C2B-939F-A411DCB5DE16}', + 'ip_addresses': [{'address': u'172.18.87.49', + 'broadcast': u'172.18.87.63', + 'loopback': u'127.0.0.1', + 'netmask': u'255.255.255.240', + 'prefix_length': 28, + 'prefix_origin': u'Manual', + 'suffix_origin': u'Manual'}], + 'ip_anycast': [], + 'ip_dns': ['10.4.0.1', '10.1.0.1', '8.8.8.8'], + 'ip_gateways': ['192.168.0.1'], + 'ip_multicast': ['224.0.0.1', + '224.0.0.251', + '224.0.0.252', + '230.230.230.230', + '239.0.0.250', + '239.255.255.250'], + 'ip_wins': [], + 'ipv6_addresses': [{'address': u'fe80::e8a4:1224:5548:2b81', + 'interface_index': 32, + 'prefix_length': 64, + 'prefix_origin': u'WellKnown', + 'suffix_origin': u'Router'}], + 'ipv6_anycast': [], + 'ipv6_dns': ['2600:740a:1:304::1'], + 'ipv6_gateways': ['fe80::208:a2ff:fe0b:de70'], + 'ipv6_multicast': ['ff01::1', + 'ff02::1', + 'ff02::c', + 'ff02::fb', + 'ff02::1:3', + 'ff02::1:ff0f:4c48', + 'ff02::1:ffa6:f6e6'], + 'physical_address': '02:D5:F1:DD:31:E0', + 'receive_only': False, + 'status': 'Up', + 'type': 'Ethernet'}} + + with patch.object(win_network, '_get_network_interfaces', mock_int), \ + patch.object(win_network, '_get_base_properties', mock_base), \ + patch.object(win_network, '_get_ip_base_properties', mock_ip_base), \ + patch.object(win_network, '_get_ip_unicast_info', mock_unicast), \ + patch.object(win_network, '_get_ip_gateway_info', mock_gateway), \ + patch.object(win_network, '_get_ip_dns_info', mock_dns), \ + patch.object(win_network, '_get_ip_multicast_info', mock_multicast), \ + patch.object(win_network, '_get_ip_anycast_info', mock_anycast), \ + patch.object(win_network, '_get_ip_wins_info', mock_wins): + + # ret = win_network._get_base_properties() + results = win_network.get_interface_info_dot_net() + + self.assertDictEqual(expected, results) + + def test_get_network_info(self): + expected = { + 'Dell GigabitEthernet': { + 'hwaddr': '02:D5:F1:DD:31:E0', + 'inet': [{'address': '172.18.87.49', + 'broadcast': '172.18.87.63', + 'gateway': '192.168.0.1', + 'label': 'Dell GigabitEthernet', + 'netmask': '255.255.255.240'}], + 'inet6': [{'address': 'fe80::e8a4:1224:5548:2b81', + 'gateway': 'fe80::208:a2ff:fe0b:de70'}], + 'up': True}} + with patch.object(win_network, '_get_network_interfaces', mock_int), \ + patch.object(win_network, '_get_base_properties', mock_base), \ + patch.object(win_network, '_get_ip_base_properties', mock_ip_base), \ + patch.object(win_network, '_get_ip_unicast_info', mock_unicast), \ + patch.object(win_network, '_get_ip_gateway_info', mock_gateway), \ + patch.object(win_network, '_get_ip_dns_info', mock_dns), \ + patch.object(win_network, '_get_ip_multicast_info', mock_multicast), \ + patch.object(win_network, '_get_ip_anycast_info', mock_anycast), \ + patch.object(win_network, '_get_ip_wins_info', mock_wins): + + # ret = win_network._get_base_properties() + results = win_network.get_interface_info() + + self.assertDictEqual(expected, results) From 07f190ad7c243d3842e435eb79fddac7508f615a Mon Sep 17 00:00:00 2001 From: twangboy Date: Wed, 30 Jan 2019 16:09:06 -0700 Subject: [PATCH 4/5] Fix some lint errors --- salt/utils/win_network.py | 4 +++- tests/unit/utils/test_win_network.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/salt/utils/win_network.py b/salt/utils/win_network.py index 87504d7ca147..69c6e9e13f42 100644 --- a/salt/utils/win_network.py +++ b/salt/utils/win_network.py @@ -30,7 +30,9 @@ from __future__ import absolute_import, unicode_literals, print_function import ipaddress import platform -from distutils.version import StrictVersion + +# Import Salt libs +from salt.utils.versions import StrictVersion IS_WINDOWS = platform.system() == 'Windows' USE_WMI = StrictVersion(platform.version()) < StrictVersion('6.2') diff --git a/tests/unit/utils/test_win_network.py b/tests/unit/utils/test_win_network.py index 236a3072c29b..54efcad4c30d 100644 --- a/tests/unit/utils/test_win_network.py +++ b/tests/unit/utils/test_win_network.py @@ -1,8 +1,8 @@ +# -*- coding: utf-8 -*- # Import Python Libs from __future__ import absolute_import, unicode_literals, print_function # Import Salt Testing Libs -from tests.support.mixins import LoaderModuleMockMixin from tests.support.mock import NO_MOCK, NO_MOCK_REASON, patch, MagicMock from tests.support.unit import TestCase, skipIf @@ -86,7 +86,7 @@ class Interface(object): @skipIf(not salt.utils.platform.is_windows(), 'System is not Windows') class WinNetworkTestCase(TestCase): def test_get_interface_info_dot_net(self): - expected ={ + expected = { 'Ethernet': { 'alias': 'Ethernet', 'description': 'Dell GigabitEthernet', From 702d454f187d2dc0add418d1f2d073d7fb5950e9 Mon Sep 17 00:00:00 2001 From: twangboy Date: Wed, 30 Jan 2019 16:17:22 -0700 Subject: [PATCH 5/5] Add another import for lint I don't know why the linter is asking for this import --- salt/utils/win_network.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/salt/utils/win_network.py b/salt/utils/win_network.py index 69c6e9e13f42..4bb4d46d9377 100644 --- a/salt/utils/win_network.py +++ b/salt/utils/win_network.py @@ -34,6 +34,10 @@ # Import Salt libs from salt.utils.versions import StrictVersion +# Import 3rd party libs +# I don't understand why I need this import, but the linter fails without it +from salt.ext.six.moves import range + IS_WINDOWS = platform.system() == 'Windows' USE_WMI = StrictVersion(platform.version()) < StrictVersion('6.2')