# This file is part of Scapy # See http://www.secdev.org/projects/scapy for more information # Copyright (C) Philippe Biondi # This program is published under a GPLv2 license """ General utility functions. """ from __future__ import absolute_import from __future__ import print_function from decimal import Decimal import os import sys import socket import collections import random import time import gzip import re import struct import array import subprocess import tempfile import threading import scapy.modules.six as six from scapy.modules.six.moves import range, input from scapy.config import conf from scapy.consts import DARWIN, WINDOWS, WINDOWS_XP, OPENBSD from scapy.data import MTU, DLT_EN10MB from scapy.compat import orb, raw, plain_str, chb, bytes_base64, \ base64_bytes, hex_bytes, lambda_tuple_converter, bytes_encode from scapy.error import log_runtime, Scapy_Exception, warning from scapy.pton_ntop import inet_pton ########### # Tools # ########### def issubtype(x, t): """issubtype(C, B) -> bool Return whether C is a class and if it is a subclass of class B. When using a tuple as the second argument issubtype(X, (A, B, ...)), is a shortcut for issubtype(X, A) or issubtype(X, B) or ... (etc.). """ if isinstance(t, str): return t in (z.__name__ for z in x.__bases__) if isinstance(x, type) and issubclass(x, t): return True return False class EDecimal(Decimal): """Extended Decimal This implements arithmetic and comparison with float for backward compatibility """ def __add__(self, other, **kwargs): return EDecimal(Decimal.__add__(self, Decimal(other), **kwargs)) def __radd__(self, other, **kwargs): return EDecimal(Decimal.__add__(self, Decimal(other), **kwargs)) def __sub__(self, other, **kwargs): return EDecimal(Decimal.__sub__(self, Decimal(other), **kwargs)) def __rsub__(self, other, **kwargs): return EDecimal(Decimal.__rsub__(self, Decimal(other), **kwargs)) def __mul__(self, other, **kwargs): return EDecimal(Decimal.__mul__(self, Decimal(other), **kwargs)) def __rmul__(self, other, **kwargs): return EDecimal(Decimal.__mul__(self, Decimal(other), **kwargs)) def __truediv__(self, other, **kwargs): return EDecimal(Decimal.__truediv__(self, Decimal(other), **kwargs)) def __floordiv__(self, other, **kwargs): return EDecimal(Decimal.__floordiv__(self, Decimal(other), **kwargs)) def __div__(self, other, **kwargs): return EDecimal(Decimal.__div__(self, Decimal(other), **kwargs)) def __rdiv__(self, other, **kwargs): return EDecimal(Decimal.__rdiv__(self, Decimal(other), **kwargs)) def __mod__(self, other, **kwargs): return EDecimal(Decimal.__mod__(self, Decimal(other), **kwargs)) def __rmod__(self, other, **kwargs): return EDecimal(Decimal.__rmod__(self, Decimal(other), **kwargs)) def __divmod__(self, other, **kwargs): return EDecimal(Decimal.__divmod__(self, Decimal(other), **kwargs)) def __rdivmod__(self, other, **kwargs): return EDecimal(Decimal.__rdivmod__(self, Decimal(other), **kwargs)) def __pow__(self, other, **kwargs): return EDecimal(Decimal.__pow__(self, Decimal(other), **kwargs)) def __rpow__(self, other, **kwargs): return EDecimal(Decimal.__rpow__(self, Decimal(other), **kwargs)) def __eq__(self, other, **kwargs): return super(EDecimal, self).__eq__(other) or float(self) == other def get_temp_file(keep=False, autoext="", fd=False): """Creates a temporary file. :param keep: If False, automatically delete the file when Scapy exits. :param autoext: Suffix to add to the generated file name. :param fd: If True, this returns a file-like object with the temporary file opened. If False (default), this returns a file path. """ f = tempfile.NamedTemporaryFile(prefix="scapy", suffix=autoext, delete=False) if not keep: conf.temp_files.append(f.name) if fd: return f else: # Close the file so something else can take it. f.close() return f.name def get_temp_dir(keep=False): """Creates a temporary file, and returns its name. :param keep: If False (default), the directory will be recursively deleted when Scapy exits. :return: A full path to a temporary directory. """ dname = tempfile.mkdtemp(prefix="scapy") if not keep: conf.temp_files.append(dname) return dname def sane_color(x): r = "" for i in x: j = orb(i) if (j < 32) or (j >= 127): r += conf.color_theme.not_printable(".") else: r += chr(j) return r def sane(x): r = "" for i in x: j = orb(i) if (j < 32) or (j >= 127): r += "." else: r += chr(j) return r @conf.commands.register def restart(): """Restarts scapy""" if not conf.interactive or not os.path.isfile(sys.argv[0]): raise OSError("Scapy was not started from console") if WINDOWS: try: res_code = subprocess.call([sys.executable] + sys.argv) except KeyboardInterrupt: res_code = 1 finally: os._exit(res_code) os.execv(sys.executable, [sys.executable] + sys.argv) def lhex(x): from scapy.volatile import VolatileValue if isinstance(x, VolatileValue): return repr(x) if type(x) in six.integer_types: return hex(x) elif isinstance(x, tuple): return "(%s)" % ", ".join(map(lhex, x)) elif isinstance(x, list): return "[%s]" % ", ".join(map(lhex, x)) else: return x @conf.commands.register def hexdump(x, dump=False): """Build a tcpdump like hexadecimal view :param x: a Packet :param dump: define if the result must be printed or returned in a variable :return: a String only when dump=True """ s = "" x = bytes_encode(x) x_len = len(x) i = 0 while i < x_len: s += "%04x " % i for j in range(16): if i + j < x_len: s += "%02X " % orb(x[i + j]) else: s += " " s += " %s\n" % sane_color(x[i:i + 16]) i += 16 # remove trailing \n s = s[:-1] if s.endswith("\n") else s if dump: return s else: print(s) @conf.commands.register def linehexdump(x, onlyasc=0, onlyhex=0, dump=False): """Build an equivalent view of hexdump() on a single line Note that setting both onlyasc and onlyhex to 1 results in a empty output :param x: a Packet :param onlyasc: 1 to display only the ascii view :param onlyhex: 1 to display only the hexadecimal view :param dump: print the view if False :return: a String only when dump=True """ s = "" s = hexstr(x, onlyasc=onlyasc, onlyhex=onlyhex, color=not dump) if dump: return s else: print(s) @conf.commands.register def chexdump(x, dump=False): """Build a per byte hexadecimal representation Example: >>> chexdump(IP()) 0x45, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x40, 0x00, 0x7c, 0xe7, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01 # noqa: E501 :param x: a Packet :param dump: print the view if False :return: a String only if dump=True """ x = bytes_encode(x) s = ", ".join("%#04x" % orb(x) for x in x) if dump: return s else: print(s) @conf.commands.register def hexstr(x, onlyasc=0, onlyhex=0, color=False): """Build a fancy tcpdump like hex from bytes.""" x = bytes_encode(x) _sane_func = sane_color if color else sane s = [] if not onlyasc: s.append(" ".join("%02X" % orb(b) for b in x)) if not onlyhex: s.append(_sane_func(x)) return " ".join(s) def repr_hex(s): """ Convert provided bitstring to a simple string of hex digits """ return "".join("%02x" % orb(x) for x in s) @conf.commands.register def hexdiff(x, y): """Show differences between 2 binary strings""" x = bytes_encode(x)[::-1] y = bytes_encode(y)[::-1] SUBST = 1 INSERT = 1 d = {(-1, -1): (0, (-1, -1))} for j in range(len(y)): d[-1, j] = d[-1, j - 1][0] + INSERT, (-1, j - 1) for i in range(len(x)): d[i, -1] = d[i - 1, -1][0] + INSERT, (i - 1, -1) for j in range(len(y)): for i in range(len(x)): d[i, j] = min((d[i - 1, j - 1][0] + SUBST * (x[i] != y[j]), (i - 1, j - 1)), # noqa: E501 (d[i - 1, j][0] + INSERT, (i - 1, j)), (d[i, j - 1][0] + INSERT, (i, j - 1))) backtrackx = [] backtracky = [] i = len(x) - 1 j = len(y) - 1 while not (i == j == -1): i2, j2 = d[i, j][1] backtrackx.append(x[i2 + 1:i + 1]) backtracky.append(y[j2 + 1:j + 1]) i, j = i2, j2 x = y = i = 0 colorize = {0: lambda x: x, -1: conf.color_theme.left, 1: conf.color_theme.right} dox = 1 doy = 0 btx_len = len(backtrackx) while i < btx_len: linex = backtrackx[i:i + 16] liney = backtracky[i:i + 16] xx = sum(len(k) for k in linex) yy = sum(len(k) for k in liney) if dox and not xx: dox = 0 doy = 1 if dox and linex == liney: doy = 1 if dox: xd = y j = 0 while not linex[j]: j += 1 xd -= 1 print(colorize[doy - dox]("%04x" % xd), end=' ') x += xx line = linex else: print(" ", end=' ') if doy: yd = y j = 0 while not liney[j]: j += 1 yd -= 1 print(colorize[doy - dox]("%04x" % yd), end=' ') y += yy line = liney else: print(" ", end=' ') print(" ", end=' ') cl = "" for j in range(16): if i + j < btx_len: if line[j]: col = colorize[(linex[j] != liney[j]) * (doy - dox)] print(col("%02X" % orb(line[j])), end=' ') if linex[j] == liney[j]: cl += sane_color(line[j]) else: cl += col(sane(line[j])) else: print(" ", end=' ') cl += " " else: print(" ", end=' ') if j == 7: print("", end=' ') print(" ", cl) if doy or not yy: doy = 0 dox = 1 i += 16 else: if yy: dox = 0 doy = 1 else: i += 16 if struct.pack("H", 1) == b"\x00\x01": # big endian checksum_endian_transform = lambda chk: chk else: checksum_endian_transform = lambda chk: ((chk >> 8) & 0xff) | chk << 8 def checksum(pkt): if len(pkt) % 2 == 1: pkt += b"\0" s = sum(array.array("H", pkt)) s = (s >> 16) + (s & 0xffff) s += s >> 16 s = ~s return checksum_endian_transform(s) & 0xffff def _fletcher16(charbuf): # This is based on the GPLed C implementation in Zebra # noqa: E501 c0 = c1 = 0 for char in charbuf: c0 += orb(char) c1 += c0 c0 %= 255 c1 %= 255 return (c0, c1) @conf.commands.register def fletcher16_checksum(binbuf): """Calculates Fletcher-16 checksum of the given buffer. Note: If the buffer contains the two checkbytes derived from the Fletcher-16 checksum # noqa: E501 the result of this function has to be 0. Otherwise the buffer has been corrupted. # noqa: E501 """ (c0, c1) = _fletcher16(binbuf) return (c1 << 8) | c0 @conf.commands.register def fletcher16_checkbytes(binbuf, offset): """Calculates the Fletcher-16 checkbytes returned as 2 byte binary-string. Including the bytes into the buffer (at the position marked by offset) the # noqa: E501 global Fletcher-16 checksum of the buffer will be 0. Thus it is easy to verify # noqa: E501 the integrity of the buffer on the receiver side. For details on the algorithm, see RFC 2328 chapter 12.1.7 and RFC 905 Annex B. # noqa: E501 """ # This is based on the GPLed C implementation in Zebra # noqa: E501 if len(binbuf) < offset: raise Exception("Packet too short for checkbytes %d" % len(binbuf)) binbuf = binbuf[:offset] + b"\x00\x00" + binbuf[offset + 2:] (c0, c1) = _fletcher16(binbuf) x = ((len(binbuf) - offset - 1) * c0 - c1) % 255 if (x <= 0): x += 255 y = 510 - c0 - x if (y > 255): y -= 255 return chb(x) + chb(y) def mac2str(mac): return b"".join(chb(int(x, 16)) for x in plain_str(mac).split(':')) def valid_mac(mac): try: return len(mac2str(mac)) == 6 except ValueError: pass return False def str2mac(s): if isinstance(s, str): return ("%02x:" * 6)[:-1] % tuple(map(ord, s)) return ("%02x:" * 6)[:-1] % tuple(s) def randstring(l): """ Returns a random string of length l (l >= 0) """ return b"".join(struct.pack('B', random.randint(0, 255)) for _ in range(l)) def zerofree_randstring(l): """ Returns a random string of length l (l >= 0) without zero in it. """ return b"".join(struct.pack('B', random.randint(1, 255)) for _ in range(l)) def strxor(s1, s2): """ Returns the binary XOR of the 2 provided strings s1 and s2. s1 and s2 must be of same length. """ return b"".join(map(lambda x, y: chb(orb(x) ^ orb(y)), s1, s2)) def strand(s1, s2): """ Returns the binary AND of the 2 provided strings s1 and s2. s1 and s2 must be of same length. """ return b"".join(map(lambda x, y: chb(orb(x) & orb(y)), s1, s2)) # Workaround bug 643005 : https://sourceforge.net/tracker/?func=detail&atid=105470&aid=643005&group_id=5470 # noqa: E501 try: socket.inet_aton("255.255.255.255") except socket.error: def inet_aton(x): if x == "255.255.255.255": return b"\xff" * 4 else: return socket.inet_aton(x) else: inet_aton = socket.inet_aton inet_ntoa = socket.inet_ntoa def atol(x): try: ip = inet_aton(x) except socket.error: ip = inet_aton(socket.gethostbyname(x)) return struct.unpack("!I", ip)[0] def valid_ip(addr): try: addr = plain_str(addr) except UnicodeDecodeError: return False try: atol(addr) except (OSError, ValueError, socket.error): return False return True def valid_net(addr): try: addr = plain_str(addr) except UnicodeDecodeError: return False if '/' in addr: ip, mask = addr.split('/', 1) return valid_ip(ip) and mask.isdigit() and 0 <= int(mask) <= 32 return valid_ip(addr) def valid_ip6(addr): try: addr = plain_str(addr) except UnicodeDecodeError: return False try: inet_pton(socket.AF_INET6, addr) except socket.error: try: socket.getaddrinfo(addr, None, socket.AF_INET6)[0][4][0] except socket.error: return False return True def valid_net6(addr): try: addr = plain_str(addr) except UnicodeDecodeError: return False if '/' in addr: ip, mask = addr.split('/', 1) return valid_ip6(ip) and mask.isdigit() and 0 <= int(mask) <= 128 return valid_ip6(addr) if WINDOWS_XP: # That is a hell of compatibility :( def ltoa(x): return inet_ntoa(struct.pack("> x) & 0xffffffff class ContextManagerSubprocess(object): """ Context manager that eases checking for unknown command, without crashing. Example: >>> with ContextManagerSubprocess("tcpdump"): >>> subprocess.Popen(["tcpdump", "--version"]) ERROR: Could not execute tcpdump, is it installed? """ def __init__(self, prog, suppress=True): self.prog = prog self.suppress = suppress def __enter__(self): pass def __exit__(self, exc_type, exc_value, traceback): if exc_value is None: return # Errored if isinstance(exc_value, EnvironmentError): msg = "Could not execute %s, is it installed?" % self.prog else: msg = "%s: execution failed (%s)" % ( self.prog, exc_type.__class__.__name__ ) if not self.suppress: raise exc_type(msg) log_runtime.error(msg, exc_info=True) return True # Suppress the exception class ContextManagerCaptureOutput(object): """ Context manager that intercept the console's output. Example: >>> with ContextManagerCaptureOutput() as cmco: ... print("hey") ... assert cmco.get_output() == "hey" """ def __init__(self): self.result_export_object = "" try: import mock # noqa: F401 except Exception: raise ImportError("The mock module needs to be installed !") def __enter__(self): import mock def write(s, decorator=self): decorator.result_export_object += s mock_stdout = mock.Mock() mock_stdout.write = write self.bck_stdout = sys.stdout sys.stdout = mock_stdout return self def __exit__(self, *exc): sys.stdout = self.bck_stdout return False def get_output(self, eval_bytes=False): if self.result_export_object.startswith("b'") and eval_bytes: return plain_str(eval(self.result_export_object)) return self.result_export_object def do_graph(graph, prog=None, format=None, target=None, type=None, string=None, options=None): """Processes graph description using an external software. This method is used to convert a graphviz format to an image. :param graph: GraphViz graph description :param prog: which graphviz program to use :param format: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option :param string: if not None, simply return the graph string :param target: filename or redirect. Defaults pipe to Imagemagick's display program :param options: options to be passed to prog """ if format is None: format = "svg" if string: return graph if type is not None: warning("type is deprecated, and was renamed format") format = type if prog is None: prog = conf.prog.dot start_viewer = False if target is None: if WINDOWS: target = get_temp_file(autoext="." + format) start_viewer = True else: with ContextManagerSubprocess(conf.prog.display): target = subprocess.Popen([conf.prog.display], stdin=subprocess.PIPE).stdin if format is not None: format = "-T%s" % format if isinstance(target, str): if target.startswith('|'): target = subprocess.Popen(target[1:].lstrip(), shell=True, stdin=subprocess.PIPE).stdin elif target.startswith('>'): target = open(target[1:].lstrip(), "wb") else: target = open(os.path.abspath(target), "wb") proc = subprocess.Popen( "\"%s\" %s %s" % (prog, options or "", format or ""), shell=True, stdin=subprocess.PIPE, stdout=target, stderr=subprocess.PIPE ) _, stderr = proc.communicate(bytes_encode(graph)) if proc.returncode != 0: raise OSError( "GraphViz call failed (is it installed?):\n" + plain_str(stderr) ) try: target.close() except Exception: pass if start_viewer: # Workaround for file not found error: We wait until tempfile is written. # noqa: E501 waiting_start = time.time() while not os.path.exists(target.name): time.sleep(0.1) if time.time() - waiting_start > 3: warning("Temporary file '%s' could not be written. Graphic will not be displayed.", tempfile) # noqa: E501 break else: if conf.prog.display == conf.prog._default: os.startfile(target.name) else: with ContextManagerSubprocess(conf.prog.display): subprocess.Popen([conf.prog.display, target.name]) _TEX_TR = { "{": "{\\tt\\char123}", "}": "{\\tt\\char125}", "\\": "{\\tt\\char92}", "^": "\\^{}", "$": "\\$", "#": "\\#", "_": "\\_", "&": "\\&", "%": "\\%", "|": "{\\tt\\char124}", "~": "{\\tt\\char126}", "<": "{\\tt\\char60}", ">": "{\\tt\\char62}", } def tex_escape(x): s = "" for c in x: s += _TEX_TR.get(c, c) return s def colgen(*lstcol, **kargs): """Returns a generator that mixes provided quantities forever trans: a function to convert the three arguments into a color. lambda x,y,z:(x,y,z) by default""" # noqa: E501 if len(lstcol) < 2: lstcol *= 2 trans = kargs.get("trans", lambda x, y, z: (x, y, z)) while True: for i in range(len(lstcol)): for j in range(len(lstcol)): for k in range(len(lstcol)): if i != j or j != k or k != i: yield trans(lstcol[(i + j) % len(lstcol)], lstcol[(j + k) % len(lstcol)], lstcol[(k + i) % len(lstcol)]) # noqa: E501 def incremental_label(label="tag%05i", start=0): while True: yield label % start start += 1 def binrepr(val): return bin(val)[2:] def long_converter(s): return int(s.replace('\n', '').replace(' ', ''), 16) ######################### # Enum management # ######################### class EnumElement: _value = None def __init__(self, key, value): self._key = key self._value = value def __repr__(self): return "<%s %s[%r]>" % ( self.__dict__.get("_name", self.__class__.__name__), self._key, self._value) # noqa: E501 def __getattr__(self, attr): return getattr(self._value, attr) def __str__(self): return self._key def __bytes__(self): return bytes_encode(self.__str__()) def __hash__(self): return self._value def __int__(self): return int(self._value) def __eq__(self, other): return self._value == int(other) def __neq__(self, other): return not self.__eq__(other) class Enum_metaclass(type): element_class = EnumElement def __new__(cls, name, bases, dct): rdict = {} for k, v in six.iteritems(dct): if isinstance(v, int): v = cls.element_class(k, v) dct[k] = v rdict[v] = k dct["__rdict__"] = rdict return super(Enum_metaclass, cls).__new__(cls, name, bases, dct) def __getitem__(self, attr): return self.__rdict__[attr] def __contains__(self, val): return val in self.__rdict__ def get(self, attr, val=None): return self.__rdict__.get(attr, val) def __repr__(self): return "<%s>" % self.__dict__.get("name", self.__name__) ################### # Object saving # ################### def export_object(obj): print(bytes_base64(gzip.zlib.compress(six.moves.cPickle.dumps(obj, 2), 9))) def import_object(obj=None): if obj is None: obj = sys.stdin.read() return six.moves.cPickle.loads(gzip.zlib.decompress(base64_bytes(obj.strip()))) # noqa: E501 def save_object(fname, obj): """Pickle a Python object""" fd = gzip.open(fname, "wb") six.moves.cPickle.dump(obj, fd) fd.close() def load_object(fname): """unpickle a Python object""" return six.moves.cPickle.load(gzip.open(fname, "rb")) @conf.commands.register def corrupt_bytes(s, p=0.01, n=None): """Corrupt a given percentage or number of bytes from a string""" s = array.array("B", bytes_encode(s)) s_len = len(s) if n is None: n = max(1, int(s_len * p)) for i in random.sample(range(s_len), n): s[i] = (s[i] + random.randint(1, 255)) % 256 return s.tostring() if six.PY2 else s.tobytes() @conf.commands.register def corrupt_bits(s, p=0.01, n=None): """Flip a given percentage or number of bits from a string""" s = array.array("B", bytes_encode(s)) s_len = len(s) * 8 if n is None: n = max(1, int(s_len * p)) for i in random.sample(range(s_len), n): s[i // 8] ^= 1 << (i % 8) return s.tostring() if six.PY2 else s.tobytes() ############################# # pcap capture file stuff # ############################# @conf.commands.register def wrpcap(filename, pkt, *args, **kargs): """Write a list of packets to a pcap file :param filename: the name of the file to write packets to, or an open, writable file-like object. The file descriptor will be closed at the end of the call, so do not use an object you do not want to close (e.g., running wrpcap(sys.stdout, []) in interactive mode will crash Scapy). :param gz: set to 1 to save a gzipped capture :param linktype: force linktype value :param endianness: "<" or ">", force endianness :param sync: do not bufferize writes to the capture file """ with PcapWriter(filename, *args, **kargs) as fdesc: fdesc.write(pkt) @conf.commands.register def rdpcap(filename, count=-1): """Read a pcap or pcapng file and return a packet list :param count: read only packets """ with PcapReader(filename) as fdesc: return fdesc.read_all(count=count) class PcapReader_metaclass(type): """Metaclass for (Raw)Pcap(Ng)Readers""" def __new__(cls, name, bases, dct): """The `alternative` class attribute is declared in the PcapNg variant, and set here to the Pcap variant. """ newcls = super(PcapReader_metaclass, cls).__new__(cls, name, bases, dct) # noqa: E501 if 'alternative' in dct: dct['alternative'].alternative = newcls return newcls def __call__(cls, filename): """Creates a cls instance, use the `alternative` if that fails. """ i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) filename, fdesc, magic = cls.open(filename) try: i.__init__(filename, fdesc, magic) except Scapy_Exception: if "alternative" in cls.__dict__: cls = cls.__dict__["alternative"] i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) try: i.__init__(filename, fdesc, magic) except Scapy_Exception: try: i.f.seek(-4, 1) except Exception: pass raise Scapy_Exception("Not a supported capture file") return i @staticmethod def open(filename): """Open (if necessary) filename, and read the magic.""" if isinstance(filename, six.string_types): try: fdesc = gzip.open(filename, "rb") magic = fdesc.read(4) except IOError: fdesc = open(filename, "rb") magic = fdesc.read(4) else: fdesc = filename filename = getattr(fdesc, "name", "No name") magic = fdesc.read(4) return filename, fdesc, magic class RawPcapReader(six.with_metaclass(PcapReader_metaclass)): """A stateful pcap reader. Each packet is returned as a string""" read_allowed_exceptions = () # emulate SuperSocket nonblocking_socket = True PacketMetadata = collections.namedtuple("PacketMetadata", ["sec", "usec", "wirelen", "caplen"]) # noqa: E501 def __init__(self, filename, fdesc, magic): self.filename = filename self.f = fdesc if magic == b"\xa1\xb2\xc3\xd4": # big endian self.endian = ">" self.nano = False elif magic == b"\xd4\xc3\xb2\xa1": # little endian self.endian = "<" self.nano = False elif magic == b"\xa1\xb2\x3c\x4d": # big endian, nanosecond-precision self.endian = ">" self.nano = True elif magic == b"\x4d\x3c\xb2\xa1": # little endian, nanosecond-precision # noqa: E501 self.endian = "<" self.nano = True else: raise Scapy_Exception( "Not a pcap capture file (bad magic: %r)" % magic ) hdr = self.f.read(20) if len(hdr) < 20: raise Scapy_Exception("Invalid pcap file (too short)") vermaj, vermin, tz, sig, snaplen, linktype = struct.unpack( self.endian + "HHIIII", hdr ) self.linktype = linktype self.snaplen = snaplen def __iter__(self): return self def next(self): """implement the iterator protocol on a set of packets in a pcap file pkt is a tuple (pkt_data, pkt_metadata) as defined in RawPcapReader.read_packet() """ try: return self.read_packet() except EOFError: raise StopIteration __next__ = next def read_packet(self, size=MTU): """return a single packet read from the file as a tuple containing (pkt_data, pkt_metadata) raise EOFError when no more packets are available """ hdr = self.f.read(16) if len(hdr) < 16: raise EOFError sec, usec, caplen, wirelen = struct.unpack(self.endian + "IIII", hdr) return (self.f.read(caplen)[:size], RawPcapReader.PacketMetadata(sec=sec, usec=usec, wirelen=wirelen, caplen=caplen)) def dispatch(self, callback): """call the specified callback routine for each packet read This is just a convenience function for the main loop that allows for easy launching of packet processing in a thread. """ for p in self: callback(p) def read_all(self, count=-1): """return a list of all packets in the pcap file """ res = [] while count != 0: count -= 1 try: p = self.read_packet() except EOFError: break res.append(p) return res def recv(self, size=MTU): """ Emulate a socket """ return self.read_packet(size=size)[0] def fileno(self): return self.f.fileno() def close(self): return self.f.close() def __enter__(self): return self def __exit__(self, exc_type, exc_value, tracback): self.close() # emulate SuperSocket @staticmethod def select(sockets, remain=None): return sockets, None class PcapReader(RawPcapReader): def __init__(self, filename, fdesc, magic): RawPcapReader.__init__(self, filename, fdesc, magic) try: self.LLcls = conf.l2types[self.linktype] except KeyError: warning("PcapReader: unknown LL type [%i]/[%#x]. Using Raw packets" % ( self.linktype, self.linktype)) # noqa: E501 self.LLcls = conf.raw_layer def read_packet(self, size=MTU): rp = super(PcapReader, self).read_packet(size=size) if rp is None: raise EOFError s, pkt_info = rp try: p = self.LLcls(s) except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: from scapy.sendrecv import debug debug.crashed_on = (self.LLcls, s) raise p = conf.raw_layer(s) power = Decimal(10) ** Decimal(-9 if self.nano else -6) p.time = EDecimal(pkt_info.sec + power * pkt_info.usec) p.wirelen = pkt_info.wirelen return p def read_all(self, count=-1): res = RawPcapReader.read_all(self, count) from scapy import plist return plist.PacketList(res, name=os.path.basename(self.filename)) def recv(self, size=MTU): return self.read_packet(size=size) class RawPcapNgReader(RawPcapReader): """A stateful pcapng reader. Each packet is returned as bytes. """ alternative = RawPcapReader PacketMetadata = collections.namedtuple("PacketMetadata", ["linktype", "tsresol", "tshigh", "tslow", "wirelen"]) def __init__(self, filename, fdesc, magic): self.filename = filename self.f = fdesc # A list of (linktype, snaplen, tsresol); will be populated by IDBs. self.interfaces = [] self.default_options = { "tsresol": 1000000 } self.blocktypes = { 1: self.read_block_idb, 2: self.read_block_pkt, 3: self.read_block_spb, 6: self.read_block_epb, } if magic != b"\x0a\x0d\x0d\x0a": # PcapNg: raise Scapy_Exception( "Not a pcapng capture file (bad magic: %r)" % magic ) # see https://github.com/pcapng/pcapng blocklen, magic = self.f.read(4), self.f.read(4) # noqa: F841 if magic == b"\x1a\x2b\x3c\x4d": self.endian = ">" elif magic == b"\x4d\x3c\x2b\x1a": self.endian = "<" else: raise Scapy_Exception("Not a pcapng capture file (bad magic)") self.f.read(12) blocklen = struct.unpack("!I", blocklen)[0] # Read default options self.default_options = self.read_options( self.f.read(blocklen - 24) ) try: self.f.seek(0) except Exception: pass def read_packet(self, size=MTU): """Read blocks until it reaches either EOF or a packet, and returns None or (packet, (linktype, sec, usec, wirelen)), where packet is a string. """ while True: try: blocktype, blocklen = struct.unpack(self.endian + "2I", self.f.read(8)) except struct.error: raise EOFError block = self.f.read(blocklen - 12) if blocklen % 4: pad = self.f.read(4 - (blocklen % 4)) warning("PcapNg: bad blocklen %d (MUST be a multiple of 4. " "Ignored padding %r" % (blocklen, pad)) try: if (blocklen,) != struct.unpack(self.endian + 'I', self.f.read(4)): warning("PcapNg: Invalid pcapng block (bad blocklen)") except struct.error: raise EOFError res = self.blocktypes.get(blocktype, lambda block, size: None)(block, size) if res is not None: return res def read_options(self, options): """Section Header Block""" opts = self.default_options.copy() while len(options) >= 4: code, length = struct.unpack(self.endian + "HH", options[:4]) # PCAP Next Generation (pcapng) Capture File Format # 4.2. - Interface Description Block # http://xml2rfc.tools.ietf.org/cgi-bin/xml2rfc.cgi?url=https://raw.githubusercontent.com/pcapng/pcapng/master/draft-tuexen-opsawg-pcapng.xml&modeAsFormat=html/ascii&type=ascii#rfc.section.4.2 if code == 9 and length == 1 and len(options) >= 5: tsresol = orb(options[4]) opts["tsresol"] = (2 if tsresol & 128 else 10) ** ( tsresol & 127 ) if code == 0: if length != 0: warning("PcapNg: invalid option length %d for end-of-option" % length) # noqa: E501 break if length % 4: length += (4 - (length % 4)) options = options[4 + length:] return opts def read_block_idb(self, block, _): """Interface Description Block""" options = self.read_options(block[16:]) self.interfaces.append(struct.unpack(self.endian + "HxxI", block[:8]) + (options["tsresol"],)) def read_block_epb(self, block, size): """Enhanced Packet Block""" intid, tshigh, tslow, caplen, wirelen = struct.unpack( self.endian + "5I", block[:20], ) return (block[20:20 + caplen][:size], RawPcapNgReader.PacketMetadata(linktype=self.interfaces[intid][0], # noqa: E501 tsresol=self.interfaces[intid][2], # noqa: E501 tshigh=tshigh, tslow=tslow, wirelen=wirelen)) def read_block_spb(self, block, size): """Simple Packet Block""" # "it MUST be assumed that all the Simple Packet Blocks have # been captured on the interface previously specified in the # first Interface Description Block." intid = 0 wirelen, = struct.unpack(self.endian + "I", block[:4]) caplen = min(wirelen, self.interfaces[intid][1]) return (block[4:4 + caplen][:size], RawPcapNgReader.PacketMetadata(linktype=self.interfaces[intid][0], # noqa: E501 tsresol=self.interfaces[intid][2], # noqa: E501 tshigh=None, tslow=None, wirelen=wirelen)) def read_block_pkt(self, block, size): """(Obsolete) Packet Block""" intid, drops, tshigh, tslow, caplen, wirelen = struct.unpack( self.endian + "HH4I", block[:20], ) return (block[20:20 + caplen][:size], RawPcapNgReader.PacketMetadata(linktype=self.interfaces[intid][0], # noqa: E501 tsresol=self.interfaces[intid][2], # noqa: E501 tshigh=tshigh, tslow=tslow, wirelen=wirelen)) class PcapNgReader(RawPcapNgReader): alternative = PcapReader def __init__(self, filename, fdesc, magic): RawPcapNgReader.__init__(self, filename, fdesc, magic) def read_packet(self, size=MTU): rp = super(PcapNgReader, self).read_packet(size=size) if rp is None: raise EOFError s, (linktype, tsresol, tshigh, tslow, wirelen) = rp try: p = conf.l2types[linktype](s) except KeyboardInterrupt: raise except Exception: if conf.debug_dissector: raise p = conf.raw_layer(s) if tshigh is not None: p.time = EDecimal((tshigh << 32) + tslow) / tsresol p.wirelen = wirelen return p def read_all(self, count=-1): res = RawPcapNgReader.read_all(self, count) from scapy import plist return plist.PacketList(res, name=os.path.basename(self.filename)) def recv(self, size=MTU): return self.read_packet() class RawPcapWriter: """A stream PCAP writer with more control than wrpcap()""" def __init__(self, filename, linktype=None, gz=False, endianness="", append=False, sync=False, nano=False, snaplen=MTU): """ :param filename: the name of the file to write packets to, or an open, writable file-like object. :param linktype: force linktype to a given value. If None, linktype is taken from the first writer packet :param gz: compress the capture on the fly :param endianness: force an endianness (little:"<", big:">"). Default is native :param append: append packets to the capture file instead of truncating it :param sync: do not bufferize writes to the capture file :param nano: use nanosecond-precision (requires libpcap >= 1.5.0) """ self.linktype = linktype self.snaplen = snaplen self.header_present = 0 self.append = append self.gz = gz self.endian = endianness self.sync = sync self.nano = nano bufsz = 4096 if sync: bufsz = 0 if isinstance(filename, six.string_types): self.filename = filename self.f = [open, gzip.open][gz](filename, append and "ab" or "wb", gz and 9 or bufsz) # noqa: E501 else: self.f = filename self.filename = getattr(filename, "name", "No name") def fileno(self): return self.f.fileno() def _write_header(self, pkt): self.header_present = 1 if self.append: # Even if prone to race conditions, this seems to be # safest way to tell whether the header is already present # because we have to handle compressed streams that # are not as flexible as basic files g = [open, gzip.open][self.gz](self.filename, "rb") if g.read(16): return self.f.write(struct.pack(self.endian + "IHHIIII", 0xa1b23c4d if self.nano else 0xa1b2c3d4, # noqa: E501 2, 4, 0, 0, self.snaplen, self.linktype)) self.f.flush() def write(self, pkt): """ Writes a Packet, a SndRcvList object, or bytes to a pcap file. :param pkt: Packet(s) to write (one record for each Packet), or raw bytes to write (as one record). :type pkt: iterable[scapy.packet.Packet], scapy.packet.Packet or bytes """ if isinstance(pkt, bytes): if not self.header_present: self._write_header(pkt) self._write_packet(pkt) else: # Import here to avoid a circular dependency from scapy.plist import SndRcvList if isinstance(pkt, SndRcvList): pkt = (p for t in pkt for p in t) else: pkt = pkt.__iter__() for p in pkt: if not self.header_present: self._write_header(p) if self.linktype != conf.l2types.get(type(p), None): warning("Inconsistent linktypes detected!" " The resulting PCAP file might contain" " invalid packets." ) self._write_packet(p) def _write_packet(self, packet, sec=None, usec=None, caplen=None, wirelen=None): """ Writes a single packet to the pcap file. :param packet: bytes for a single packet :type packet: bytes :param sec: time the packet was captured, in seconds since epoch. If not supplied, defaults to now. :type sec: int or long :param usec: If ``nano=True``, then number of nanoseconds after the second that the packet was captured. If ``nano=False``, then the number of microseconds after the second the packet was captured :type usec: int or long :param caplen: The length of the packet in the capture file. If not specified, uses ``len(packet)``. :type caplen: int :param wirelen: The length of the packet on the wire. If not specified, uses ``caplen``. :type wirelen: int :return: None :rtype: None """ if caplen is None: caplen = len(packet) if wirelen is None: wirelen = caplen if sec is None or usec is None: t = time.time() it = int(t) if sec is None: sec = it usec = int(round((t - it) * (1000000000 if self.nano else 1000000))) elif usec is None: usec = 0 self.f.write(struct.pack(self.endian + "IIII", sec, usec, caplen, wirelen)) self.f.write(packet) if self.sync: self.f.flush() def flush(self): return self.f.flush() def close(self): if not self.header_present: self._write_header(None) return self.f.close() def __enter__(self): return self def __exit__(self, exc_type, exc_value, tracback): self.flush() self.close() class PcapWriter(RawPcapWriter): """A stream PCAP writer with more control than wrpcap()""" def _write_header(self, pkt): if self.linktype is None: try: self.linktype = conf.l2types[pkt.__class__] # Import here to prevent import loops from scapy.layers.inet import IP from scapy.layers.inet6 import IPv6 if OPENBSD and isinstance(pkt, (IP, IPv6)): self.linktype = 14 # DLT_RAW except KeyError: warning("PcapWriter: unknown LL type for %s. Using type 1 (Ethernet)", pkt.__class__.__name__) # noqa: E501 self.linktype = DLT_EN10MB RawPcapWriter._write_header(self, pkt) def _write_packet(self, packet, sec=None, usec=None, caplen=None, wirelen=None): """ Writes a single packet to the pcap file. :param packet: Packet, or bytes for a single packet :type packet: scapy.packet.Packet or bytes :param sec: time the packet was captured, in seconds since epoch. If not supplied, defaults to now. :type sec: int or long :param usec: If ``nano=True``, then number of nanoseconds after the second that the packet was captured. If ``nano=False``, then the number of microseconds after the second the packet was captured. If ``sec`` is not specified, this value is ignored. :type usec: int or long :param caplen: The length of the packet in the capture file. If not specified, uses ``len(raw(packet))``. :type caplen: int :param wirelen: The length of the packet on the wire. If not specified, tries ``packet.wirelen``, otherwise uses ``caplen``. :type wirelen: int :return: None :rtype: None """ if hasattr(packet, "time"): if sec is None: sec = int(packet.time) usec = int(round((packet.time - sec) * (1000000000 if self.nano else 1000000))) if usec is None: usec = 0 rawpkt = raw(packet) caplen = len(rawpkt) if caplen is None else caplen if wirelen is None: if hasattr(packet, "wirelen"): wirelen = packet.wirelen if wirelen is None: wirelen = caplen RawPcapWriter._write_packet( self, rawpkt, sec=sec, usec=usec, caplen=caplen, wirelen=wirelen) @conf.commands.register def import_hexcap(input_string=None): """Imports a tcpdump like hexadecimal view e.g: exported via hexdump() or tcpdump or wireshark's "export as hex" :param input_string: String containing the hexdump input to parse. If None, read from standard input. """ re_extract_hexcap = re.compile(r"^((0x)?[0-9a-fA-F]{2,}[ :\t]{,3}|) *(([0-9a-fA-F]{2} {,2}){,16})") # noqa: E501 p = "" try: if input_string: input_function = six.StringIO(input_string).readline else: input_function = input while True: line = input_function().strip() if not line: break try: p += re_extract_hexcap.match(line).groups()[2] except Exception: warning("Parsing error during hexcap") continue except EOFError: pass p = p.replace(" ", "") return hex_bytes(p) @conf.commands.register def wireshark(pktlist, wait=False, **kwargs): """ Runs Wireshark on a list of packets. See :func:`tcpdump` for more parameter description. Note: this defaults to wait=False, to run Wireshark in the background. """ return tcpdump(pktlist, prog=conf.prog.wireshark, wait=wait, **kwargs) @conf.commands.register def tdecode(pktlist, args=None, **kwargs): """ Run tshark on a list of packets. :param args: If not specified, defaults to ``tshark -V``. See :func:`tcpdump` for more parameters. """ if args is None: args = ["-V"] return tcpdump(pktlist, prog=conf.prog.tshark, args=args, **kwargs) def _guess_linktype_name(value): """Guess the DLT name from its value.""" import scapy.data return next( k[4:] for k, v in six.iteritems(scapy.data.__dict__) if k.startswith("DLT") and v == value ) def _guess_linktype_value(name): """Guess the value of a DLT name.""" import scapy.data if not name.startswith("DLT_"): name = "DLT_" + name return scapy.data.__dict__[name] @conf.commands.register def tcpdump(pktlist=None, dump=False, getfd=False, args=None, prog=None, getproc=False, quiet=False, use_tempfile=None, read_stdin_opts=None, linktype=None, wait=True, _suppress=False): """Run tcpdump or tshark on a list of packets. When using ``tcpdump`` on OSX (``prog == conf.prog.tcpdump``), this uses a temporary file to store the packets. This works around a bug in Apple's version of ``tcpdump``: http://apple.stackexchange.com/questions/152682/ Otherwise, the packets are passed in stdin. This function can be explicitly enabled or disabled with the ``use_tempfile`` parameter. When using ``wireshark``, it will be called with ``-ki -`` to start immediately capturing packets from stdin. Otherwise, the command will be run with ``-r -`` (which is correct for ``tcpdump`` and ``tshark``). This can be overridden with ``read_stdin_opts``. This has no effect when ``use_tempfile=True``, or otherwise reading packets from a regular file. :param pktlist: a Packet instance, a PacketList instance or a list of Packet instances. Can also be a filename (as a string), an open file-like object that must be a file format readable by tshark (Pcap, PcapNg, etc.) or None (to sniff) :param dump: when set to True, returns a string instead of displaying it. :param getfd: when set to True, returns a file-like object to read data from tcpdump or tshark from. :param getproc: when set to True, the subprocess.Popen object is returned :param args: arguments (as a list) to pass to tshark (example for tshark: args=["-T", "json"]). :param prog: program to use (defaults to tcpdump, will work with tshark) :param quiet: when set to True, the process stderr is discarded :param use_tempfile: When set to True, always use a temporary file to store packets. When set to False, pipe packets through stdin. When set to None (default), only use a temporary file with ``tcpdump`` on OSX. :param read_stdin_opts: When set, a list of arguments needed to capture from stdin. Otherwise, attempts to guess. :param linktype: A custom DLT value or name, to overwrite the default values. :param wait: If True (default), waits for the process to terminate before returning to Scapy. If False, the process will be detached to the background. If dump, getproc or getfd is True, these have the same effect as ``wait=False``. Examples:: >>> tcpdump([IP()/TCP(), IP()/UDP()]) reading from file -, link-type RAW (Raw IP) 16:46:00.474515 IP 127.0.0.1.20 > 127.0.0.1.80: Flags [S], seq 0, win 8192, length 0 # noqa: E501 16:46:00.475019 IP 127.0.0.1.53 > 127.0.0.1.53: [|domain] >>> tcpdump([IP()/TCP(), IP()/UDP()], prog=conf.prog.tshark) 1 0.000000 127.0.0.1 -> 127.0.0.1 TCP 40 20->80 [SYN] Seq=0 Win=8192 Len=0 # noqa: E501 2 0.000459 127.0.0.1 -> 127.0.0.1 UDP 28 53->53 Len=0 To get a JSON representation of a tshark-parsed PacketList(), one can:: >>> import json, pprint >>> json_data = json.load(tcpdump(IP(src="217.25.178.5", ... dst="45.33.32.156"), ... prog=conf.prog.tshark, ... args=["-T", "json"], ... getfd=True)) >>> pprint.pprint(json_data) [{u'_index': u'packets-2016-12-23', u'_score': None, u'_source': {u'layers': {u'frame': {u'frame.cap_len': u'20', u'frame.encap_type': u'7', [...] }, u'ip': {u'ip.addr': u'45.33.32.156', u'ip.checksum': u'0x0000a20d', [...] u'ip.ttl': u'64', u'ip.version': u'4'}, u'raw': u'Raw packet data'}}, u'_type': u'pcap_file'}] >>> json_data[0]['_source']['layers']['ip']['ip.ttl'] u'64' """ getfd = getfd or getproc if prog is None: if not conf.prog.tcpdump: raise Scapy_Exception( "tcpdump is not available" ) prog = [conf.prog.tcpdump] elif isinstance(prog, six.string_types): prog = [prog] else: raise ValueError("prog must be a string") if linktype is not None: # Tcpdump does not support integers in -y (yet) # https://github.com/the-tcpdump-group/tcpdump/issues/758 if isinstance(linktype, int): # Guess name from value try: linktype_name = _guess_linktype_name(linktype) except StopIteration: linktype = -1 else: # Guess value from name if linktype.startswith("DLT_"): linktype = linktype[4:] linktype_name = linktype try: linktype = _guess_linktype_value(linktype) except KeyError: linktype = -1 if linktype == -1: raise ValueError( "Unknown linktype. Try passing its datalink name instead" ) prog += ["-y", linktype_name] # Build Popen arguments if args is None: args = [] else: # Make a copy of args args = list(args) stdout = subprocess.PIPE if dump or getfd else None stderr = open(os.devnull) if quiet else None proc = None if use_tempfile is None: # Apple's tcpdump cannot read from stdin, see: # http://apple.stackexchange.com/questions/152682/ use_tempfile = DARWIN and prog[0] == conf.prog.tcpdump if read_stdin_opts is None: if prog[0] == conf.prog.wireshark: # Start capturing immediately (-k) from stdin (-i -) read_stdin_opts = ["-ki", "-"] else: read_stdin_opts = ["-r", "-"] else: # Make a copy of read_stdin_opts read_stdin_opts = list(read_stdin_opts) if pktlist is None: # sniff with ContextManagerSubprocess(prog[0], suppress=_suppress): proc = subprocess.Popen( prog + args, stdout=stdout, stderr=stderr, ) elif isinstance(pktlist, six.string_types): # file with ContextManagerSubprocess(prog[0], suppress=_suppress): proc = subprocess.Popen( prog + ["-r", pktlist] + args, stdout=stdout, stderr=stderr, ) elif use_tempfile: tmpfile = get_temp_file(autoext=".pcap", fd=True) try: tmpfile.writelines(iter(lambda: pktlist.read(1048576), b"")) except AttributeError: wrpcap(tmpfile, pktlist, linktype=linktype) else: tmpfile.close() with ContextManagerSubprocess(prog[0], suppress=_suppress): proc = subprocess.Popen( prog + ["-r", tmpfile.name] + args, stdout=stdout, stderr=stderr, ) else: # pass the packet stream with ContextManagerSubprocess(prog[0], suppress=_suppress): proc = subprocess.Popen( prog + read_stdin_opts + args, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, ) if proc is None: # An error has occurred return try: proc.stdin.writelines(iter(lambda: pktlist.read(1048576), b"")) except AttributeError: wrpcap(proc.stdin, pktlist, linktype=linktype) except UnboundLocalError: # The error was handled by ContextManagerSubprocess pass else: proc.stdin.close() if proc is None: # An error has occurred return if dump: return b"".join(iter(lambda: proc.stdout.read(1048576), b"")) if getproc: return proc if getfd: return proc.stdout if wait: proc.wait() @conf.commands.register def hexedit(pktlist): """Run hexedit on a list of packets, then return the edited packets.""" f = get_temp_file() wrpcap(f, pktlist) with ContextManagerSubprocess(conf.prog.hexedit): subprocess.call([conf.prog.hexedit, f]) pktlist = rdpcap(f) os.unlink(f) return pktlist def get_terminal_width(): """Get terminal width (number of characters) if in a window. Notice: this will try several methods in order to support as many terminals and OS as possible. """ # Let's first try using the official API # (Python 3.3+) if not six.PY2: import shutil sizex = shutil.get_terminal_size(fallback=(0, 0))[0] if sizex != 0: return sizex # Backups / Python 2.7 if WINDOWS: from ctypes import windll, create_string_buffer # http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/ h = windll.kernel32.GetStdHandle(-12) csbi = create_string_buffer(22) res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) if res: (bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) # noqa: E501 sizex = right - left + 1 # sizey = bottom - top + 1 return sizex return None else: # We have various methods sizex = None # COLUMNS is set on some terminals try: sizex = int(os.environ['COLUMNS']) except Exception: pass if sizex: return sizex # We can query TIOCGWINSZ try: import fcntl import termios s = struct.pack('HHHH', 0, 0, 0, 0) x = fcntl.ioctl(1, termios.TIOCGWINSZ, s) sizex = struct.unpack('HHHH', x)[1] except IOError: pass return sizex def pretty_list(rtlst, header, sortBy=0, borders=False): """Pretty list to fit the terminal, and add header""" if borders: _space = "|" else: _space = " " # Windows has a fat terminal border _spacelen = len(_space) * (len(header) - 1) + (10 if WINDOWS else 0) _croped = False # Sort correctly rtlst.sort(key=lambda x: x[sortBy]) # Append tag rtlst = header + rtlst # Detect column's width colwidth = [max([len(y) for y in x]) for x in zip(*rtlst)] # Make text fit in box (if required) width = get_terminal_width() if conf.auto_crop_tables and width: width = width - _spacelen while sum(colwidth) > width: _croped = True # Needs to be cropped # Get the longest row i = colwidth.index(max(colwidth)) # Get all elements of this row row = [len(x[i]) for x in rtlst] # Get biggest element of this row: biggest of the array j = row.index(max(row)) # Re-build column tuple with the edited element t = list(rtlst[j]) t[i] = t[i][:-2] + "_" rtlst[j] = tuple(t) # Update max size row[j] = len(t[i]) colwidth[i] = max(row) if _croped: log_runtime.info("Table cropped to fit the terminal (conf.auto_crop_tables==True)") # noqa: E501 # Generate padding scheme fmt = _space.join(["%%-%ds" % x for x in colwidth]) # Append separation line if needed if borders: rtlst.insert(1, tuple("-" * x for x in colwidth)) # Compile rt = "\n".join(((fmt % x).strip() for x in rtlst)) return rt def __make_table(yfmtfunc, fmtfunc, endline, data, fxyz, sortx=None, sorty=None, seplinefunc=None): # noqa: E501 """Core function of the make_table suite, which generates the table""" vx = {} vy = {} vz = {} vxf = {} # Python 2 backward compatibility fxyz = lambda_tuple_converter(fxyz) tmp_len = 0 for e in data: xx, yy, zz = [str(s) for s in fxyz(*e)] tmp_len = max(len(yy), tmp_len) vx[xx] = max(vx.get(xx, 0), len(xx), len(zz)) vy[yy] = None vz[(xx, yy)] = zz vxk = list(vx) vyk = list(vy) if sortx: vxk.sort(key=sortx) else: try: vxk.sort(key=int) except Exception: try: vxk.sort(key=atol) except Exception: vxk.sort() if sorty: vyk.sort(key=sorty) else: try: vyk.sort(key=int) except Exception: try: vyk.sort(key=atol) except Exception: vyk.sort() if seplinefunc: sepline = seplinefunc(tmp_len, [vx[x] for x in vxk]) print(sepline) fmt = yfmtfunc(tmp_len) print(fmt % "", end=' ') for x in vxk: vxf[x] = fmtfunc(vx[x]) print(vxf[x] % x, end=' ') print(endline) if seplinefunc: print(sepline) for y in vyk: print(fmt % y, end=' ') for x in vxk: print(vxf[x] % vz.get((x, y), "-"), end=' ') print(endline) if seplinefunc: print(sepline) def make_table(*args, **kargs): __make_table(lambda l: "%%-%is" % l, lambda l: "%%-%is" % l, "", *args, **kargs) # noqa: E501 def make_lined_table(*args, **kargs): __make_table(lambda l: "%%-%is |" % l, lambda l: "%%-%is |" % l, "", seplinefunc=lambda a, x: "+".join('-' * (y + 2) for y in [a - 1] + x + [-2]), # noqa: E501 *args, **kargs) def make_tex_table(*args, **kargs): __make_table(lambda l: "%s", lambda l: "& %s", "\\\\", seplinefunc=lambda a, x: "\\hline", *args, **kargs) # noqa: E501 #################### # WHOIS CLIENT # #################### def whois(ip_address): """Whois client for Python""" whois_ip = str(ip_address) try: query = socket.gethostbyname(whois_ip) except Exception: query = whois_ip s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("whois.ripe.net", 43)) s.send(query.encode("utf8") + b"\r\n") answer = b"" while True: d = s.recv(4096) answer += d if not d: break s.close() ignore_tag = b"remarks:" # ignore all lines starting with the ignore_tag lines = [line for line in answer.split(b"\n") if not line or (line and not line.startswith(ignore_tag))] # noqa: E501 # remove empty lines at the bottom for i in range(1, len(lines)): if not lines[-i].strip(): del lines[-i] else: break return b"\n".join(lines[3:]) ####################### # PERIODIC SENDER # ####################### class PeriodicSenderThread(threading.Thread): def __init__(self, sock, pkt, interval=0.5): """ Thread to send packets periodically Args: sock: socket where packet is sent periodically pkt: packet to send interval: interval between two packets """ self._pkt = pkt self._socket = sock self._stopped = threading.Event() self._interval = interval threading.Thread.__init__(self) def run(self): while not self._stopped.is_set(): self._socket.send(self._pkt) time.sleep(self._interval) def stop(self): self._stopped.set()