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