86890704fd
todo: add documentation & wireshark dissector
2088 lines
67 KiB
Python
Executable file
2088 lines
67 KiB
Python
Executable file
# This file is part of Scapy
|
|
# See http://www.secdev.org/projects/scapy for more information
|
|
# Copyright (C) Philippe Biondi <phil@secdev.org>
|
|
# 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 <http://www.zebra.org/> # 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 <http://www.zebra.org/> # 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("<I", x & 0xffffffff))
|
|
else:
|
|
def ltoa(x):
|
|
return inet_ntoa(struct.pack("!I", x & 0xffffffff))
|
|
|
|
|
|
def itom(x):
|
|
return (0xffffffff00000000 >> 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 <count> 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()
|