# -*- coding: utf-8 -*- # Copyright 2008-2016 Mir Calculate. http://www.calculate-linux.org # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from .files import (process, checkUtils, readFile, listDirectory, getRunCommands, getProgPath, FilesError) from . import device import sys import os import re import struct import fcntl import socket import math import ctypes from os import path import select import time from ..cl_lang import setLocalTranslate _ = lambda x: x setLocalTranslate('cl_lib3', sys.modules[__name__]) PROCFS_NET_PATH = "/proc/net/dev" # From linux/sockios.h SIOCGIFINDEX = 0x8933 SIOCGIFFLAGS = 0x8913 SIOCSIFFLAGS = 0x8914 SIOCSIFHWADDR = 0x8924 SIOCSIFADDR = 0x8916 SIOCSIFNETMASK = 0x891C SIOCETHTOOL = 0x8946 SIOCGIFADDR = 0x8915 SIOCGIFNETMASK = 0x891B SIOCGIFHWADDR = 0x8927 # Resources allocated IFF_RUNNING = 0x40 IFF_MASTER = 0x400 IFF_SLAVE = 0x800 # ip digit from 0|1-255|254 (template) IP_DIG = "[%s-9]|(?:1[0-9]|[1-9])[0-9]|2[0-4][0-9]|25[0-%s]" # ip net 0-32 IP_NET_SUFFIX = "[0-9]|[12][0-9]|3[012]" # ip digs 1-254,0-254,0-255 IP_DIGS = {'dig1_254': IP_DIG % (1, 4), 'dig0_254': IP_DIG % (0, 4), 'dig0_255': IP_DIG % (0, 5), } # ip addr 10.0.0.12 IP_ADDR = "(%(dig1_254)s)\.(%(dig0_254)s)\.(%(dig0_254)s)\.(%(dig1_254)s)" % \ IP_DIGS IP_MASK = "(%(dig0_255)s)\.(%(dig0_255)s)\.(%(dig0_255)s)\.(%(dig0_255)s)" % \ IP_DIGS # ip addr for net 10.0.0.0 IP_NET = "(%(dig1_254)s)\.(%(dig0_254)s)\.(%(dig0_254)s)\.(%(dig0_254)s)" % \ IP_DIGS # ip and net 192.168.0.0/16 IP_ADDR_NET = "(%(ipaddr)s)/((%(ipnet)s))" % {'ipaddr': IP_NET, 'ipnet': IP_NET_SUFFIX} reIp = re.compile("^{0}$".format(IP_ADDR)) reNetSuffix = re.compile("^{0}$".format(IP_NET_SUFFIX)) reNet = re.compile("^{0}$".format(IP_ADDR_NET)) reMask = re.compile("^{0}$".format(IP_MASK)) def checkIp(ip): """Check ip""" return reIp.match(ip) def checkNetSuffix(netSuffix): """Check net suffix""" return reNetSuffix.match(netSuffix) def checkNet(net): """Check net""" if not reNet.match(net): return False ip, op, cidr = net.partition('/') mask = strIpToIntIp(cidrToMask(int(cidr))) return (strIpToIntIp(ip) & mask) == (strIpToIntIp(ip)) maskDigs = [str(x) for x in (0b10000000, 0b11000000, 0b11100000, 0b11110000, 0b11111000, 0b11111100, 0b11111110, 0b11111111)] def checkMask(mask): """Check net""" if not mask: return False if mask.count('.') != 3: return False zero = False for dig in mask.split('.'): if zero or not dig in maskDigs: if dig == "0": zero = True else: return False return True def getIpAndMask(interface="eth0"): """Get ip and mask from interface""" ifconfig = process('/sbin/ifconfig', interface) res = re.search(r"inet addr:(\S+)\s.*Mask:(\S+)", ifconfig.read(), re.S) if res: return res.groups() else: return "", "" def strIpToIntIp(addr): """Convert ip specified by string to integer""" addr = addr.split('.') return ((int(addr[0]) << 24) | (int(addr[1]) << 16) | (int(addr[2]) << 8) | (int(addr[3]))) def intIpToStrIp(addr): """Convert ip specified by integer to string""" return "{0}.{1}.{2}.{3}".format( addr >> 24, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff) def numMaskToCidr(netmask): """ Convert integer mask to cidr """ neg_net = ctypes.c_uint32(~netmask).value return 32 - int(math.log(neg_net, 2)) - 1 if neg_net else 32 def maskToCidr(mask): """Convert mask specified by str to net""" mask = strIpToIntIp(mask) return numMaskToCidr(mask) def cidrToMask(cidr): """Convert net to mask specified by str""" return intIpToStrIp((2 ** cidr - 1) << (32 - cidr)) def getIpNet(ip, mask=None, cidr=None): """Get net (xx.xx.xx.xx/xx) by ip address and mask""" ip = strIpToIntIp(ip) if not mask is None: net = maskToCidr(mask) else: net = int(cidr) mask = cidrToMask(net) mask = strIpToIntIp(mask) return "{ip}/{net}".format(ip=intIpToStrIp(ip & mask), net=net) def isIpInNet(checkip, *ipnets): """Check is ip in specified nets""" return [x[0] for x in [(y[0], y[1][0], strIpToIntIp(cidrToMask(int(y[1][1])))) for y in [(z, z.partition('/')[0::2]) for z in ipnets]] if strIpToIntIp(checkip) & x[2] == strIpToIntIp(x[1]) & x[2]] def isUsingNetworkManager(): try: p = process("/usr/bin/nmcli", "general", "status") return p.success() except FilesError: return False def isNMDhcp(interface="eth0"): p = process("/usr/bin/nmcli", "-g", "ipv4.method", "connection", "show", "eth0") if p.success(): if "auto" in p.read(): return True return False def isDhcpIp(interface="eth0"): """Get ip by dhcp or static""" # dhclients (dhcpcd, dhclient (dhcp), udhcpc (busybox) if isUsingNetworkManager() and isNMDhcp(interface): return True commands = getRunCommands() dhcpProgs = ("dhcpcd", "dhclient", "udhcpc") if [x for x in commands if interface in x and any(prog in x for prog in dhcpProgs)]: return True else: # если запущен демон dhcpcd if [x for x in commands if "dhcpcd\x00-q" in x]: curIp = getIp(interface) dhcpcd = getProgPath('/sbin/dhcpcd') leaseIp = [x.group(1) for x in map(re.compile('^ip_address=(.*)$').search, process(dhcpcd, '-U', interface)) if x] if not curIp or leaseIp and leaseIp[0] == curIp: return True return False def getRouteTable(onlyIface=()): """Get route table, exclude specifed iface""" ipProg = checkUtils('/sbin/ip') routes = process(ipProg, "route") if onlyIface: filterRe = re.compile("|".join(("dev %s" % x for x in onlyIface))) routes = filter(filterRe.search, routes) for line in routes: network, op, line = line.partition(" ") routeParams = [x.strip() for x in line.split()] # (network,{'via':value,'dev':value}) if network: yield (network, dict(zip(routeParams[0::2], routeParams[1::2]))) def getInterfaces(): """ Get available interfaces (discard which hasn't device) """ def filter_ethernet(x): # exclude loopback if device.sysfs.read(x, "type").strip() != "1": return False # physical device if device.sysfs.exists(x, "device"): return True # exclude bridges if device.sysfs.exists(x, "brport"): return False if device.sysfs.exists(x, "bridge"): return False # exclude bonding if device.sysfs.exists(x, "bonding"): return False if device.sysfs.exists(x, "bonding_slave"): return False # exclude slave devices if any(device.sysfs.glob(x, "lower_*")): return False return True return sorted(path.basename(x) for x in device.sysfs.listdir( device.sysfs.Path.ClassNet, fullpath=True) if filter_ethernet(x)) def getMaster(iface): master_path = [device.sysfs.Path.ClassNet, iface, "master"] if device.sysfs.exists(*master_path): return path.split(device.sysfs.realpath(*master_path))[-1] return None def getIp(iface): sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ifreq = struct.pack('16sH14s', iface.encode("UTF-8"), socket.AF_INET, b'\x00' * 14) try: res = fcntl.ioctl(sockfd, SIOCGIFADDR, ifreq) except IOError: return "" finally: sockfd.close() ip = struct.unpack('16sH2x4s8x', res)[2] return socket.inet_ntoa(ip) def checkFlag(iface, flag): sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ifreq = struct.pack('16sH14s', iface.encode("UTF-8"), socket.AF_INET, b'\x00' * 14) try: res = fcntl.ioctl(sockfd, SIOCGIFFLAGS, ifreq) except IOError: return False finally: sockfd.close() return bool(struct.unpack('16sH2x4s8x', res)[1] & flag) def getPlugged(iface): return checkFlag(iface, IFF_RUNNING) def isSlaveInterface(iface): return checkFlag(iface, IFF_SLAVE) def isMasterInterface(iface): return checkFlag(iface, IFF_MASTER) def getMask(iface): """ Get mask for interface """ sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ifreq = struct.pack('16sH14s', iface.encode("UTF-8"), socket.AF_INET, b'\x00' * 14) try: res = fcntl.ioctl(sockfd, SIOCGIFNETMASK, ifreq) except IOError: return 0 finally: sockfd.close() netmask = socket.ntohl(struct.unpack('16sH2xI8x', res)[2]) return numMaskToCidr(netmask) def isWireless(iface): """ Является ли данное устройство беспроводным """ return device.sysfs.exists(device.sysfs.Path.ClassNet, iface, "wireless") def getMac(iface): """ Get mac for interface """ sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ifreq = struct.pack('16sH14s', iface.encode("UTF-8"), socket.AF_UNIX, b'\x00' * 14) try: res = fcntl.ioctl(sockfd, SIOCGIFHWADDR, ifreq) except IOError: return "" address = struct.unpack('16sH14s', res)[2] mac = struct.unpack('6B8x', address) sockfd.close() return ":".join(['%02X' % i for i in mac]) def getOperState(iface): """ Get interface state up or down """ if device.sysfs.read(device.sysfs.Path.ClassNet, iface, "operstate") == "down": return "down" return "up" def isOpenPort(ip, port): """ Test if an [ip:port] is open """ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((ip, int(port))) s.shutdown(2) return True except Exception: return False class IPError(Exception): """Error received on work with ip""" pass class Pinger(): # ICMP parameters ICMP_ECHO = 8 # Echo request (per RFC792) ICMP_MAX_RECV = 2048 # Max size of incoming buffer def checksum(self, source_string): """ A port of the functionality of in_cksum() from ping.c Ideally this would act on the string as a series of 16-bit ints (host packed), but this works. Network data is big-endian, hosts are typically little-endian """ countTo = (int(len(source_string) / 2)) * 2 sum = 0 count = 0 # Handle bytes in pairs (decoding as short ints) while count < countTo: #note: no need for ord(bytes[i]) in py3, bytes already # return int values when accessed via indeces if sys.byteorder == "little": loByte = source_string[count] hiByte = source_string[count + 1] else: loByte = source_string[count + 1] hiByte = source_string[count] sum += hiByte * 256 + loByte count += 2 # Handle last byte if applicable (odd-number of bytes) # Endianness should be irrelevant in this case if countTo < len(source_string): # Check for odd length loByte = ord(source_string[len(source_string) - 1]) sum += loByte # Truncate sum to 32 bits (a variance from ping.c,which sum &= 0xffffffff # uses signed ints, but overflow is unlikely in ping) sum = (sum >> 16) + (sum & 0xffff) # Add high 16 bits to low 16 bits sum += (sum >> 16) # Add carry from above (if any) answer = ~sum & 0xffff # Invert and truncate to 16 bits answer = socket.htons(answer) return answer def ping(self, destIP, timeout, numDataBytes): """ Returns either the delay (in ms) or None on timeout. """ delay = None try: # One could use UDP here, but it's obscure mySocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp")) except socket.error as e: raise IPError(_("failed. (socket error: '%s')" % e.args[1])) my_ID = os.getpid() & 0xFFFF sentTime = self.send_one_ping(mySocket, destIP, my_ID, 0, numDataBytes) if sentTime is None: mySocket.close() return delay recvTime, dataSize, iphSrcIP, icmpSeqNumber, iphTTL = \ self.receive_one_ping(mySocket, my_ID, timeout) mySocket.close() if recvTime: delay = (recvTime - sentTime) * 1000 return (dataSize, socket.inet_ntoa(struct.pack("!I", iphSrcIP)), iphTTL, delay) else: raise IPError(_("Request timed out")) def send_one_ping(self, mySocket, destIP, myID, mySeqNumber, numDataBytes): """ Send one ping to the given >destIP<. """ try: destIP = socket.gethostbyname(destIP) except socket.gaierror as e: raise IPError(e.strerror) # Header is type (8), code (8), checksum (16), id (16), sequence (16) myChecksum = 0 # Make a dummy heder with a 0 checksum. header = struct.pack( "!BBHHH", self.ICMP_ECHO, 0, myChecksum, myID, mySeqNumber ) padBytes = [] startVal = 0x42 for i in range(startVal, startVal + numDataBytes): padBytes += [(i & 0xff)] # Keep chars in the 0-255 range data = bytes(padBytes) # Calculate the checksum on the data and the dummy header. # Checksum is in network order myChecksum = self.checksum(header + data) # Now that we have the right checksum, we put that in. It's just easier # to make up a new header than to stuff it into the dummy. header = struct.pack( "!BBHHH", self.ICMP_ECHO, 0, myChecksum, myID, mySeqNumber ) packet = header + data sendTime = time.time() try: # Port number is irrelevant for ICMP mySocket.sendto(packet, (destIP, 1)) except socket.error as e: raise IPError("General failure (%s)" % (e.args[1])) return sendTime def receive_one_ping(self, mySocket, myID, timeout): """ Receive the ping from the socket. Timeout = in ms """ timeLeft = timeout / 1000.0 while True: # Loop while waiting for packet or timeout startedSelect = time.time() whatReady = select.select([mySocket], [], [], timeLeft) howLongInSelect = (time.time() - startedSelect) if isinstance(whatReady[0], list) and not whatReady[0]: # Timeout return None, 0, 0, 0, 0 timeReceived = time.time() recPacket, addr = mySocket.recvfrom(self.ICMP_MAX_RECV) ipHeader = recPacket[:20] iphVersion, iphTypeOfSvc, iphLength, \ iphID, iphFlags, iphTTL, iphProtocol, \ iphChecksum, iphSrcIP, iphDestIP = struct.unpack( "!BBHHHBBHII", ipHeader ) icmpHeader = recPacket[20:28] icmpType, icmpCode, icmpChecksum, \ icmpPacketID, icmpSeqNumber = struct.unpack( "!BBHHH", icmpHeader ) if icmpPacketID == myID: # Our packet dataSize = len(recPacket) - 28 return timeReceived, dataSize, iphSrcIP, icmpSeqNumber, iphTTL timeLeft = timeLeft - howLongInSelect if timeLeft <= 0: return None, 0, 0, 0, 0 def check_port(target, port, timeout=5): """ Проверить порт (port) на хосте (target) :param target: хост :param port: порт :param timeout: таймаут :return: True - порт открыт, False - нет хоста, порт закрыт, firewall """ try: targetIP = socket.gethostbyname(target) except socket.gaierror: return False s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(timeout) try: s.connect((targetIP, port)) s.close() except (socket.error, socket.timeout): return False return True