1""" 2The networking module for Windows based systems 3""" 4 5import logging 6import time 7 8import salt.utils.network 9import salt.utils.platform 10import salt.utils.validate.net 11from salt.exceptions import CommandExecutionError, SaltInvocationError 12 13# Set up logging 14log = logging.getLogger(__name__) 15 16# Define the module's virtual name 17__virtualname__ = "ip" 18 19 20def __virtual__(): 21 """ 22 Confine this module to Windows systems 23 """ 24 if salt.utils.platform.is_windows(): 25 return __virtualname__ 26 return (False, "Module win_ip: module only works on Windows systems") 27 28 29def _interface_configs(): 30 """ 31 Return all interface configs 32 """ 33 cmd = ["netsh", "interface", "ip", "show", "config"] 34 lines = __salt__["cmd.run"](cmd, python_shell=False).splitlines() 35 ret = {} 36 current_iface = None 37 current_ip_list = None 38 39 for line in lines: 40 41 line = line.strip() 42 if not line: 43 current_iface = None 44 current_ip_list = None 45 continue 46 47 if "Configuration for interface" in line: 48 _, iface = line.rstrip('"').split('"', 1) # get iface name 49 current_iface = {} 50 ret[iface] = current_iface 51 continue 52 53 if ":" not in line: 54 if current_ip_list: 55 current_ip_list.append(line) 56 else: 57 log.warning('Cannot parse "%s"', line) 58 continue 59 60 key, val = line.split(":", 1) 61 key = key.strip() 62 val = val.strip() 63 64 lkey = key.lower() 65 if ("dns servers" in lkey) or ("wins servers" in lkey): 66 current_ip_list = [] 67 current_iface[key] = current_ip_list 68 current_ip_list.append(val) 69 70 elif "ip address" in lkey: 71 current_iface.setdefault("ip_addrs", []).append({key: val}) 72 73 elif "subnet prefix" in lkey: 74 subnet, _, netmask = val.split(" ", 2) 75 last_ip = current_iface["ip_addrs"][-1] 76 last_ip["Subnet"] = subnet.strip() 77 last_ip["Netmask"] = netmask.lstrip().rstrip(")") 78 79 else: 80 current_iface[key] = val 81 82 return ret 83 84 85def raw_interface_configs(): 86 """ 87 Return raw configs for all interfaces 88 89 CLI Example: 90 91 .. code-block:: bash 92 93 salt -G 'os_family:Windows' ip.raw_interface_configs 94 """ 95 cmd = ["netsh", "interface", "ip", "show", "config"] 96 return __salt__["cmd.run"](cmd, python_shell=False) 97 98 99def get_all_interfaces(): 100 """ 101 Return configs for all interfaces 102 103 CLI Example: 104 105 .. code-block:: bash 106 107 salt -G 'os_family:Windows' ip.get_all_interfaces 108 """ 109 return _interface_configs() 110 111 112def get_interface(iface): 113 """ 114 Return the configuration of a network interface 115 116 CLI Example: 117 118 .. code-block:: bash 119 120 salt -G 'os_family:Windows' ip.get_interface 'Local Area Connection' 121 """ 122 return _interface_configs().get(iface, {}) 123 124 125def is_enabled(iface): 126 """ 127 Returns ``True`` if interface is enabled, otherwise ``False`` 128 129 CLI Example: 130 131 .. code-block:: bash 132 133 salt -G 'os_family:Windows' ip.is_enabled 'Local Area Connection #2' 134 """ 135 cmd = ["netsh", "interface", "show", "interface", "name={}".format(iface)] 136 iface_found = False 137 for line in __salt__["cmd.run"](cmd, python_shell=False).splitlines(): 138 if "Connect state:" in line: 139 iface_found = True 140 return line.split()[-1] == "Connected" 141 if not iface_found: 142 raise CommandExecutionError("Interface '{}' not found".format(iface)) 143 return False 144 145 146def is_disabled(iface): 147 """ 148 Returns ``True`` if interface is disabled, otherwise ``False`` 149 150 CLI Example: 151 152 .. code-block:: bash 153 154 salt -G 'os_family:Windows' ip.is_disabled 'Local Area Connection #2' 155 """ 156 return not is_enabled(iface) 157 158 159def enable(iface): 160 """ 161 Enable an interface 162 163 CLI Example: 164 165 .. code-block:: bash 166 167 salt -G 'os_family:Windows' ip.enable 'Local Area Connection #2' 168 """ 169 if is_enabled(iface): 170 return True 171 cmd = [ 172 "netsh", 173 "interface", 174 "set", 175 "interface", 176 "name={}".format(iface), 177 "admin=ENABLED", 178 ] 179 __salt__["cmd.run"](cmd, python_shell=False) 180 return is_enabled(iface) 181 182 183def disable(iface): 184 """ 185 Disable an interface 186 187 CLI Example: 188 189 .. code-block:: bash 190 191 salt -G 'os_family:Windows' ip.disable 'Local Area Connection #2' 192 """ 193 if is_disabled(iface): 194 return True 195 cmd = [ 196 "netsh", 197 "interface", 198 "set", 199 "interface", 200 "name={}".format(iface), 201 "admin=DISABLED", 202 ] 203 __salt__["cmd.run"](cmd, python_shell=False) 204 return is_disabled(iface) 205 206 207def get_subnet_length(mask): 208 """ 209 Convenience function to convert the netmask to the CIDR subnet length 210 211 CLI Example: 212 213 .. code-block:: bash 214 215 salt -G 'os_family:Windows' ip.get_subnet_length 255.255.255.0 216 """ 217 if not salt.utils.validate.net.netmask(mask): 218 raise SaltInvocationError("'{}' is not a valid netmask".format(mask)) 219 return salt.utils.network.get_net_size(mask) 220 221 222def set_static_ip(iface, addr, gateway=None, append=False): 223 """ 224 Set static IP configuration on a Windows NIC 225 226 iface 227 The name of the interface to manage 228 229 addr 230 IP address with subnet length (ex. ``10.1.2.3/24``). The 231 :mod:`ip.get_subnet_length <salt.modules.win_ip.get_subnet_length>` 232 function can be used to calculate the subnet length from a netmask. 233 234 gateway : None 235 If specified, the default gateway will be set to this value. 236 237 append : False 238 If ``True``, this IP address will be added to the interface. Default is 239 ``False``, which overrides any existing configuration for the interface 240 and sets ``addr`` as the only address on the interface. 241 242 CLI Example: 243 244 .. code-block:: bash 245 246 salt -G 'os_family:Windows' ip.set_static_ip 'Local Area Connection' 10.1.2.3/24 gateway=10.1.2.1 247 salt -G 'os_family:Windows' ip.set_static_ip 'Local Area Connection' 10.1.2.4/24 append=True 248 """ 249 250 def _find_addr(iface, addr, timeout=1): 251 ip, cidr = addr.rsplit("/", 1) 252 netmask = salt.utils.network.cidr_to_ipv4_netmask(cidr) 253 for idx in range(timeout): 254 for addrinfo in get_interface(iface).get("ip_addrs", []): 255 if addrinfo["IP Address"] == ip and addrinfo["Netmask"] == netmask: 256 return addrinfo 257 time.sleep(1) 258 return {} 259 260 if not salt.utils.validate.net.ipv4_addr(addr): 261 raise SaltInvocationError("Invalid address '{}'".format(addr)) 262 263 if gateway and not salt.utils.validate.net.ipv4_addr(addr): 264 raise SaltInvocationError("Invalid default gateway '{}'".format(gateway)) 265 266 if "/" not in addr: 267 addr += "/32" 268 269 if append and _find_addr(iface, addr): 270 raise CommandExecutionError( 271 "Address '{}' already exists on interface '{}'".format(addr, iface) 272 ) 273 274 cmd = ["netsh", "interface", "ip"] 275 if append: 276 cmd.append("add") 277 else: 278 cmd.append("set") 279 cmd.extend(["address", "name={}".format(iface)]) 280 if not append: 281 cmd.append("source=static") 282 cmd.append("address={}".format(addr)) 283 if gateway: 284 cmd.append("gateway={}".format(gateway)) 285 286 result = __salt__["cmd.run_all"](cmd, python_shell=False) 287 if result["retcode"] != 0: 288 raise CommandExecutionError( 289 "Unable to set IP address: {}".format(result["stderr"]) 290 ) 291 292 new_addr = _find_addr(iface, addr, timeout=10) 293 if not new_addr: 294 return {} 295 296 ret = {"Address Info": new_addr} 297 if gateway: 298 ret["Default Gateway"] = gateway 299 return ret 300 301 302def set_dhcp_ip(iface): 303 """ 304 Set Windows NIC to get IP from DHCP 305 306 CLI Example: 307 308 .. code-block:: bash 309 310 salt -G 'os_family:Windows' ip.set_dhcp_ip 'Local Area Connection' 311 """ 312 cmd = ["netsh", "interface", "ip", "set", "address", iface, "dhcp"] 313 __salt__["cmd.run"](cmd, python_shell=False) 314 return {"Interface": iface, "DHCP enabled": "Yes"} 315 316 317def set_static_dns(iface, *addrs): 318 """ 319 Set static DNS configuration on a Windows NIC 320 321 Args: 322 323 iface (str): The name of the interface to set 324 325 addrs (*): 326 One or more DNS servers to be added. To clear the list of DNS 327 servers pass an empty list (``[]``). If undefined or ``None`` no 328 changes will be made. 329 330 Returns: 331 dict: A dictionary containing the new DNS settings 332 333 CLI Example: 334 335 .. code-block:: bash 336 337 salt -G 'os_family:Windows' ip.set_static_dns 'Local Area Connection' '192.168.1.1' 338 salt -G 'os_family:Windows' ip.set_static_dns 'Local Area Connection' '192.168.1.252' '192.168.1.253' 339 """ 340 if not addrs or str(addrs[0]).lower() == "none": 341 return {"Interface": iface, "DNS Server": "No Changes"} 342 # Clear the list of DNS servers if [] is passed 343 if str(addrs[0]).lower() == "[]": 344 log.debug("Clearing list of DNS servers") 345 cmd = [ 346 "netsh", 347 "interface", 348 "ip", 349 "set", 350 "dns", 351 "name={}".format(iface), 352 "source=static", 353 "address=none", 354 ] 355 __salt__["cmd.run"](cmd, python_shell=False) 356 return {"Interface": iface, "DNS Server": []} 357 addr_index = 1 358 for addr in addrs: 359 if addr_index == 1: 360 cmd = [ 361 "netsh", 362 "interface", 363 "ip", 364 "set", 365 "dns", 366 "name={}".format(iface), 367 "source=static", 368 "address={}".format(addr), 369 "register=primary", 370 ] 371 __salt__["cmd.run"](cmd, python_shell=False) 372 addr_index = addr_index + 1 373 else: 374 cmd = [ 375 "netsh", 376 "interface", 377 "ip", 378 "add", 379 "dns", 380 "name={}".format(iface), 381 "address={}".format(addr), 382 "index={}".format(addr_index), 383 ] 384 __salt__["cmd.run"](cmd, python_shell=False) 385 addr_index = addr_index + 1 386 return {"Interface": iface, "DNS Server": addrs} 387 388 389def set_dhcp_dns(iface): 390 """ 391 Set DNS source to DHCP on Windows 392 393 CLI Example: 394 395 .. code-block:: bash 396 397 salt -G 'os_family:Windows' ip.set_dhcp_dns 'Local Area Connection' 398 """ 399 cmd = ["netsh", "interface", "ip", "set", "dns", iface, "dhcp"] 400 __salt__["cmd.run"](cmd, python_shell=False) 401 return {"Interface": iface, "DNS Server": "DHCP"} 402 403 404def set_dhcp_all(iface): 405 """ 406 Set both IP Address and DNS to DHCP 407 408 CLI Example: 409 410 .. code-block:: bash 411 412 salt -G 'os_family:Windows' ip.set_dhcp_all 'Local Area Connection' 413 """ 414 set_dhcp_ip(iface) 415 set_dhcp_dns(iface) 416 return {"Interface": iface, "DNS Server": "DHCP", "DHCP enabled": "Yes"} 417 418 419def get_default_gateway(): 420 """ 421 Set DNS source to DHCP on Windows 422 423 CLI Example: 424 425 .. code-block:: bash 426 427 salt -G 'os_family:Windows' ip.get_default_gateway 428 """ 429 try: 430 return next( 431 iter( 432 x.split()[-1] 433 for x in __salt__["cmd.run"]( 434 ["netsh", "interface", "ip", "show", "config"], python_shell=False 435 ).splitlines() 436 if "Default Gateway:" in x 437 ) 438 ) 439 except StopIteration: 440 raise CommandExecutionError("Unable to find default gateway") 441