1# pylint: disable=invalid-name 2""" 3Define some generic socket functions for network modules 4""" 5 6 7import fnmatch 8import itertools 9import logging 10import os 11import platform 12import random 13import re 14import socket 15import subprocess 16import types 17from collections.abc import Mapping, Sequence 18from string import ascii_letters, digits 19 20import salt.utils.args 21import salt.utils.files 22import salt.utils.path 23import salt.utils.platform 24import salt.utils.stringutils 25import salt.utils.zeromq 26from salt._compat import ipaddress 27from salt.exceptions import SaltClientError, SaltSystemExit 28from salt.utils.decorators.jinja import jinja_filter 29from salt.utils.versions import LooseVersion 30 31try: 32 import salt.utils.win_network 33 34 WIN_NETWORK_LOADED = True 35except ImportError: 36 WIN_NETWORK_LOADED = False 37 38log = logging.getLogger(__name__) 39 40try: 41 import ctypes 42 import ctypes.util 43 44 LIBC = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c")) 45 RES_INIT = LIBC.__res_init 46except (ImportError, OSError, AttributeError, TypeError): 47 pass 48 49 50class Interfaces: 51 __slots__ = ("interfaces",) 52 53 def __init__(self, interfaces=None): 54 if interfaces is None: 55 interfaces = {} 56 self.interfaces = interfaces 57 58 def __call__(self, *args, **kwargs): 59 if not self.interfaces: 60 self.interfaces = interfaces() 61 return self.interfaces 62 63 def clear(self): 64 self.interfaces = {} 65 66 67_get_interfaces = Interfaces() 68_clear_interfaces = _get_interfaces.clear 69 70 71def sanitize_host(host): 72 """ 73 Sanitize host string. 74 https://tools.ietf.org/html/rfc1123#section-2.1 75 """ 76 RFC952_characters = ascii_letters + digits + ".-_" 77 return "".join([c for c in host[0:255] if c in RFC952_characters]) 78 79 80def isportopen(host, port): 81 """ 82 Return status of a port 83 """ 84 85 if not 1 <= int(port) <= 65535: 86 return False 87 88 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 89 out = sock.connect_ex((sanitize_host(host), int(port))) 90 91 return out 92 93 94def host_to_ips(host): 95 """ 96 Returns a list of IP addresses of a given hostname or None if not found. 97 """ 98 ips = [] 99 try: 100 for family, socktype, proto, canonname, sockaddr in socket.getaddrinfo( 101 host, 0, socket.AF_UNSPEC, socket.SOCK_STREAM 102 ): 103 if family == socket.AF_INET: 104 ip, port = sockaddr 105 elif family == socket.AF_INET6: 106 ip, port, flow_info, scope_id = sockaddr 107 ips.append(ip) 108 if not ips: 109 ips = None 110 except Exception: # pylint: disable=broad-except 111 ips = None 112 return ips 113 114 115def _generate_minion_id(): 116 """ 117 Get list of possible host names and convention names. 118 119 :return: 120 """ 121 # There are three types of hostnames: 122 # 1. Network names. How host is accessed from the network. 123 # 2. Host aliases. They might be not available in all the network or only locally (/etc/hosts) 124 # 3. Convention names, an internal nodename. 125 126 class DistinctList(list): 127 """ 128 List, which allows one to append only distinct objects. 129 Needs to work on Python 2.6, because of collections.OrderedDict only since 2.7 version. 130 Override 'filter()' for custom filtering. 131 """ 132 133 localhost_matchers = [ 134 r"localhost.*", 135 r"ip6-.*", 136 r"127[.]\d", 137 r"0\.0\.0\.0", 138 r"::1.*", 139 r"ipv6-.*", 140 r"fe00::.*", 141 r"fe02::.*", 142 r"1.0.0.*.ip6.arpa", 143 ] 144 145 def append(self, p_object): 146 if p_object and p_object not in self and not self.filter(p_object): 147 super().append(p_object) 148 return self 149 150 def extend(self, iterable): 151 for obj in iterable: 152 self.append(obj) 153 return self 154 155 def filter(self, element): 156 "Returns True if element needs to be filtered" 157 for rgx in self.localhost_matchers: 158 if re.match(rgx, element): 159 return True 160 161 def first(self): 162 return self and self[0] or None 163 164 hostname = socket.gethostname() 165 166 hosts = ( 167 DistinctList() 168 .append( 169 salt.utils.stringutils.to_unicode( 170 socket.getfqdn(salt.utils.stringutils.to_bytes(hostname)) 171 ) 172 ) 173 .append(platform.node()) 174 .append(hostname) 175 ) 176 if not hosts: 177 try: 178 for a_nfo in socket.getaddrinfo( 179 hosts.first() or "localhost", 180 None, 181 socket.AF_INET, 182 socket.SOCK_RAW, 183 socket.IPPROTO_IP, 184 socket.AI_CANONNAME, 185 ): 186 if len(a_nfo) > 3: 187 hosts.append(a_nfo[3]) 188 except socket.gaierror: 189 log.warning( 190 "Cannot resolve address %s info via socket: %s", 191 hosts.first() or "localhost (N/A)", 192 socket.gaierror, 193 ) 194 # Universal method for everywhere (Linux, Slowlaris, Windows etc) 195 for f_name in ( 196 "/etc/hostname", 197 "/etc/nodename", 198 "/etc/hosts", 199 r"{win}\system32\drivers\etc\hosts".format(win=os.getenv("WINDIR")), 200 ): 201 try: 202 with salt.utils.files.fopen(f_name) as f_hdl: 203 for line in f_hdl: 204 line = salt.utils.stringutils.to_unicode(line) 205 hst = line.strip().split("#")[0].strip().split() 206 if hst: 207 if hst[0][:4] in ("127.", "::1") or len(hst) == 1: 208 hosts.extend(hst) 209 except OSError: 210 pass 211 212 # include public and private ipaddresses 213 return hosts.extend( 214 [addr for addr in ip_addrs() if not ipaddress.ip_address(addr).is_loopback] 215 ) 216 217 218def generate_minion_id(): 219 """ 220 Return only first element of the hostname from all possible list. 221 222 :return: 223 """ 224 try: 225 ret = salt.utils.stringutils.to_unicode(_generate_minion_id().first()) 226 except TypeError: 227 ret = None 228 return ret or "localhost" 229 230 231def get_socket(addr, type=socket.SOCK_STREAM, proto=0): 232 """ 233 Return a socket object for the addr 234 IP-version agnostic 235 """ 236 237 version = ipaddress.ip_address(addr).version 238 if version == 4: 239 family = socket.AF_INET 240 elif version == 6: 241 family = socket.AF_INET6 242 return socket.socket(family, type, proto) 243 244 245def get_fqhostname(): 246 """ 247 Returns the fully qualified hostname 248 """ 249 l = [socket.getfqdn()] 250 251 # try socket.getaddrinfo 252 try: 253 addrinfo = socket.getaddrinfo( 254 socket.gethostname(), 255 0, 256 socket.AF_UNSPEC, 257 socket.SOCK_STREAM, 258 socket.SOL_TCP, 259 socket.AI_CANONNAME, 260 ) 261 for info in addrinfo: 262 # info struct [family, socktype, proto, canonname, sockaddr] 263 # On Windows `canonname` can be an empty string 264 # This can cause the function to return `None` 265 if len(info) >= 4 and info[3]: 266 l = [info[3]] 267 except socket.gaierror: 268 pass 269 270 return l and l[0] or None 271 272 273def ip_to_host(ip): 274 """ 275 Returns the hostname of a given IP 276 """ 277 try: 278 hostname, aliaslist, ipaddrlist = socket.gethostbyaddr(ip) 279 except Exception as exc: # pylint: disable=broad-except 280 log.debug("salt.utils.network.ip_to_host(%r) failed: %s", ip, exc) 281 hostname = None 282 return hostname 283 284 285def is_reachable_host(entity_name): 286 """ 287 Returns a bool telling if the entity name is a reachable host (IPv4/IPv6/FQDN/etc). 288 :param hostname: 289 :return: 290 """ 291 try: 292 assert type(socket.getaddrinfo(entity_name, 0, 0, 0, 0)) == list 293 ret = True 294 except socket.gaierror: 295 ret = False 296 297 return ret 298 299 300def is_ip(ip_addr): 301 """ 302 Returns a bool telling if the passed IP is a valid IPv4 or IPv6 address. 303 """ 304 return is_ipv4(ip_addr) or is_ipv6(ip_addr) 305 306 307def is_ipv4(ip_addr): 308 """ 309 Returns a bool telling if the value passed to it was a valid IPv4 address 310 """ 311 try: 312 return ipaddress.ip_address(ip_addr).version == 4 313 except ValueError: 314 return False 315 316 317def is_ipv6(ip_addr): 318 """ 319 Returns a bool telling if the value passed to it was a valid IPv6 address 320 """ 321 try: 322 return ipaddress.ip_address(ip_addr).version == 6 323 except ValueError: 324 return False 325 326 327def is_subnet(cidr): 328 """ 329 Returns a bool telling if the passed string is an IPv4 or IPv6 subnet 330 """ 331 return is_ipv4_subnet(cidr) or is_ipv6_subnet(cidr) 332 333 334def is_ipv4_subnet(cidr): 335 """ 336 Returns a bool telling if the passed string is an IPv4 subnet 337 """ 338 try: 339 return "/" in cidr and bool(ipaddress.IPv4Network(cidr)) 340 except Exception: # pylint: disable=broad-except 341 return False 342 343 344def is_ipv6_subnet(cidr): 345 """ 346 Returns a bool telling if the passed string is an IPv6 subnet 347 """ 348 try: 349 return "/" in cidr and bool(ipaddress.IPv6Network(cidr)) 350 except Exception: # pylint: disable=broad-except 351 return False 352 353 354@jinja_filter("is_ip") 355def is_ip_filter(ip_addr, options=None): 356 """ 357 Returns a bool telling if the passed IP is a valid IPv4 or IPv6 address. 358 """ 359 return is_ipv4_filter(ip_addr, options=options) or is_ipv6_filter( 360 ip_addr, options=options 361 ) 362 363 364def _ip_options_global(ip_obj, version): 365 return not ip_obj.is_private 366 367 368def _ip_options_multicast(ip_obj, version): 369 return ip_obj.is_multicast 370 371 372def _ip_options_loopback(ip_obj, version): 373 return ip_obj.is_loopback 374 375 376def _ip_options_link_local(ip_obj, version): 377 return ip_obj.is_link_local 378 379 380def _ip_options_private(ip_obj, version): 381 return ip_obj.is_private 382 383 384def _ip_options_reserved(ip_obj, version): 385 return ip_obj.is_reserved 386 387 388def _ip_options_site_local(ip_obj, version): 389 if version == 6: 390 return ip_obj.is_site_local 391 return False 392 393 394def _ip_options_unspecified(ip_obj, version): 395 return ip_obj.is_unspecified 396 397 398def _ip_options(ip_obj, version, options=None): 399 400 # will process and IP options 401 options_fun_map = { 402 "global": _ip_options_global, 403 "link-local": _ip_options_link_local, 404 "linklocal": _ip_options_link_local, 405 "ll": _ip_options_link_local, 406 "link_local": _ip_options_link_local, 407 "loopback": _ip_options_loopback, 408 "lo": _ip_options_loopback, 409 "multicast": _ip_options_multicast, 410 "private": _ip_options_private, 411 "public": _ip_options_global, 412 "reserved": _ip_options_reserved, 413 "site-local": _ip_options_site_local, 414 "sl": _ip_options_site_local, 415 "site_local": _ip_options_site_local, 416 "unspecified": _ip_options_unspecified, 417 } 418 419 if not options: 420 return str(ip_obj) # IP version already checked 421 422 options_list = [option.strip() for option in options.split(",")] 423 424 for option, fun in options_fun_map.items(): 425 if option in options_list: 426 fun_res = fun(ip_obj, version) 427 if not fun_res: 428 return None 429 # stop at first failed test 430 # else continue 431 return str(ip_obj) 432 433 434def _is_ipv(ip_addr, version, options=None): 435 436 if not version: 437 version = 4 438 439 if version not in (4, 6): 440 return None 441 442 try: 443 ip_obj = ipaddress.ip_address(ip_addr) 444 except ValueError: 445 # maybe it is an IP network 446 try: 447 ip_obj = ipaddress.ip_interface(ip_addr) 448 except ValueError: 449 # nope, still not :( 450 return None 451 452 if not ip_obj.version == version: 453 return None 454 455 # has the right version, let's move on 456 return _ip_options(ip_obj, version, options=options) 457 458 459@jinja_filter("is_ipv4") 460def is_ipv4_filter(ip_addr, options=None): 461 """ 462 Returns a bool telling if the value passed to it was a valid IPv4 address. 463 464 ip 465 The IP address. 466 467 net: False 468 Consider IP addresses followed by netmask. 469 470 options 471 CSV of options regarding the nature of the IP address. E.g.: loopback, multicast, private etc. 472 """ 473 _is_ipv4 = _is_ipv(ip_addr, 4, options=options) 474 return isinstance(_is_ipv4, str) 475 476 477@jinja_filter("is_ipv6") 478def is_ipv6_filter(ip_addr, options=None): 479 """ 480 Returns a bool telling if the value passed to it was a valid IPv6 address. 481 482 ip 483 The IP address. 484 485 net: False 486 Consider IP addresses followed by netmask. 487 488 options 489 CSV of options regarding the nature of the IP address. E.g.: loopback, multicast, private etc. 490 """ 491 _is_ipv6 = _is_ipv(ip_addr, 6, options=options) 492 return isinstance(_is_ipv6, str) 493 494 495def _ipv_filter(value, version, options=None): 496 497 if version not in (4, 6): 498 return 499 500 if isinstance(value, (str, bytes)): 501 return _is_ipv( 502 value, version, options=options 503 ) # calls is_ipv4 or is_ipv6 for `value` 504 elif isinstance(value, (list, tuple, types.GeneratorType)): 505 # calls is_ipv4 or is_ipv6 for each element in the list 506 # os it filters and returns only those elements having the desired IP version 507 return [ 508 _is_ipv(addr, version, options=options) 509 for addr in value 510 if _is_ipv(addr, version, options=options) is not None 511 ] 512 return None 513 514 515@jinja_filter("ipv4") 516def ipv4(value, options=None): 517 """ 518 Filters a list and returns IPv4 values only. 519 """ 520 return _ipv_filter(value, 4, options=options) 521 522 523@jinja_filter("ipv6") 524def ipv6(value, options=None): 525 """ 526 Filters a list and returns IPv6 values only. 527 """ 528 return _ipv_filter(value, 6, options=options) 529 530 531@jinja_filter("ipaddr") 532def ipaddr(value, options=None): 533 """ 534 Filters and returns only valid IP objects. 535 """ 536 ipv4_obj = ipv4(value, options=options) 537 ipv6_obj = ipv6(value, options=options) 538 if ipv4_obj is None or ipv6_obj is None: 539 # an IP address can be either IPv4 either IPv6 540 # therefofe if the value passed as arg is not a list, at least one of the calls above will return None 541 # if one of them is none, means that we should return only one of them 542 return ipv4_obj or ipv6_obj # one of them 543 else: 544 return ipv4_obj + ipv6_obj # extend lists 545 546 547def _filter_ipaddr(value, options, version=None): 548 ipaddr_filter_out = None 549 if version: 550 if version == 4: 551 ipaddr_filter_out = ipv4(value, options) 552 elif version == 6: 553 ipaddr_filter_out = ipv6(value, options) 554 else: 555 ipaddr_filter_out = ipaddr(value, options) 556 if not ipaddr_filter_out: 557 return 558 if not isinstance(ipaddr_filter_out, (list, tuple, types.GeneratorType)): 559 ipaddr_filter_out = [ipaddr_filter_out] 560 return ipaddr_filter_out 561 562 563@jinja_filter("ip_host") 564def ip_host(value, options=None, version=None): 565 """ 566 Returns the interfaces IP address, e.g.: 192.168.0.1/28. 567 """ 568 ipaddr_filter_out = _filter_ipaddr(value, options=options, version=version) 569 if not ipaddr_filter_out: 570 return 571 if not isinstance(value, (list, tuple, types.GeneratorType)): 572 return str(ipaddress.ip_interface(ipaddr_filter_out[0])) 573 return [str(ipaddress.ip_interface(ip_a)) for ip_a in ipaddr_filter_out] 574 575 576def _network_hosts(ip_addr_entry): 577 return [ 578 str(host) for host in ipaddress.ip_network(ip_addr_entry, strict=False).hosts() 579 ] 580 581 582@jinja_filter("network_hosts") 583def network_hosts(value, options=None, version=None): 584 """ 585 Return the list of hosts within a network. 586 587 .. note:: 588 589 When running this command with a large IPv6 network, the command will 590 take a long time to gather all of the hosts. 591 """ 592 ipaddr_filter_out = _filter_ipaddr(value, options=options, version=version) 593 if not ipaddr_filter_out: 594 return 595 if not isinstance(value, (list, tuple, types.GeneratorType)): 596 return _network_hosts(ipaddr_filter_out[0]) 597 return [_network_hosts(ip_a) for ip_a in ipaddr_filter_out] 598 599 600def _network_size(ip_addr_entry): 601 return ipaddress.ip_network(ip_addr_entry, strict=False).num_addresses 602 603 604@jinja_filter("network_size") 605def network_size(value, options=None, version=None): 606 """ 607 Get the size of a network. 608 """ 609 ipaddr_filter_out = _filter_ipaddr(value, options=options, version=version) 610 if not ipaddr_filter_out: 611 return 612 if not isinstance(value, (list, tuple, types.GeneratorType)): 613 return _network_size(ipaddr_filter_out[0]) 614 return [_network_size(ip_a) for ip_a in ipaddr_filter_out] 615 616 617def natural_ipv4_netmask(ip_addr, fmt="prefixlen"): 618 """ 619 Returns the "natural" mask of an IPv4 address 620 """ 621 bits = _ipv4_to_bits(ip_addr) 622 623 if bits.startswith("11"): 624 mask = "24" 625 elif bits.startswith("1"): 626 mask = "16" 627 else: 628 mask = "8" 629 630 if fmt == "netmask": 631 return cidr_to_ipv4_netmask(mask) 632 else: 633 return "/" + mask 634 635 636def rpad_ipv4_network(ip_addr): 637 """ 638 Returns an IP network address padded with zeros. 639 640 Ex: '192.168.3' -> '192.168.3.0' 641 '10.209' -> '10.209.0.0' 642 """ 643 return ".".join(itertools.islice(itertools.chain(ip_addr.split("."), "0000"), 0, 4)) 644 645 646def cidr_to_ipv4_netmask(cidr_bits): 647 """ 648 Returns an IPv4 netmask 649 """ 650 try: 651 cidr_bits = int(cidr_bits) 652 if not 1 <= cidr_bits <= 32: 653 return "" 654 except ValueError: 655 return "" 656 657 netmask = "" 658 for idx in range(4): 659 if idx: 660 netmask += "." 661 if cidr_bits >= 8: 662 netmask += "255" 663 cidr_bits -= 8 664 else: 665 netmask += "{:d}".format(256 - (2 ** (8 - cidr_bits))) 666 cidr_bits = 0 667 return netmask 668 669 670def _number_of_set_bits_to_ipv4_netmask(set_bits): 671 """ 672 Returns an IPv4 netmask from the integer representation of that mask. 673 674 Ex. 0xffffff00 -> '255.255.255.0' 675 """ 676 return cidr_to_ipv4_netmask(_number_of_set_bits(set_bits)) 677 678 679def _number_of_set_bits(x): 680 """ 681 Returns the number of bits that are set in a 32bit int 682 """ 683 # Taken from http://stackoverflow.com/a/4912729. Many thanks! 684 x -= (x >> 1) & 0x55555555 685 x = ((x >> 2) & 0x33333333) + (x & 0x33333333) 686 x = ((x >> 4) + x) & 0x0F0F0F0F 687 x += x >> 8 688 x += x >> 16 689 return x & 0x0000003F 690 691 692def _interfaces_ip(out): 693 """ 694 Uses ip to return a dictionary of interfaces with various information about 695 each (up/down state, ip address, netmask, and hwaddr) 696 """ 697 ret = dict() 698 699 def parse_network(value, cols): 700 """ 701 Return a tuple of ip, netmask, broadcast 702 based on the current set of cols 703 """ 704 brd = None 705 scope = None 706 if "/" in value: # we have a CIDR in this address 707 ip, cidr = value.split("/") 708 else: 709 ip = value 710 cidr = 32 711 712 if type_ == "inet": 713 mask = cidr_to_ipv4_netmask(int(cidr)) 714 if "brd" in cols: 715 brd = cols[cols.index("brd") + 1] 716 elif type_ == "inet6": 717 mask = cidr 718 if "scope" in cols: 719 scope = cols[cols.index("scope") + 1] 720 return (ip, mask, brd, scope) 721 722 groups = re.compile("\r?\n\\d").split(out) 723 for group in groups: 724 iface = None 725 data = dict() 726 727 for line in group.splitlines(): 728 if " " not in line: 729 continue 730 match = re.match(r"^\d*:\s+([\w.\-]+)(?:@)?([\w.\-]+)?:\s+<(.+)>", line) 731 if match: 732 iface, parent, attrs = match.groups() 733 if "UP" in attrs.split(","): 734 data["up"] = True 735 else: 736 data["up"] = False 737 if parent: 738 data["parent"] = parent 739 continue 740 741 cols = line.split() 742 if len(cols) >= 2: 743 type_, value = tuple(cols[0:2]) 744 iflabel = cols[-1:][0] 745 if type_ in ("inet", "inet6"): 746 if "secondary" not in cols: 747 ipaddr, netmask, broadcast, scope = parse_network(value, cols) 748 if type_ == "inet": 749 if "inet" not in data: 750 data["inet"] = list() 751 addr_obj = dict() 752 addr_obj["address"] = ipaddr 753 addr_obj["netmask"] = netmask 754 addr_obj["broadcast"] = broadcast 755 addr_obj["label"] = iflabel 756 data["inet"].append(addr_obj) 757 elif type_ == "inet6": 758 if "inet6" not in data: 759 data["inet6"] = list() 760 addr_obj = dict() 761 addr_obj["address"] = ipaddr 762 addr_obj["prefixlen"] = netmask 763 addr_obj["scope"] = scope 764 data["inet6"].append(addr_obj) 765 else: 766 if "secondary" not in data: 767 data["secondary"] = list() 768 ip_, mask, brd, scp = parse_network(value, cols) 769 data["secondary"].append( 770 { 771 "type": type_, 772 "address": ip_, 773 "netmask": mask, 774 "broadcast": brd, 775 "label": iflabel, 776 } 777 ) 778 del ip_, mask, brd, scp 779 elif type_.startswith("link"): 780 data["hwaddr"] = value 781 if iface: 782 ret[iface] = data 783 del iface, data 784 return ret 785 786 787def _interfaces_ifconfig(out): 788 """ 789 Uses ifconfig to return a dictionary of interfaces with various information 790 about each (up/down state, ip address, netmask, and hwaddr) 791 """ 792 ret = dict() 793 794 piface = re.compile(r"^([^\s:]+)") 795 pmac = re.compile(".*?(?:HWaddr|ether|address:|lladdr) ([0-9a-fA-F:]+)") 796 if salt.utils.platform.is_sunos(): 797 pip = re.compile(r".*?(?:inet\s+)([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*)") 798 pip6 = re.compile(".*?(?:inet6 )([0-9a-fA-F:]+)") 799 pmask6 = re.compile(r".*?(?:inet6 [0-9a-fA-F:]+/(\d+)).*") 800 else: 801 pip = re.compile(r".*?(?:inet addr:|inet [^\d]*)(.*?)\s") 802 pip6 = re.compile(".*?(?:inet6 addr: (.*?)/|inet6 )([0-9a-fA-F:]+)") 803 pmask6 = re.compile( 804 r".*?(?:inet6 addr: [0-9a-fA-F:]+/(\d+)|prefixlen (\d+))(?:" 805 r" Scope:([a-zA-Z]+)| scopeid (0x[0-9a-fA-F]))?" 806 ) 807 pmask = re.compile(r".*?(?:Mask:|netmask )(?:((?:0x)?[0-9a-fA-F]{8})|([\d\.]+))") 808 pupdown = re.compile("UP") 809 pbcast = re.compile(r".*?(?:Bcast:|broadcast )([\d\.]+)") 810 811 groups = re.compile("\r?\n(?=\\S)").split(out) 812 for group in groups: 813 data = dict() 814 iface = "" 815 updown = False 816 for line in group.splitlines(): 817 miface = piface.match(line) 818 mmac = pmac.match(line) 819 mip = pip.match(line) 820 mip6 = pip6.match(line) 821 mupdown = pupdown.search(line) 822 if miface: 823 iface = miface.group(1) 824 if mmac: 825 data["hwaddr"] = mmac.group(1) 826 if salt.utils.platform.is_sunos(): 827 expand_mac = [] 828 for chunk in data["hwaddr"].split(":"): 829 expand_mac.append( 830 "0{}".format(chunk) 831 if len(chunk) < 2 832 else "{}".format(chunk) 833 ) 834 data["hwaddr"] = ":".join(expand_mac) 835 if mip: 836 if "inet" not in data: 837 data["inet"] = list() 838 addr_obj = dict() 839 addr_obj["address"] = mip.group(1) 840 mmask = pmask.match(line) 841 if mmask: 842 if mmask.group(1): 843 mmask = _number_of_set_bits_to_ipv4_netmask( 844 int(mmask.group(1), 16) 845 ) 846 else: 847 mmask = mmask.group(2) 848 addr_obj["netmask"] = mmask 849 mbcast = pbcast.match(line) 850 if mbcast: 851 addr_obj["broadcast"] = mbcast.group(1) 852 data["inet"].append(addr_obj) 853 if mupdown: 854 updown = True 855 if mip6: 856 if "inet6" not in data: 857 data["inet6"] = list() 858 addr_obj = dict() 859 addr_obj["address"] = mip6.group(1) or mip6.group(2) 860 mmask6 = pmask6.match(line) 861 if mmask6: 862 addr_obj["prefixlen"] = mmask6.group(1) or mmask6.group(2) 863 if not salt.utils.platform.is_sunos(): 864 ipv6scope = mmask6.group(3) or mmask6.group(4) 865 addr_obj["scope"] = ( 866 ipv6scope.lower() if ipv6scope is not None else ipv6scope 867 ) 868 # SunOS sometimes has ::/0 as inet6 addr when using addrconf 869 if ( 870 not salt.utils.platform.is_sunos() 871 or addr_obj["address"] != "::" 872 and addr_obj["prefixlen"] != 0 873 ): 874 data["inet6"].append(addr_obj) 875 data["up"] = updown 876 if iface in ret: 877 # SunOS optimization, where interfaces occur twice in 'ifconfig -a' 878 # output with the same name: for ipv4 and then for ipv6 addr family. 879 # Every instance has its own 'UP' status and we assume that ipv4 880 # status determines global interface status. 881 # 882 # merge items with higher priority for older values 883 # after that merge the inet and inet6 sub items for both 884 ret[iface] = dict(list(data.items()) + list(ret[iface].items())) 885 if "inet" in data: 886 ret[iface]["inet"].extend( 887 x for x in data["inet"] if x not in ret[iface]["inet"] 888 ) 889 if "inet6" in data: 890 ret[iface]["inet6"].extend( 891 x for x in data["inet6"] if x not in ret[iface]["inet6"] 892 ) 893 else: 894 ret[iface] = data 895 del data 896 return ret 897 898 899def linux_interfaces(): 900 """ 901 Obtain interface information for *NIX/BSD variants 902 """ 903 ifaces = dict() 904 ip_path = salt.utils.path.which("ip") 905 ifconfig_path = None if ip_path else salt.utils.path.which("ifconfig") 906 if ip_path: 907 cmd1 = subprocess.Popen( 908 [ip_path, "link", "show"], 909 close_fds=True, 910 stdout=subprocess.PIPE, 911 stderr=subprocess.STDOUT, 912 ).communicate()[0] 913 cmd2 = subprocess.Popen( 914 [ip_path, "addr", "show"], 915 close_fds=True, 916 stdout=subprocess.PIPE, 917 stderr=subprocess.STDOUT, 918 ).communicate()[0] 919 ifaces = _interfaces_ip( 920 "{}\n{}".format( 921 salt.utils.stringutils.to_str(cmd1), salt.utils.stringutils.to_str(cmd2) 922 ) 923 ) 924 elif ifconfig_path: 925 cmd = subprocess.Popen( 926 [ifconfig_path, "-a"], 927 stdout=subprocess.PIPE, 928 stderr=subprocess.STDOUT, 929 ).communicate()[0] 930 ifaces = _interfaces_ifconfig(salt.utils.stringutils.to_str(cmd)) 931 return ifaces 932 933 934def _netbsd_interfaces_ifconfig(out): 935 """ 936 Uses ifconfig to return a dictionary of interfaces with various information 937 about each (up/down state, ip address, netmask, and hwaddr) 938 """ 939 ret = dict() 940 941 piface = re.compile(r"^([^\s:]+)") 942 pmac = re.compile(".*?address: ([0-9a-f:]+)") 943 944 pip = re.compile(r".*?inet [^\d]*(.*?)/([\d]*)\s") 945 pip6 = re.compile(r".*?inet6 ([0-9a-f:]+)%([a-zA-Z0-9]*)/([\d]*)\s") 946 947 pupdown = re.compile("UP") 948 pbcast = re.compile(r".*?broadcast ([\d\.]+)") 949 950 groups = re.compile("\r?\n(?=\\S)").split(out) 951 for group in groups: 952 data = dict() 953 iface = "" 954 updown = False 955 for line in group.splitlines(): 956 miface = piface.match(line) 957 mmac = pmac.match(line) 958 mip = pip.match(line) 959 mip6 = pip6.match(line) 960 mupdown = pupdown.search(line) 961 if miface: 962 iface = miface.group(1) 963 if mmac: 964 data["hwaddr"] = mmac.group(1) 965 if mip: 966 if "inet" not in data: 967 data["inet"] = list() 968 addr_obj = dict() 969 addr_obj["address"] = mip.group(1) 970 mmask = mip.group(2) 971 if mip.group(2): 972 addr_obj["netmask"] = cidr_to_ipv4_netmask(mip.group(2)) 973 mbcast = pbcast.match(line) 974 if mbcast: 975 addr_obj["broadcast"] = mbcast.group(1) 976 data["inet"].append(addr_obj) 977 if mupdown: 978 updown = True 979 if mip6: 980 if "inet6" not in data: 981 data["inet6"] = list() 982 addr_obj = dict() 983 addr_obj["address"] = mip6.group(1) 984 mmask6 = mip6.group(3) 985 addr_obj["scope"] = mip6.group(2) 986 addr_obj["prefixlen"] = mip6.group(3) 987 data["inet6"].append(addr_obj) 988 data["up"] = updown 989 ret[iface] = data 990 del data 991 return ret 992 993 994def _junos_interfaces_ifconfig(out): 995 """ 996 Uses ifconfig to return a dictionary of interfaces with various information 997 about each (up/down state, ip address, netmask, and hwaddr) 998 """ 999 ret = dict() 1000 1001 piface = re.compile(r"^([^\s:]+)") 1002 pmac = re.compile("curr media .*? ([0-9a-f:]+)") 1003 1004 pip = re.compile( 1005 r".*?inet\s*(primary)*\s+mtu" 1006 r" (\d+)\s+local=[^\d]*(.*?)\s{0,40}dest=[^\d]*(.*?)\/([\d]*)\s{0,40}bcast=((?:[0-9]{1,3}\.){3}[0-9]{1,3})" 1007 ) 1008 pip6 = re.compile( 1009 r".*?inet6 mtu [^\d]+\s{0,40}local=([0-9a-f:]+)%([a-zA-Z0-9]*)/([\d]*)\s" 1010 ) 1011 1012 pupdown = re.compile("UP") 1013 pbcast = re.compile(r".*?broadcast ([\d\.]+)") 1014 1015 groups = re.compile("\r?\n(?=\\S)").split(out) 1016 for group in groups: 1017 data = dict() 1018 iface = "" 1019 updown = False 1020 primary = False 1021 for line in group.splitlines(): 1022 miface = piface.match(line) 1023 mmac = pmac.match(line) 1024 mip = pip.match(line) 1025 mip6 = pip6.match(line) 1026 mupdown = pupdown.search(line) 1027 if miface: 1028 iface = miface.group(1) 1029 if mmac: 1030 data["hwaddr"] = mmac.group(1) 1031 if mip: 1032 if "primary" in data: 1033 primary = True 1034 if "inet" not in data: 1035 data["inet"] = list() 1036 if mip.group(2): 1037 data["mtu"] = int(mip.group(2)) 1038 addr_obj = dict() 1039 addr_obj["address"] = mip.group(3) 1040 mmask = mip.group(5) 1041 if mip.group(5): 1042 addr_obj["netmask"] = cidr_to_ipv4_netmask(mip.group(5)) 1043 mbcast = pbcast.match(line) 1044 if mbcast: 1045 addr_obj["broadcast"] = mbcast.group(1) 1046 data["inet"].append(addr_obj) 1047 if mupdown: 1048 updown = True 1049 if mip6: 1050 if "inet6" not in data: 1051 data["inet6"] = list() 1052 addr_obj = dict() 1053 addr_obj["address"] = mip6.group(1) 1054 mmask6 = mip6.group(3) 1055 addr_obj["scope"] = mip6.group(2) 1056 addr_obj["prefixlen"] = mip6.group(3) 1057 data["inet6"].append(addr_obj) 1058 data["up"] = updown 1059 ret[iface] = data 1060 del data 1061 return ret 1062 1063 1064def junos_interfaces(): 1065 """ 1066 Obtain interface information for Junos; ifconfig 1067 output diverged from other BSD variants (Netmask is now part of the 1068 address) 1069 """ 1070 ifconfig_path = salt.utils.path.which("ifconfig") 1071 cmd = subprocess.Popen( 1072 [ifconfig_path, "-a"], 1073 stdout=subprocess.PIPE, 1074 stderr=subprocess.STDOUT, 1075 ).communicate()[0] 1076 return _junos_interfaces_ifconfig(salt.utils.stringutils.to_str(cmd)) 1077 1078 1079def netbsd_interfaces(): 1080 """ 1081 Obtain interface information for NetBSD >= 8 where the ifconfig 1082 output diverged from other BSD variants (Netmask is now part of the 1083 address) 1084 """ 1085 # NetBSD versions prior to 8.0 can still use linux_interfaces() 1086 if LooseVersion(os.uname()[2]) < LooseVersion("8.0"): 1087 return linux_interfaces() 1088 1089 ifconfig_path = salt.utils.path.which("ifconfig") 1090 cmd = subprocess.Popen( 1091 [ifconfig_path, "-a"], 1092 stdout=subprocess.PIPE, 1093 stderr=subprocess.STDOUT, 1094 ).communicate()[0] 1095 return _netbsd_interfaces_ifconfig(salt.utils.stringutils.to_str(cmd)) 1096 1097 1098def _interfaces_ipconfig(out): 1099 """ 1100 Returns a dictionary of interfaces with various information about each 1101 (up/down state, ip address, netmask, and hwaddr) 1102 1103 NOTE: This is not used by any function and may be able to be removed in the 1104 future. 1105 """ 1106 ifaces = dict() 1107 iface = None 1108 addr = None 1109 adapter_iface_regex = re.compile(r"adapter (\S.+):$") 1110 1111 for line in out.splitlines(): 1112 if not line: 1113 continue 1114 # TODO what does Windows call Infiniband and 10/40gige adapters 1115 if line.startswith("Ethernet"): 1116 iface = ifaces[adapter_iface_regex.search(line).group(1)] 1117 iface["up"] = True 1118 addr = {} 1119 continue 1120 if iface: 1121 key, val = line.split(",", 1) 1122 key = key.strip(" .") 1123 val = val.strip() 1124 if addr and key == "Subnet Mask": 1125 addr["netmask"] = val 1126 elif key in ("IP Address", "IPv4 Address"): 1127 if "inet" not in iface: 1128 iface["inet"] = list() 1129 addr = { 1130 "address": val.rstrip("(Preferred)"), 1131 "netmask": None, 1132 "broadcast": None, 1133 } # TODO find the broadcast 1134 iface["inet"].append(addr) 1135 elif "IPv6 Address" in key: 1136 if "inet6" not in iface: 1137 iface["inet"] = list() 1138 # XXX What is the prefixlen!? 1139 addr = {"address": val.rstrip("(Preferred)"), "prefixlen": None} 1140 iface["inet6"].append(addr) 1141 elif key == "Physical Address": 1142 iface["hwaddr"] = val 1143 elif key == "Media State": 1144 # XXX seen used for tunnel adaptors 1145 # might be useful 1146 iface["up"] = val != "Media disconnected" 1147 1148 1149def win_interfaces(): 1150 """ 1151 Obtain interface information for Windows systems 1152 """ 1153 if WIN_NETWORK_LOADED is False: 1154 # Let's throw the ImportException again 1155 import salt.utils.win_network as _ 1156 return salt.utils.win_network.get_interface_info() 1157 1158 1159def interfaces(): 1160 """ 1161 Return a dictionary of information about all the interfaces on the minion 1162 """ 1163 if salt.utils.platform.is_windows(): 1164 return win_interfaces() 1165 elif salt.utils.platform.is_junos(): 1166 return junos_interfaces() 1167 elif salt.utils.platform.is_netbsd(): 1168 return netbsd_interfaces() 1169 else: 1170 return linux_interfaces() 1171 1172 1173def get_net_start(ipaddr, netmask): 1174 """ 1175 Return the address of the network 1176 """ 1177 net = ipaddress.ip_network("{}/{}".format(ipaddr, netmask), strict=False) 1178 return str(net.network_address) 1179 1180 1181def get_net_size(mask): 1182 """ 1183 Turns an IPv4 netmask into its corresponding prefix length 1184 (255.255.255.0 -> 24 as in 192.168.1.10/24). 1185 """ 1186 binary_str = "" 1187 for octet in mask.split("."): 1188 binary_str += bin(int(octet))[2:].zfill(8) 1189 return len(binary_str.rstrip("0")) 1190 1191 1192def calc_net(ipaddr, netmask=None): 1193 """ 1194 Takes IP (CIDR notation supported) and optionally netmask 1195 and returns the network in CIDR-notation. 1196 (The IP can be any IP inside the subnet) 1197 """ 1198 if netmask is not None: 1199 ipaddr = "{}/{}".format(ipaddr, netmask) 1200 1201 return str(ipaddress.ip_network(ipaddr, strict=False)) 1202 1203 1204def _ipv4_to_bits(ipaddr): 1205 """ 1206 Accepts an IPv4 dotted quad and returns a string representing its binary 1207 counterpart 1208 """ 1209 return "".join([bin(int(x))[2:].rjust(8, "0") for x in ipaddr.split(".")]) 1210 1211 1212def _get_iface_info(iface): 1213 """ 1214 If `iface` is available, return interface info and no error, otherwise 1215 return no info and log and return an error 1216 """ 1217 iface_info = interfaces() 1218 1219 if iface in iface_info.keys(): 1220 return iface_info, False 1221 else: 1222 error_msg = 'Interface "{}" not in available interfaces: "{}"'.format( 1223 iface, '", "'.join(iface_info.keys()) 1224 ) 1225 log.error(error_msg) 1226 return None, error_msg 1227 1228 1229def _hw_addr_aix(iface): 1230 """ 1231 Return the hardware address (a.k.a. MAC address) for a given interface on AIX 1232 MAC address not available in through interfaces 1233 """ 1234 cmd = subprocess.Popen( 1235 ["grep", "Hardware Address"], 1236 stdin=subprocess.Popen( 1237 ["entstat", "-d", iface], 1238 stdout=subprocess.PIPE, 1239 stderr=subprocess.STDOUT, 1240 ).stdout, 1241 stdout=subprocess.PIPE, 1242 stderr=subprocess.STDOUT, 1243 ).communicate()[0] 1244 1245 if cmd: 1246 comps = cmd.split(" ") 1247 if len(comps) == 3: 1248 mac_addr = comps[2].strip("'").strip() 1249 return mac_addr 1250 1251 error_msg = 'Interface "{}" either not available or does not contain a hardware address'.format( 1252 iface 1253 ) 1254 log.error(error_msg) 1255 return error_msg 1256 1257 1258def hw_addr(iface): 1259 """ 1260 Return the hardware address (a.k.a. MAC address) for a given interface 1261 1262 .. versionchanged:: 2016.11.4 1263 Added support for AIX 1264 1265 """ 1266 if salt.utils.platform.is_aix(): 1267 return _hw_addr_aix 1268 1269 iface_info, error = _get_iface_info(iface) 1270 1271 if error is False: 1272 return iface_info.get(iface, {}).get("hwaddr", "") 1273 else: 1274 return error 1275 1276 1277def interface(iface): 1278 """ 1279 Return the details of `iface` or an error if it does not exist 1280 """ 1281 iface_info, error = _get_iface_info(iface) 1282 1283 if error is False: 1284 return iface_info.get(iface, {}).get("inet", "") 1285 else: 1286 return error 1287 1288 1289def interface_ip(iface): 1290 """ 1291 Return `iface` IPv4 addr or an error if `iface` does not exist 1292 """ 1293 iface_info, error = _get_iface_info(iface) 1294 1295 if error is False: 1296 inet = iface_info.get(iface, {}).get("inet", None) 1297 return inet[0].get("address", "") if inet else "" 1298 else: 1299 return error 1300 1301 1302def _subnets(proto="inet", interfaces_=None): 1303 """ 1304 Returns a list of subnets to which the host belongs 1305 """ 1306 if interfaces_ is None: 1307 ifaces = interfaces() 1308 elif isinstance(interfaces_, list): 1309 ifaces = {} 1310 for key, value in interfaces().items(): 1311 if key in interfaces_: 1312 ifaces[key] = value 1313 else: 1314 ifaces = {interfaces_: interfaces().get(interfaces_, {})} 1315 1316 ret = set() 1317 1318 if proto == "inet": 1319 subnet = "netmask" 1320 dflt_cidr = 32 1321 elif proto == "inet6": 1322 subnet = "prefixlen" 1323 dflt_cidr = 128 1324 else: 1325 log.error("Invalid proto %s calling subnets()", proto) 1326 return 1327 1328 for ip_info in ifaces.values(): 1329 addrs = ip_info.get(proto, []) 1330 addrs.extend( 1331 [addr for addr in ip_info.get("secondary", []) if addr.get("type") == proto] 1332 ) 1333 1334 for intf in addrs: 1335 if subnet in intf: 1336 intf = ipaddress.ip_interface( 1337 "{}/{}".format(intf["address"], intf[subnet]) 1338 ) 1339 else: 1340 intf = ipaddress.ip_interface( 1341 "{}/{}".format(intf["address"], dflt_cidr) 1342 ) 1343 if not intf.is_loopback: 1344 ret.add(intf.network) 1345 return [str(net) for net in sorted(ret)] 1346 1347 1348def subnets(interfaces=None): 1349 """ 1350 Returns a list of IPv4 subnets to which the host belongs 1351 """ 1352 return _subnets("inet", interfaces_=interfaces) 1353 1354 1355def subnets6(): 1356 """ 1357 Returns a list of IPv6 subnets to which the host belongs 1358 """ 1359 return _subnets("inet6") 1360 1361 1362def in_subnet(cidr, addr=None): 1363 """ 1364 Returns True if host or (any of) addrs is within specified subnet, otherwise False 1365 """ 1366 try: 1367 cidr = ipaddress.ip_network(cidr) 1368 except ValueError: 1369 log.error("Invalid CIDR '%s'", cidr) 1370 return False 1371 1372 if addr is None: 1373 addr = ip_addrs() 1374 addr.extend(ip_addrs6()) 1375 elif not isinstance(addr, (list, tuple)): 1376 addr = (addr,) 1377 1378 return any(ipaddress.ip_address(item) in cidr for item in addr) 1379 1380 1381def _get_ips(ifaces, proto="inet"): 1382 """ 1383 Accepts a dict of interface data and returns a list of dictionaries 1384 """ 1385 ret = [] 1386 for ip_info in ifaces.values(): 1387 ret.extend(ip_info.get(proto, [])) 1388 ret.extend( 1389 [addr for addr in ip_info.get("secondary", []) if addr.get("type") == proto] 1390 ) 1391 return ret 1392 1393 1394def _filter_interfaces(interface=None, interface_data=None): 1395 """ 1396 Gather interface data if not passed in, and optionally filter by the 1397 specified interface name. 1398 """ 1399 ifaces = interface_data if isinstance(interface_data, dict) else interfaces() 1400 if interface is None: 1401 ret = ifaces 1402 else: 1403 interface = salt.utils.args.split_input(interface) 1404 # pylint: disable=not-an-iterable 1405 ret = { 1406 k: v 1407 for k, v in ifaces.items() 1408 if any(fnmatch.fnmatch(k, pat) for pat in interface) 1409 } 1410 # pylint: enable=not-an-iterable 1411 return ret 1412 1413 1414def _ip_addrs( 1415 interface=None, include_loopback=False, interface_data=None, proto="inet" 1416): 1417 """ 1418 Return the full list of IP adresses matching the criteria 1419 1420 proto = inet|inet6 1421 """ 1422 addrs = _get_ips(_filter_interfaces(interface, interface_data), proto=proto) 1423 1424 ret = set() 1425 for addr in addrs: 1426 addr = ipaddress.ip_address(addr.get("address")) 1427 if not addr.is_loopback or include_loopback: 1428 ret.add(addr) 1429 1430 return [str(addr) for addr in sorted(ret)] 1431 1432 1433def ip_addrs(interface=None, include_loopback=False, interface_data=None): 1434 """ 1435 Returns a list of IPv4 addresses assigned to the host. 127.0.0.1 is 1436 ignored, unless 'include_loopback=True' is indicated. If 'interface' is 1437 provided, then only IP addresses from that interface will be returned. 1438 """ 1439 return _ip_addrs(interface, include_loopback, interface_data, "inet") 1440 1441 1442def ip_addrs6(interface=None, include_loopback=False, interface_data=None): 1443 """ 1444 Returns a list of IPv6 addresses assigned to the host. ::1 is ignored, 1445 unless 'include_loopback=True' is indicated. If 'interface' is provided, 1446 then only IP addresses from that interface will be returned. 1447 """ 1448 return _ip_addrs(interface, include_loopback, interface_data, "inet6") 1449 1450 1451def _ip_networks( 1452 interface=None, 1453 include_loopback=False, 1454 verbose=False, 1455 interface_data=None, 1456 proto="inet", 1457): 1458 """ 1459 Returns a list of networks to which the minion belongs. The results can be 1460 restricted to a single interface using the ``interface`` argument. 1461 """ 1462 addrs = _get_ips(_filter_interfaces(interface, interface_data), proto=proto) 1463 1464 ret = set() 1465 for addr in addrs: 1466 _ip = addr.get("address") 1467 _net = addr.get("netmask" if proto == "inet" else "prefixlen") 1468 if _ip and _net: 1469 try: 1470 ip_net = ipaddress.ip_network("{}/{}".format(_ip, _net), strict=False) 1471 except Exception: # pylint: disable=broad-except 1472 continue 1473 if not ip_net.is_loopback or include_loopback: 1474 ret.add(ip_net) 1475 1476 if not verbose: 1477 return [str(addr) for addr in sorted(ret)] 1478 1479 verbose_ret = { 1480 str(x): { 1481 "address": str(x.network_address), 1482 "netmask": str(x.netmask), 1483 "num_addresses": x.num_addresses, 1484 "prefixlen": x.prefixlen, 1485 } 1486 for x in ret 1487 } 1488 return verbose_ret 1489 1490 1491def ip_networks( 1492 interface=None, include_loopback=False, verbose=False, interface_data=None 1493): 1494 """ 1495 Returns the IPv4 networks to which the minion belongs. Networks will be 1496 returned as a list of network/prefixlen. To get more information about a 1497 each network, use verbose=True and a dictionary with more information will 1498 be returned. 1499 """ 1500 return _ip_networks( 1501 interface=interface, 1502 include_loopback=include_loopback, 1503 verbose=verbose, 1504 interface_data=interface_data, 1505 proto="inet", 1506 ) 1507 1508 1509def ip_networks6( 1510 interface=None, include_loopback=False, verbose=False, interface_data=None 1511): 1512 """ 1513 Returns the IPv6 networks to which the minion belongs. Networks will be 1514 returned as a list of network/prefixlen. To get more information about a 1515 each network, use verbose=True and a dictionary with more information will 1516 be returned. 1517 """ 1518 return _ip_networks( 1519 interface=interface, 1520 include_loopback=include_loopback, 1521 verbose=verbose, 1522 interface_data=interface_data, 1523 proto="inet6", 1524 ) 1525 1526 1527def hex2ip(hex_ip, invert=False): 1528 """ 1529 Convert a hex string to an ip, if a failure occurs the original hex is 1530 returned. If 'invert=True' assume that ip from /proc/net/<proto> 1531 """ 1532 if len(hex_ip) == 32: # ipv6 1533 ip_addr = [] 1534 for i in range(0, 32, 8): 1535 ip_part = hex_ip[i : i + 8] 1536 ip_part = [ip_part[x : x + 2] for x in range(0, 8, 2)] 1537 if invert: 1538 ip_addr.append("{0[3]}{0[2]}:{0[1]}{0[0]}".format(ip_part)) 1539 else: 1540 ip_addr.append("{0[0]}{0[1]}:{0[2]}{0[3]}".format(ip_part)) 1541 try: 1542 address = ipaddress.IPv6Address(":".join(ip_addr)) 1543 if address.ipv4_mapped: 1544 return str(address.ipv4_mapped) 1545 else: 1546 return address.compressed 1547 except ipaddress.AddressValueError as ex: 1548 log.error("hex2ip - ipv6 address error: %s", ex) 1549 return hex_ip 1550 1551 try: 1552 hip = int(hex_ip, 16) 1553 except ValueError: 1554 return hex_ip 1555 if invert: 1556 return "{3}.{2}.{1}.{0}".format( 1557 hip >> 24 & 255, hip >> 16 & 255, hip >> 8 & 255, hip & 255 1558 ) 1559 return "{}.{}.{}.{}".format( 1560 hip >> 24 & 255, hip >> 16 & 255, hip >> 8 & 255, hip & 255 1561 ) 1562 1563 1564def mac2eui64(mac, prefix=None): 1565 """ 1566 Convert a MAC address to a EUI64 identifier 1567 or, with prefix provided, a full IPv6 address 1568 """ 1569 # http://tools.ietf.org/html/rfc4291#section-2.5.1 1570 eui64 = re.sub(r"[.:-]", "", mac).lower() 1571 eui64 = eui64[0:6] + "fffe" + eui64[6:] 1572 eui64 = hex(int(eui64[0:2], 16) | 2)[2:].zfill(2) + eui64[2:] 1573 1574 if prefix is None: 1575 return ":".join(re.findall(r".{4}", eui64)) 1576 else: 1577 try: 1578 net = ipaddress.ip_network(prefix, strict=False) 1579 euil = int("0x{}".format(eui64), 16) 1580 return "{}/{}".format(net[euil], net.prefixlen) 1581 except Exception: # pylint: disable=broad-except 1582 return 1583 1584 1585def active_tcp(): 1586 """ 1587 Return a dict describing all active tcp connections as quickly as possible 1588 """ 1589 ret = {} 1590 for statf in ["/proc/net/tcp", "/proc/net/tcp6"]: 1591 if not os.path.isfile(statf): 1592 continue 1593 with salt.utils.files.fopen(statf, "rb") as fp_: 1594 for line in fp_: 1595 line = salt.utils.stringutils.to_unicode(line) 1596 if line.strip().startswith("sl"): 1597 continue 1598 iret = _parse_tcp_line(line) 1599 slot = next(iter(iret)) 1600 if iret[slot]["state"] == 1: # 1 is ESTABLISHED 1601 del iret[slot]["state"] 1602 ret[len(ret)] = iret[slot] 1603 return ret 1604 1605 1606def local_port_tcp(port): 1607 """ 1608 Return a set of remote ip addrs attached to the specified local port 1609 """ 1610 ret = _remotes_on(port, "local_port") 1611 return ret 1612 1613 1614def remote_port_tcp(port): 1615 """ 1616 Return a set of ip addrs the current host is connected to on given port 1617 """ 1618 ret = _remotes_on(port, "remote_port") 1619 return ret 1620 1621 1622def _remotes_on(port, which_end): 1623 """ 1624 Return a set of ip addrs active tcp connections 1625 """ 1626 port = int(port) 1627 1628 ret = _netlink_tool_remote_on(port, which_end) 1629 if ret is not None: 1630 return ret 1631 1632 ret = set() 1633 proc_available = False 1634 for statf in ["/proc/net/tcp", "/proc/net/tcp6"]: 1635 if not os.path.isfile(statf): 1636 continue 1637 proc_available = True 1638 with salt.utils.files.fopen(statf, "r") as fp_: 1639 for line in fp_: 1640 line = salt.utils.stringutils.to_unicode(line) 1641 if line.strip().startswith("sl"): 1642 continue 1643 iret = _parse_tcp_line(line) 1644 slot = next(iter(iret)) 1645 if ( 1646 iret[slot][which_end] == port and iret[slot]["state"] == 1 1647 ): # 1 is ESTABLISHED 1648 ret.add(iret[slot]["remote_addr"]) 1649 1650 if not proc_available: # Fallback to use OS specific tools 1651 if salt.utils.platform.is_sunos(): 1652 return _sunos_remotes_on(port, which_end) 1653 if salt.utils.platform.is_freebsd(): 1654 return _freebsd_remotes_on(port, which_end) 1655 if salt.utils.platform.is_netbsd(): 1656 return _netbsd_remotes_on(port, which_end) 1657 if salt.utils.platform.is_openbsd(): 1658 return _openbsd_remotes_on(port, which_end) 1659 if salt.utils.platform.is_windows(): 1660 return _windows_remotes_on(port, which_end) 1661 if salt.utils.platform.is_aix(): 1662 return _aix_remotes_on(port, which_end) 1663 1664 return _linux_remotes_on(port, which_end) 1665 1666 return ret 1667 1668 1669def _parse_tcp_line(line): 1670 """ 1671 Parse a single line from the contents of /proc/net/tcp or /proc/net/tcp6 1672 """ 1673 ret = {} 1674 comps = line.strip().split() 1675 slot = comps[0].rstrip(":") 1676 ret[slot] = {} 1677 l_addr, l_port = comps[1].split(":") 1678 r_addr, r_port = comps[2].split(":") 1679 ret[slot]["local_addr"] = hex2ip(l_addr, True) 1680 ret[slot]["local_port"] = int(l_port, 16) 1681 ret[slot]["remote_addr"] = hex2ip(r_addr, True) 1682 ret[slot]["remote_port"] = int(r_port, 16) 1683 ret[slot]["state"] = int(comps[3], 16) 1684 return ret 1685 1686 1687def _netlink_tool_remote_on(port, which_end): 1688 """ 1689 Returns set of IPv4/IPv6 host addresses of remote established connections 1690 on local or remote tcp port. 1691 1692 Parses output of shell 'ss' to get connections 1693 1694 [root@salt-master ~]# ss -ant 1695 State Recv-Q Send-Q Local Address:Port Peer Address:Port 1696 LISTEN 0 511 *:80 *:* 1697 LISTEN 0 128 *:22 *:* 1698 ESTAB 0 0 127.0.0.1:56726 127.0.0.1:4505 1699 ESTAB 0 0 [::ffff:127.0.0.1]:41323 [::ffff:127.0.0.1]:4505 1700 """ 1701 remotes = set() 1702 valid = False 1703 tcp_end = "dst" if which_end == "remote_port" else "src" 1704 try: 1705 data = subprocess.check_output( 1706 ["ss", "-ant", tcp_end, ":{}".format(port)] 1707 ) # pylint: disable=minimum-python-version 1708 except subprocess.CalledProcessError: 1709 log.error("Failed ss") 1710 raise 1711 except OSError: # not command "No such file or directory" 1712 return None 1713 1714 lines = salt.utils.stringutils.to_str(data).split("\n") 1715 for line in lines: 1716 if "Address:Port" in line: # ss tools may not be valid 1717 valid = True 1718 continue 1719 elif "ESTAB" not in line: 1720 continue 1721 chunks = line.split() 1722 remote_host, remote_port = chunks[4].rsplit(":", 1) 1723 1724 remotes.add(remote_host.strip("[]")) 1725 1726 if valid is False: 1727 remotes = None 1728 return remotes 1729 1730 1731def _sunos_remotes_on(port, which_end): 1732 """ 1733 SunOS specific helper function. 1734 Returns set of ipv4 host addresses of remote established connections 1735 on local or remote tcp port. 1736 1737 Parses output of shell 'netstat' to get connections 1738 1739 [root@salt-master ~]# netstat -f inet -n 1740 TCP: IPv4 1741 Local Address Remote Address Swind Send-Q Rwind Recv-Q State 1742 -------------------- -------------------- ----- ------ ----- ------ ----------- 1743 10.0.0.101.4505 10.0.0.1.45329 1064800 0 1055864 0 ESTABLISHED 1744 10.0.0.101.4505 10.0.0.100.50798 1064800 0 1055864 0 ESTABLISHED 1745 """ 1746 remotes = set() 1747 try: 1748 data = subprocess.check_output( 1749 ["netstat", "-f", "inet", "-n"] 1750 ) # pylint: disable=minimum-python-version 1751 except subprocess.CalledProcessError: 1752 log.error("Failed netstat") 1753 raise 1754 1755 lines = salt.utils.stringutils.to_str(data).split("\n") 1756 for line in lines: 1757 if "ESTABLISHED" not in line: 1758 continue 1759 chunks = line.split() 1760 local_host, local_port = chunks[0].rsplit(".", 1) 1761 remote_host, remote_port = chunks[1].rsplit(".", 1) 1762 1763 if which_end == "remote_port" and int(remote_port) != port: 1764 continue 1765 if which_end == "local_port" and int(local_port) != port: 1766 continue 1767 remotes.add(remote_host) 1768 return remotes 1769 1770 1771def _freebsd_remotes_on(port, which_end): 1772 """ 1773 Returns set of ipv4 host addresses of remote established connections 1774 on local tcp port port. 1775 1776 Parses output of shell 'sockstat' (FreeBSD) 1777 to get connections 1778 1779 $ sudo sockstat -4 1780 USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS 1781 root python2.7 1456 29 tcp4 *:4505 *:* 1782 root python2.7 1445 17 tcp4 *:4506 *:* 1783 root python2.7 1294 14 tcp4 127.0.0.1:11813 127.0.0.1:4505 1784 root python2.7 1294 41 tcp4 127.0.0.1:61115 127.0.0.1:4506 1785 1786 $ sudo sockstat -4 -c -p 4506 1787 USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS 1788 root python2.7 1294 41 tcp4 127.0.0.1:61115 127.0.0.1:4506 1789 """ 1790 1791 port = int(port) 1792 remotes = set() 1793 1794 try: 1795 cmd = salt.utils.args.shlex_split("sockstat -4 -c -p {}".format(port)) 1796 data = subprocess.check_output(cmd) # pylint: disable=minimum-python-version 1797 except subprocess.CalledProcessError as ex: 1798 log.error('Failed "sockstat" with returncode = %s', ex.returncode) 1799 raise 1800 1801 lines = salt.utils.stringutils.to_str(data).split("\n") 1802 1803 for line in lines: 1804 chunks = line.split() 1805 if not chunks: 1806 continue 1807 # ['root', 'python2.7', '1456', '37', 'tcp4', 1808 # '127.0.0.1:4505-', '127.0.0.1:55703'] 1809 # print chunks 1810 if "COMMAND" in chunks[1]: 1811 continue # ignore header 1812 if len(chunks) < 2: 1813 continue 1814 # sockstat -4 -c -p 4506 does this with high PIDs: 1815 # USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS 1816 # salt-master python2.781106 35 tcp4 192.168.12.34:4506 192.168.12.45:60143 1817 local = chunks[-2] 1818 remote = chunks[-1] 1819 lhost, lport = local.split(":") 1820 rhost, rport = remote.split(":") 1821 if which_end == "local" and int(lport) != port: # ignore if local port not port 1822 continue 1823 if ( 1824 which_end == "remote" and int(rport) != port 1825 ): # ignore if remote port not port 1826 continue 1827 1828 remotes.add(rhost) 1829 1830 return remotes 1831 1832 1833def _netbsd_remotes_on(port, which_end): 1834 """ 1835 Returns set of ipv4 host addresses of remote established connections 1836 on local tcp port port. 1837 1838 Parses output of shell 'sockstat' (NetBSD) 1839 to get connections 1840 1841 $ sudo sockstat -4 -n 1842 USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS 1843 root python2.7 1456 29 tcp *.4505 *.* 1844 root python2.7 1445 17 tcp *.4506 *.* 1845 root python2.7 1294 14 tcp 127.0.0.1.11813 127.0.0.1.4505 1846 root python2.7 1294 41 tcp 127.0.0.1.61115 127.0.0.1.4506 1847 1848 $ sudo sockstat -4 -c -n -p 4506 1849 USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS 1850 root python2.7 1294 41 tcp 127.0.0.1.61115 127.0.0.1.4506 1851 """ 1852 1853 port = int(port) 1854 remotes = set() 1855 1856 try: 1857 cmd = salt.utils.args.shlex_split("sockstat -4 -c -n -p {}".format(port)) 1858 data = subprocess.check_output(cmd) # pylint: disable=minimum-python-version 1859 except subprocess.CalledProcessError as ex: 1860 log.error('Failed "sockstat" with returncode = %s', ex.returncode) 1861 raise 1862 1863 lines = salt.utils.stringutils.to_str(data).split("\n") 1864 1865 for line in lines: 1866 chunks = line.split() 1867 if not chunks: 1868 continue 1869 # ['root', 'python2.7', '1456', '37', 'tcp', 1870 # '127.0.0.1.4505-', '127.0.0.1.55703'] 1871 # print chunks 1872 if "COMMAND" in chunks[1]: 1873 continue # ignore header 1874 if len(chunks) < 2: 1875 continue 1876 local = chunks[5].split(".") 1877 lport = local.pop() 1878 lhost = ".".join(local) 1879 remote = chunks[6].split(".") 1880 rport = remote.pop() 1881 rhost = ".".join(remote) 1882 if which_end == "local" and int(lport) != port: # ignore if local port not port 1883 continue 1884 if ( 1885 which_end == "remote" and int(rport) != port 1886 ): # ignore if remote port not port 1887 continue 1888 1889 remotes.add(rhost) 1890 1891 return remotes 1892 1893 1894def _openbsd_remotes_on(port, which_end): 1895 """ 1896 OpenBSD specific helper function. 1897 Returns set of ipv4 host addresses of remote established connections 1898 on local or remote tcp port. 1899 1900 Parses output of shell 'netstat' to get connections 1901 1902 $ netstat -nf inet 1903 Active Internet connections 1904 Proto Recv-Q Send-Q Local Address Foreign Address (state) 1905 tcp 0 0 10.0.0.101.4505 10.0.0.1.45329 ESTABLISHED 1906 tcp 0 0 10.0.0.101.4505 10.0.0.100.50798 ESTABLISHED 1907 """ 1908 remotes = set() 1909 try: 1910 data = subprocess.check_output( 1911 ["netstat", "-nf", "inet"] 1912 ) # pylint: disable=minimum-python-version 1913 except subprocess.CalledProcessError: 1914 log.error("Failed netstat") 1915 raise 1916 1917 lines = data.split("\n") 1918 for line in lines: 1919 if "ESTABLISHED" not in line: 1920 continue 1921 chunks = line.split() 1922 local_host, local_port = chunks[3].rsplit(".", 1) 1923 remote_host, remote_port = chunks[4].rsplit(".", 1) 1924 1925 if which_end == "remote_port" and int(remote_port) != port: 1926 continue 1927 if which_end == "local_port" and int(local_port) != port: 1928 continue 1929 remotes.add(remote_host) 1930 return remotes 1931 1932 1933def _windows_remotes_on(port, which_end): 1934 r""" 1935 Windows specific helper function. 1936 Returns set of ipv4 host addresses of remote established connections 1937 on local or remote tcp port. 1938 1939 Parses output of shell 'netstat' to get connections 1940 1941 C:\>netstat -n 1942 1943 Active Connections 1944 1945 Proto Local Address Foreign Address State 1946 TCP 10.2.33.17:3007 130.164.12.233:10123 ESTABLISHED 1947 TCP 10.2.33.17:3389 130.164.30.5:10378 ESTABLISHED 1948 """ 1949 remotes = set() 1950 try: 1951 data = subprocess.check_output( 1952 ["netstat", "-n"] 1953 ) # pylint: disable=minimum-python-version 1954 except subprocess.CalledProcessError: 1955 log.error("Failed netstat") 1956 raise 1957 1958 lines = salt.utils.stringutils.to_str(data).split("\n") 1959 for line in lines: 1960 if "ESTABLISHED" not in line: 1961 continue 1962 chunks = line.split() 1963 local_host, local_port = chunks[1].rsplit(":", 1) 1964 remote_host, remote_port = chunks[2].rsplit(":", 1) 1965 if which_end == "remote_port" and int(remote_port) != port: 1966 continue 1967 if which_end == "local_port" and int(local_port) != port: 1968 continue 1969 remotes.add(remote_host) 1970 return remotes 1971 1972 1973def _linux_remotes_on(port, which_end): 1974 """ 1975 Linux specific helper function. 1976 Returns set of ip host addresses of remote established connections 1977 on local tcp port port. 1978 1979 Parses output of shell 'lsof' 1980 to get connections 1981 1982 $ sudo lsof -iTCP:4505 -n 1983 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME 1984 Python 9971 root 35u IPv4 0x18a8464a29ca329d 0t0 TCP *:4505 (LISTEN) 1985 Python 9971 root 37u IPv4 0x18a8464a29b2b29d 0t0 TCP 127.0.0.1:4505->127.0.0.1:55703 (ESTABLISHED) 1986 Python 10152 root 22u IPv4 0x18a8464a29c8cab5 0t0 TCP 127.0.0.1:55703->127.0.0.1:4505 (ESTABLISHED) 1987 Python 10153 root 22u IPv4 0x18a8464a29c8cab5 0t0 TCP [fe80::249a]:4505->[fe80::150]:59367 (ESTABLISHED) 1988 1989 """ 1990 remotes = set() 1991 1992 try: 1993 data = subprocess.check_output( 1994 [ 1995 "lsof", 1996 "-iTCP:{:d}".format(port), 1997 "-n", 1998 "-P", 1999 ] # pylint: disable=minimum-python-version 2000 ) 2001 except subprocess.CalledProcessError as ex: 2002 if ex.returncode == 1: 2003 # Lsof return 1 if any error was detected, including the failure 2004 # to locate Internet addresses, and it is not an error in this case. 2005 log.warning('"lsof" returncode = 1, likely no active TCP sessions.') 2006 return remotes 2007 log.error('Failed "lsof" with returncode = %s', ex.returncode) 2008 raise 2009 2010 lines = salt.utils.stringutils.to_str(data).split("\n") 2011 for line in lines: 2012 chunks = line.split() 2013 if not chunks: 2014 continue 2015 # ['Python', '9971', 'root', '37u', 'IPv4', '0x18a8464a29b2b29d', '0t0', 2016 # 'TCP', '127.0.0.1:4505->127.0.0.1:55703', '(ESTABLISHED)'] 2017 # print chunks 2018 if "COMMAND" in chunks[0]: 2019 continue # ignore header 2020 if "ESTABLISHED" not in chunks[-1]: 2021 continue # ignore if not ESTABLISHED 2022 # '127.0.0.1:4505->127.0.0.1:55703' 2023 local, remote = chunks[8].split("->") 2024 _, lport = local.rsplit(":", 1) 2025 rhost, rport = remote.rsplit(":", 1) 2026 if which_end == "remote_port" and int(rport) != port: 2027 continue 2028 if which_end == "local_port" and int(lport) != port: 2029 continue 2030 remotes.add(rhost.strip("[]")) 2031 2032 return remotes 2033 2034 2035def _aix_remotes_on(port, which_end): 2036 """ 2037 AIX specific helper function. 2038 Returns set of ipv4 host addresses of remote established connections 2039 on local or remote tcp port. 2040 2041 Parses output of shell 'netstat' to get connections 2042 2043 root@la68pp002_pub:/opt/salt/lib/python2.7/site-packages/salt/modules# netstat -f inet -n 2044 Active Internet connections 2045 Proto Recv-Q Send-Q Local Address Foreign Address (state) 2046 tcp4 0 0 172.29.149.95.50093 209.41.78.13.4505 ESTABLISHED 2047 tcp4 0 0 127.0.0.1.9514 *.* LISTEN 2048 tcp4 0 0 127.0.0.1.9515 *.* LISTEN 2049 tcp4 0 0 127.0.0.1.199 127.0.0.1.32779 ESTABLISHED 2050 tcp4 0 0 127.0.0.1.32779 127.0.0.1.199 ESTABLISHED 2051 tcp4 0 40 172.29.149.95.22 172.29.96.83.41022 ESTABLISHED 2052 tcp4 0 0 172.29.149.95.22 172.29.96.83.41032 ESTABLISHED 2053 tcp4 0 0 127.0.0.1.32771 127.0.0.1.32775 ESTABLISHED 2054 tcp 0 0 127.0.0.1.32775 127.0.0.1.32771 ESTABLISHED 2055 tcp4 0 0 127.0.0.1.32771 127.0.0.1.32776 ESTABLISHED 2056 tcp 0 0 127.0.0.1.32776 127.0.0.1.32771 ESTABLISHED 2057 tcp4 0 0 127.0.0.1.32771 127.0.0.1.32777 ESTABLISHED 2058 tcp 0 0 127.0.0.1.32777 127.0.0.1.32771 ESTABLISHED 2059 tcp4 0 0 127.0.0.1.32771 127.0.0.1.32778 ESTABLISHED 2060 tcp 0 0 127.0.0.1.32778 127.0.0.1.32771 ESTABLISHED 2061 """ 2062 remotes = set() 2063 try: 2064 data = subprocess.check_output( 2065 ["netstat", "-f", "inet", "-n"] 2066 ) # pylint: disable=minimum-python-version 2067 except subprocess.CalledProcessError: 2068 log.error("Failed netstat") 2069 raise 2070 2071 lines = salt.utils.stringutils.to_str(data).split("\n") 2072 for line in lines: 2073 if "ESTABLISHED" not in line: 2074 continue 2075 chunks = line.split() 2076 local_host, local_port = chunks[3].rsplit(".", 1) 2077 remote_host, remote_port = chunks[4].rsplit(".", 1) 2078 2079 if which_end == "remote_port" and int(remote_port) != port: 2080 continue 2081 if which_end == "local_port" and int(local_port) != port: 2082 continue 2083 remotes.add(remote_host) 2084 return remotes 2085 2086 2087@jinja_filter("gen_mac") 2088def gen_mac(prefix="AC:DE:48"): 2089 """ 2090 Generates a MAC address with the defined OUI prefix. 2091 2092 Common prefixes: 2093 2094 - ``00:16:3E`` -- Xen 2095 - ``00:18:51`` -- OpenVZ 2096 - ``00:50:56`` -- VMware (manually generated) 2097 - ``52:54:00`` -- QEMU/KVM 2098 - ``AC:DE:48`` -- PRIVATE 2099 2100 References: 2101 2102 - http://standards.ieee.org/develop/regauth/oui/oui.txt 2103 - https://www.wireshark.org/tools/oui-lookup.html 2104 - https://en.wikipedia.org/wiki/MAC_address 2105 """ 2106 return "{}:{:02X}:{:02X}:{:02X}".format( 2107 prefix, 2108 random.randint(0, 0xFF), 2109 random.randint(0, 0xFF), 2110 random.randint(0, 0xFF), 2111 ) 2112 2113 2114@jinja_filter("mac_str_to_bytes") 2115def mac_str_to_bytes(mac_str): 2116 """ 2117 Convert a MAC address string into bytes. Works with or without separators: 2118 2119 b1 = mac_str_to_bytes('08:00:27:13:69:77') 2120 b2 = mac_str_to_bytes('080027136977') 2121 assert b1 == b2 2122 assert isinstance(b1, bytes) 2123 """ 2124 if len(mac_str) == 12: 2125 pass 2126 elif len(mac_str) == 17: 2127 sep = mac_str[2] 2128 mac_str = mac_str.replace(sep, "") 2129 else: 2130 raise ValueError("Invalid MAC address") 2131 chars = (int(mac_str[s : s + 2], 16) for s in range(0, 12, 2)) 2132 return bytes(chars) 2133 2134 2135def refresh_dns(): 2136 """ 2137 issue #21397: force glibc to re-read resolv.conf 2138 """ 2139 try: 2140 RES_INIT() 2141 except NameError: 2142 # Exception raised loading the library, thus RES_INIT is not defined 2143 pass 2144 2145 2146@jinja_filter("dns_check") 2147def dns_check(addr, port, safe=False, ipv6=None): 2148 """ 2149 Return an ip address resolved by dns in a format usable in URLs (ipv6 in brackets). 2150 Obeys system preference for IPv4/6 address resolution - this can be overridden by 2151 the ipv6 flag. Tries to connect to the address before considering it useful. If no 2152 address can be reached, the first one resolved is used as a fallback. 2153 Does not exit on failure, raises an exception. 2154 """ 2155 ip_addrs = [] 2156 family = ( 2157 socket.AF_INET6 2158 if ipv6 2159 else socket.AF_INET 2160 if ipv6 is False 2161 else socket.AF_UNSPEC 2162 ) 2163 try: 2164 refresh_dns() 2165 addrinfo = socket.getaddrinfo(addr, port, family, socket.SOCK_STREAM) 2166 ip_addrs = _test_addrs(addrinfo, port) 2167 except TypeError: 2168 raise SaltSystemExit( 2169 code=42, 2170 msg=( 2171 "Attempt to resolve address '{}' failed. Invalid or unresolveable" 2172 " address".format(addr) 2173 ), 2174 ) 2175 except OSError: 2176 pass 2177 2178 if not ip_addrs: 2179 err = "DNS lookup or connection check of '{}' failed.".format(addr) 2180 if safe: 2181 if salt.log.is_console_configured(): 2182 # If logging is not configured it also means that either 2183 # the master or minion instance calling this hasn't even 2184 # started running 2185 log.error(err) 2186 raise SaltClientError() 2187 raise SaltSystemExit(code=42, msg=err) 2188 2189 return salt.utils.zeromq.ip_bracket(ip_addrs[0]) 2190 2191 2192def _test_addrs(addrinfo, port): 2193 """ 2194 Attempt to connect to all addresses, return one if it succeeds. 2195 Otherwise, return all addrs. 2196 """ 2197 ip_addrs = [] 2198 # test for connectivity, short circuit on success 2199 for a in addrinfo: 2200 ip_family = a[0] 2201 ip_addr = a[4][0] 2202 if ip_addr in ip_addrs: 2203 continue 2204 ip_addrs.append(ip_addr) 2205 2206 try: 2207 s = socket.socket(ip_family, socket.SOCK_STREAM) 2208 s.settimeout(2) 2209 s.connect((ip_addr, port)) 2210 s.close() 2211 2212 ip_addrs = [ip_addr] 2213 break 2214 except OSError: 2215 pass 2216 return ip_addrs 2217 2218 2219def parse_host_port(host_port): 2220 """ 2221 Takes a string argument specifying host or host:port. 2222 2223 Returns a (hostname, port) or (ip_address, port) tuple. If no port is given, 2224 the second (port) element of the returned tuple will be None. 2225 2226 host:port argument, for example, is accepted in the forms of: 2227 - hostname 2228 - hostname:1234 2229 - hostname.domain.tld 2230 - hostname.domain.tld:5678 2231 - [1234::5]:5678 2232 - 1234::5 2233 - 10.11.12.13:4567 2234 - 10.11.12.13 2235 """ 2236 host, port = None, None # default 2237 2238 _s_ = host_port[:] 2239 if _s_[0] == "[": 2240 if "]" in host_port: 2241 host, _s_ = _s_.lstrip("[").rsplit("]", 1) 2242 host = ipaddress.IPv6Address(host).compressed 2243 if _s_[0] == ":": 2244 port = int(_s_.lstrip(":")) 2245 else: 2246 if len(_s_) > 1: 2247 raise ValueError( 2248 'found ambiguous "{}" port in "{}"'.format(_s_, host_port) 2249 ) 2250 else: 2251 if _s_.count(":") == 1: 2252 host, _hostport_separator_, port = _s_.partition(":") 2253 try: 2254 port = int(port) 2255 except ValueError as _e_: 2256 errmsg = 'host_port "{}" port value "{}" is not an integer.'.format( 2257 host_port, port 2258 ) 2259 log.error(errmsg) 2260 raise ValueError(errmsg) 2261 else: 2262 host = _s_ 2263 try: 2264 if not isinstance(host, ipaddress._BaseAddress): 2265 host_ip = ipaddress.ip_address(host).compressed 2266 host = host_ip 2267 except ValueError: 2268 log.debug('"%s" Not an IP address? Assuming it is a hostname.', host) 2269 if host != sanitize_host(host): 2270 log.error('bad hostname: "%s"', host) 2271 raise ValueError('bad hostname: "{}"'.format(host)) 2272 2273 return host, port 2274 2275 2276@jinja_filter("filter_by_networks") 2277def filter_by_networks(values, networks): 2278 """ 2279 Returns the list of IPs filtered by the network list. 2280 If the network list is an empty sequence, no IPs are returned. 2281 If the network list is None, all IPs are returned. 2282 2283 {% set networks = ['192.168.0.0/24', 'fe80::/64'] %} 2284 {{ grains['ip_interfaces'] | filter_by_networks(networks) }} 2285 {{ grains['ipv6'] | filter_by_networks(networks) }} 2286 {{ grains['ipv4'] | filter_by_networks(networks) }} 2287 """ 2288 2289 _filter = lambda ips, networks: [ 2290 ip for ip in ips for net in networks if ipaddress.ip_address(ip) in net 2291 ] 2292 2293 if networks is not None: 2294 networks = [ipaddress.ip_network(network) for network in networks] 2295 if isinstance(values, Mapping): 2296 return { 2297 interface: _filter(values[interface], networks) for interface in values 2298 } 2299 elif isinstance(values, Sequence): 2300 return _filter(values, networks) 2301 else: 2302 raise ValueError("Do not know how to filter a {}".format(type(values))) 2303 else: 2304 return values 2305