1"""
2This salt util uses WMI to gather network information on Windows 7 and .NET 4.0+
3on newer systems.
4The reason for this is that calls to WMI tend to be slower. Especially if the
5query has not been optimized. For example, timing to gather NIC info from WMI
6and .NET were as follows in testing:
7WMI: 3.4169998168945312 seconds
8NET: 1.0390000343322754 seconds
9Since this is used to generate grain information we want to avoid using WMI as
10much as possible.
11There are 3 functions in this salt util.
12- get_interface_info_dot_net
13- get_interface_info_wmi
14- get_interface_info
15The ``get_interface_info`` function will call one of the other two functions
16depending on the version of Windows this is run on. Once support for Windows
177 is dropped we can remove the WMI stuff and just use .NET.
18:depends: - pythonnet
19          - wmi
20"""
21# https://docs.microsoft.com/en-us/dotnet/api/system.net.networkinformation.networkinterface.getallnetworkinterfaces?view=netframework-4.7.2
22
23import platform
24
25from salt._compat import ipaddress
26from salt.utils.versions import StrictVersion
27
28IS_WINDOWS = platform.system() == "Windows"
29
30__virtualname__ = "win_network"
31
32if IS_WINDOWS:
33    USE_WMI = StrictVersion(platform.version()) < StrictVersion("6.2")
34    if USE_WMI:
35        import wmi
36        import salt.utils.winapi
37    else:
38        import clr
39        from System.Net import NetworkInformation
40
41enum_adapter_types = {
42    1: "Unknown",
43    6: "Ethernet",
44    9: "TokenRing",
45    15: "FDDI",
46    20: "BasicISDN",
47    21: "PrimaryISDN",
48    23: "PPP",
49    24: "Loopback",
50    26: "Ethernet3Megabit",
51    28: "Slip",
52    37: "ATM",
53    48: "GenericModem",
54    53: "TAPAdapter",  # Not in MSDN Defined enumeration
55    62: "FastEthernetT",
56    63: "ISDN",
57    69: "FastEthernetFx",
58    71: "Wireless802.11",
59    94: "AsymmetricDSL",
60    95: "RateAdaptDSL",
61    96: "SymmetricDSL",
62    97: "VeryHighSpeedDSL",
63    114: "IPOverATM",
64    117: "GigabitEthernet",
65    131: "Tunnel",
66    143: "MultiRateSymmetricDSL",
67    144: "HighPerformanceSerialBus",
68    237: "WMAN",
69    243: "WWANPP",
70    244: "WWANPP2",
71}
72
73enum_operational_status = {
74    1: "Up",
75    2: "Down",
76    3: "Testing",
77    4: "Unknown",
78    5: "Dormant",
79    6: "NotPresent",
80    7: "LayerDown",
81}
82
83enum_prefix_suffix = {
84    0: "Other",
85    1: "Manual",
86    2: "WellKnown",
87    3: "DHCP",
88    4: "Router",
89    5: "Random",
90}
91
92af_inet = 2
93af_inet6 = 23
94
95
96def __virtual__():
97    """
98    Only load if windows
99    """
100    if not IS_WINDOWS:
101        return False, "This utility will only run on Windows"
102
103    return __virtualname__
104
105
106def _get_base_properties(i_face):
107    raw_mac = i_face.GetPhysicalAddress().ToString()
108    try:
109        i_face_type = enum_adapter_types[i_face.NetworkInterfaceType]
110    except KeyError:
111        i_face_type = i_face.Description
112    return {
113        "alias": i_face.Name,
114        "description": i_face.Description,
115        "id": i_face.Id,
116        "receive_only": i_face.IsReceiveOnly,
117        "type": i_face_type,
118        "status": enum_operational_status[i_face.OperationalStatus],
119        "physical_address": ":".join(raw_mac[i : i + 2] for i in range(0, 12, 2)),
120    }
121
122
123def _get_ip_base_properties(i_face):
124    ip_properties = i_face.GetIPProperties()
125    return {
126        "dns_suffix": ip_properties.DnsSuffix,
127        "dns_enabled": ip_properties.IsDnsEnabled,
128        "dynamic_dns_enabled": ip_properties.IsDynamicDnsEnabled,
129    }
130
131
132def _get_ip_unicast_info(i_face):
133    ip_properties = i_face.GetIPProperties()
134    int_dict = {}
135    if ip_properties.UnicastAddresses.Count > 0:
136        names = {af_inet: "ip_addresses", af_inet6: "ipv6_addresses"}
137        for addrs in ip_properties.UnicastAddresses:
138            if addrs.Address.AddressFamily == af_inet:
139                ip = addrs.Address.ToString()
140                mask = addrs.IPv4Mask.ToString()
141                net = ipaddress.IPv4Network(ip + "/" + mask, False)
142                ip_info = {
143                    "address": ip,
144                    "netmask": mask,
145                    "broadcast": net.broadcast_address.compressed,
146                    "loopback": addrs.Address.Loopback.ToString(),
147                }
148            else:
149                ip_info = {
150                    "address": addrs.Address.ToString().split("%")[0],
151                    # ScopeID is a suffix affixed to the end of an IPv6
152                    # address it denotes the adapter. This is different from
153                    # ScopeLevel. Need to figure out how to get ScopeLevel
154                    # for feature parity with Linux
155                    "interface_index": int(addrs.Address.ScopeId),
156                }
157            ip_info.update(
158                {
159                    "prefix_length": addrs.PrefixLength,
160                    "prefix_origin": enum_prefix_suffix[addrs.PrefixOrigin],
161                    "suffix_origin": enum_prefix_suffix[addrs.SuffixOrigin],
162                }
163            )
164            int_dict.setdefault(names[addrs.Address.AddressFamily], []).append(ip_info)
165    return int_dict
166
167
168def _get_ip_gateway_info(i_face):
169    ip_properties = i_face.GetIPProperties()
170    int_dict = {}
171    if ip_properties.GatewayAddresses.Count > 0:
172        names = {af_inet: "ip_gateways", af_inet6: "ipv6_gateways"}
173        for addrs in ip_properties.GatewayAddresses:
174            int_dict.setdefault(names[addrs.Address.AddressFamily], []).append(
175                addrs.Address.ToString().split("%")[0]
176            )
177    return int_dict
178
179
180def _get_ip_dns_info(i_face):
181    ip_properties = i_face.GetIPProperties()
182    int_dict = {}
183    if ip_properties.DnsAddresses.Count > 0:
184        names = {af_inet: "ip_dns", af_inet6: "ipv6_dns"}
185        for addrs in ip_properties.DnsAddresses:
186            int_dict.setdefault(names[addrs.AddressFamily], []).append(
187                addrs.ToString().split("%")[0]
188            )
189    return int_dict
190
191
192def _get_ip_multicast_info(i_face):
193    ip_properties = i_face.GetIPProperties()
194    int_dict = {}
195    if ip_properties.MulticastAddresses.Count > 0:
196        names = {af_inet: "ip_multicast", af_inet6: "ipv6_multicast"}
197        for addrs in ip_properties.MulticastAddresses:
198            int_dict.setdefault(names[addrs.Address.AddressFamily], []).append(
199                addrs.Address.ToString().split("%")[0]
200            )
201    return int_dict
202
203
204def _get_ip_anycast_info(i_face):
205    ip_properties = i_face.GetIPProperties()
206    int_dict = {}
207    if ip_properties.AnycastAddresses.Count > 0:
208        names = {af_inet: "ip_anycast", af_inet6: "ipv6_anycast"}
209        for addrs in ip_properties.AnycastAddresses:
210            int_dict.setdefault(names[addrs.Address.AddressFamily], []).append(
211                addrs.Address.ToString()
212            )
213    return int_dict
214
215
216def _get_ip_wins_info(i_face):
217    ip_properties = i_face.GetIPProperties()
218    int_dict = {}
219    if ip_properties.WinsServersAddresses.Count > 0:
220        for addrs in ip_properties.WinsServersAddresses:
221            int_dict.setdefault("ip_wins", []).append(addrs.ToString())
222    return int_dict
223
224
225def _get_network_interfaces():
226    clr.AddReference("System.Net")
227    return NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
228
229
230def get_interface_info_dot_net_formatted():
231    """
232    Returns data gathered via ``get_interface_info_dot_net`` and returns the
233    info in the same manner as ``get_interface_info_wmi``
234
235    Returns:
236        dict: A dictionary of information about all interfaces on the system
237    """
238    # Massage the data returned by dotnet to mirror that returned by wmi
239    interfaces = get_interface_info_dot_net()
240    i_faces = {}
241    for i_face in interfaces:
242        if interfaces[i_face]["status"] == "Up":
243            name = interfaces[i_face]["description"]
244            i_faces.setdefault(name, {}).update(
245                {"hwaddr": interfaces[i_face]["physical_address"], "up": True}
246            )
247            for ip in interfaces[i_face].get("ip_addresses", []):
248                i_faces[name].setdefault("inet", []).append(
249                    {
250                        "address": ip["address"],
251                        "broadcast": ip["broadcast"],
252                        "netmask": ip["netmask"],
253                        "gateway": interfaces[i_face].get("ip_gateways", [""])[0],
254                        "label": name,
255                    }
256                )
257            for ip in interfaces[i_face].get("ipv6_addresses", []):
258                i_faces[name].setdefault("inet6", []).append(
259                    {
260                        "address": ip["address"],
261                        "gateway": interfaces[i_face].get("ipv6_gateways", [""])[0],
262                        # Add prefix length
263                    }
264                )
265
266    return i_faces
267
268
269def get_interface_info_dot_net():
270    """
271    Uses .NET 4.0+ to gather Network Interface information. Should only run on
272    Windows systems newer than Windows 7/Server 2008R2
273
274    Returns:
275        dict: A dictionary of information about all interfaces on the system
276    """
277    interfaces = {}
278    for i_face in _get_network_interfaces():
279        temp_dict = _get_base_properties(i_face)
280        temp_dict.update(_get_ip_base_properties(i_face))
281        temp_dict.update(_get_ip_unicast_info(i_face))
282        temp_dict.update(_get_ip_gateway_info(i_face))
283        temp_dict.update(_get_ip_dns_info(i_face))
284        temp_dict.update(_get_ip_multicast_info(i_face))
285        temp_dict.update(_get_ip_anycast_info(i_face))
286        temp_dict.update(_get_ip_wins_info(i_face))
287        interfaces[i_face.Name] = temp_dict
288
289    return interfaces
290
291
292def get_interface_info_wmi():
293    """
294    Uses WMI to gather Network Interface information. Should only run on
295    Windows 7/2008 R2 and lower systems. This code was pulled from the
296    ``win_interfaces`` function in ``salt.utils.network`` unchanged.
297
298    Returns:
299        dict: A dictionary of information about all interfaces on the system
300    """
301    with salt.utils.winapi.Com():
302        c = wmi.WMI()
303        i_faces = {}
304        for i_face in c.Win32_NetworkAdapterConfiguration(IPEnabled=1):
305            i_faces[i_face.Description] = {}
306            if i_face.MACAddress:
307                i_faces[i_face.Description]["hwaddr"] = i_face.MACAddress
308            if i_face.IPEnabled:
309                i_faces[i_face.Description]["up"] = True
310                for ip in i_face.IPAddress:
311                    if "." in ip:
312                        if "inet" not in i_faces[i_face.Description]:
313                            i_faces[i_face.Description]["inet"] = []
314                        item = {"address": ip, "label": i_face.Description}
315                        if i_face.DefaultIPGateway:
316                            broadcast = next(
317                                (i for i in i_face.DefaultIPGateway if "." in i), ""
318                            )
319                            if broadcast:
320                                item["broadcast"] = broadcast
321                        if i_face.IPSubnet:
322                            netmask = next((i for i in i_face.IPSubnet if "." in i), "")
323                            if netmask:
324                                item["netmask"] = netmask
325                        i_faces[i_face.Description]["inet"].append(item)
326                    if ":" in ip:
327                        if "inet6" not in i_faces[i_face.Description]:
328                            i_faces[i_face.Description]["inet6"] = []
329                        item = {"address": ip}
330                        if i_face.DefaultIPGateway:
331                            broadcast = next(
332                                (i for i in i_face.DefaultIPGateway if ":" in i), ""
333                            )
334                            if broadcast:
335                                item["broadcast"] = broadcast
336                        if i_face.IPSubnet:
337                            netmask = next((i for i in i_face.IPSubnet if ":" in i), "")
338                            if netmask:
339                                item["netmask"] = netmask
340                        i_faces[i_face.Description]["inet6"].append(item)
341            else:
342                i_faces[i_face.Description]["up"] = False
343    return i_faces
344
345
346def get_interface_info():
347    """
348    This function will return network interface information for the system and
349    will use the best method to retrieve that information. Windows 7/2008R2 and
350    below will use WMI. Newer systems will use .NET.
351    Returns:
352        dict: A dictionary of information about the Network interfaces
353    """
354    # On Windows 7 machines, use WMI as dotnet 4.0 is not available by default
355    if USE_WMI:
356        return get_interface_info_wmi()
357    else:
358        return get_interface_info_dot_net_formatted()
359