%PDF- %PDF-
Direktori : /lib/python2.7/site-packages/salt/modules/ |
Current File : //lib/python2.7/site-packages/salt/modules/network.py |
# -*- coding: utf-8 -*- ''' Module for gathering and managing network information ''' # Import python libs from __future__ import absolute_import, unicode_literals, print_function import datetime import hashlib import logging import re import os import socket # Import salt libs import salt.utils.decorators.path import salt.utils.functools import salt.utils.files import salt.utils.network import salt.utils.path import salt.utils.platform import salt.utils.stringutils import salt.utils.validate.net from salt.exceptions import CommandExecutionError # Import 3rd-party libs from salt.ext import six from salt.ext.six.moves import range # pylint: disable=import-error,no-name-in-module,redefined-builtin from salt._compat import ipaddress log = logging.getLogger(__name__) def __virtual__(): ''' Only work on POSIX-like systems ''' # Disable on Windows, a specific file module exists: if salt.utils.platform.is_windows(): return (False, 'The network execution module cannot be loaded on Windows: use win_network instead.') return True def wol(mac, bcast='255.255.255.255', destport=9): ''' Send Wake On Lan packet to a host CLI Example: .. code-block:: bash salt '*' network.wol 08-00-27-13-69-77 salt '*' network.wol 080027136977 255.255.255.255 7 salt '*' network.wol 08:00:27:13:69:77 255.255.255.255 7 ''' dest = salt.utils.network.mac_str_to_bytes(mac) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) sock.sendto(b'\xff' * 6 + dest * 16, (bcast, int(destport))) return True def ping(host, timeout=False, return_boolean=False): ''' Performs an ICMP ping to a host .. versionchanged:: 2015.8.0 Added support for SunOS CLI Example: .. code-block:: bash salt '*' network.ping archlinux.org .. versionadded:: 2015.5.0 Return a True or False instead of ping output. .. code-block:: bash salt '*' network.ping archlinux.org return_boolean=True Set the time to wait for a response in seconds. .. code-block:: bash salt '*' network.ping archlinux.org timeout=3 ''' if timeout: if __grains__['kernel'] == 'SunOS': cmd = 'ping -c 4 {1} {0}'.format(timeout, salt.utils.network.sanitize_host(host)) else: cmd = 'ping -W {0} -c 4 {1}'.format(timeout, salt.utils.network.sanitize_host(host)) else: cmd = 'ping -c 4 {0}'.format(salt.utils.network.sanitize_host(host)) if return_boolean: ret = __salt__['cmd.run_all'](cmd) if ret['retcode'] != 0: return False else: return True else: return __salt__['cmd.run'](cmd) # FIXME: Does not work with: netstat 1.42 (2001-04-15) from net-tools # 1.6.0 (Ubuntu 10.10) def _netstat_linux(): ''' Return netstat information for Linux distros ''' ret = [] cmd = 'netstat -tulpnea' out = __salt__['cmd.run'](cmd) for line in out.splitlines(): comps = line.split() if line.startswith('tcp'): ret.append({ 'proto': comps[0], 'recv-q': comps[1], 'send-q': comps[2], 'local-address': comps[3], 'remote-address': comps[4], 'state': comps[5], 'user': comps[6], 'inode': comps[7], 'program': comps[8]}) if line.startswith('udp'): ret.append({ 'proto': comps[0], 'recv-q': comps[1], 'send-q': comps[2], 'local-address': comps[3], 'remote-address': comps[4], 'user': comps[5], 'inode': comps[6], 'program': comps[7]}) return ret def _ss_linux(): ''' Return ss information for Linux distros (netstat is deprecated and may not be available) ''' ret = [] cmd = 'ss -tulpnea' out = __salt__['cmd.run'](cmd) for line in out.splitlines(): comps = line.split() ss_user = 0 ss_inode = 0 ss_program = '' length = len(comps) if line.startswith('tcp') or line.startswith('udp'): i = 6 while i < (length - 1): fields = comps[i].split(":") if fields[0] == "users": users = fields[1].split(",") ss_program = users[0].split("\"")[1] if fields[0] == "uid": ss_user = fields[1] if fields[0] == "ino": ss_inode = fields[1] i += 1 if line.startswith('tcp'): ss_state = comps[1] if ss_state == "ESTAB": ss_state = "ESTABLISHED" ret.append({ 'proto': comps[0], 'recv-q': comps[2], 'send-q': comps[3], 'local-address': comps[4], 'remote-address': comps[5], 'state': ss_state, 'user': ss_user, 'inode': ss_inode, 'program': ss_program}) if line.startswith('udp'): ret.append({ 'proto': comps[0], 'recv-q': comps[2], 'send-q': comps[3], 'local-address': comps[4], 'remote-address': comps[5], 'user': ss_user, 'inode': ss_inode, 'program': ss_program}) return ret def _netinfo_openbsd(): ''' Get process information for network connections using fstat ''' ret = {} _fstat_re = re.compile( r'internet(6)? (?:stream tcp 0x\S+ (\S+)|dgram udp (\S+))' r'(?: [<>=-]+ (\S+))?$' ) out = __salt__['cmd.run']('fstat') for line in out.splitlines(): try: user, cmd, pid, _, details = line.split(None, 4) ipv6, tcp, udp, remote_addr = _fstat_re.match(details).groups() except (ValueError, AttributeError): # Line either doesn't have the right number of columns, or the # regex which looks for address information did not match. Either # way, ignore this line and continue on to the next one. continue if tcp: local_addr = tcp proto = 'tcp{0}'.format('' if ipv6 is None else ipv6) else: local_addr = udp proto = 'udp{0}'.format('' if ipv6 is None else ipv6) if ipv6: # IPv6 addresses have the address part enclosed in brackets (if the # address part is not a wildcard) to distinguish the address from # the port number. Remove them. local_addr = ''.join(x for x in local_addr if x not in '[]') # Normalize to match netstat output local_addr = '.'.join(local_addr.rsplit(':', 1)) if remote_addr is None: remote_addr = '*.*' else: remote_addr = '.'.join(remote_addr.rsplit(':', 1)) ret.setdefault( local_addr, {}).setdefault( remote_addr, {}).setdefault( proto, {}).setdefault( pid, {})['user'] = user ret[local_addr][remote_addr][proto][pid]['cmd'] = cmd return ret def _netinfo_freebsd_netbsd(): ''' Get process information for network connections using sockstat ''' ret = {} # NetBSD requires '-n' to disable port-to-service resolution out = __salt__['cmd.run']( 'sockstat -46 {0} | tail -n+2'.format( '-n' if __grains__['kernel'] == 'NetBSD' else '' ), python_shell=True ) for line in out.splitlines(): user, cmd, pid, _, proto, local_addr, remote_addr = line.split() local_addr = '.'.join(local_addr.rsplit(':', 1)) remote_addr = '.'.join(remote_addr.rsplit(':', 1)) ret.setdefault( local_addr, {}).setdefault( remote_addr, {}).setdefault( proto, {}).setdefault( pid, {})['user'] = user ret[local_addr][remote_addr][proto][pid]['cmd'] = cmd return ret def _ppid(): ''' Return a dict of pid to ppid mappings ''' ret = {} if __grains__['kernel'] == 'SunOS': cmd = 'ps -a -o pid,ppid | tail +2' else: cmd = 'ps -ax -o pid,ppid | tail -n+2' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): pid, ppid = line.split() ret[pid] = ppid return ret def _netstat_bsd(): ''' Return netstat information for BSD flavors ''' ret = [] if __grains__['kernel'] == 'NetBSD': for addr_family in ('inet', 'inet6'): cmd = 'netstat -f {0} -an | tail -n+3'.format(addr_family) out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() entry = { 'proto': comps[0], 'recv-q': comps[1], 'send-q': comps[2], 'local-address': comps[3], 'remote-address': comps[4] } if entry['proto'].startswith('tcp'): entry['state'] = comps[5] ret.append(entry) else: # Lookup TCP connections cmd = 'netstat -p tcp -an | tail -n+3' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'proto': comps[0], 'recv-q': comps[1], 'send-q': comps[2], 'local-address': comps[3], 'remote-address': comps[4], 'state': comps[5]}) # Lookup UDP connections cmd = 'netstat -p udp -an | tail -n+3' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'proto': comps[0], 'recv-q': comps[1], 'send-q': comps[2], 'local-address': comps[3], 'remote-address': comps[4]}) # Add in user and program info ppid = _ppid() if __grains__['kernel'] == 'OpenBSD': netinfo = _netinfo_openbsd() elif __grains__['kernel'] in ('FreeBSD', 'NetBSD'): netinfo = _netinfo_freebsd_netbsd() for idx in range(len(ret)): local = ret[idx]['local-address'] remote = ret[idx]['remote-address'] proto = ret[idx]['proto'] try: # Make a pointer to the info for this connection for easier # reference below ptr = netinfo[local][remote][proto] except KeyError: continue # Get the pid-to-ppid mappings for this connection conn_ppid = dict((x, y) for x, y in six.iteritems(ppid) if x in ptr) try: # Master pid for this connection will be the pid whose ppid isn't # in the subset dict we created above master_pid = next(iter( x for x, y in six.iteritems(conn_ppid) if y not in ptr )) except StopIteration: continue ret[idx]['user'] = ptr[master_pid]['user'] ret[idx]['program'] = '/'.join((master_pid, ptr[master_pid]['cmd'])) return ret def _netstat_sunos(): ''' Return netstat information for SunOS flavors ''' log.warning('User and program not (yet) supported on SunOS') ret = [] for addr_family in ('inet', 'inet6'): # Lookup TCP connections cmd = 'netstat -f {0} -P tcp -an | tail +5'.format(addr_family) out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'proto': 'tcp6' if addr_family == 'inet6' else 'tcp', 'recv-q': comps[5], 'send-q': comps[4], 'local-address': comps[0], 'remote-address': comps[1], 'state': comps[6]}) # Lookup UDP connections cmd = 'netstat -f {0} -P udp -an | tail +5'.format(addr_family) out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'proto': 'udp6' if addr_family == 'inet6' else 'udp', 'local-address': comps[0], 'remote-address': comps[1] if len(comps) > 2 else ''}) return ret def _netstat_aix(): ''' Return netstat information for SunOS flavors ''' ret = [] ## AIX 6.1 - 7.2, appears to ignore addr_family field contents ## for addr_family in ('inet', 'inet6'): for addr_family in ('inet',): # Lookup connections cmd = 'netstat -n -a -f {0} | tail -n +3'.format(addr_family) out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() if len(comps) < 5: continue proto_seen = None tcp_flag = True if 'tcp' == comps[0] or 'tcp4' == comps[0]: proto_seen = 'tcp' elif 'tcp6' == comps[0]: proto_seen = 'tcp6' elif 'udp' == comps[0] or 'udp4' == comps[0]: proto_seen = 'udp' tcp_flag = False elif 'udp6' == comps[0]: proto_seen = 'udp6' tcp_flag = False if tcp_flag: if len(comps) >= 6: ret.append({ 'proto': proto_seen, 'recv-q': comps[1], 'send-q': comps[2], 'local-address': comps[3], 'remote-address': comps[4], 'state': comps[5]}) else: if len(comps) >= 5: ret.append({ 'proto': proto_seen, 'local-address': comps[3], 'remote-address': comps[4]}) return ret def _netstat_route_linux(): ''' Return netstat routing information for Linux distros ''' ret = [] cmd = 'netstat -A inet -rn | tail -n+3' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'addr_family': 'inet', 'destination': comps[0], 'gateway': comps[1], 'netmask': comps[2], 'flags': comps[3], 'interface': comps[7]}) cmd = 'netstat -A inet6 -rn | tail -n+3' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() if len(comps) == 6: ret.append({ 'addr_family': 'inet6', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[2], 'interface': comps[5]}) elif len(comps) == 7: ret.append({ 'addr_family': 'inet6', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[2], 'interface': comps[6]}) else: continue return ret def _ip_route_linux(): ''' Return ip routing information for Linux distros (netstat is deprecated and may not be available) ''' # table main closest to old netstat inet output ret = [] cmd = 'ip -4 route show table main' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() # need to fake similar output to that provided by netstat # to maintain output format if comps[0] == "unreachable": continue if comps[0] == "default": ip_interface = '' if comps[3] == "dev": ip_interface = comps[4] ret.append({ 'addr_family': 'inet', 'destination': '0.0.0.0', 'gateway': comps[2], 'netmask': '0.0.0.0', 'flags': 'UG', 'interface': ip_interface}) else: address_mask = convert_cidr(comps[0]) ip_interface = '' if comps[1] == "dev": ip_interface = comps[2] ret.append({ 'addr_family': 'inet', 'destination': address_mask['network'], 'gateway': '0.0.0.0', 'netmask': address_mask['netmask'], 'flags': 'U', 'interface': ip_interface}) # table all closest to old netstat inet6 output cmd = 'ip -6 route show table all' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() # need to fake similar output to that provided by netstat # to maintain output format if comps[0] == "unreachable": continue if comps[0] == "default": ip_interface = '' if comps[3] == "dev": ip_interface = comps[4] ret.append({ 'addr_family': 'inet6', 'destination': '::/0', 'gateway': comps[2], 'netmask': '', 'flags': 'UG', 'interface': ip_interface}) elif comps[0] == "local": ip_interface = '' if comps[2] == "dev": ip_interface = comps[3] local_address = comps[1] + "/128" ret.append({ 'addr_family': 'inet6', 'destination': local_address, 'gateway': '::', 'netmask': '', 'flags': 'U', 'interface': ip_interface}) else: address_mask = convert_cidr(comps[0]) ip_interface = '' if comps[1] == "dev": ip_interface = comps[2] ret.append({ 'addr_family': 'inet6', 'destination': comps[0], 'gateway': '::', 'netmask': '', 'flags': 'U', 'interface': ip_interface}) return ret def _netstat_route_freebsd(): ''' Return netstat routing information for FreeBSD and macOS ''' ret = [] cmd = 'netstat -f inet -rn | tail -n+5' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() if __grains__['os'] == 'FreeBSD' and int(__grains__.get('osmajorrelease', 0)) < 10: ret.append({ 'addr_family': 'inet', 'destination': comps[0], 'gateway': comps[1], 'netmask': comps[2], 'flags': comps[3], 'interface': comps[5]}) else: ret.append({ 'addr_family': 'inet', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[2], 'interface': comps[3]}) cmd = 'netstat -f inet6 -rn | tail -n+5' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'addr_family': 'inet6', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[2], 'interface': comps[3]}) return ret def _netstat_route_netbsd(): ''' Return netstat routing information for NetBSD ''' ret = [] cmd = 'netstat -f inet -rn | tail -n+5' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'addr_family': 'inet', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[3], 'interface': comps[6]}) cmd = 'netstat -f inet6 -rn | tail -n+5' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'addr_family': 'inet6', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[3], 'interface': comps[6]}) return ret def _netstat_route_openbsd(): ''' Return netstat routing information for OpenBSD ''' ret = [] cmd = 'netstat -f inet -rn | tail -n+5' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'addr_family': 'inet', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[2], 'interface': comps[7]}) cmd = 'netstat -f inet6 -rn | tail -n+5' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'addr_family': 'inet6', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[2], 'interface': comps[7]}) return ret def _netstat_route_sunos(): ''' Return netstat routing information for SunOS ''' ret = [] cmd = 'netstat -f inet -rn | tail +5' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'addr_family': 'inet', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[2], 'interface': comps[5] if len(comps) >= 6 else ''}) cmd = 'netstat -f inet6 -rn | tail +5' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'addr_family': 'inet6', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[2], 'interface': comps[5] if len(comps) >= 6 else ''}) return ret def _netstat_route_aix(): ''' Return netstat routing information for AIX ''' ret = [] cmd = 'netstat -f inet -rn | tail -n +5' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'addr_family': 'inet', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[2], 'interface': comps[5] if len(comps) >= 6 else ''}) cmd = 'netstat -f inet6 -rn | tail -n +5' out = __salt__['cmd.run'](cmd, python_shell=True) for line in out.splitlines(): comps = line.split() ret.append({ 'addr_family': 'inet6', 'destination': comps[0], 'gateway': comps[1], 'netmask': '', 'flags': comps[2], 'interface': comps[5] if len(comps) >= 6 else ''}) return ret def netstat(): ''' Return information on open ports and states .. note:: On BSD minions, the output contains PID info (where available) for each netstat entry, fetched from sockstat/fstat output. .. versionchanged:: 2014.1.4 Added support for OpenBSD, FreeBSD, and NetBSD .. versionchanged:: 2015.8.0 Added support for SunOS .. versionchanged:: 2016.11.4 Added support for AIX CLI Example: .. code-block:: bash salt '*' network.netstat ''' if __grains__['kernel'] == 'Linux': if not salt.utils.path.which('netstat'): return _ss_linux() else: return _netstat_linux() elif __grains__['kernel'] in ('OpenBSD', 'FreeBSD', 'NetBSD'): return _netstat_bsd() elif __grains__['kernel'] == 'SunOS': return _netstat_sunos() elif __grains__['kernel'] == 'AIX': return _netstat_aix() raise CommandExecutionError('Not yet supported on this platform') def active_tcp(): ''' Return a dict containing information on all of the running TCP connections (currently linux and solaris only) .. versionchanged:: 2015.8.4 Added support for SunOS CLI Example: .. code-block:: bash salt '*' network.active_tcp ''' if __grains__['kernel'] == 'Linux': return salt.utils.network.active_tcp() elif __grains__['kernel'] == 'SunOS': # lets use netstat to mimic linux as close as possible ret = {} for connection in _netstat_sunos(): if not connection['proto'].startswith('tcp'): continue if connection['state'] != 'ESTABLISHED': continue ret[len(ret)+1] = { 'local_addr': '.'.join(connection['local-address'].split('.')[:-1]), 'local_port': '.'.join(connection['local-address'].split('.')[-1:]), 'remote_addr': '.'.join(connection['remote-address'].split('.')[:-1]), 'remote_port': '.'.join(connection['remote-address'].split('.')[-1:]) } return ret elif __grains__['kernel'] == 'AIX': # lets use netstat to mimic linux as close as possible ret = {} for connection in _netstat_aix(): if not connection['proto'].startswith('tcp'): continue if connection['state'] != 'ESTABLISHED': continue ret[len(ret)+1] = { 'local_addr': '.'.join(connection['local-address'].split('.')[:-1]), 'local_port': '.'.join(connection['local-address'].split('.')[-1:]), 'remote_addr': '.'.join(connection['remote-address'].split('.')[:-1]), 'remote_port': '.'.join(connection['remote-address'].split('.')[-1:]) } return ret else: return {} @salt.utils.decorators.path.which('traceroute') def traceroute(host): ''' Performs a traceroute to a 3rd party host .. versionchanged:: 2015.8.0 Added support for SunOS .. versionchanged:: 2016.11.4 Added support for AIX CLI Example: .. code-block:: bash salt '*' network.traceroute archlinux.org ''' ret = [] cmd = 'traceroute {0}'.format(salt.utils.network.sanitize_host(host)) out = __salt__['cmd.run'](cmd) # Parse version of traceroute if salt.utils.platform.is_sunos() or salt.utils.platform.is_aix(): traceroute_version = [0, 0, 0] else: version_out = __salt__['cmd.run']('traceroute --version') try: # Linux traceroute version looks like: # Modern traceroute for Linux, version 2.0.19, Dec 10 2012 # Darwin and FreeBSD traceroute version looks like: Version 1.4a12+[FreeBSD|Darwin] version_raw = re.findall(r'.*[Vv]ersion (\d+)\.([\w\+]+)\.*(\w*)', version_out)[0] log.debug('traceroute_version_raw: %s', version_raw) traceroute_version = [] for t in version_raw: try: traceroute_version.append(int(t)) except ValueError: traceroute_version.append(t) if len(traceroute_version) < 3: traceroute_version.append(0) log.debug('traceroute_version: %s', traceroute_version) except IndexError: traceroute_version = [0, 0, 0] for line in out.splitlines(): # Pre requirements for line parsing skip_line = False if ' ' not in line: skip_line = True if line.startswith('traceroute'): skip_line = True if salt.utils.platform.is_aix(): if line.startswith('trying to get source for'): skip_line = True if line.startswith('source should be'): skip_line = True if line.startswith('outgoing MTU'): skip_line = True if line.startswith('fragmentation required'): skip_line = True if skip_line: log.debug('Skipping traceroute output line: %s', line) continue # Parse output from unix variants if 'Darwin' in six.text_type(traceroute_version[1]) or \ 'FreeBSD' in six.text_type(traceroute_version[1]) or \ __grains__['kernel'] in ('SunOS', 'AIX'): try: traceline = re.findall(r'\s*(\d*)\s+(.*)\s+\((.*)\)\s+(.*)$', line)[0] except IndexError: traceline = re.findall(r'\s*(\d*)\s+(\*\s+\*\s+\*)', line)[0] log.debug('traceline: %s', traceline) delays = re.findall(r'(\d+\.\d+)\s*ms', six.text_type(traceline)) try: if traceline[1] == '* * *': result = { 'count': traceline[0], 'hostname': '*' } else: result = { 'count': traceline[0], 'hostname': traceline[1], 'ip': traceline[2], } for idx in range(0, len(delays)): result['ms{0}'.format(idx + 1)] = delays[idx] except IndexError: result = {} # Parse output from specific version ranges elif (traceroute_version[0] >= 2 and traceroute_version[2] >= 14 or traceroute_version[0] >= 2 and traceroute_version[1] > 0): comps = line.split(' ') if len(comps) >= 2 and comps[1] == '* * *': result = { 'count': int(comps[0]), 'hostname': '*'} elif len(comps) >= 5: result = { 'count': int(comps[0]), 'hostname': comps[1].split()[0], 'ip': comps[1].split()[1].strip('()'), 'ms1': float(comps[2].split()[0]), 'ms2': float(comps[3].split()[0]), 'ms3': float(comps[4].split()[0])} else: result = {} # Parse anything else else: comps = line.split() if len(comps) >= 8: result = { 'count': comps[0], 'hostname': comps[1], 'ip': comps[2], 'ms1': comps[4], 'ms2': comps[6], 'ms3': comps[8], 'ping1': comps[3], 'ping2': comps[5], 'ping3': comps[7]} else: result = {} ret.append(result) if not result: log.warn('Cannot parse traceroute output line: %s', line) return ret @salt.utils.decorators.path.which('dig') def dig(host): ''' Performs a DNS lookup with dig CLI Example: .. code-block:: bash salt '*' network.dig archlinux.org ''' cmd = 'dig {0}'.format(salt.utils.network.sanitize_host(host)) return __salt__['cmd.run'](cmd) @salt.utils.decorators.path.which('arp') def arp(): ''' Return the arp table from the minion .. versionchanged:: 2015.8.0 Added support for SunOS CLI Example: .. code-block:: bash salt '*' network.arp ''' ret = {} out = __salt__['cmd.run']('arp -an') for line in out.splitlines(): comps = line.split() if len(comps) < 4: continue if __grains__['kernel'] == 'SunOS': if ':' not in comps[-1]: continue ret[comps[-1]] = comps[1] elif __grains__['kernel'] == 'OpenBSD': if comps[0] == 'Host' or comps[1] == '(incomplete)': continue ret[comps[1]] = comps[0] elif __grains__['kernel'] == 'AIX': if comps[0] in ('bucket', 'There'): continue ret[comps[3]] = comps[1].strip('(').strip(')') else: ret[comps[3]] = comps[1].strip('(').strip(')') return ret def interfaces(): ''' Return a dictionary of information about all the interfaces on the minion CLI Example: .. code-block:: bash salt '*' network.interfaces ''' return salt.utils.network.interfaces() def hw_addr(iface): ''' Return the hardware address (a.k.a. MAC address) for a given interface CLI Example: .. code-block:: bash salt '*' network.hw_addr eth0 ''' return salt.utils.network.hw_addr(iface) # Alias hwaddr to preserve backward compat hwaddr = salt.utils.functools.alias_function(hw_addr, 'hwaddr') def interface(iface): ''' Return the inet address for a given interface .. versionadded:: 2014.7.0 CLI Example: .. code-block:: bash salt '*' network.interface eth0 ''' return salt.utils.network.interface(iface) def interface_ip(iface): ''' Return the inet address for a given interface .. versionadded:: 2014.7.0 CLI Example: .. code-block:: bash salt '*' network.interface_ip eth0 ''' return salt.utils.network.interface_ip(iface) def subnets(interfaces=None): ''' Returns a list of IPv4 subnets to which the host belongs CLI Example: .. code-block:: bash salt '*' network.subnets salt '*' network.subnets interfaces=eth1 ''' return salt.utils.network.subnets(interfaces) def subnets6(): ''' Returns a list of IPv6 subnets to which the host belongs CLI Example: .. code-block:: bash salt '*' network.subnets ''' return salt.utils.network.subnets6() def in_subnet(cidr): ''' Returns True if host is within specified subnet, otherwise False. CLI Example: .. code-block:: bash salt '*' network.in_subnet 10.0.0.0/16 ''' return salt.utils.network.in_subnet(cidr) def ip_in_subnet(ip_addr, cidr): ''' Returns True if given IP is within specified subnet, otherwise False. CLI Example: .. code-block:: bash salt '*' network.ip_in_subnet 172.17.0.4 172.16.0.0/12 ''' return salt.utils.network.in_subnet(cidr, ip_addr) def convert_cidr(cidr): ''' returns the network and subnet mask of a cidr addr .. versionadded:: 2016.3.0 CLI Example: .. code-block:: bash salt '*' network.convert_cidr 172.31.0.0/16 ''' ret = {'network': None, 'netmask': None} cidr = calc_net(cidr) network_info = ipaddress.ip_network(cidr) ret['network'] = six.text_type(network_info.network_address) ret['netmask'] = six.text_type(network_info.netmask) return ret def calc_net(ip_addr, netmask=None): ''' Returns the CIDR of a subnet based on an IP address (CIDR notation supported) and optional netmask. CLI Example: .. code-block:: bash salt '*' network.calc_net 172.17.0.5 255.255.255.240 salt '*' network.calc_net 2a02:f6e:a000:80:84d8:8332:7866:4e07/64 .. versionadded:: 2015.8.0 ''' return salt.utils.network.calc_net(ip_addr, netmask) def ip_addrs(interface=None, include_loopback=False, cidr=None, type=None): ''' Returns a list of IPv4 addresses assigned to the host. 127.0.0.1 is ignored, unless 'include_loopback=True' is indicated. If 'interface' is provided, then only IP addresses from that interface will be returned. Providing a CIDR via 'cidr="10.0.0.0/8"' will return only the addresses which are within that subnet. If 'type' is 'public', then only public addresses will be returned. Ditto for 'type'='private'. CLI Example: .. code-block:: bash salt '*' network.ip_addrs ''' addrs = salt.utils.network.ip_addrs(interface=interface, include_loopback=include_loopback) if cidr: return [i for i in addrs if salt.utils.network.in_subnet(cidr, [i])] else: if type == 'public': return [i for i in addrs if not is_private(i)] elif type == 'private': return [i for i in addrs if is_private(i)] else: return addrs ipaddrs = salt.utils.functools.alias_function(ip_addrs, 'ipaddrs') def ip_addrs6(interface=None, include_loopback=False, cidr=None): ''' Returns a list of IPv6 addresses assigned to the host. ::1 is ignored, unless 'include_loopback=True' is indicated. If 'interface' is provided, then only IP addresses from that interface will be returned. Providing a CIDR via 'cidr="2000::/3"' will return only the addresses which are within that subnet. CLI Example: .. code-block:: bash salt '*' network.ip_addrs6 ''' addrs = salt.utils.network.ip_addrs6(interface=interface, include_loopback=include_loopback) if cidr: return [i for i in addrs if salt.utils.network.in_subnet(cidr, [i])] else: return addrs ipaddrs6 = salt.utils.functools.alias_function(ip_addrs6, 'ipaddrs6') def get_hostname(): ''' Get hostname CLI Example: .. code-block:: bash salt '*' network.get_hostname ''' return socket.gethostname() def get_fqdn(): ''' Get fully qualified domain name CLI Example: .. code-block:: bash salt '*' network.get_fqdn ''' return socket.getfqdn() def mod_hostname(hostname): ''' Modify hostname .. versionchanged:: 2015.8.0 Added support for SunOS (Solaris 10, Illumos, SmartOS) CLI Example: .. code-block:: bash salt '*' network.mod_hostname master.saltstack.com ''' # # SunOS tested on SmartOS and OmniOS (Solaris 10 compatible) # Oracle Solaris 11 uses smf, currently not supported # # /etc/nodename is the hostname only, not fqdn # /etc/defaultdomain is the domain # /etc/hosts should have both fqdn and hostname entries # if hostname is None: return False hostname_cmd = salt.utils.path.which('hostnamectl') or salt.utils.path.which('hostname') if salt.utils.platform.is_sunos(): uname_cmd = '/usr/bin/uname' if salt.utils.platform.is_smartos() else salt.utils.path.which('uname') check_hostname_cmd = salt.utils.path.which('check-hostname') # Grab the old hostname so we know which hostname to change and then # change the hostname using the hostname command if hostname_cmd.endswith('hostnamectl'): result = __salt__['cmd.run_all']('{0} status'.format(hostname_cmd)) if 0 == result['retcode']: out = result['stdout'] for line in out.splitlines(): line = line.split(':') if 'Static hostname' in line[0]: o_hostname = line[1].strip() else: log.debug('{0} was unable to get hostname'.format(hostname_cmd)) o_hostname = __salt__['network.get_hostname']() elif not salt.utils.platform.is_sunos(): # don't run hostname -f because -f is not supported on all platforms o_hostname = socket.getfqdn() else: # output: Hostname core OK: fully qualified as core.acheron.be o_hostname = __salt__['cmd.run'](check_hostname_cmd).split(' ')[-1] if hostname_cmd.endswith('hostnamectl'): result = __salt__['cmd.run_all']('{0} set-hostname {1}'.format( hostname_cmd, hostname, )) if result['retcode'] != 0: log.debug('{0} was unable to set hostname. Error: {1}'.format( hostname_cmd, result['stderr'], )) return False elif not salt.utils.platform.is_sunos(): __salt__['cmd.run']('{0} {1}'.format(hostname_cmd, hostname)) else: __salt__['cmd.run']('{0} -S {1}'.format(uname_cmd, hostname.split('.')[0])) # Modify the /etc/hosts file to replace the old hostname with the # new hostname with salt.utils.files.fopen('/etc/hosts', 'r') as fp_: host_c = [salt.utils.stringutils.to_unicode(_l) for _l in fp_.readlines()] with salt.utils.files.fopen('/etc/hosts', 'w') as fh_: for host in host_c: host = host.split() try: host[host.index(o_hostname)] = hostname if salt.utils.platform.is_sunos(): # also set a copy of the hostname host[host.index(o_hostname.split('.')[0])] = hostname.split('.')[0] except ValueError: pass fh_.write(salt.utils.stringutils.to_str('\t'.join(host) + '\n')) # Modify the /etc/sysconfig/network configuration file to set the # new hostname if __grains__['os_family'] == 'RedHat': with salt.utils.files.fopen('/etc/sysconfig/network', 'r') as fp_: network_c = [salt.utils.stringutils.to_unicode(_l) for _l in fp_.readlines()] with salt.utils.files.fopen('/etc/sysconfig/network', 'w') as fh_: for net in network_c: if net.startswith('HOSTNAME'): old_hostname = net.split('=', 1)[1].rstrip() quote_type = salt.utils.stringutils.is_quoted(old_hostname) fh_.write(salt.utils.stringutils.to_str( 'HOSTNAME={1}{0}{1}\n'.format( salt.utils.stringutils.dequote(hostname), quote_type))) else: fh_.write(salt.utils.stringutils.to_str(net)) elif __grains__['os_family'] in ('Debian', 'NILinuxRT'): with salt.utils.files.fopen('/etc/hostname', 'w') as fh_: fh_.write(salt.utils.stringutils.to_str(hostname + '\n')) if __grains__['lsb_distrib_id'] == 'nilrt': str_hostname = salt.utils.stringutils.to_str(hostname) nirtcfg_cmd = '/usr/local/natinst/bin/nirtcfg' nirtcfg_cmd += ' --set section=SystemSettings,token=\'Host_Name\',value=\'{0}\''.format(str_hostname) if __salt__['cmd.run_all'](nirtcfg_cmd)['retcode'] != 0: raise CommandExecutionError('Couldn\'t set hostname to: {0}\n'.format(str_hostname)) elif __grains__['os_family'] == 'OpenBSD': with salt.utils.files.fopen('/etc/myname', 'w') as fh_: fh_.write(salt.utils.stringutils.to_str(hostname + '\n')) # Update /etc/nodename and /etc/defaultdomain on SunOS if salt.utils.platform.is_sunos(): with salt.utils.files.fopen('/etc/nodename', 'w') as fh_: fh_.write(salt.utils.stringutils.to_str( hostname.split('.')[0] + '\n') ) with salt.utils.files.fopen('/etc/defaultdomain', 'w') as fh_: fh_.write(salt.utils.stringutils.to_str( ".".join(hostname.split('.')[1:]) + '\n') ) return True def connect(host, port=None, **kwargs): ''' Test connectivity to a host using a particular port from the minion. .. versionadded:: 2014.7.0 CLI Example: .. code-block:: bash salt '*' network.connect archlinux.org 80 salt '*' network.connect archlinux.org 80 timeout=3 salt '*' network.connect archlinux.org 80 timeout=3 family=ipv4 salt '*' network.connect google-public-dns-a.google.com port=53 proto=udp timeout=3 ''' ret = {'result': None, 'comment': ''} if not host: ret['result'] = False ret['comment'] = 'Required argument, host, is missing.' return ret if not port: ret['result'] = False ret['comment'] = 'Required argument, port, is missing.' return ret proto = kwargs.get('proto', 'tcp') timeout = kwargs.get('timeout', 5) family = kwargs.get('family', None) if salt.utils.validate.net.ipv4_addr(host) or salt.utils.validate.net.ipv6_addr(host): address = host else: address = '{0}'.format(salt.utils.network.sanitize_host(host)) try: if proto == 'udp': __proto = socket.SOL_UDP else: __proto = socket.SOL_TCP proto = 'tcp' if family: if family == 'ipv4': __family = socket.AF_INET elif family == 'ipv6': __family = socket.AF_INET6 else: __family = 0 else: __family = 0 (family, socktype, _proto, garbage, _address) = socket.getaddrinfo(address, port, __family, 0, __proto)[0] except socket.gaierror: ret['result'] = False ret['comment'] = 'Unable to resolve host {0} on {1} port {2}'.format(host, proto, port) return ret try: skt = socket.socket(family, socktype, _proto) skt.settimeout(timeout) if proto == 'udp': # Generate a random string of a # decent size to test UDP connection md5h = hashlib.md5() md5h.update(datetime.datetime.now().strftime('%s')) msg = md5h.hexdigest() skt.sendto(msg, _address) recv, svr = skt.recvfrom(255) skt.close() else: skt.connect(_address) skt.shutdown(2) except Exception as exc: ret['result'] = False ret['comment'] = 'Unable to connect to {0} ({1}) on {2} port {3}'.format(host, _address[0], proto, port) return ret ret['result'] = True ret['comment'] = 'Successfully connected to {0} ({1}) on {2} port {3}'.format(host, _address[0], proto, port) return ret def is_private(ip_addr): ''' Check if the given IP address is a private address .. versionadded:: 2014.7.0 .. versionchanged:: 2015.8.0 IPv6 support CLI Example: .. code-block:: bash salt '*' network.is_private 10.0.0.3 ''' return ipaddress.ip_address(ip_addr).is_private def is_loopback(ip_addr): ''' Check if the given IP address is a loopback address .. versionadded:: 2014.7.0 .. versionchanged:: 2015.8.0 IPv6 support CLI Example: .. code-block:: bash salt '*' network.is_loopback 127.0.0.1 ''' return ipaddress.ip_address(ip_addr).is_loopback def reverse_ip(ip_addr): ''' Returns the reversed IP address .. versionchanged:: 2015.8.0 IPv6 support CLI Example: .. code-block:: bash salt '*' network.reverse_ip 172.17.0.4 ''' return ipaddress.ip_address(ip_addr).reverse_pointer def _get_bufsize_linux(iface): ''' Return network interface buffer information using ethtool ''' ret = {'result': False} cmd = '/sbin/ethtool -g {0}'.format(iface) out = __salt__['cmd.run'](cmd) pat = re.compile(r'^(.+):\s+(\d+)$') suffix = 'max-' for line in out.splitlines(): res = pat.match(line) if res: ret[res.group(1).lower().replace(' ', '-') + suffix] = int(res.group(2)) ret['result'] = True elif line.endswith('maximums:'): suffix = '-max' elif line.endswith('settings:'): suffix = '' if not ret['result']: parts = out.split() # remove shell cmd prefix from msg if parts[0].endswith('sh:'): out = ' '.join(parts[1:]) ret['comment'] = out return ret def get_bufsize(iface): ''' Return network buffer sizes as a dict (currently linux only) CLI Example: .. code-block:: bash salt '*' network.get_bufsize eth0 ''' if __grains__['kernel'] == 'Linux': if os.path.exists('/sbin/ethtool'): return _get_bufsize_linux(iface) return {} def _mod_bufsize_linux(iface, *args, **kwargs): ''' Modify network interface buffer sizes using ethtool ''' ret = {'result': False, 'comment': 'Requires rx=<val> tx==<val> rx-mini=<val> and/or rx-jumbo=<val>'} cmd = '/sbin/ethtool -G ' + iface if not kwargs: return ret if args: ret['comment'] = 'Unknown arguments: ' + ' '.join([six.text_type(item) for item in args]) return ret eargs = '' for kw in ['rx', 'tx', 'rx-mini', 'rx-jumbo']: value = kwargs.get(kw) if value is not None: eargs += ' ' + kw + ' ' + six.text_type(value) if not eargs: return ret cmd += eargs out = __salt__['cmd.run'](cmd) if out: ret['comment'] = out else: ret['comment'] = eargs.strip() ret['result'] = True return ret def mod_bufsize(iface, *args, **kwargs): ''' Modify network interface buffers (currently linux only) CLI Example: .. code-block:: bash salt '*' network.mod_bufsize tx=<val> rx=<val> rx-mini=<val> rx-jumbo=<val> ''' if __grains__['kernel'] == 'Linux': if os.path.exists('/sbin/ethtool'): return _mod_bufsize_linux(iface, *args, **kwargs) return False def routes(family=None): ''' Return currently configured routes from routing table .. versionchanged:: 2015.8.0 Added support for SunOS (Solaris 10, Illumos, SmartOS) .. versionchanged:: 2016.11.4 Added support for AIX CLI Example: .. code-block:: bash salt '*' network.routes ''' if family != 'inet' and family != 'inet6' and family is not None: raise CommandExecutionError('Invalid address family {0}'.format(family)) if __grains__['kernel'] == 'Linux': if not salt.utils.path.which('netstat'): routes_ = _ip_route_linux() else: routes_ = _netstat_route_linux() elif __grains__['kernel'] == 'SunOS': routes_ = _netstat_route_sunos() elif __grains__['os'] in ['FreeBSD', 'MacOS', 'Darwin']: routes_ = _netstat_route_freebsd() elif __grains__['os'] in ['NetBSD']: routes_ = _netstat_route_netbsd() elif __grains__['os'] in ['OpenBSD']: routes_ = _netstat_route_openbsd() elif __grains__['os'] in ['AIX']: routes_ = _netstat_route_aix() else: raise CommandExecutionError('Not yet supported on this platform') if not family: return routes_ else: ret = [route for route in routes_ if route['addr_family'] == family] return ret def default_route(family=None): ''' Return default route(s) from routing table .. versionchanged:: 2015.8.0 Added support for SunOS (Solaris 10, Illumos, SmartOS) .. versionchanged:: 2016.11.4 Added support for AIX CLI Example: .. code-block:: bash salt '*' network.default_route ''' if family != 'inet' and family != 'inet6' and family is not None: raise CommandExecutionError('Invalid address family {0}'.format(family)) _routes = routes() default_route = {} if __grains__['kernel'] == 'Linux': default_route['inet'] = ['0.0.0.0', 'default'] default_route['inet6'] = ['::/0', 'default'] elif __grains__['os'] in ['FreeBSD', 'NetBSD', 'OpenBSD', 'MacOS', 'Darwin'] or \ __grains__['kernel'] in ('SunOS', 'AIX'): default_route['inet'] = ['default'] default_route['inet6'] = ['default'] else: raise CommandExecutionError('Not yet supported on this platform') ret = [] for route in _routes: if family: if route['destination'] in default_route[family]: if __grains__['kernel'] == 'SunOS' and route['addr_family'] != family: continue ret.append(route) else: if route['destination'] in default_route['inet'] or \ route['destination'] in default_route['inet6']: ret.append(route) return ret def get_route(ip): ''' Return routing information for given destination ip .. versionadded:: 2015.5.3 .. versionchanged:: 2015.8.0 Added support for SunOS (Solaris 10, Illumos, SmartOS) Added support for OpenBSD .. versionchanged:: 2016.11.4 Added support for AIX CLI Example:: salt '*' network.get_route 10.10.10.10 ''' if __grains__['kernel'] == 'Linux': cmd = 'ip route get {0}'.format(ip) out = __salt__['cmd.run'](cmd, python_shell=True) regexp = re.compile(r'(via\s+(?P<gateway>[\w\.:]+))?\s+dev\s+(?P<interface>[\w\.\:\-]+)\s+.*src\s+(?P<source>[\w\.:]+)') m = regexp.search(out.splitlines()[0]) ret = { 'destination': ip, 'gateway': m.group('gateway'), 'interface': m.group('interface'), 'source': m.group('source') } return ret if __grains__['kernel'] == 'SunOS': # [root@nacl ~]# route -n get 172.16.10.123 # route to: 172.16.10.123 #destination: 172.16.10.0 # mask: 255.255.255.0 # interface: net0 # flags: <UP,DONE,KERNEL> # recvpipe sendpipe ssthresh rtt,ms rttvar,ms hopcount mtu expire # 0 0 0 0 0 0 1500 0 cmd = '/usr/sbin/route -n get {0}'.format(ip) out = __salt__['cmd.run'](cmd, python_shell=False) ret = { 'destination': ip, 'gateway': None, 'interface': None, 'source': None } for line in out.splitlines(): line = line.split(':') if 'route to' in line[0]: ret['destination'] = line[1].strip() if 'gateway' in line[0]: ret['gateway'] = line[1].strip() if 'interface' in line[0]: ret['interface'] = line[1].strip() ret['source'] = salt.utils.network.interface_ip(line[1].strip()) return ret if __grains__['kernel'] == 'OpenBSD': # [root@exosphere] route -n get blackdot.be # route to: 5.135.127.100 #destination: default # mask: default # gateway: 192.168.0.1 # interface: vio0 # if address: 192.168.0.2 # priority: 8 (static) # flags: <UP,GATEWAY,DONE,STATIC> # use mtu expire # 8352657 0 0 cmd = 'route -n get {0}'.format(ip) out = __salt__['cmd.run'](cmd, python_shell=False) ret = { 'destination': ip, 'gateway': None, 'interface': None, 'source': None } for line in out.splitlines(): line = line.split(':') if 'route to' in line[0]: ret['destination'] = line[1].strip() if 'gateway' in line[0]: ret['gateway'] = line[1].strip() if 'interface' in line[0]: ret['interface'] = line[1].strip() if 'if address' in line[0]: ret['source'] = line[1].strip() return ret if __grains__['kernel'] == 'AIX': # root@la68pp002_pub:~# route -n get 172.29.149.95 # route to: 172.29.149.95 #destination: 172.29.149.95 # gateway: 127.0.0.1 # interface: lo0 #interf addr: 127.0.0.1 # flags: <UP,GATEWAY,HOST,DONE,STATIC> #recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire # 0 0 0 0 0 0 0 -68642 cmd = 'route -n get {0}'.format(ip) out = __salt__['cmd.run'](cmd, python_shell=False) ret = { 'destination': ip, 'gateway': None, 'interface': None, 'source': None } for line in out.splitlines(): line = line.split(':') if 'route to' in line[0]: ret['destination'] = line[1].strip() if 'gateway' in line[0]: ret['gateway'] = line[1].strip() if 'interface' in line[0]: ret['interface'] = line[1].strip() if 'interf addr' in line[0]: ret['source'] = line[1].strip() return ret else: raise CommandExecutionError('Not yet supported on this platform') def ifacestartswith(cidr): ''' Retrieve the interface name from a specific CIDR .. versionadded:: 2016.11.0 CLI Example: .. code-block:: bash salt '*' network.ifacestartswith 10.0 ''' net_list = interfaces() intfnames = [] pattern = six.text_type(cidr) size = len(pattern) for ifname, ifval in six.iteritems(net_list): if 'inet' in ifval: for inet in ifval['inet']: if inet['address'][0:size] == pattern: if 'label' in inet: intfnames.append(inet['label']) else: intfnames.append(ifname) return intfnames def iphexval(ip): ''' Retrieve the hexadecimal representation of an IP address .. versionadded:: 2016.11.0 CLI Example: .. code-block:: bash salt '*' network.iphexval 10.0.0.1 ''' a = ip.split('.') hexval = ['%02X' % int(x) for x in a] # pylint: disable=E1321 return ''.join(hexval)