From b96c54785799e223d23addc48d0a8fcb03b61586 Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Fri, 25 Jul 2014 23:48:49 +0300 Subject: [PATCH] Adds cloudbaseinit.utils.windows.network module Replaces WMI queries to obtain DHCP client configuration with Win32 calls in order to execute plugins that require DHCP options in the Specialize pass after Sysprep. --- cloudbaseinit/osutils/windows.py | 24 +-- cloudbaseinit/tests/osutils/test_windows.py | 18 +- cloudbaseinit/utils/windows/iphlpapi.py | 182 ++++++++++++++++++++ cloudbaseinit/utils/windows/kernel32.py | 56 ++++++ cloudbaseinit/utils/windows/network.py | 164 ++++++++++++++++++ cloudbaseinit/utils/windows/ws2_32.py | 61 +++++++ 6 files changed, 483 insertions(+), 22 deletions(-) create mode 100644 cloudbaseinit/utils/windows/iphlpapi.py create mode 100644 cloudbaseinit/utils/windows/kernel32.py create mode 100644 cloudbaseinit/utils/windows/network.py create mode 100644 cloudbaseinit/utils/windows/ws2_32.py diff --git a/cloudbaseinit/osutils/windows.py b/cloudbaseinit/osutils/windows.py index 5b405520..ee1778bf 100644 --- a/cloudbaseinit/osutils/windows.py +++ b/cloudbaseinit/osutils/windows.py @@ -29,6 +29,7 @@ from win32com import client from cloudbaseinit.openstack.common import log as logging from cloudbaseinit.osutils import base +from cloudbaseinit.utils.windows import network LOG = logging.getLogger(__name__) @@ -465,12 +466,10 @@ class WindowsUtils(base.BaseOSUtils): def get_dhcp_hosts_in_use(self): dhcp_hosts = [] - conn = wmi.WMI(moniker='//./root/cimv2') - for net_cfg in conn.Win32_NetworkAdapterConfiguration( - DHCPEnabled=True): - if net_cfg.DHCPServer: - dhcp_hosts.append((str(net_cfg.MACAddress), - str(net_cfg.DHCPServer))) + for net_addr in network.get_adapter_addresses(): + if net_addr["dhcp_enabled"]: + dhcp_hosts.append((net_addr["mac_address"], + net_addr["dhcp_server"])) return dhcp_hosts def set_ntp_client_config(self, ntp_host): @@ -491,15 +490,16 @@ class WindowsUtils(base.BaseOSUtils): raise Exception('Setting the MTU is currently not supported on ' 'Windows XP and Windows Server 2003') - conn = wmi.WMI(moniker='//./root/cimv2') - net_cfg_list = conn.Win32_NetworkAdapterConfiguration( - MACAddress=mac_address) + iface_index_list = [ + net_addr["interface_index"] for net_addr + in network.get_adapter_addresses() + if net_addr["mac_address"] == mac_address] - if not net_cfg_list: + if not iface_index_list: raise Exception('Network interface with MAC address "%s" ' 'not found' % mac_address) else: - net_cfg = net_cfg_list[0] + iface_index = iface_index_list[0] LOG.debug('Setting MTU for interface "%(mac_address)s" with ' 'value "%(mtu)s"' % @@ -509,7 +509,7 @@ class WindowsUtils(base.BaseOSUtils): netsh_path = os.path.join(base_dir, 'netsh.exe') args = [netsh_path, "interface", "ipv4", "set", "subinterface", - str(net_cfg.InterfaceIndex), "mtu=%s" % mtu, + str(iface_index), "mtu=%s" % mtu, "store=persistent"] (out, err, ret_val) = self.execute_process(args, False) if ret_val: diff --git a/cloudbaseinit/tests/osutils/test_windows.py b/cloudbaseinit/tests/osutils/test_windows.py index b5f132d1..5d565aa4 100644 --- a/cloudbaseinit/tests/osutils/test_windows.py +++ b/cloudbaseinit/tests/osutils/test_windows.py @@ -1192,17 +1192,15 @@ class WindowsUtilsTest(unittest.TestCase): def test_execute_powershell_script_system32(self): self._test_execute_powershell_script(ret_val=False) - @mock.patch('wmi.WMI') - def test_get_dhcp_hosts_in_use(self, mock_WMI): - mock_net_cfg = mock.MagicMock() - mock_net_cfg.MACAddress = 'fake mac address' - mock_net_cfg.DHCPServer = 'fake dhcp server' - mock_WMI().Win32_NetworkAdapterConfiguration.return_value = [ - mock_net_cfg] + @mock.patch('cloudbaseinit.utils.windows.network.get_adapter_addresses') + def test_get_dhcp_hosts_in_use(self, mock_get_adapter_addresses): + net_addr = {} + net_addr["mac_address"] = 'fake mac address' + net_addr["dhcp_server"] = 'fake dhcp server' + net_addr["dhcp_enabled"] = True + mock_get_adapter_addresses.return_value = [net_addr] response = self._winutils.get_dhcp_hosts_in_use() - mock_WMI.assert_called_with(moniker='//./root/cimv2') - mock_WMI().Win32_NetworkAdapterConfiguration.assert_called_with( - DHCPEnabled=True) + mock_get_adapter_addresses.assert_called() self.assertEqual(response, [('fake mac address', 'fake dhcp server')]) @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' diff --git a/cloudbaseinit/utils/windows/iphlpapi.py b/cloudbaseinit/utils/windows/iphlpapi.py new file mode 100644 index 00000000..2fd91452 --- /dev/null +++ b/cloudbaseinit/utils/windows/iphlpapi.py @@ -0,0 +1,182 @@ +# Copyright 2014 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import ctypes + +from ctypes import windll +from ctypes import wintypes + +from cloudbaseinit.utils.windows import kernel32 +from cloudbaseinit.utils.windows import ws2_32 + +MAX_ADAPTER_NAME_LENGTH = 256 +MAX_ADAPTER_DESCRIPTION_LENGTH = 128 +MAX_ADAPTER_ADDRESS_LENGTH = 8 + +GAA_FLAG_SKIP_ANYCAST = 2 +GAA_FLAG_SKIP_ANYCAST = 4 + +IP_ADAPTER_DHCP_ENABLED = 4 +IP_ADAPTER_IPV4_ENABLED = 0x80 +IP_ADAPTER_IPV6_ENABLED = 0x0100 + +MAX_DHCPV6_DUID_LENGTH = 130 + +IF_TYPE_ETHERNET_CSMACD = 6 +IF_TYPE_SOFTWARE_LOOPBACK = 24 +IF_TYPE_IEEE80211 = 71 +IF_TYPE_TUNNEL = 131 + +IP_ADAPTER_ADDRESSES_SIZE_2003 = 144 + + +class SOCKET_ADDRESS(ctypes.Structure): + _fields_ = [ + ('lpSockaddr', ctypes.POINTER(ws2_32.SOCKADDR)), + ('iSockaddrLength', wintypes.INT), + ] + + +class IP_ADAPTER_ADDRESSES_Struct1(ctypes.Structure): + _fields_ = [ + ('Length', wintypes.ULONG), + ('IfIndex', wintypes.DWORD), + ] + + +class IP_ADAPTER_ADDRESSES_Union1(ctypes.Union): + _fields_ = [ + ('Alignment', wintypes.ULARGE_INTEGER), + ('Struct1', IP_ADAPTER_ADDRESSES_Struct1), + ] + + +class IP_ADAPTER_UNICAST_ADDRESS(ctypes.Structure): + _fields_ = [ + ('Union1', IP_ADAPTER_ADDRESSES_Union1), + ('Next', wintypes.LPVOID), + ('Address', SOCKET_ADDRESS), + ('PrefixOrigin', wintypes.DWORD), + ('SuffixOrigin', wintypes.DWORD), + ('DadState', wintypes.DWORD), + ('ValidLifetime', wintypes.ULONG), + ('PreferredLifetime', wintypes.ULONG), + ('LeaseLifetime', wintypes.ULONG), + ] + + +class IP_ADAPTER_DNS_SERVER_ADDRESS_Struct1(ctypes.Structure): + _fields_ = [ + ('Length', wintypes.ULONG), + ('Reserved', wintypes.DWORD), + ] + + +class IP_ADAPTER_DNS_SERVER_ADDRESS_Union1(ctypes.Union): + _fields_ = [ + ('Alignment', wintypes.ULARGE_INTEGER), + ('Struct1', IP_ADAPTER_DNS_SERVER_ADDRESS_Struct1), + ] + + +class IP_ADAPTER_DNS_SERVER_ADDRESS(ctypes.Structure): + _fields_ = [ + ('Union1', IP_ADAPTER_DNS_SERVER_ADDRESS_Union1), + ('Next', wintypes.LPVOID), + ('Address', SOCKET_ADDRESS), + ] + + +class IP_ADAPTER_PREFIX_Struct1(ctypes.Structure): + _fields_ = [ + ('Length', wintypes.ULONG), + ('Flags', wintypes.DWORD), + ] + + +class IP_ADAPTER_PREFIX_Union1(ctypes.Union): + _fields_ = [ + ('Alignment', wintypes.ULARGE_INTEGER), + ('Struct1', IP_ADAPTER_PREFIX_Struct1), + ] + + +class IP_ADAPTER_PREFIX(ctypes.Structure): + _fields_ = [ + ('Union1', IP_ADAPTER_PREFIX_Union1), + ('Next', wintypes.LPVOID), + ('Address', SOCKET_ADDRESS), + ('PrefixLength', wintypes.ULONG), + ] + + +class NET_LUID_LH(ctypes.Union): + _fields_ = [ + ('Value', wintypes.ULARGE_INTEGER), + ('Info', wintypes.ULARGE_INTEGER), + ] + + +class IP_ADAPTER_ADDRESSES(ctypes.Structure): + _fields_ = [ + ('Union1', IP_ADAPTER_ADDRESSES_Union1), + ('Next', wintypes.LPVOID), + ('AdapterName', ctypes.c_char_p), + ('FirstUnicastAddress', + ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)), + ('FirstAnycastAddress', + ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)), + ('FirstMulticastAddress', + ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)), + ('FirstDnsServerAddress', + ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)), + ('DnsSuffix', wintypes.LPWSTR), + ('Description', wintypes.LPWSTR), + ('FriendlyName', wintypes.LPWSTR), + ('PhysicalAddress', ctypes.c_ubyte*MAX_ADAPTER_ADDRESS_LENGTH), + ('PhysicalAddressLength', wintypes.DWORD), + ('Flags', wintypes.DWORD), + ('Mtu', wintypes.DWORD), + ('IfType', wintypes.DWORD), + ('OperStatus', wintypes.DWORD), + ('Ipv6IfIndex', wintypes.DWORD), + ('ZoneIndices', wintypes.DWORD*16), + ('FirstPrefix', ctypes.POINTER(IP_ADAPTER_PREFIX)), + # kernel >= 6.0 + ('TransmitLinkSpeed', wintypes.ULARGE_INTEGER), + ('ReceiveLinkSpeed', wintypes.ULARGE_INTEGER), + ('FirstWinsServerAddress', + ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)), + ('FirstGatewayAddress', + ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)), + ('Ipv4Metric', wintypes.ULONG), + ('Ipv6Metric', wintypes.ULONG), + ('Luid', NET_LUID_LH), + ('Dhcpv4Server', SOCKET_ADDRESS), + ('CompartmentId', wintypes.DWORD), + ('NetworkGuid', kernel32.GUID), + ('ConnectionType', wintypes.DWORD), + ('TunnelType', wintypes.DWORD), + ('Dhcpv6Server', SOCKET_ADDRESS), + ('Dhcpv6ClientDuid', ctypes.c_ubyte*MAX_DHCPV6_DUID_LENGTH), + ('Dhcpv6ClientDuidLength', wintypes.ULONG), + ('Dhcpv6Iaid', wintypes.ULONG), + ] + +GetAdaptersAddresses = windll.Iphlpapi.GetAdaptersAddresses +GetAdaptersAddresses.argtypes = [ + wintypes.ULONG, wintypes.ULONG, wintypes.LPVOID, + ctypes.POINTER(IP_ADAPTER_ADDRESSES), + ctypes.POINTER(wintypes.ULONG)] +GetAdaptersAddresses.restype = wintypes.ULONG diff --git a/cloudbaseinit/utils/windows/kernel32.py b/cloudbaseinit/utils/windows/kernel32.py new file mode 100644 index 00000000..976ab033 --- /dev/null +++ b/cloudbaseinit/utils/windows/kernel32.py @@ -0,0 +1,56 @@ +# Copyright 2014 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import ctypes + +from ctypes import windll +from ctypes import wintypes + +ERROR_BUFFER_OVERFLOW = 111 +ERROR_NO_DATA = 232 + + +class GUID(ctypes.Structure): + _fields_ = [ + ("data1", wintypes.DWORD), + ("data2", wintypes.WORD), + ("data3", wintypes.WORD), + ("data4", wintypes.BYTE * 8)] + + def __init__(self, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8): + self.data1 = l + self.data2 = w1 + self.data3 = w2 + self.data4[0] = b1 + self.data4[1] = b2 + self.data4[2] = b3 + self.data4[3] = b4 + self.data4[4] = b5 + self.data4[5] = b6 + self.data4[6] = b7 + self.data4[7] = b8 + + +GetProcessHeap = windll.kernel32.GetProcessHeap +GetProcessHeap.argtypes = [] +GetProcessHeap.restype = wintypes.HANDLE + +HeapAlloc = windll.kernel32.HeapAlloc +# Note: wintypes.ULONG must be replaced with a 64 bit variable on x64 +HeapAlloc.argtypes = [wintypes.HANDLE, wintypes.DWORD, wintypes.ULONG] +HeapAlloc.restype = wintypes.LPVOID + +HeapFree = windll.kernel32.HeapFree +HeapFree.argtypes = [wintypes.HANDLE, wintypes.DWORD, wintypes.LPVOID] +HeapFree.restype = wintypes.BOOL diff --git a/cloudbaseinit/utils/windows/network.py b/cloudbaseinit/utils/windows/network.py new file mode 100644 index 00000000..61851313 --- /dev/null +++ b/cloudbaseinit/utils/windows/network.py @@ -0,0 +1,164 @@ +# Copyright 2014 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import _winreg +import ctypes + +from ctypes import wintypes + +from cloudbaseinit.utils.windows import iphlpapi +from cloudbaseinit.utils.windows import kernel32 +from cloudbaseinit.utils.windows import ws2_32 + + +def _format_mac_address(phys_address, phys_address_len): + mac_address = "" + for i in range(0, phys_address_len): + b = phys_address[i] + if mac_address: + mac_address += ":" + mac_address += "%02X" % b + return mac_address + + +def _socket_addr_to_str(socket_addr): + addr_str_len = wintypes.DWORD(256) + addr_str = ctypes.create_unicode_buffer(256) + + ret_val = ws2_32.WSAAddressToStringW( + socket_addr.lpSockaddr, + socket_addr.iSockaddrLength, + None, addr_str, ctypes.byref(addr_str_len)) + if ret_val: + raise Exception("WSAAddressToStringW failed") + + return addr_str.value + + +def _get_registry_dhcp_server(adapter_name): + with _winreg.OpenKey( + _winreg.HKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Services\\" + + "Tcpip\\Parameters\\Interfaces\\%s" % adapter_name, 0, + _winreg.KEY_READ) as key: + try: + dhcp_server = _winreg.QueryValueEx(key, "DhcpServer")[0] + if dhcp_server == "255.255.255.255": + dhcp_server = None + return dhcp_server + except Exception, ex: + # Not found + if ex.errno != 2: + raise + + +def get_adapter_addresses(): + net_adapters = [] + + size = wintypes.ULONG() + ret_val = iphlpapi.GetAdaptersAddresses( + ws2_32.AF_UNSPEC, + iphlpapi.GAA_FLAG_SKIP_ANYCAST | iphlpapi.GAA_FLAG_SKIP_ANYCAST, + None, None, ctypes.byref(size)) + + if ret_val == kernel32.ERROR_NO_DATA: + return net_adapters + + if ret_val == kernel32.ERROR_BUFFER_OVERFLOW: + proc_heap = kernel32.GetProcessHeap() + p = kernel32.HeapAlloc(proc_heap, 0, size.value) + if not p: + raise Exception("Cannot allocate memory") + + ws2_32.init_wsa() + + try: + p_addr = ctypes.cast(p, ctypes.POINTER( + iphlpapi.IP_ADAPTER_ADDRESSES)) + + ret_val = iphlpapi.GetAdaptersAddresses( + ws2_32.AF_UNSPEC, + iphlpapi.GAA_FLAG_SKIP_ANYCAST | + iphlpapi.GAA_FLAG_SKIP_ANYCAST, + None, p_addr, ctypes.byref(size)) + + if ret_val == kernel32.ERROR_NO_DATA: + return net_adapters + + if ret_val: + raise Exception("GetAdaptersAddresses failed") + + p_curr_addr = p_addr + while p_curr_addr: + curr_addr = p_curr_addr.contents + + xp_data_only = (curr_addr.Union1.Struct1.Length <= + iphlpapi.IP_ADAPTER_ADDRESSES_SIZE_2003) + + mac_address = _format_mac_address( + curr_addr.PhysicalAddress, + curr_addr.PhysicalAddressLength) + + dhcp_enabled = ( + curr_addr.Flags & iphlpapi.IP_ADAPTER_DHCP_ENABLED) != 0 + dhcp_server = None + + if dhcp_enabled: + if not xp_data_only: + if curr_addr.Flags & iphlpapi.IP_ADAPTER_IPV4_ENABLED: + dhcp_addr = curr_addr.Dhcpv4Server + elif (curr_addr.Flags & + iphlpapi.IP_ADAPTER_IPV6_ENABLED): + dhcp_addr = curr_addr.Dhcpv6Server + + dhcp_server = _socket_addr_to_str(dhcp_addr) + else: + dhcp_server = _get_registry_dhcp_server( + curr_addr.AdapterName) + + unicast_addresses = [] + + p_unicast_addr = curr_addr.FirstUnicastAddress + while p_unicast_addr: + unicast_addr = p_unicast_addr.contents + unicast_addresses.append(( + _socket_addr_to_str(unicast_addr.Address), + unicast_addr.Address.lpSockaddr.contents.sa_family)) + p_unicast_addr = ctypes.cast( + unicast_addr.Next, + ctypes.POINTER(iphlpapi.IP_ADAPTER_UNICAST_ADDRESS)) + + net_adapters.append( + { + "interface_index": curr_addr.Union1.Struct1.IfIndex, + "adapter_name": curr_addr.AdapterName, + "friendly_name": curr_addr.FriendlyName, + "description": curr_addr.Description, + "mtu": curr_addr.Mtu, + "mac_address": mac_address, + "dhcp_enabled": dhcp_enabled, + "dhcp_server": dhcp_server, + "interface_type": curr_addr.IfType, + "unicast_addresses": unicast_addresses + }) + + p_curr_addr = ctypes.cast( + curr_addr.Next, ctypes.POINTER( + iphlpapi.IP_ADAPTER_ADDRESSES)) + + finally: + kernel32.HeapFree(proc_heap, 0, p) + ws2_32.WSACleanup() + + return net_adapters diff --git a/cloudbaseinit/utils/windows/ws2_32.py b/cloudbaseinit/utils/windows/ws2_32.py new file mode 100644 index 00000000..dc08f9ff --- /dev/null +++ b/cloudbaseinit/utils/windows/ws2_32.py @@ -0,0 +1,61 @@ +# Copyright 2014 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import ctypes + +from ctypes import windll +from ctypes import wintypes + +AF_UNSPEC = 0 +AF_INET = 2 +AF_INET6 = 23 + +VERSION_2_2 = (2 << 8) + 2 + + +class SOCKADDR(ctypes.Structure): + _fields_ = [ + ('sa_family', wintypes.USHORT), + ('sa_data', ctypes.c_char*14), + ] + + +class WSADATA(ctypes.Structure): + _fields_ = [ + ('opaque_data', wintypes.BYTE*400), + ] + + +WSAGetLastError = windll.Ws2_32.WSAGetLastError +WSAGetLastError.argtypes = [] +WSAGetLastError.restype = wintypes.INT + +WSAStartup = windll.Ws2_32.WSAStartup +WSAStartup.argtypes = [wintypes.WORD, ctypes.POINTER(WSADATA)] +WSAStartup.restype = wintypes.INT + +WSACleanup = windll.Ws2_32.WSACleanup +WSACleanup.argtypes = [] +WSACleanup.restype = wintypes.INT + +WSAAddressToStringW = windll.Ws2_32.WSAAddressToStringW +WSAAddressToStringW.argtypes = [ + ctypes.POINTER(SOCKADDR), wintypes.DWORD, wintypes.LPVOID, + wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD)] +WSAAddressToStringW.restype = wintypes.INT + + +def init_wsa(version=VERSION_2_2): + wsadata = WSADATA() + WSAStartup(version, ctypes.byref(wsadata))