1""" 2Module for gathering and managing network information 3""" 4 5import datetime 6import hashlib 7import logging 8import os 9import re 10import socket 11import time 12from multiprocessing.pool import ThreadPool 13 14import salt.utils.decorators.path 15import salt.utils.functools 16import salt.utils.network 17import salt.utils.platform 18import salt.utils.validate.net 19from salt._compat import ipaddress 20from salt.exceptions import CommandExecutionError 21 22log = logging.getLogger(__name__) 23 24 25def __virtual__(): 26 """ 27 Only work on POSIX-like systems 28 """ 29 # Disable on Windows, a specific file module exists: 30 if salt.utils.platform.is_windows(): 31 return ( 32 False, 33 "The network execution module cannot be loaded on Windows: use win_network" 34 " instead.", 35 ) 36 return True 37 38 39def wol(mac, bcast="255.255.255.255", destport=9): 40 """ 41 Send Wake On Lan packet to a host 42 43 CLI Example: 44 45 .. code-block:: bash 46 47 salt '*' network.wol 08-00-27-13-69-77 48 salt '*' network.wol 080027136977 255.255.255.255 7 49 salt '*' network.wol 08:00:27:13:69:77 255.255.255.255 7 50 """ 51 dest = __utils__["network.mac_str_to_bytes"](mac) 52 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 53 sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 54 sock.sendto(b"\xff" * 6 + dest * 16, (bcast, int(destport))) 55 return True 56 57 58def ping(host, timeout=False, return_boolean=False): 59 """ 60 Performs an ICMP ping to a host 61 62 .. versionchanged:: 2015.8.0 63 Added support for SunOS 64 65 CLI Example: 66 67 .. code-block:: bash 68 69 salt '*' network.ping archlinux.org 70 71 .. versionadded:: 2015.5.0 72 73 Return a True or False instead of ping output. 74 75 .. code-block:: bash 76 77 salt '*' network.ping archlinux.org return_boolean=True 78 79 Set the time to wait for a response in seconds. 80 81 .. code-block:: bash 82 83 salt '*' network.ping archlinux.org timeout=3 84 """ 85 if timeout: 86 if __grains__["kernel"] == "SunOS": 87 cmd = "ping -c 4 {} {}".format( 88 __utils__["network.sanitize_host"](host), timeout 89 ) 90 else: 91 cmd = "ping -W {} -c 4 {}".format( 92 timeout, __utils__["network.sanitize_host"](host) 93 ) 94 else: 95 cmd = "ping -c 4 {}".format(__utils__["network.sanitize_host"](host)) 96 if return_boolean: 97 ret = __salt__["cmd.run_all"](cmd) 98 if ret["retcode"] != 0: 99 return False 100 else: 101 return True 102 else: 103 return __salt__["cmd.run"](cmd) 104 105 106# FIXME: Does not work with: netstat 1.42 (2001-04-15) from net-tools 107# 1.6.0 (Ubuntu 10.10) 108def _netstat_linux(): 109 """ 110 Return netstat information for Linux distros 111 """ 112 ret = [] 113 cmd = "netstat -tulpnea" 114 out = __salt__["cmd.run"](cmd) 115 for line in out.splitlines(): 116 comps = line.split() 117 if line.startswith("tcp"): 118 ret.append( 119 { 120 "proto": comps[0], 121 "recv-q": comps[1], 122 "send-q": comps[2], 123 "local-address": comps[3], 124 "remote-address": comps[4], 125 "state": comps[5], 126 "user": comps[6], 127 "inode": comps[7], 128 "program": comps[8], 129 } 130 ) 131 if line.startswith("udp"): 132 ret.append( 133 { 134 "proto": comps[0], 135 "recv-q": comps[1], 136 "send-q": comps[2], 137 "local-address": comps[3], 138 "remote-address": comps[4], 139 "user": comps[5], 140 "inode": comps[6], 141 "program": comps[7], 142 } 143 ) 144 return ret 145 146 147def _ss_linux(): 148 """ 149 Return ss information for Linux distros 150 (netstat is deprecated and may not be available) 151 """ 152 ret = [] 153 cmd = "ss -tulpnea" 154 out = __salt__["cmd.run"](cmd) 155 for line in out.splitlines(): 156 comps = line.split() 157 ss_user = 0 158 ss_inode = 0 159 ss_program = "" 160 length = len(comps) 161 if line.startswith("tcp") or line.startswith("udp"): 162 i = 6 163 while i < (length - 1): 164 fields = comps[i].split(":") 165 if fields[0] == "users": 166 users = fields[1].split(",") 167 ss_program = users[0].split('"')[1] 168 169 if fields[0] == "uid": 170 ss_user = fields[1] 171 172 if fields[0] == "ino": 173 ss_inode = fields[1] 174 175 i += 1 176 177 if line.startswith("tcp"): 178 ss_state = comps[1] 179 if ss_state == "ESTAB": 180 ss_state = "ESTABLISHED" 181 ret.append( 182 { 183 "proto": comps[0], 184 "recv-q": comps[2], 185 "send-q": comps[3], 186 "local-address": comps[4], 187 "remote-address": comps[5], 188 "state": ss_state, 189 "user": ss_user, 190 "inode": ss_inode, 191 "program": ss_program, 192 } 193 ) 194 if line.startswith("udp"): 195 ret.append( 196 { 197 "proto": comps[0], 198 "recv-q": comps[2], 199 "send-q": comps[3], 200 "local-address": comps[4], 201 "remote-address": comps[5], 202 "user": ss_user, 203 "inode": ss_inode, 204 "program": ss_program, 205 } 206 ) 207 return ret 208 209 210def _netinfo_openbsd(): 211 """ 212 Get process information for network connections using fstat 213 """ 214 ret = {} 215 _fstat_re = re.compile( 216 r"internet(6)? (?:stream tcp 0x\S+ (\S+)|dgram udp (\S+))" 217 r"(?: [<>=-]+ (\S+))?$" 218 ) 219 out = __salt__["cmd.run"]("fstat") 220 for line in out.splitlines(): 221 try: 222 user, cmd, pid, _, details = line.split(None, 4) 223 ipv6, tcp, udp, remote_addr = _fstat_re.match(details).groups() 224 except (ValueError, AttributeError): 225 # Line either doesn't have the right number of columns, or the 226 # regex which looks for address information did not match. Either 227 # way, ignore this line and continue on to the next one. 228 continue 229 if tcp: 230 local_addr = tcp 231 proto = "tcp{}".format("" if ipv6 is None else ipv6) 232 else: 233 local_addr = udp 234 proto = "udp{}".format("" if ipv6 is None else ipv6) 235 if ipv6: 236 # IPv6 addresses have the address part enclosed in brackets (if the 237 # address part is not a wildcard) to distinguish the address from 238 # the port number. Remove them. 239 local_addr = "".join(x for x in local_addr if x not in "[]") 240 241 # Normalize to match netstat output 242 local_addr = ".".join(local_addr.rsplit(":", 1)) 243 if remote_addr is None: 244 remote_addr = "*.*" 245 else: 246 remote_addr = ".".join(remote_addr.rsplit(":", 1)) 247 248 ret.setdefault(local_addr, {}).setdefault(remote_addr, {}).setdefault( 249 proto, {} 250 ).setdefault(pid, {})["user"] = user 251 ret[local_addr][remote_addr][proto][pid]["cmd"] = cmd 252 return ret 253 254 255def _netinfo_freebsd_netbsd(): 256 """ 257 Get process information for network connections using sockstat 258 """ 259 ret = {} 260 # NetBSD requires '-n' to disable port-to-service resolution 261 out = __salt__["cmd.run"]( 262 "sockstat -46 {} | tail -n+2".format( 263 "-n" if __grains__["kernel"] == "NetBSD" else "" 264 ), 265 python_shell=True, 266 ) 267 for line in out.splitlines(): 268 user, cmd, pid, _, proto, local_addr, remote_addr = line.split() 269 local_addr = ".".join(local_addr.rsplit(":", 1)) 270 remote_addr = ".".join(remote_addr.rsplit(":", 1)) 271 ret.setdefault(local_addr, {}).setdefault(remote_addr, {}).setdefault( 272 proto, {} 273 ).setdefault(pid, {})["user"] = user 274 ret[local_addr][remote_addr][proto][pid]["cmd"] = cmd 275 return ret 276 277 278def _ppid(): 279 """ 280 Return a dict of pid to ppid mappings 281 """ 282 ret = {} 283 if __grains__["kernel"] == "SunOS": 284 cmd = "ps -a -o pid,ppid | tail +2" 285 else: 286 cmd = "ps -ax -o pid,ppid | tail -n+2" 287 out = __salt__["cmd.run"](cmd, python_shell=True) 288 for line in out.splitlines(): 289 pid, ppid = line.split() 290 ret[pid] = ppid 291 return ret 292 293 294def _netstat_bsd(): 295 """ 296 Return netstat information for BSD flavors 297 """ 298 ret = [] 299 if __grains__["kernel"] == "NetBSD": 300 for addr_family in ("inet", "inet6"): 301 cmd = "netstat -f {} -an | tail -n+3".format(addr_family) 302 out = __salt__["cmd.run"](cmd, python_shell=True) 303 for line in out.splitlines(): 304 comps = line.split() 305 entry = { 306 "proto": comps[0], 307 "recv-q": comps[1], 308 "send-q": comps[2], 309 "local-address": comps[3], 310 "remote-address": comps[4], 311 } 312 if entry["proto"].startswith("tcp"): 313 entry["state"] = comps[5] 314 ret.append(entry) 315 else: 316 # Lookup TCP connections 317 cmd = "netstat -p tcp -an | tail -n+3" 318 out = __salt__["cmd.run"](cmd, python_shell=True) 319 for line in out.splitlines(): 320 comps = line.split() 321 ret.append( 322 { 323 "proto": comps[0], 324 "recv-q": comps[1], 325 "send-q": comps[2], 326 "local-address": comps[3], 327 "remote-address": comps[4], 328 "state": comps[5], 329 } 330 ) 331 # Lookup UDP connections 332 cmd = "netstat -p udp -an | tail -n+3" 333 out = __salt__["cmd.run"](cmd, python_shell=True) 334 for line in out.splitlines(): 335 comps = line.split() 336 ret.append( 337 { 338 "proto": comps[0], 339 "recv-q": comps[1], 340 "send-q": comps[2], 341 "local-address": comps[3], 342 "remote-address": comps[4], 343 } 344 ) 345 346 # Add in user and program info 347 ppid = _ppid() 348 if __grains__["kernel"] == "OpenBSD": 349 netinfo = _netinfo_openbsd() 350 elif __grains__["kernel"] in ("FreeBSD", "NetBSD"): 351 netinfo = _netinfo_freebsd_netbsd() 352 for idx, _ in enumerate(ret): 353 local = ret[idx]["local-address"] 354 remote = ret[idx]["remote-address"] 355 proto = ret[idx]["proto"] 356 try: 357 # Make a pointer to the info for this connection for easier 358 # reference below 359 ptr = netinfo[local][remote][proto] 360 except KeyError: 361 continue 362 # Get the pid-to-ppid mappings for this connection 363 conn_ppid = {x: y for x, y in ppid.items() if x in ptr} 364 try: 365 # Master pid for this connection will be the pid whose ppid isn't 366 # in the subset dict we created above 367 master_pid = next(iter(x for x, y in conn_ppid.items() if y not in ptr)) 368 except StopIteration: 369 continue 370 ret[idx]["user"] = ptr[master_pid]["user"] 371 ret[idx]["program"] = "/".join((master_pid, ptr[master_pid]["cmd"])) 372 return ret 373 374 375def _netstat_sunos(): 376 """ 377 Return netstat information for SunOS flavors 378 """ 379 log.warning("User and program not (yet) supported on SunOS") 380 381 ret = [] 382 for addr_family in ("inet", "inet6"): 383 # Lookup TCP connections 384 cmd = "netstat -f {} -P tcp -an | tail +5".format(addr_family) 385 out = __salt__["cmd.run"](cmd, python_shell=True) 386 for line in out.splitlines(): 387 comps = line.split() 388 ret.append( 389 { 390 "proto": "tcp6" if addr_family == "inet6" else "tcp", 391 "recv-q": comps[5], 392 "send-q": comps[4], 393 "local-address": comps[0], 394 "remote-address": comps[1], 395 "state": comps[6], 396 } 397 ) 398 # Lookup UDP connections 399 cmd = "netstat -f {} -P udp -an | tail +5".format(addr_family) 400 out = __salt__["cmd.run"](cmd, python_shell=True) 401 for line in out.splitlines(): 402 comps = line.split() 403 ret.append( 404 { 405 "proto": "udp6" if addr_family == "inet6" else "udp", 406 "local-address": comps[0], 407 "remote-address": comps[1] if len(comps) > 2 else "", 408 } 409 ) 410 411 return ret 412 413 414def _netstat_aix(): 415 """ 416 Return netstat information for SunOS flavors 417 """ 418 ret = [] 419 ## AIX 6.1 - 7.2, appears to ignore addr_family field contents 420 ## for addr_family in ('inet', 'inet6'): 421 for addr_family in ("inet",): 422 # Lookup connections 423 cmd = "netstat -n -a -f {} | tail -n +3".format(addr_family) 424 out = __salt__["cmd.run"](cmd, python_shell=True) 425 for line in out.splitlines(): 426 comps = line.split() 427 if len(comps) < 5: 428 continue 429 430 proto_seen = None 431 tcp_flag = True 432 if "tcp" == comps[0] or "tcp4" == comps[0]: 433 proto_seen = "tcp" 434 elif "tcp6" == comps[0]: 435 proto_seen = "tcp6" 436 elif "udp" == comps[0] or "udp4" == comps[0]: 437 proto_seen = "udp" 438 tcp_flag = False 439 elif "udp6" == comps[0]: 440 proto_seen = "udp6" 441 tcp_flag = False 442 443 if tcp_flag: 444 if len(comps) >= 6: 445 ret.append( 446 { 447 "proto": proto_seen, 448 "recv-q": comps[1], 449 "send-q": comps[2], 450 "local-address": comps[3], 451 "remote-address": comps[4], 452 "state": comps[5], 453 } 454 ) 455 else: 456 if len(comps) >= 5: 457 ret.append( 458 { 459 "proto": proto_seen, 460 "local-address": comps[3], 461 "remote-address": comps[4], 462 } 463 ) 464 return ret 465 466 467def _netstat_route_linux(): 468 """ 469 Return netstat routing information for Linux distros 470 """ 471 ret = [] 472 cmd = "netstat -A inet -rn | tail -n+3" 473 out = __salt__["cmd.run"](cmd, python_shell=True) 474 for line in out.splitlines(): 475 comps = line.split() 476 ret.append( 477 { 478 "addr_family": "inet", 479 "destination": comps[0], 480 "gateway": comps[1], 481 "netmask": comps[2], 482 "flags": comps[3], 483 "interface": comps[7], 484 } 485 ) 486 cmd = "netstat -A inet6 -rn | tail -n+3" 487 out = __salt__["cmd.run"](cmd, python_shell=True) 488 for line in out.splitlines(): 489 comps = line.split() 490 if len(comps) == 6: 491 ret.append( 492 { 493 "addr_family": "inet6", 494 "destination": comps[0], 495 "gateway": comps[1], 496 "netmask": "", 497 "flags": comps[2], 498 "interface": comps[5], 499 } 500 ) 501 elif len(comps) == 7: 502 ret.append( 503 { 504 "addr_family": "inet6", 505 "destination": comps[0], 506 "gateway": comps[1], 507 "netmask": "", 508 "flags": comps[2], 509 "interface": comps[6], 510 } 511 ) 512 else: 513 continue 514 return ret 515 516 517def _ip_route_linux(): 518 """ 519 Return ip routing information for Linux distros 520 (netstat is deprecated and may not be available) 521 """ 522 # table main closest to old netstat inet output 523 ret = [] 524 cmd = "ip -4 route show table main" 525 out = __salt__["cmd.run"](cmd, python_shell=True) 526 for line in out.splitlines(): 527 comps = line.split() 528 529 # need to fake similar output to that provided by netstat 530 # to maintain output format 531 if comps[0] == "unreachable": 532 continue 533 534 if comps[0] == "default": 535 ip_interface = "" 536 if comps[3] == "dev": 537 ip_interface = comps[4] 538 539 ret.append( 540 { 541 "addr_family": "inet", 542 "destination": "0.0.0.0", 543 "gateway": comps[2], 544 "netmask": "0.0.0.0", 545 "flags": "UG", 546 "interface": ip_interface, 547 } 548 ) 549 else: 550 address_mask = convert_cidr(comps[0]) 551 ip_interface = "" 552 if comps[1] == "dev": 553 ip_interface = comps[2] 554 555 ret.append( 556 { 557 "addr_family": "inet", 558 "destination": address_mask["network"], 559 "gateway": "0.0.0.0", 560 "netmask": address_mask["netmask"], 561 "flags": "U", 562 "interface": ip_interface, 563 } 564 ) 565 566 # table all closest to old netstat inet6 output 567 cmd = "ip -6 route show table all" 568 out = __salt__["cmd.run"](cmd, python_shell=True) 569 for line in out.splitlines(): 570 comps = line.split() 571 572 # need to fake similar output to that provided by netstat 573 # to maintain output format 574 if comps[0] in ( 575 "unicast", 576 "broadcast", 577 "throw", 578 "unreachable", 579 "prohibit", 580 "blackhole", 581 "nat", 582 "anycast", 583 "multicast", 584 ): 585 continue 586 587 if comps[0] == "default": 588 ip_interface = "" 589 if comps[3] == "dev": 590 ip_interface = comps[4] 591 592 ret.append( 593 { 594 "addr_family": "inet6", 595 "destination": "::/0", 596 "gateway": comps[2], 597 "netmask": "", 598 "flags": "UG", 599 "interface": ip_interface, 600 } 601 ) 602 603 elif comps[0] == "local": 604 ip_interface = "" 605 if comps[2] == "dev": 606 ip_interface = comps[3] 607 608 local_address = comps[1] + "/128" 609 ret.append( 610 { 611 "addr_family": "inet6", 612 "destination": local_address, 613 "gateway": "::", 614 "netmask": "", 615 "flags": "U", 616 "interface": ip_interface, 617 } 618 ) 619 else: 620 address_mask = convert_cidr(comps[0]) 621 ip_interface = "" 622 if comps[1] == "dev": 623 ip_interface = comps[2] 624 625 ret.append( 626 { 627 "addr_family": "inet6", 628 "destination": comps[0], 629 "gateway": "::", 630 "netmask": "", 631 "flags": "U", 632 "interface": ip_interface, 633 } 634 ) 635 return ret 636 637 638def _netstat_route_freebsd(): 639 """ 640 Return netstat routing information for FreeBSD and macOS 641 """ 642 ret = [] 643 cmd = "netstat -f inet -rn | tail -n+5" 644 out = __salt__["cmd.run"](cmd, python_shell=True) 645 for line in out.splitlines(): 646 comps = line.split() 647 if ( 648 __grains__["os"] == "FreeBSD" 649 and int(__grains__.get("osmajorrelease", 0)) < 10 650 ): 651 ret.append( 652 { 653 "addr_family": "inet", 654 "destination": comps[0], 655 "gateway": comps[1], 656 "netmask": comps[2], 657 "flags": comps[3], 658 "interface": comps[5], 659 } 660 ) 661 else: 662 ret.append( 663 { 664 "addr_family": "inet", 665 "destination": comps[0], 666 "gateway": comps[1], 667 "netmask": "", 668 "flags": comps[2], 669 "interface": comps[3], 670 } 671 ) 672 cmd = "netstat -f inet6 -rn | tail -n+5" 673 out = __salt__["cmd.run"](cmd, python_shell=True) 674 for line in out.splitlines(): 675 comps = line.split() 676 ret.append( 677 { 678 "addr_family": "inet6", 679 "destination": comps[0], 680 "gateway": comps[1], 681 "netmask": "", 682 "flags": comps[2], 683 "interface": comps[3], 684 } 685 ) 686 return ret 687 688 689def _netstat_route_netbsd(): 690 """ 691 Return netstat routing information for NetBSD 692 """ 693 ret = [] 694 cmd = "netstat -f inet -rn | tail -n+5" 695 out = __salt__["cmd.run"](cmd, python_shell=True) 696 for line in out.splitlines(): 697 comps = line.split() 698 ret.append( 699 { 700 "addr_family": "inet", 701 "destination": comps[0], 702 "gateway": comps[1], 703 "netmask": "", 704 "flags": comps[3], 705 "interface": comps[6], 706 } 707 ) 708 cmd = "netstat -f inet6 -rn | tail -n+5" 709 out = __salt__["cmd.run"](cmd, python_shell=True) 710 for line in out.splitlines(): 711 comps = line.split() 712 ret.append( 713 { 714 "addr_family": "inet6", 715 "destination": comps[0], 716 "gateway": comps[1], 717 "netmask": "", 718 "flags": comps[3], 719 "interface": comps[6], 720 } 721 ) 722 return ret 723 724 725def _netstat_route_openbsd(): 726 """ 727 Return netstat routing information for OpenBSD 728 """ 729 ret = [] 730 cmd = "netstat -f inet -rn | tail -n+5" 731 out = __salt__["cmd.run"](cmd, python_shell=True) 732 for line in out.splitlines(): 733 comps = line.split() 734 ret.append( 735 { 736 "addr_family": "inet", 737 "destination": comps[0], 738 "gateway": comps[1], 739 "netmask": "", 740 "flags": comps[2], 741 "interface": comps[7], 742 } 743 ) 744 cmd = "netstat -f inet6 -rn | tail -n+5" 745 out = __salt__["cmd.run"](cmd, python_shell=True) 746 for line in out.splitlines(): 747 comps = line.split() 748 ret.append( 749 { 750 "addr_family": "inet6", 751 "destination": comps[0], 752 "gateway": comps[1], 753 "netmask": "", 754 "flags": comps[2], 755 "interface": comps[7], 756 } 757 ) 758 return ret 759 760 761def _netstat_route_sunos(): 762 """ 763 Return netstat routing information for SunOS 764 """ 765 ret = [] 766 cmd = "netstat -f inet -rn | tail +5" 767 out = __salt__["cmd.run"](cmd, python_shell=True) 768 for line in out.splitlines(): 769 comps = line.split() 770 ret.append( 771 { 772 "addr_family": "inet", 773 "destination": comps[0], 774 "gateway": comps[1], 775 "netmask": "", 776 "flags": comps[2], 777 "interface": comps[5] if len(comps) >= 6 else "", 778 } 779 ) 780 cmd = "netstat -f inet6 -rn | tail +5" 781 out = __salt__["cmd.run"](cmd, python_shell=True) 782 for line in out.splitlines(): 783 comps = line.split() 784 ret.append( 785 { 786 "addr_family": "inet6", 787 "destination": comps[0], 788 "gateway": comps[1], 789 "netmask": "", 790 "flags": comps[2], 791 "interface": comps[5] if len(comps) >= 6 else "", 792 } 793 ) 794 return ret 795 796 797def _netstat_route_aix(): 798 """ 799 Return netstat routing information for AIX 800 """ 801 ret = [] 802 cmd = "netstat -f inet -rn | tail -n +5" 803 out = __salt__["cmd.run"](cmd, python_shell=True) 804 for line in out.splitlines(): 805 comps = line.split() 806 ret.append( 807 { 808 "addr_family": "inet", 809 "destination": comps[0], 810 "gateway": comps[1], 811 "netmask": "", 812 "flags": comps[2], 813 "interface": comps[5] if len(comps) >= 6 else "", 814 } 815 ) 816 cmd = "netstat -f inet6 -rn | tail -n +5" 817 out = __salt__["cmd.run"](cmd, python_shell=True) 818 for line in out.splitlines(): 819 comps = line.split() 820 ret.append( 821 { 822 "addr_family": "inet6", 823 "destination": comps[0], 824 "gateway": comps[1], 825 "netmask": "", 826 "flags": comps[2], 827 "interface": comps[5] if len(comps) >= 6 else "", 828 } 829 ) 830 return ret 831 832 833def netstat(): 834 """ 835 Return information on open ports and states 836 837 .. note:: 838 On BSD minions, the output contains PID info (where available) for each 839 netstat entry, fetched from sockstat/fstat output. 840 841 .. versionchanged:: 2014.1.4 842 Added support for OpenBSD, FreeBSD, and NetBSD 843 844 .. versionchanged:: 2015.8.0 845 Added support for SunOS 846 847 .. versionchanged:: 2016.11.4 848 Added support for AIX 849 850 CLI Example: 851 852 .. code-block:: bash 853 854 salt '*' network.netstat 855 """ 856 if __grains__["kernel"] == "Linux": 857 if not __utils__["path.which"]("netstat"): 858 return _ss_linux() 859 else: 860 return _netstat_linux() 861 elif __grains__["kernel"] in ("OpenBSD", "FreeBSD", "NetBSD"): 862 return _netstat_bsd() 863 elif __grains__["kernel"] == "SunOS": 864 return _netstat_sunos() 865 elif __grains__["kernel"] == "AIX": 866 return _netstat_aix() 867 raise CommandExecutionError("Not yet supported on this platform") 868 869 870def active_tcp(): 871 """ 872 Return a dict containing information on all of the running TCP connections (currently linux and solaris only) 873 874 .. versionchanged:: 2015.8.4 875 876 Added support for SunOS 877 878 CLI Example: 879 880 .. code-block:: bash 881 882 salt '*' network.active_tcp 883 """ 884 if __grains__["kernel"] == "Linux": 885 return __utils__["network.active_tcp"]() 886 elif __grains__["kernel"] == "SunOS": 887 # lets use netstat to mimic linux as close as possible 888 ret = {} 889 for connection in _netstat_sunos(): 890 if not connection["proto"].startswith("tcp"): 891 continue 892 if connection["state"] != "ESTABLISHED": 893 continue 894 ret[len(ret) + 1] = { 895 "local_addr": ".".join(connection["local-address"].split(".")[:-1]), 896 "local_port": ".".join(connection["local-address"].split(".")[-1:]), 897 "remote_addr": ".".join(connection["remote-address"].split(".")[:-1]), 898 "remote_port": ".".join(connection["remote-address"].split(".")[-1:]), 899 } 900 return ret 901 elif __grains__["kernel"] == "AIX": 902 # lets use netstat to mimic linux as close as possible 903 ret = {} 904 for connection in _netstat_aix(): 905 if not connection["proto"].startswith("tcp"): 906 continue 907 if connection["state"] != "ESTABLISHED": 908 continue 909 ret[len(ret) + 1] = { 910 "local_addr": ".".join(connection["local-address"].split(".")[:-1]), 911 "local_port": ".".join(connection["local-address"].split(".")[-1:]), 912 "remote_addr": ".".join(connection["remote-address"].split(".")[:-1]), 913 "remote_port": ".".join(connection["remote-address"].split(".")[-1:]), 914 } 915 return ret 916 else: 917 return {} 918 919 920@salt.utils.decorators.path.which("traceroute") 921def traceroute(host): 922 """ 923 Performs a traceroute to a 3rd party host 924 925 .. versionchanged:: 2015.8.0 926 Added support for SunOS 927 928 .. versionchanged:: 2016.11.4 929 Added support for AIX 930 931 CLI Example: 932 933 .. code-block:: bash 934 935 salt '*' network.traceroute archlinux.org 936 """ 937 ret = [] 938 cmd = "traceroute {}".format(__utils__["network.sanitize_host"](host)) 939 out = __salt__["cmd.run"](cmd) 940 941 # Parse version of traceroute 942 if __utils__["platform.is_sunos"]() or __utils__["platform.is_aix"](): 943 traceroute_version = [0, 0, 0] 944 else: 945 version_out = __salt__["cmd.run"]("traceroute --version") 946 try: 947 # Linux traceroute version looks like: 948 # Modern traceroute for Linux, version 2.0.19, Dec 10 2012 949 # Darwin and FreeBSD traceroute version looks like: Version 1.4a12+[FreeBSD|Darwin] 950 951 version_raw = re.findall( 952 r".*[Vv]ersion (\d+)\.([\w\+]+)\.*(\w*)", version_out 953 )[0] 954 log.debug("traceroute_version_raw: %s", version_raw) 955 traceroute_version = [] 956 for t in version_raw: 957 try: 958 traceroute_version.append(int(t)) 959 except ValueError: 960 traceroute_version.append(t) 961 962 if len(traceroute_version) < 3: 963 traceroute_version.append(0) 964 965 log.debug("traceroute_version: %s", traceroute_version) 966 967 except IndexError: 968 traceroute_version = [0, 0, 0] 969 970 for line in out.splitlines(): 971 # Pre requirements for line parsing 972 skip_line = False 973 if " " not in line: 974 skip_line = True 975 if line.startswith("traceroute"): 976 skip_line = True 977 if __utils__["platform.is_aix"](): 978 if line.startswith("trying to get source for"): 979 skip_line = True 980 if line.startswith("source should be"): 981 skip_line = True 982 if line.startswith("outgoing MTU"): 983 skip_line = True 984 if line.startswith("fragmentation required"): 985 skip_line = True 986 if skip_line: 987 log.debug("Skipping traceroute output line: %s", line) 988 continue 989 990 # Parse output from unix variants 991 if ( 992 "Darwin" in str(traceroute_version[1]) 993 or "FreeBSD" in str(traceroute_version[1]) 994 or __grains__["kernel"] in ("SunOS", "AIX") 995 ): 996 try: 997 traceline = re.findall(r"\s*(\d*)\s+(.*)\s+\((.*)\)\s+(.*)$", line)[0] 998 except IndexError: 999 traceline = re.findall(r"\s*(\d*)\s+(\*\s+\*\s+\*)", line)[0] 1000 1001 log.debug("traceline: %s", traceline) 1002 delays = re.findall(r"(\d+\.\d+)\s*ms", str(traceline)) 1003 1004 try: 1005 if traceline[1] == "* * *": 1006 result = {"count": traceline[0], "hostname": "*"} 1007 else: 1008 result = { 1009 "count": traceline[0], 1010 "hostname": traceline[1], 1011 "ip": traceline[2], 1012 } 1013 for idx, delay in enumerate(delays): 1014 result["ms{}".format(idx + 1)] = delay 1015 except IndexError: 1016 result = {} 1017 1018 # Parse output from specific version ranges 1019 elif ( 1020 traceroute_version[0] >= 2 1021 and traceroute_version[2] >= 14 1022 or traceroute_version[0] >= 2 1023 and traceroute_version[1] > 0 1024 ): 1025 comps = line.split(" ") 1026 if len(comps) >= 2 and comps[1] == "* * *": 1027 result = {"count": int(comps[0]), "hostname": "*"} 1028 elif len(comps) >= 5: 1029 result = { 1030 "count": int(comps[0]), 1031 "hostname": comps[1].split()[0], 1032 "ip": comps[1].split()[1].strip("()"), 1033 "ms1": float(comps[2].split()[0]), 1034 "ms2": float(comps[3].split()[0]), 1035 "ms3": float(comps[4].split()[0]), 1036 } 1037 else: 1038 result = {} 1039 1040 # Parse anything else 1041 else: 1042 comps = line.split() 1043 if len(comps) >= 8: 1044 result = { 1045 "count": comps[0], 1046 "hostname": comps[1], 1047 "ip": comps[2], 1048 "ms1": comps[4], 1049 "ms2": comps[6], 1050 "ms3": comps[8], 1051 "ping1": comps[3], 1052 "ping2": comps[5], 1053 "ping3": comps[7], 1054 } 1055 else: 1056 result = {} 1057 1058 ret.append(result) 1059 if not result: 1060 log.warning("Cannot parse traceroute output line: %s", line) 1061 return ret 1062 1063 1064@salt.utils.decorators.path.which("dig") 1065def dig(host): 1066 """ 1067 Performs a DNS lookup with dig 1068 1069 CLI Example: 1070 1071 .. code-block:: bash 1072 1073 salt '*' network.dig archlinux.org 1074 """ 1075 cmd = "dig {}".format(__utils__["network.sanitize_host"](host)) 1076 return __salt__["cmd.run"](cmd) 1077 1078 1079@salt.utils.decorators.path.which("arp") 1080def arp(): 1081 """ 1082 Return the arp table from the minion 1083 1084 .. versionchanged:: 2015.8.0 1085 Added support for SunOS 1086 1087 CLI Example: 1088 1089 .. code-block:: bash 1090 1091 salt '*' network.arp 1092 """ 1093 ret = {} 1094 out = __salt__["cmd.run"]("arp -an") 1095 for line in out.splitlines(): 1096 comps = line.split() 1097 if len(comps) < 4: 1098 continue 1099 if __grains__["kernel"] == "SunOS": 1100 if ":" not in comps[-1]: 1101 continue 1102 ret[comps[-1]] = comps[1] 1103 elif __grains__["kernel"] == "OpenBSD": 1104 if comps[0] == "Host" or comps[1] == "(incomplete)": 1105 continue 1106 ret[comps[1]] = comps[0] 1107 elif __grains__["kernel"] == "AIX": 1108 if comps[0] in ("bucket", "There"): 1109 continue 1110 ret[comps[3]] = comps[1].strip("(").strip(")") 1111 else: 1112 ret[comps[3]] = comps[1].strip("(").strip(")") 1113 1114 return ret 1115 1116 1117def interfaces(): 1118 """ 1119 Return a dictionary of information about all the interfaces on the minion 1120 1121 CLI Example: 1122 1123 .. code-block:: bash 1124 1125 salt '*' network.interfaces 1126 """ 1127 return __utils__["network.interfaces"]() 1128 1129 1130def hw_addr(iface): 1131 """ 1132 Return the hardware address (a.k.a. MAC address) for a given interface 1133 1134 CLI Example: 1135 1136 .. code-block:: bash 1137 1138 salt '*' network.hw_addr eth0 1139 """ 1140 return __utils__["network.hw_addr"](iface) 1141 1142 1143# Alias hwaddr to preserve backward compat 1144hwaddr = salt.utils.functools.alias_function(hw_addr, "hwaddr") 1145 1146 1147def interface(iface): 1148 """ 1149 Return the inet address for a given interface 1150 1151 .. versionadded:: 2014.7.0 1152 1153 CLI Example: 1154 1155 .. code-block:: bash 1156 1157 salt '*' network.interface eth0 1158 """ 1159 return __utils__["network.interface"](iface) 1160 1161 1162def interface_ip(iface): 1163 """ 1164 Return the inet address for a given interface 1165 1166 .. versionadded:: 2014.7.0 1167 1168 CLI Example: 1169 1170 .. code-block:: bash 1171 1172 salt '*' network.interface_ip eth0 1173 """ 1174 return __utils__["network.interface_ip"](iface) 1175 1176 1177def subnets(interfaces=None): 1178 """ 1179 Returns a list of IPv4 subnets to which the host belongs 1180 1181 CLI Example: 1182 1183 .. code-block:: bash 1184 1185 salt '*' network.subnets 1186 salt '*' network.subnets interfaces=eth1 1187 """ 1188 return __utils__["network.subnets"](interfaces) 1189 1190 1191def subnets6(): 1192 """ 1193 Returns a list of IPv6 subnets to which the host belongs 1194 1195 CLI Example: 1196 1197 .. code-block:: bash 1198 1199 salt '*' network.subnets 1200 """ 1201 return __utils__["network.subnets6"]() 1202 1203 1204def in_subnet(cidr): 1205 """ 1206 Returns True if host is within specified subnet, otherwise False. 1207 1208 CLI Example: 1209 1210 .. code-block:: bash 1211 1212 salt '*' network.in_subnet 10.0.0.0/16 1213 """ 1214 return __utils__["network.in_subnet"](cidr) 1215 1216 1217def ip_in_subnet(ip_addr, cidr): 1218 """ 1219 Returns True if given IP is within specified subnet, otherwise False. 1220 1221 CLI Example: 1222 1223 .. code-block:: bash 1224 1225 salt '*' network.ip_in_subnet 172.17.0.4 172.16.0.0/12 1226 """ 1227 return __utils__["network.in_subnet"](cidr, ip_addr) 1228 1229 1230def convert_cidr(cidr): 1231 """ 1232 returns the network address, subnet mask and broadcast address of a cidr address 1233 1234 .. versionadded:: 2016.3.0 1235 1236 CLI Example: 1237 1238 .. code-block:: bash 1239 1240 salt '*' network.convert_cidr 172.31.0.0/16 1241 """ 1242 ret = {"network": None, "netmask": None, "broadcast": None} 1243 cidr = calc_net(cidr) 1244 network_info = ipaddress.ip_network(cidr) 1245 ret["network"] = str(network_info.network_address) 1246 ret["netmask"] = str(network_info.netmask) 1247 ret["broadcast"] = str(network_info.broadcast_address) 1248 return ret 1249 1250 1251def calc_net(ip_addr, netmask=None): 1252 """ 1253 Returns the CIDR of a subnet based on 1254 an IP address (CIDR notation supported) 1255 and optional netmask. 1256 1257 CLI Example: 1258 1259 .. code-block:: bash 1260 1261 salt '*' network.calc_net 172.17.0.5 255.255.255.240 1262 salt '*' network.calc_net 2a02:f6e:a000:80:84d8:8332:7866:4e07/64 1263 1264 .. versionadded:: 2015.8.0 1265 """ 1266 return __utils__["network.calc_net"](ip_addr, netmask) 1267 1268 1269def ip_addrs(interface=None, include_loopback=False, cidr=None, type=None): 1270 """ 1271 Returns a list of IPv4 addresses assigned to the host. 127.0.0.1 is 1272 ignored, unless 'include_loopback=True' is indicated. If 'interface' is 1273 provided, then only IP addresses from that interface will be returned. 1274 Providing a CIDR via 'cidr="10.0.0.0/8"' will return only the addresses 1275 which are within that subnet. If 'type' is 'public', then only public 1276 addresses will be returned. Ditto for 'type'='private'. 1277 1278 .. versionchanged:: 3001 1279 ``interface`` can now be a single interface name or a list of 1280 interfaces. Globbing is also supported. 1281 1282 CLI Example: 1283 1284 .. code-block:: bash 1285 1286 salt '*' network.ip_addrs 1287 """ 1288 addrs = __utils__["network.ip_addrs"]( 1289 interface=interface, include_loopback=include_loopback 1290 ) 1291 if cidr: 1292 return [i for i in addrs if __utils__["network.in_subnet"](cidr, [i])] 1293 else: 1294 if type == "public": 1295 return [i for i in addrs if not is_private(i)] 1296 elif type == "private": 1297 return [i for i in addrs if is_private(i)] 1298 else: 1299 return addrs 1300 1301 1302ipaddrs = salt.utils.functools.alias_function(ip_addrs, "ipaddrs") 1303 1304 1305def ip_addrs6(interface=None, include_loopback=False, cidr=None): 1306 """ 1307 Returns a list of IPv6 addresses assigned to the host. ::1 is ignored, 1308 unless 'include_loopback=True' is indicated. If 'interface' is provided, 1309 then only IP addresses from that interface will be returned. 1310 Providing a CIDR via 'cidr="2000::/3"' will return only the addresses 1311 which are within that subnet. 1312 1313 .. versionchanged:: 3001 1314 ``interface`` can now be a single interface name or a list of 1315 interfaces. Globbing is also supported. 1316 1317 CLI Example: 1318 1319 .. code-block:: bash 1320 1321 salt '*' network.ip_addrs6 1322 """ 1323 addrs = __utils__["network.ip_addrs6"]( 1324 interface=interface, include_loopback=include_loopback 1325 ) 1326 if cidr: 1327 return [i for i in addrs if __utils__["network.in_subnet"](cidr, [i])] 1328 else: 1329 return addrs 1330 1331 1332ipaddrs6 = salt.utils.functools.alias_function(ip_addrs6, "ipaddrs6") 1333 1334 1335def get_hostname(): 1336 """ 1337 Get hostname 1338 1339 CLI Example: 1340 1341 .. code-block:: bash 1342 1343 salt '*' network.get_hostname 1344 """ 1345 1346 return socket.gethostname() 1347 1348 1349def get_fqdn(): 1350 """ 1351 Get fully qualified domain name 1352 1353 CLI Example: 1354 1355 .. code-block:: bash 1356 1357 salt '*' network.get_fqdn 1358 """ 1359 1360 return socket.getfqdn() 1361 1362 1363def mod_hostname(hostname): 1364 """ 1365 Modify hostname 1366 1367 .. versionchanged:: 2015.8.0 1368 Added support for SunOS (Solaris 10, Illumos, SmartOS) 1369 1370 CLI Example: 1371 1372 .. code-block:: bash 1373 1374 salt '*' network.mod_hostname master.saltstack.com 1375 """ 1376 # 1377 # SunOS tested on SmartOS and OmniOS (Solaris 10 compatible) 1378 # Oracle Solaris 11 uses smf, currently not supported 1379 # 1380 # /etc/nodename is the hostname only, not fqdn 1381 # /etc/defaultdomain is the domain 1382 # /etc/hosts should have both fqdn and hostname entries 1383 # 1384 1385 if hostname is None: 1386 return False 1387 1388 hostname_cmd = __utils__["path.which"]("hostnamectl") or __utils__["path.which"]( 1389 "hostname" 1390 ) 1391 if __utils__["platform.is_sunos"](): 1392 uname_cmd = ( 1393 "/usr/bin/uname" 1394 if __utils__["platform.is_smartos"]() 1395 else __utils__["path.which"]("uname") 1396 ) 1397 check_hostname_cmd = __utils__["path.which"]("check-hostname") 1398 1399 # Grab the old hostname so we know which hostname to change and then 1400 # change the hostname using the hostname command 1401 if hostname_cmd.endswith("hostnamectl"): 1402 result = __salt__["cmd.run_all"]("{} status".format(hostname_cmd)) 1403 if 0 == result["retcode"]: 1404 out = result["stdout"] 1405 for line in out.splitlines(): 1406 line = line.split(":") 1407 if "Static hostname" in line[0]: 1408 o_hostname = line[1].strip() 1409 else: 1410 log.debug("%s was unable to get hostname", hostname_cmd) 1411 o_hostname = __salt__["network.get_hostname"]() 1412 elif not __utils__["platform.is_sunos"](): 1413 # don't run hostname -f because -f is not supported on all platforms 1414 o_hostname = socket.getfqdn() 1415 else: 1416 # output: Hostname core OK: fully qualified as core.acheron.be 1417 o_hostname = __salt__["cmd.run"](check_hostname_cmd).split(" ")[-1] 1418 1419 if hostname_cmd.endswith("hostnamectl"): 1420 result = __salt__["cmd.run_all"]( 1421 "{} set-hostname {}".format( 1422 hostname_cmd, 1423 hostname, 1424 ) 1425 ) 1426 if result["retcode"] != 0: 1427 log.debug( 1428 "%s was unable to set hostname. Error: %s", 1429 hostname_cmd, 1430 result["stderr"], 1431 ) 1432 return False 1433 elif not __utils__["platform.is_sunos"](): 1434 __salt__["cmd.run"]("{} {}".format(hostname_cmd, hostname)) 1435 else: 1436 __salt__["cmd.run"]("{} -S {}".format(uname_cmd, hostname.split(".")[0])) 1437 1438 # Modify the /etc/hosts file to replace the old hostname with the 1439 # new hostname 1440 with __utils__["files.fopen"]("/etc/hosts", "r") as fp_: 1441 host_c = [__utils__["stringutils.to_unicode"](_l) for _l in fp_.readlines()] 1442 1443 with __utils__["files.fopen"]("/etc/hosts", "w") as fh_: 1444 for host in host_c: 1445 host = host.split() 1446 1447 try: 1448 host[host.index(o_hostname)] = hostname 1449 if __utils__["platform.is_sunos"](): 1450 # also set a copy of the hostname 1451 host[host.index(o_hostname.split(".")[0])] = hostname.split(".")[0] 1452 except ValueError: 1453 pass 1454 1455 fh_.write(__utils__["stringutils.to_str"]("\t".join(host) + "\n")) 1456 1457 # Modify the /etc/sysconfig/network configuration file to set the 1458 # new hostname 1459 if __grains__["os_family"] == "RedHat": 1460 with __utils__["files.fopen"]("/etc/sysconfig/network", "r") as fp_: 1461 network_c = [ 1462 __utils__["stringutils.to_unicode"](_l) for _l in fp_.readlines() 1463 ] 1464 1465 with __utils__["files.fopen"]("/etc/sysconfig/network", "w") as fh_: 1466 for net in network_c: 1467 if net.startswith("HOSTNAME"): 1468 old_hostname = net.split("=", 1)[1].rstrip() 1469 quote_type = __utils__["stringutils.is_quoted"](old_hostname) 1470 fh_.write( 1471 __utils__["stringutils.to_str"]( 1472 "HOSTNAME={1}{0}{1}\n".format( 1473 __utils__["stringutils.dequote"](hostname), quote_type 1474 ) 1475 ) 1476 ) 1477 else: 1478 fh_.write(__utils__["stringutils.to_str"](net)) 1479 elif __grains__["os_family"] in ("Debian", "NILinuxRT"): 1480 with __utils__["files.fopen"]("/etc/hostname", "w") as fh_: 1481 fh_.write(__utils__["stringutils.to_str"](hostname + "\n")) 1482 if __grains__["lsb_distrib_id"] == "nilrt": 1483 str_hostname = __utils__["stringutils.to_str"](hostname) 1484 nirtcfg_cmd = "/usr/local/natinst/bin/nirtcfg" 1485 nirtcfg_cmd += ( 1486 " --set section=SystemSettings,token='Host_Name',value='{}'".format( 1487 str_hostname 1488 ) 1489 ) 1490 if __salt__["cmd.run_all"](nirtcfg_cmd)["retcode"] != 0: 1491 raise CommandExecutionError( 1492 "Couldn't set hostname to: {}\n".format(str_hostname) 1493 ) 1494 elif __grains__["os_family"] == "OpenBSD": 1495 with __utils__["files.fopen"]("/etc/myname", "w") as fh_: 1496 fh_.write(__utils__["stringutils.to_str"](hostname + "\n")) 1497 1498 # Update /etc/nodename and /etc/defaultdomain on SunOS 1499 if __utils__["platform.is_sunos"](): 1500 with __utils__["files.fopen"]("/etc/nodename", "w") as fh_: 1501 fh_.write(__utils__["stringutils.to_str"](hostname.split(".")[0] + "\n")) 1502 with __utils__["files.fopen"]("/etc/defaultdomain", "w") as fh_: 1503 fh_.write( 1504 __utils__["stringutils.to_str"]( 1505 ".".join(hostname.split(".")[1:]) + "\n" 1506 ) 1507 ) 1508 1509 return True 1510 1511 1512def connect(host, port=None, **kwargs): 1513 """ 1514 Test connectivity to a host using a particular 1515 port from the minion. 1516 1517 .. versionadded:: 2014.7.0 1518 1519 CLI Example: 1520 1521 .. code-block:: bash 1522 1523 salt '*' network.connect archlinux.org 80 1524 1525 salt '*' network.connect archlinux.org 80 timeout=3 1526 1527 salt '*' network.connect archlinux.org 80 timeout=3 family=ipv4 1528 1529 salt '*' network.connect google-public-dns-a.google.com port=53 proto=udp timeout=3 1530 """ 1531 1532 ret = {"result": None, "comment": ""} 1533 1534 if not host: 1535 ret["result"] = False 1536 ret["comment"] = "Required argument, host, is missing." 1537 return ret 1538 1539 if not port: 1540 ret["result"] = False 1541 ret["comment"] = "Required argument, port, is missing." 1542 return ret 1543 1544 proto = kwargs.get("proto", "tcp") 1545 timeout = kwargs.get("timeout", 5) 1546 family = kwargs.get("family", None) 1547 1548 if salt.utils.validate.net.ipv4_addr(host) or salt.utils.validate.net.ipv6_addr( 1549 host 1550 ): 1551 address = host 1552 else: 1553 address = "{}".format(__utils__["network.sanitize_host"](host)) 1554 1555 try: 1556 if proto == "udp": 1557 __proto = socket.SOL_UDP 1558 else: 1559 __proto = socket.SOL_TCP 1560 proto = "tcp" 1561 1562 if family: 1563 if family == "ipv4": 1564 __family = socket.AF_INET 1565 elif family == "ipv6": 1566 __family = socket.AF_INET6 1567 else: 1568 __family = 0 1569 else: 1570 __family = 0 1571 1572 (family, socktype, _proto, garbage, _address) = socket.getaddrinfo( 1573 address, port, __family, 0, __proto 1574 )[0] 1575 except socket.gaierror: 1576 ret["result"] = False 1577 ret["comment"] = "Unable to resolve host {} on {} port {}".format( 1578 host, proto, port 1579 ) 1580 return ret 1581 1582 try: 1583 skt = socket.socket(family, socktype, _proto) 1584 skt.settimeout(timeout) 1585 1586 if proto == "udp": 1587 # Generate a random string of a 1588 # decent size to test UDP connection 1589 md5h = hashlib.md5() 1590 md5h.update(datetime.datetime.now().strftime("%s")) 1591 msg = md5h.hexdigest() 1592 skt.sendto(msg, _address) 1593 recv, svr = skt.recvfrom(255) 1594 skt.close() 1595 else: 1596 skt.connect(_address) 1597 skt.shutdown(2) 1598 except Exception as exc: # pylint: disable=broad-except 1599 ret["result"] = False 1600 ret["comment"] = "Unable to connect to {} ({}) on {} port {}".format( 1601 host, _address[0], proto, port 1602 ) 1603 return ret 1604 1605 ret["result"] = True 1606 ret["comment"] = "Successfully connected to {} ({}) on {} port {}".format( 1607 host, _address[0], proto, port 1608 ) 1609 return ret 1610 1611 1612def is_private(ip_addr): 1613 """ 1614 Check if the given IP address is a private address 1615 1616 .. versionadded:: 2014.7.0 1617 .. versionchanged:: 2015.8.0 1618 IPv6 support 1619 1620 CLI Example: 1621 1622 .. code-block:: bash 1623 1624 salt '*' network.is_private 10.0.0.3 1625 """ 1626 return ipaddress.ip_address(ip_addr).is_private 1627 1628 1629def is_loopback(ip_addr): 1630 """ 1631 Check if the given IP address is a loopback address 1632 1633 .. versionadded:: 2014.7.0 1634 .. versionchanged:: 2015.8.0 1635 IPv6 support 1636 1637 CLI Example: 1638 1639 .. code-block:: bash 1640 1641 salt '*' network.is_loopback 127.0.0.1 1642 """ 1643 return ipaddress.ip_address(ip_addr).is_loopback 1644 1645 1646def reverse_ip(ip_addr): 1647 """ 1648 Returns the reversed IP address 1649 1650 .. versionchanged:: 2015.8.0 1651 IPv6 support 1652 1653 CLI Example: 1654 1655 .. code-block:: bash 1656 1657 salt '*' network.reverse_ip 172.17.0.4 1658 """ 1659 return ipaddress.ip_address(ip_addr).reverse_pointer 1660 1661 1662def _get_bufsize_linux(iface): 1663 """ 1664 Return network interface buffer information using ethtool 1665 """ 1666 ret = {"result": False} 1667 1668 cmd = "/sbin/ethtool -g {}".format(iface) 1669 out = __salt__["cmd.run"](cmd) 1670 pat = re.compile(r"^(.+):\s+(\d+)$") 1671 suffix = "max-" 1672 for line in out.splitlines(): 1673 res = pat.match(line) 1674 if res: 1675 ret[res.group(1).lower().replace(" ", "-") + suffix] = int(res.group(2)) 1676 ret["result"] = True 1677 elif line.endswith("maximums:"): 1678 suffix = "-max" 1679 elif line.endswith("settings:"): 1680 suffix = "" 1681 if not ret["result"]: 1682 parts = out.split() 1683 # remove shell cmd prefix from msg 1684 if parts[0].endswith("sh:"): 1685 out = " ".join(parts[1:]) 1686 ret["comment"] = out 1687 return ret 1688 1689 1690def get_bufsize(iface): 1691 """ 1692 Return network buffer sizes as a dict (currently linux only) 1693 1694 CLI Example: 1695 1696 .. code-block:: bash 1697 1698 salt '*' network.get_bufsize eth0 1699 """ 1700 if __grains__["kernel"] == "Linux": 1701 if os.path.exists("/sbin/ethtool"): 1702 return _get_bufsize_linux(iface) 1703 1704 return {} 1705 1706 1707def _mod_bufsize_linux(iface, *args, **kwargs): 1708 """ 1709 Modify network interface buffer sizes using ethtool 1710 """ 1711 ret = { 1712 "result": False, 1713 "comment": "Requires rx=<val> tx==<val> rx-mini=<val> and/or rx-jumbo=<val>", 1714 } 1715 cmd = "/sbin/ethtool -G " + iface 1716 if not kwargs: 1717 return ret 1718 if args: 1719 ret["comment"] = "Unknown arguments: " + " ".join([str(item) for item in args]) 1720 return ret 1721 eargs = "" 1722 for kw in ["rx", "tx", "rx-mini", "rx-jumbo"]: 1723 value = kwargs.get(kw) 1724 if value is not None: 1725 eargs += " " + kw + " " + str(value) 1726 if not eargs: 1727 return ret 1728 cmd += eargs 1729 out = __salt__["cmd.run"](cmd) 1730 if out: 1731 ret["comment"] = out 1732 else: 1733 ret["comment"] = eargs.strip() 1734 ret["result"] = True 1735 return ret 1736 1737 1738def mod_bufsize(iface, *args, **kwargs): 1739 """ 1740 Modify network interface buffers (currently linux only) 1741 1742 CLI Example: 1743 1744 .. code-block:: bash 1745 1746 salt '*' network.mod_bufsize tx=<val> rx=<val> rx-mini=<val> rx-jumbo=<val> 1747 """ 1748 if __grains__["kernel"] == "Linux": 1749 if os.path.exists("/sbin/ethtool"): 1750 return _mod_bufsize_linux(iface, *args, **kwargs) 1751 1752 return False 1753 1754 1755def routes(family=None): 1756 """ 1757 Return currently configured routes from routing table 1758 1759 .. versionchanged:: 2015.8.0 1760 Added support for SunOS (Solaris 10, Illumos, SmartOS) 1761 1762 .. versionchanged:: 2016.11.4 1763 Added support for AIX 1764 1765 CLI Example: 1766 1767 .. code-block:: bash 1768 1769 salt '*' network.routes 1770 """ 1771 if family != "inet" and family != "inet6" and family is not None: 1772 raise CommandExecutionError("Invalid address family {}".format(family)) 1773 1774 if __grains__["kernel"] == "Linux": 1775 if not __utils__["path.which"]("netstat"): 1776 routes_ = _ip_route_linux() 1777 else: 1778 routes_ = _netstat_route_linux() 1779 elif __grains__["kernel"] == "SunOS": 1780 routes_ = _netstat_route_sunos() 1781 elif __grains__["os"] in ["FreeBSD", "MacOS", "Darwin"]: 1782 routes_ = _netstat_route_freebsd() 1783 elif __grains__["os"] in ["NetBSD"]: 1784 routes_ = _netstat_route_netbsd() 1785 elif __grains__["os"] in ["OpenBSD"]: 1786 routes_ = _netstat_route_openbsd() 1787 elif __grains__["os"] in ["AIX"]: 1788 routes_ = _netstat_route_aix() 1789 else: 1790 raise CommandExecutionError("Not yet supported on this platform") 1791 1792 if not family: 1793 return routes_ 1794 else: 1795 ret = [route for route in routes_ if route["addr_family"] == family] 1796 return ret 1797 1798 1799def default_route(family=None): 1800 """ 1801 Return default route(s) from routing table 1802 1803 .. versionchanged:: 2015.8.0 1804 Added support for SunOS (Solaris 10, Illumos, SmartOS) 1805 1806 .. versionchanged:: 2016.11.4 1807 Added support for AIX 1808 1809 CLI Example: 1810 1811 .. code-block:: bash 1812 1813 salt '*' network.default_route 1814 """ 1815 if family != "inet" and family != "inet6" and family is not None: 1816 raise CommandExecutionError("Invalid address family {}".format(family)) 1817 1818 _routes = routes(family) 1819 1820 default_route = {} 1821 if __grains__["kernel"] == "Linux": 1822 default_route["inet"] = ["0.0.0.0", "default"] 1823 default_route["inet6"] = ["::/0", "default"] 1824 elif ( 1825 __grains__["os"] 1826 in [ 1827 "FreeBSD", 1828 "NetBSD", 1829 "OpenBSD", 1830 "MacOS", 1831 "Darwin", 1832 ] 1833 or __grains__["kernel"] in ("SunOS", "AIX") 1834 ): 1835 default_route["inet"] = ["default"] 1836 default_route["inet6"] = ["default"] 1837 else: 1838 raise CommandExecutionError("Not yet supported on this platform") 1839 1840 ret = [] 1841 for route in _routes: 1842 if family: 1843 if route["destination"] in default_route[family]: 1844 if __grains__["kernel"] == "SunOS" and route["addr_family"] != family: 1845 continue 1846 ret.append(route) 1847 else: 1848 if ( 1849 route["destination"] in default_route["inet"] 1850 or route["destination"] in default_route["inet6"] 1851 ): 1852 ret.append(route) 1853 1854 return ret 1855 1856 1857def get_route(ip): 1858 """ 1859 Return routing information for given destination ip 1860 1861 .. versionadded:: 2015.5.3 1862 1863 .. versionchanged:: 2015.8.0 1864 Added support for SunOS (Solaris 10, Illumos, SmartOS) 1865 Added support for OpenBSD 1866 1867 .. versionchanged:: 2016.11.4 1868 Added support for AIX 1869 1870 CLI Example: 1871 1872 .. code-block:: bash 1873 1874 salt '*' network.get_route 10.10.10.10 1875 """ 1876 1877 if __grains__["kernel"] == "Linux": 1878 cmd = "ip route get {}".format(ip) 1879 out = __salt__["cmd.run"](cmd, python_shell=True) 1880 regexp = re.compile( 1881 r"(via\s+(?P<gateway>[\w\.:]+))?\s+dev\s+(?P<interface>[\w\.\:\-]+)\s+.*src\s+(?P<source>[\w\.:]+)" 1882 ) 1883 m = regexp.search(out.splitlines()[0]) 1884 ret = { 1885 "destination": ip, 1886 "gateway": m.group("gateway"), 1887 "interface": m.group("interface"), 1888 "source": m.group("source"), 1889 } 1890 1891 return ret 1892 1893 if __grains__["kernel"] == "SunOS": 1894 # [root@nacl ~]# route -n get 172.16.10.123 1895 # route to: 172.16.10.123 1896 # destination: 172.16.10.0 1897 # mask: 255.255.255.0 1898 # interface: net0 1899 # flags: <UP,DONE,KERNEL> 1900 # recvpipe sendpipe ssthresh rtt,ms rttvar,ms hopcount mtu expire 1901 # 0 0 0 0 0 0 1500 0 1902 cmd = "/usr/sbin/route -n get {}".format(ip) 1903 out = __salt__["cmd.run"](cmd, python_shell=False) 1904 1905 ret = {"destination": ip, "gateway": None, "interface": None, "source": None} 1906 1907 for line in out.splitlines(): 1908 line = line.split(":") 1909 if "route to" in line[0]: 1910 ret["destination"] = line[1].strip() 1911 if "gateway" in line[0]: 1912 ret["gateway"] = line[1].strip() 1913 if "interface" in line[0]: 1914 ret["interface"] = line[1].strip() 1915 ret["source"] = __utils__["network.interface_ip"](line[1].strip()) 1916 1917 return ret 1918 1919 if __grains__["kernel"] == "OpenBSD": 1920 # [root@exosphere] route -n get blackdot.be 1921 # route to: 5.135.127.100 1922 # destination: default 1923 # mask: default 1924 # gateway: 192.168.0.1 1925 # interface: vio0 1926 # if address: 192.168.0.2 1927 # priority: 8 (static) 1928 # flags: <UP,GATEWAY,DONE,STATIC> 1929 # use mtu expire 1930 # 8352657 0 0 1931 cmd = "route -n get {}".format(ip) 1932 out = __salt__["cmd.run"](cmd, python_shell=False) 1933 1934 ret = {"destination": ip, "gateway": None, "interface": None, "source": None} 1935 1936 for line in out.splitlines(): 1937 line = line.split(":") 1938 if "route to" in line[0]: 1939 ret["destination"] = line[1].strip() 1940 if "gateway" in line[0]: 1941 ret["gateway"] = line[1].strip() 1942 if "interface" in line[0]: 1943 ret["interface"] = line[1].strip() 1944 if "if address" in line[0]: 1945 ret["source"] = line[1].strip() 1946 1947 return ret 1948 1949 if __grains__["kernel"] == "AIX": 1950 # root@la68pp002_pub:~# route -n get 172.29.149.95 1951 # route to: 172.29.149.95 1952 # destination: 172.29.149.95 1953 # gateway: 127.0.0.1 1954 # interface: lo0 1955 # interf addr: 127.0.0.1 1956 # flags: <UP,GATEWAY,HOST,DONE,STATIC> 1957 # recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire 1958 # 0 0 0 0 0 0 0 -68642 1959 cmd = "route -n get {}".format(ip) 1960 out = __salt__["cmd.run"](cmd, python_shell=False) 1961 1962 ret = {"destination": ip, "gateway": None, "interface": None, "source": None} 1963 1964 for line in out.splitlines(): 1965 line = line.split(":") 1966 if "route to" in line[0]: 1967 ret["destination"] = line[1].strip() 1968 if "gateway" in line[0]: 1969 ret["gateway"] = line[1].strip() 1970 if "interface" in line[0]: 1971 ret["interface"] = line[1].strip() 1972 if "interf addr" in line[0]: 1973 ret["source"] = line[1].strip() 1974 1975 return ret 1976 1977 else: 1978 raise CommandExecutionError("Not yet supported on this platform") 1979 1980 1981def ifacestartswith(cidr): 1982 """ 1983 Retrieve the interface name from a specific CIDR 1984 1985 .. versionadded:: 2016.11.0 1986 1987 CLI Example: 1988 1989 .. code-block:: bash 1990 1991 salt '*' network.ifacestartswith 10.0 1992 """ 1993 net_list = interfaces() 1994 intfnames = [] 1995 pattern = str(cidr) 1996 size = len(pattern) 1997 for ifname, ifval in net_list.items(): 1998 if "inet" in ifval: 1999 for inet in ifval["inet"]: 2000 if inet["address"][0:size] == pattern: 2001 if "label" in inet: 2002 intfnames.append(inet["label"]) 2003 else: 2004 intfnames.append(ifname) 2005 return intfnames 2006 2007 2008def iphexval(ip): 2009 """ 2010 Retrieve the hexadecimal representation of an IP address 2011 2012 .. versionadded:: 2016.11.0 2013 2014 CLI Example: 2015 2016 .. code-block:: bash 2017 2018 salt '*' network.iphexval 10.0.0.1 2019 """ 2020 a = ip.split(".") 2021 hexval = ["%02X" % int(x) for x in a] # pylint: disable=E1321 2022 return "".join(hexval) 2023 2024 2025def ip_networks(interface=None, include_loopback=False, verbose=False): 2026 """ 2027 .. versionadded:: 3001 2028 2029 Returns a list of IPv4 networks to which the minion belongs. 2030 2031 interface 2032 Restrict results to the specified interface(s). This value can be 2033 either a single interface name or a list of interfaces. Globbing is 2034 also supported. 2035 2036 CLI Example: 2037 2038 .. code-block:: bash 2039 2040 salt '*' network.list_networks 2041 salt '*' network.list_networks interface=docker0 2042 salt '*' network.list_networks interface=docker0,enp* 2043 salt '*' network.list_networks interface=eth* 2044 """ 2045 return __utils__["network.ip_networks"]( 2046 interface=interface, include_loopback=include_loopback, verbose=verbose 2047 ) 2048 2049 2050def ip_networks6(interface=None, include_loopback=False, verbose=False): 2051 """ 2052 .. versionadded:: 3001 2053 2054 Returns a list of IPv6 networks to which the minion belongs. 2055 2056 interface 2057 Restrict results to the specified interface(s). This value can be 2058 either a single interface name or a list of interfaces. Globbing is 2059 also supported. 2060 2061 CLI Example: 2062 2063 .. code-block:: bash 2064 2065 salt '*' network.list_networks6 2066 salt '*' network.list_networks6 interface=docker0 2067 salt '*' network.list_networks6 interface=docker0,enp* 2068 salt '*' network.list_networks6 interface=eth* 2069 """ 2070 return __utils__["network.ip_networks6"]( 2071 interface=interface, include_loopback=include_loopback, verbose=verbose 2072 ) 2073 2074 2075def fqdns(): 2076 """ 2077 Return all known FQDNs for the system by enumerating all interfaces and 2078 then trying to reverse resolve them (excluding 'lo' interface). 2079 """ 2080 # Provides: 2081 # fqdns 2082 2083 # Possible value for h_errno defined in netdb.h 2084 HOST_NOT_FOUND = 1 2085 NO_DATA = 4 2086 2087 grains = {} 2088 fqdns = set() 2089 2090 def _lookup_fqdn(ip): 2091 try: 2092 return [socket.getfqdn(socket.gethostbyaddr(ip)[0])] 2093 except socket.herror as err: 2094 if err.errno in (0, HOST_NOT_FOUND, NO_DATA): 2095 # No FQDN for this IP address, so we don't need to know this all the time. 2096 log.debug("Unable to resolve address %s: %s", ip, err) 2097 else: 2098 log.error(err_message, err) 2099 except (OSError, socket.gaierror, socket.timeout) as err: 2100 log.error(err_message, err) 2101 2102 start = time.time() 2103 2104 addresses = salt.utils.network.ip_addrs( 2105 include_loopback=False, interface_data=salt.utils.network._get_interfaces() 2106 ) 2107 addresses.extend( 2108 salt.utils.network.ip_addrs6( 2109 include_loopback=False, interface_data=salt.utils.network._get_interfaces() 2110 ) 2111 ) 2112 err_message = "Exception during resolving address: %s" 2113 2114 # Create a ThreadPool to process the underlying calls to 'socket.gethostbyaddr' in parallel. 2115 # This avoid blocking the execution when the "fqdn" is not defined for certains IP addresses, which was causing 2116 # that "socket.timeout" was reached multiple times secuencially, blocking execution for several seconds. 2117 2118 results = [] 2119 try: 2120 pool = ThreadPool(8) 2121 results = pool.map(_lookup_fqdn, addresses) 2122 pool.close() 2123 pool.join() 2124 except Exception as exc: # pylint: disable=broad-except 2125 log.error("Exception while creating a ThreadPool for resolving FQDNs: %s", exc) 2126 2127 for item in results: 2128 if item: 2129 fqdns.update(item) 2130 2131 elapsed = time.time() - start 2132 log.debug("Elapsed time getting FQDNs: %s seconds", elapsed) 2133 2134 return {"fqdns": sorted(list(fqdns))} 2135