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.
This commit is contained in:
Alessandro Pilotti 2014-07-25 23:48:49 +03:00
parent 6ddd3f093a
commit b96c547857
6 changed files with 483 additions and 22 deletions

View File

@ -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:

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))