86890704fd
todo: add documentation & wireshark dissector
1144 lines
41 KiB
Python
Executable file
1144 lines
41 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
|
|
|
|
"""
|
|
Functions to send and receive packets.
|
|
"""
|
|
|
|
from __future__ import absolute_import, print_function
|
|
import itertools
|
|
from threading import Thread, Event
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import time
|
|
import types
|
|
|
|
from scapy.compat import plain_str
|
|
from scapy.data import ETH_P_ALL
|
|
from scapy.config import conf
|
|
from scapy.error import warning
|
|
from scapy.packet import Gen, Packet
|
|
from scapy.utils import get_temp_file, tcpdump, wrpcap, \
|
|
ContextManagerSubprocess, PcapReader
|
|
from scapy.plist import PacketList, SndRcvList
|
|
from scapy.error import log_runtime, log_interactive, Scapy_Exception
|
|
from scapy.base_classes import SetGen
|
|
from scapy.modules import six
|
|
from scapy.modules.six.moves import map
|
|
from scapy.sessions import DefaultSession
|
|
from scapy.supersocket import SuperSocket
|
|
if conf.route is None:
|
|
# unused import, only to initialize conf.route
|
|
import scapy.route # noqa: F401
|
|
|
|
#################
|
|
# Debug class #
|
|
#################
|
|
|
|
|
|
class debug:
|
|
recv = []
|
|
sent = []
|
|
match = []
|
|
crashed_on = None
|
|
|
|
|
|
####################
|
|
# Send / Receive #
|
|
####################
|
|
|
|
_DOC_SNDRCV_PARAMS = """
|
|
:param pks: SuperSocket instance to send/receive packets
|
|
:param pkt: the packet to send
|
|
:param rcv_pks: if set, will be used instead of pks to receive packets.
|
|
packets will still be sent through pks
|
|
:param nofilter: put 1 to avoid use of BPF filters
|
|
:param retry: if positive, how many times to resend unanswered packets
|
|
if negative, how many times to retry when no more packets
|
|
are answered
|
|
:param timeout: how much time to wait after the last packet has been sent
|
|
:param verbose: set verbosity level
|
|
:param multi: whether to accept multiple answers for the same stimulus
|
|
:param store_unanswered: whether to store not-answered packets or not.
|
|
setting it to False will increase speed, and will return
|
|
None as the unans list.
|
|
:param process: if specified, only result from process(pkt) will be stored.
|
|
the function should follow the following format:
|
|
``lambda sent, received: (func(sent), func2(received))``
|
|
if the packet is unanswered, `received` will be None.
|
|
if `store_unanswered` is False, the function won't be called on
|
|
un-answered packets.
|
|
:param prebuild: pre-build the packets before starting to send them.
|
|
Automatically enabled when a generator is passed as the packet
|
|
"""
|
|
|
|
|
|
class SndRcvHandler(object):
|
|
"""
|
|
Util to send/receive packets, used by sr*().
|
|
Do not use directly.
|
|
|
|
This matches the requests and answers.
|
|
|
|
Notes::
|
|
- threaded mode: enabling threaded mode will likely
|
|
break packet timestamps, but might result in a speedup
|
|
when sending a big amount of packets. Disabled by default
|
|
- DEVS: store the outgoing timestamp right BEFORE sending the packet
|
|
to avoid races that could result in negative latency. We aren't Stadia
|
|
"""
|
|
def __init__(self, pks, pkt,
|
|
timeout=None, inter=0, verbose=None,
|
|
chainCC=False,
|
|
retry=0, multi=False, rcv_pks=None,
|
|
prebuild=False, _flood=None,
|
|
threaded=False,
|
|
session=None):
|
|
# Instantiate all arguments
|
|
if verbose is None:
|
|
verbose = conf.verb
|
|
if conf.debug_match:
|
|
debug.recv = PacketList([], "Received")
|
|
debug.sent = PacketList([], "Sent")
|
|
debug.match = SndRcvList([], "Matched")
|
|
self.nbrecv = 0
|
|
self.ans = []
|
|
self.pks = pks
|
|
self.rcv_pks = rcv_pks or pks
|
|
self.inter = inter
|
|
self.verbose = verbose
|
|
self.chainCC = chainCC
|
|
self.multi = multi
|
|
self.timeout = timeout
|
|
self.session = session
|
|
# Instantiate packet holders
|
|
if _flood:
|
|
self.tobesent = pkt
|
|
self.notans = _flood[0]
|
|
else:
|
|
if isinstance(pkt, types.GeneratorType) or prebuild:
|
|
self.tobesent = [p for p in pkt]
|
|
self.notans = len(self.tobesent)
|
|
else:
|
|
self.tobesent = (
|
|
SetGen(pkt) if not isinstance(pkt, Gen) else pkt
|
|
)
|
|
self.notans = self.tobesent.__iterlen__()
|
|
|
|
if retry < 0:
|
|
autostop = retry = -retry
|
|
else:
|
|
autostop = 0
|
|
|
|
if timeout is not None and timeout < 0:
|
|
self.timeout = None
|
|
|
|
while retry >= 0:
|
|
self.hsent = {}
|
|
|
|
if threaded or _flood:
|
|
# Send packets in thread.
|
|
# https://github.com/secdev/scapy/issues/1791
|
|
snd_thread = Thread(
|
|
target=self._sndrcv_snd
|
|
)
|
|
snd_thread.setDaemon(True)
|
|
|
|
# Start routine with callback
|
|
self._sndrcv_rcv(snd_thread.start)
|
|
|
|
# Ended. Let's close gracefully
|
|
if _flood:
|
|
# Flood: stop send thread
|
|
_flood[1]()
|
|
snd_thread.join()
|
|
else:
|
|
self._sndrcv_rcv(self._sndrcv_snd)
|
|
|
|
if multi:
|
|
remain = [
|
|
p for p in itertools.chain(*six.itervalues(self.hsent))
|
|
if not hasattr(p, '_answered')
|
|
]
|
|
else:
|
|
remain = list(itertools.chain(*six.itervalues(self.hsent)))
|
|
|
|
if autostop and len(remain) > 0 and \
|
|
len(remain) != len(self.tobesent):
|
|
retry = autostop
|
|
|
|
self.tobesent = remain
|
|
if len(self.tobesent) == 0:
|
|
break
|
|
retry -= 1
|
|
|
|
if conf.debug_match:
|
|
debug.sent = PacketList(remain[:], "Sent")
|
|
debug.match = SndRcvList(self.ans[:])
|
|
|
|
# Clean the ans list to delete the field _answered
|
|
if multi:
|
|
for snd, _ in self.ans:
|
|
if hasattr(snd, '_answered'):
|
|
del snd._answered
|
|
|
|
if verbose:
|
|
print(
|
|
"\nReceived %i packets, got %i answers, "
|
|
"remaining %i packets" % (
|
|
self.nbrecv + len(self.ans), len(self.ans), self.notans
|
|
)
|
|
)
|
|
|
|
self.ans_result = SndRcvList(self.ans)
|
|
self.unans_result = PacketList(remain, "Unanswered")
|
|
|
|
def results(self):
|
|
return self.ans_result, self.unans_result
|
|
|
|
def _sndrcv_snd(self):
|
|
"""Function used in the sending thread of sndrcv()"""
|
|
try:
|
|
if self.verbose:
|
|
print("Begin emission:")
|
|
i = 0
|
|
for p in self.tobesent:
|
|
# Populate the dictionary of _sndrcv_rcv
|
|
# _sndrcv_rcv won't miss the answer of a packet that
|
|
# has not been sent
|
|
self.hsent.setdefault(p.hashret(), []).append(p)
|
|
# Send packet
|
|
self.pks.send(p)
|
|
time.sleep(self.inter)
|
|
i += 1
|
|
if self.verbose:
|
|
print("Finished sending %i packets." % i)
|
|
except SystemExit:
|
|
pass
|
|
except Exception:
|
|
log_runtime.exception("--- Error sending packets")
|
|
|
|
def _process_packet(self, r):
|
|
"""Internal function used to process each packet."""
|
|
if r is None:
|
|
return
|
|
ok = False
|
|
h = r.hashret()
|
|
if h in self.hsent:
|
|
hlst = self.hsent[h]
|
|
for i, sentpkt in enumerate(hlst):
|
|
if r.answers(sentpkt):
|
|
self.ans.append((sentpkt, r))
|
|
if self.verbose > 1:
|
|
os.write(1, b"*")
|
|
ok = True
|
|
if not self.multi:
|
|
del hlst[i]
|
|
self.notans -= 1
|
|
else:
|
|
if not hasattr(sentpkt, '_answered'):
|
|
self.notans -= 1
|
|
sentpkt._answered = 1
|
|
break
|
|
if self.notans <= 0 and not self.multi:
|
|
self.sniffer.stop(join=False)
|
|
if not ok:
|
|
if self.verbose > 1:
|
|
os.write(1, b".")
|
|
self.nbrecv += 1
|
|
if conf.debug_match:
|
|
debug.recv.append(r)
|
|
|
|
def _sndrcv_rcv(self, callback):
|
|
"""Function used to receive packets and check their hashret"""
|
|
self.sniffer = None
|
|
try:
|
|
self.sniffer = AsyncSniffer()
|
|
self.sniffer._run(
|
|
prn=self._process_packet,
|
|
timeout=self.timeout,
|
|
store=False,
|
|
opened_socket=self.pks,
|
|
session=self.session,
|
|
started_callback=callback
|
|
)
|
|
except KeyboardInterrupt:
|
|
if self.chainCC:
|
|
raise
|
|
|
|
|
|
def sndrcv(*args, **kwargs):
|
|
"""Scapy raw function to send a packet and receive its answer.
|
|
WARNING: This is an internal function. Using sr/srp/sr1/srp is
|
|
more appropriate in many cases.
|
|
"""
|
|
sndrcver = SndRcvHandler(*args, **kwargs)
|
|
return sndrcver.results()
|
|
|
|
|
|
def __gen_send(s, x, inter=0, loop=0, count=None, verbose=None, realtime=None, return_packets=False, *args, **kargs): # noqa: E501
|
|
if isinstance(x, str):
|
|
x = conf.raw_layer(load=x)
|
|
if not isinstance(x, Gen):
|
|
x = SetGen(x)
|
|
if verbose is None:
|
|
verbose = conf.verb
|
|
n = 0
|
|
if count is not None:
|
|
loop = -count
|
|
elif not loop:
|
|
loop = -1
|
|
if return_packets:
|
|
sent_packets = PacketList()
|
|
try:
|
|
while loop:
|
|
dt0 = None
|
|
for p in x:
|
|
if realtime:
|
|
ct = time.time()
|
|
if dt0:
|
|
st = dt0 + float(p.time) - ct
|
|
if st > 0:
|
|
time.sleep(st)
|
|
else:
|
|
dt0 = ct - float(p.time)
|
|
s.send(p)
|
|
if return_packets:
|
|
sent_packets.append(p)
|
|
n += 1
|
|
if verbose:
|
|
os.write(1, b".")
|
|
time.sleep(inter)
|
|
if loop < 0:
|
|
loop += 1
|
|
except KeyboardInterrupt:
|
|
pass
|
|
if verbose:
|
|
print("\nSent %i packets." % n)
|
|
if return_packets:
|
|
return sent_packets
|
|
|
|
|
|
@conf.commands.register
|
|
def send(x, inter=0, loop=0, count=None, verbose=None, realtime=None,
|
|
return_packets=False, socket=None, iface=None, *args, **kargs):
|
|
"""
|
|
Send packets at layer 3
|
|
|
|
:param x: the packets
|
|
:param inter: time (in s) between two packets (default 0)
|
|
:param loop: send packet indefinetly (default 0)
|
|
:param count: number of packets to send (default None=1)
|
|
:param verbose: verbose mode (default None=conf.verbose)
|
|
:param realtime: check that a packet was sent before sending the next one
|
|
:param return_packets: return the sent packets
|
|
:param socket: the socket to use (default is conf.L3socket(kargs))
|
|
:param iface: the interface to send the packets on
|
|
:param monitor: (not on linux) send in monitor mode
|
|
:returns: None
|
|
"""
|
|
need_closing = socket is None
|
|
kargs["iface"] = _interface_selection(iface, x)
|
|
socket = socket or conf.L3socket(*args, **kargs)
|
|
results = __gen_send(socket, x, inter=inter, loop=loop,
|
|
count=count, verbose=verbose,
|
|
realtime=realtime, return_packets=return_packets)
|
|
if need_closing:
|
|
socket.close()
|
|
return results
|
|
|
|
|
|
@conf.commands.register
|
|
def sendp(x, inter=0, loop=0, iface=None, iface_hint=None, count=None,
|
|
verbose=None, realtime=None,
|
|
return_packets=False, socket=None, *args, **kargs):
|
|
"""
|
|
Send packets at layer 2
|
|
|
|
:param x: the packets
|
|
:param inter: time (in s) between two packets (default 0)
|
|
:param loop: send packet indefinetly (default 0)
|
|
:param count: number of packets to send (default None=1)
|
|
:param verbose: verbose mode (default None=conf.verbose)
|
|
:param realtime: check that a packet was sent before sending the next one
|
|
:param return_packets: return the sent packets
|
|
:param socket: the socket to use (default is conf.L3socket(kargs))
|
|
:param iface: the interface to send the packets on
|
|
:param monitor: (not on linux) send in monitor mode
|
|
:returns: None
|
|
"""
|
|
if iface is None and iface_hint is not None and socket is None:
|
|
iface = conf.route.route(iface_hint)[0]
|
|
need_closing = socket is None
|
|
socket = socket or conf.L2socket(iface=iface, *args, **kargs)
|
|
results = __gen_send(socket, x, inter=inter, loop=loop,
|
|
count=count, verbose=verbose,
|
|
realtime=realtime, return_packets=return_packets)
|
|
if need_closing:
|
|
socket.close()
|
|
return results
|
|
|
|
|
|
@conf.commands.register
|
|
def sendpfast(x, pps=None, mbps=None, realtime=None, loop=0, file_cache=False, iface=None, replay_args=None, # noqa: E501
|
|
parse_results=False):
|
|
"""Send packets at layer 2 using tcpreplay for performance
|
|
|
|
:param pps: packets per second
|
|
:param mpbs: MBits per second
|
|
:param realtime: use packet's timestamp, bending time with real-time value
|
|
:param loop: number of times to process the packet list
|
|
:param file_cache: cache packets in RAM instead of reading from
|
|
disk at each iteration
|
|
:param iface: output interface
|
|
:param replay_args: List of additional tcpreplay args (List[str])
|
|
:param parse_results: Return a dictionary of information
|
|
outputted by tcpreplay (default=False)
|
|
:returns: stdout, stderr, command used
|
|
"""
|
|
if iface is None:
|
|
iface = conf.iface
|
|
argv = [conf.prog.tcpreplay, "--intf1=%s" % iface]
|
|
if pps is not None:
|
|
argv.append("--pps=%i" % pps)
|
|
elif mbps is not None:
|
|
argv.append("--mbps=%f" % mbps)
|
|
elif realtime is not None:
|
|
argv.append("--multiplier=%f" % realtime)
|
|
else:
|
|
argv.append("--topspeed")
|
|
|
|
if loop:
|
|
argv.append("--loop=%i" % loop)
|
|
if file_cache:
|
|
argv.append("--preload-pcap")
|
|
|
|
# Check for any additional args we didn't cover.
|
|
if replay_args is not None:
|
|
argv.extend(replay_args)
|
|
|
|
f = get_temp_file()
|
|
argv.append(f)
|
|
wrpcap(f, x)
|
|
results = None
|
|
with ContextManagerSubprocess(conf.prog.tcpreplay):
|
|
try:
|
|
cmd = subprocess.Popen(argv, stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
except KeyboardInterrupt:
|
|
log_interactive.info("Interrupted by user")
|
|
except Exception:
|
|
os.unlink(f)
|
|
raise
|
|
else:
|
|
stdout, stderr = cmd.communicate()
|
|
if stderr:
|
|
log_runtime.warning(stderr.decode())
|
|
if parse_results:
|
|
results = _parse_tcpreplay_result(stdout, stderr, argv)
|
|
elif conf.verb > 2:
|
|
log_runtime.info(stdout.decode())
|
|
os.unlink(f)
|
|
return results
|
|
|
|
|
|
def _parse_tcpreplay_result(stdout, stderr, argv):
|
|
"""
|
|
Parse the output of tcpreplay and modify the results_dict to populate output information. # noqa: E501
|
|
Tested with tcpreplay v3.4.4
|
|
Tested with tcpreplay v4.1.2
|
|
:param stdout: stdout of tcpreplay subprocess call
|
|
:param stderr: stderr of tcpreplay subprocess call
|
|
:param argv: the command used in the subprocess call
|
|
:return: dictionary containing the results
|
|
"""
|
|
try:
|
|
results = {}
|
|
stdout = plain_str(stdout).lower()
|
|
stderr = plain_str(stderr).strip().split("\n")
|
|
elements = {
|
|
"actual": (int, int, float),
|
|
"rated": (float, float, float),
|
|
"flows": (int, float, int, int),
|
|
"attempted": (int,),
|
|
"successful": (int,),
|
|
"failed": (int,),
|
|
"truncated": (int,),
|
|
"retried packets (eno": (int,),
|
|
"retried packets (eag": (int,),
|
|
}
|
|
multi = {
|
|
"actual": ("packets", "bytes", "time"),
|
|
"rated": ("bps", "mbps", "pps"),
|
|
"flows": ("flows", "fps", "flow_packets", "non_flow"),
|
|
"retried packets (eno": ("retried_enobufs",),
|
|
"retried packets (eag": ("retried_eagain",),
|
|
}
|
|
float_reg = r"([0-9]*\.[0-9]+|[0-9]+)"
|
|
int_reg = r"([0-9]+)"
|
|
any_reg = r"[^0-9]*"
|
|
r_types = {int: int_reg, float: float_reg}
|
|
for line in stdout.split("\n"):
|
|
line = line.strip()
|
|
for elt, _types in elements.items():
|
|
if line.startswith(elt):
|
|
regex = any_reg.join([r_types[x] for x in _types])
|
|
matches = re.search(regex, line)
|
|
for i, typ in enumerate(_types):
|
|
name = multi.get(elt, [elt])[i]
|
|
results[name] = typ(matches.group(i + 1))
|
|
results["command"] = " ".join(argv)
|
|
results["warnings"] = stderr[:-1]
|
|
return results
|
|
except Exception as parse_exception:
|
|
if not conf.interactive:
|
|
raise
|
|
log_runtime.error("Error parsing output: " + str(parse_exception))
|
|
return {}
|
|
|
|
|
|
@conf.commands.register
|
|
def sr(x, promisc=None, filter=None, iface=None, nofilter=0, *args, **kargs):
|
|
"""
|
|
Send and receive packets at layer 3
|
|
"""
|
|
s = conf.L3socket(promisc=promisc, filter=filter,
|
|
iface=iface, nofilter=nofilter)
|
|
result = sndrcv(s, x, *args, **kargs)
|
|
s.close()
|
|
return result
|
|
|
|
|
|
def _interface_selection(iface, packet):
|
|
"""
|
|
Select the network interface according to the layer 3 destination
|
|
"""
|
|
|
|
if iface is None:
|
|
try:
|
|
iff = packet.route()[0]
|
|
except AttributeError:
|
|
iff = None
|
|
return iff or conf.iface
|
|
|
|
return iface
|
|
|
|
|
|
@conf.commands.register
|
|
def sr1(x, promisc=None, filter=None, iface=None, nofilter=0, *args, **kargs):
|
|
"""
|
|
Send packets at layer 3 and return only the first answer
|
|
"""
|
|
iface = _interface_selection(iface, x)
|
|
s = conf.L3socket(promisc=promisc, filter=filter,
|
|
nofilter=nofilter, iface=iface)
|
|
ans, _ = sndrcv(s, x, *args, **kargs)
|
|
s.close()
|
|
if len(ans) > 0:
|
|
return ans[0][1]
|
|
|
|
|
|
@conf.commands.register
|
|
def srp(x, promisc=None, iface=None, iface_hint=None, filter=None,
|
|
nofilter=0, type=ETH_P_ALL, *args, **kargs):
|
|
"""
|
|
Send and receive packets at layer 2
|
|
"""
|
|
if iface is None and iface_hint is not None:
|
|
iface = conf.route.route(iface_hint)[0]
|
|
s = conf.L2socket(promisc=promisc, iface=iface,
|
|
filter=filter, nofilter=nofilter, type=type)
|
|
result = sndrcv(s, x, *args, **kargs)
|
|
s.close()
|
|
return result
|
|
|
|
|
|
@conf.commands.register
|
|
def srp1(*args, **kargs):
|
|
"""
|
|
Send and receive packets at layer 2 and return only the first answer
|
|
"""
|
|
ans, _ = srp(*args, **kargs)
|
|
if len(ans) > 0:
|
|
return ans[0][1]
|
|
|
|
|
|
# Append doc
|
|
for sr_func in [srp, srp1, sr, sr1]:
|
|
if sr_func.__doc__ is not None:
|
|
sr_func.__doc__ += _DOC_SNDRCV_PARAMS
|
|
|
|
|
|
# SEND/RECV LOOP METHODS
|
|
|
|
|
|
def __sr_loop(srfunc, pkts, prn=lambda x: x[1].summary(),
|
|
prnfail=lambda x: x.summary(),
|
|
inter=1, timeout=None, count=None, verbose=None, store=1,
|
|
*args, **kargs):
|
|
n = 0
|
|
r = 0
|
|
ct = conf.color_theme
|
|
if verbose is None:
|
|
verbose = conf.verb
|
|
parity = 0
|
|
ans = []
|
|
unans = []
|
|
if timeout is None:
|
|
timeout = min(2 * inter, 5)
|
|
try:
|
|
while True:
|
|
parity ^= 1
|
|
col = [ct.even, ct.odd][parity]
|
|
if count is not None:
|
|
if count == 0:
|
|
break
|
|
count -= 1
|
|
start = time.time()
|
|
if verbose > 1:
|
|
print("\rsend...\r", end=' ')
|
|
res = srfunc(pkts, timeout=timeout, verbose=0, chainCC=True, *args, **kargs) # noqa: E501
|
|
n += len(res[0]) + len(res[1])
|
|
r += len(res[0])
|
|
if verbose > 1 and prn and len(res[0]) > 0:
|
|
msg = "RECV %i:" % len(res[0])
|
|
print("\r" + ct.success(msg), end=' ')
|
|
for p in res[0]:
|
|
print(col(prn(p)))
|
|
print(" " * len(msg), end=' ')
|
|
if verbose > 1 and prnfail and len(res[1]) > 0:
|
|
msg = "fail %i:" % len(res[1])
|
|
print("\r" + ct.fail(msg), end=' ')
|
|
for p in res[1]:
|
|
print(col(prnfail(p)))
|
|
print(" " * len(msg), end=' ')
|
|
if verbose > 1 and not (prn or prnfail):
|
|
print("recv:%i fail:%i" % tuple(map(len, res[:2])))
|
|
if store:
|
|
ans += res[0]
|
|
unans += res[1]
|
|
end = time.time()
|
|
if end - start < inter:
|
|
time.sleep(inter + start - end)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
if verbose and n > 0:
|
|
print(ct.normal("\nSent %i packets, received %i packets. %3.1f%% hits." % (n, r, 100.0 * r / n))) # noqa: E501
|
|
return SndRcvList(ans), PacketList(unans)
|
|
|
|
|
|
@conf.commands.register
|
|
def srloop(pkts, *args, **kargs):
|
|
"""Send a packet at layer 3 in loop and print the answer each time
|
|
srloop(pkts, [prn], [inter], [count], ...) --> None"""
|
|
return __sr_loop(sr, pkts, *args, **kargs)
|
|
|
|
|
|
@conf.commands.register
|
|
def srploop(pkts, *args, **kargs):
|
|
"""Send a packet at layer 2 in loop and print the answer each time
|
|
srloop(pkts, [prn], [inter], [count], ...) --> None"""
|
|
return __sr_loop(srp, pkts, *args, **kargs)
|
|
|
|
# SEND/RECV FLOOD METHODS
|
|
|
|
|
|
def sndrcvflood(pks, pkt, inter=0, verbose=None, chainCC=False, timeout=None):
|
|
"""sndrcv equivalent for flooding."""
|
|
stopevent = Event()
|
|
|
|
def send_in_loop(tobesent, stopevent):
|
|
"""Infinite generator that produces the same
|
|
packet until stopevent is triggered."""
|
|
while True:
|
|
for p in tobesent:
|
|
if stopevent.is_set():
|
|
return
|
|
yield p
|
|
|
|
infinite_gen = send_in_loop(pkt, stopevent)
|
|
_flood_len = pkt.__iterlen__() if isinstance(pkt, Gen) else len(pkt)
|
|
_flood = [_flood_len, stopevent.set]
|
|
return sndrcv(
|
|
pks, infinite_gen,
|
|
inter=inter, verbose=verbose,
|
|
chainCC=chainCC, timeout=None,
|
|
_flood=_flood
|
|
)
|
|
|
|
|
|
@conf.commands.register
|
|
def srflood(x, promisc=None, filter=None, iface=None, nofilter=None, *args, **kargs): # noqa: E501
|
|
"""Flood and receive packets at layer 3
|
|
|
|
:param prn: function applied to packets received
|
|
:param unique: only consider packets whose print
|
|
:param nofilter: put 1 to avoid use of BPF filters
|
|
:param filter: provide a BPF filter
|
|
:param iface: listen answers only on the given interface
|
|
"""
|
|
s = conf.L3socket(promisc=promisc, filter=filter, iface=iface, nofilter=nofilter) # noqa: E501
|
|
r = sndrcvflood(s, x, *args, **kargs)
|
|
s.close()
|
|
return r
|
|
|
|
|
|
@conf.commands.register
|
|
def sr1flood(x, promisc=None, filter=None, iface=None, nofilter=0, *args, **kargs): # noqa: E501
|
|
"""Flood and receive packets at layer 3 and return only the first answer
|
|
|
|
:param prn: function applied to packets received
|
|
:param verbose: set verbosity level
|
|
:param nofilter: put 1 to avoid use of BPF filters
|
|
:param filter: provide a BPF filter
|
|
:param iface: listen answers only on the given interface
|
|
"""
|
|
s = conf.L3socket(promisc=promisc, filter=filter, nofilter=nofilter, iface=iface) # noqa: E501
|
|
ans, _ = sndrcvflood(s, x, *args, **kargs)
|
|
s.close()
|
|
if len(ans) > 0:
|
|
return ans[0][1]
|
|
|
|
|
|
@conf.commands.register
|
|
def srpflood(x, promisc=None, filter=None, iface=None, iface_hint=None, nofilter=None, *args, **kargs): # noqa: E501
|
|
"""Flood and receive packets at layer 2
|
|
|
|
:param prn: function applied to packets received
|
|
:param unique: only consider packets whose print
|
|
:param nofilter: put 1 to avoid use of BPF filters
|
|
:param filter: provide a BPF filter
|
|
:param iface: listen answers only on the given interface
|
|
"""
|
|
if iface is None and iface_hint is not None:
|
|
iface = conf.route.route(iface_hint)[0]
|
|
s = conf.L2socket(promisc=promisc, filter=filter, iface=iface, nofilter=nofilter) # noqa: E501
|
|
r = sndrcvflood(s, x, *args, **kargs)
|
|
s.close()
|
|
return r
|
|
|
|
|
|
@conf.commands.register
|
|
def srp1flood(x, promisc=None, filter=None, iface=None, nofilter=0, *args, **kargs): # noqa: E501
|
|
"""Flood and receive packets at layer 2 and return only the first answer
|
|
|
|
:param prn: function applied to packets received
|
|
:param verbose: set verbosity level
|
|
:param nofilter: put 1 to avoid use of BPF filters
|
|
:param filter: provide a BPF filter
|
|
:param iface: listen answers only on the given interface
|
|
"""
|
|
s = conf.L2socket(promisc=promisc, filter=filter, nofilter=nofilter, iface=iface) # noqa: E501
|
|
ans, _ = sndrcvflood(s, x, *args, **kargs)
|
|
s.close()
|
|
if len(ans) > 0:
|
|
return ans[0][1]
|
|
|
|
# SNIFF METHODS
|
|
|
|
|
|
class AsyncSniffer(object):
|
|
"""
|
|
Sniff packets and return a list of packets.
|
|
|
|
Args:
|
|
count: number of packets to capture. 0 means infinity.
|
|
store: whether to store sniffed packets or discard them
|
|
prn: function to apply to each packet. If something is returned, it
|
|
is displayed.
|
|
--Ex: prn = lambda x: x.summary()
|
|
session: a session = a flow decoder used to handle stream of packets.
|
|
e.g: IPSession (to defragment on-the-flow) or NetflowSession
|
|
filter: BPF filter to apply.
|
|
lfilter: Python function applied to each packet to determine if
|
|
further action may be done.
|
|
--Ex: lfilter = lambda x: x.haslayer(Padding)
|
|
offline: PCAP file (or list of PCAP files) to read packets from,
|
|
instead of sniffing them
|
|
timeout: stop sniffing after a given time (default: None).
|
|
L2socket: use the provided L2socket (default: use conf.L2listen).
|
|
opened_socket: provide an object (or a list of objects) ready to use
|
|
.recv() on.
|
|
stop_filter: Python function applied to each packet to determine if
|
|
we have to stop the capture after this packet.
|
|
--Ex: stop_filter = lambda x: x.haslayer(TCP)
|
|
iface: interface or list of interfaces (default: None for sniffing
|
|
on all interfaces).
|
|
monitor: use monitor mode. May not be available on all OS
|
|
started_callback: called as soon as the sniffer starts sniffing
|
|
(default: None).
|
|
|
|
The iface, offline and opened_socket parameters can be either an
|
|
element, a list of elements, or a dict object mapping an element to a
|
|
label (see examples below).
|
|
|
|
Examples: synchronous
|
|
>>> sniff(filter="arp")
|
|
>>> sniff(filter="tcp",
|
|
... session=IPSession, # defragment on-the-flow
|
|
... prn=lambda x: x.summary())
|
|
>>> sniff(lfilter=lambda pkt: ARP in pkt)
|
|
>>> sniff(iface="eth0", prn=Packet.summary)
|
|
>>> sniff(iface=["eth0", "mon0"],
|
|
... prn=lambda pkt: "%s: %s" % (pkt.sniffed_on,
|
|
... pkt.summary()))
|
|
>>> sniff(iface={"eth0": "Ethernet", "mon0": "Wifi"},
|
|
... prn=lambda pkt: "%s: %s" % (pkt.sniffed_on,
|
|
... pkt.summary()))
|
|
|
|
Examples: asynchronous
|
|
>>> t = AsyncSniffer(iface="enp0s3")
|
|
>>> t.start()
|
|
>>> time.sleep(1)
|
|
>>> print("nice weather today")
|
|
>>> t.stop()
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
# Store keyword arguments
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
self.running = False
|
|
self.thread = None
|
|
self.results = None
|
|
|
|
def _setup_thread(self):
|
|
# Prepare sniffing thread
|
|
self.thread = Thread(
|
|
target=self._run,
|
|
args=self.args,
|
|
kwargs=self.kwargs
|
|
)
|
|
self.thread.setDaemon(True)
|
|
|
|
def _run(self,
|
|
count=0, store=True, offline=None,
|
|
prn=None, lfilter=None,
|
|
L2socket=None, timeout=None, opened_socket=None,
|
|
stop_filter=None, iface=None, started_callback=None,
|
|
session=None, session_args=[], session_kwargs={},
|
|
*arg, **karg):
|
|
self.running = True
|
|
# Start main thread
|
|
# instantiate session
|
|
if not isinstance(session, DefaultSession):
|
|
session = session or DefaultSession
|
|
session = session(prn, store, *session_args, **session_kwargs)
|
|
else:
|
|
session.prn = prn
|
|
session.store = store
|
|
# sniff_sockets follows: {socket: label}
|
|
sniff_sockets = {}
|
|
if opened_socket is not None:
|
|
if isinstance(opened_socket, list):
|
|
sniff_sockets.update(
|
|
(s, "socket%d" % i)
|
|
for i, s in enumerate(opened_socket)
|
|
)
|
|
elif isinstance(opened_socket, dict):
|
|
sniff_sockets.update(
|
|
(s, label)
|
|
for s, label in six.iteritems(opened_socket)
|
|
)
|
|
else:
|
|
sniff_sockets[opened_socket] = "socket0"
|
|
if offline is not None:
|
|
flt = karg.get('filter')
|
|
|
|
if isinstance(offline, list) and \
|
|
all(isinstance(elt, str) for elt in offline):
|
|
sniff_sockets.update((PcapReader(
|
|
fname if flt is None else
|
|
tcpdump(fname, args=["-w", "-", flt], getfd=True)
|
|
), fname) for fname in offline)
|
|
elif isinstance(offline, dict):
|
|
sniff_sockets.update((PcapReader(
|
|
fname if flt is None else
|
|
tcpdump(fname, args=["-w", "-", flt], getfd=True)
|
|
), label) for fname, label in six.iteritems(offline))
|
|
else:
|
|
# Write Scapy Packet objects to a pcap file
|
|
def _write_to_pcap(packets_list):
|
|
filename = get_temp_file(autoext=".pcap")
|
|
wrpcap(filename, offline)
|
|
return filename, filename
|
|
|
|
if isinstance(offline, Packet):
|
|
tempfile_written, offline = _write_to_pcap([offline])
|
|
elif isinstance(offline, list) and \
|
|
all(isinstance(elt, Packet) for elt in offline):
|
|
tempfile_written, offline = _write_to_pcap(offline)
|
|
|
|
sniff_sockets[PcapReader(
|
|
offline if flt is None else
|
|
tcpdump(offline, args=["-w", "-", flt], getfd=True)
|
|
)] = offline
|
|
if not sniff_sockets or iface is not None:
|
|
if L2socket is None:
|
|
L2socket = conf.L2listen
|
|
if isinstance(iface, list):
|
|
sniff_sockets.update(
|
|
(L2socket(type=ETH_P_ALL, iface=ifname, *arg, **karg),
|
|
ifname)
|
|
for ifname in iface
|
|
)
|
|
elif isinstance(iface, dict):
|
|
sniff_sockets.update(
|
|
(L2socket(type=ETH_P_ALL, iface=ifname, *arg, **karg),
|
|
iflabel)
|
|
for ifname, iflabel in six.iteritems(iface)
|
|
)
|
|
else:
|
|
sniff_sockets[L2socket(type=ETH_P_ALL, iface=iface,
|
|
*arg, **karg)] = iface
|
|
|
|
# Get select information from the sockets
|
|
_main_socket = next(iter(sniff_sockets))
|
|
read_allowed_exceptions = _main_socket.read_allowed_exceptions
|
|
select_func = _main_socket.select
|
|
_backup_read_func = _main_socket.__class__.recv
|
|
nonblocking_socket = _main_socket.nonblocking_socket
|
|
# We check that all sockets use the same select(), or raise a warning
|
|
if not all(select_func == sock.select for sock in sniff_sockets):
|
|
warning("Warning: inconsistent socket types ! "
|
|
"The used select function "
|
|
"will be the one of the first socket")
|
|
|
|
# Fill if empty
|
|
if not read_allowed_exceptions:
|
|
read_allowed_exceptions = (IOError,)
|
|
|
|
if nonblocking_socket:
|
|
# select is non blocking
|
|
def stop_cb():
|
|
self.continue_sniff = False
|
|
self.stop_cb = stop_cb
|
|
close_pipe = None
|
|
else:
|
|
# select is blocking: Add special control socket
|
|
from scapy.automaton import ObjectPipe
|
|
close_pipe = ObjectPipe()
|
|
sniff_sockets[close_pipe] = "control_socket"
|
|
|
|
def stop_cb():
|
|
if self.running:
|
|
close_pipe.send(None)
|
|
self.continue_sniff = False
|
|
self.stop_cb = stop_cb
|
|
|
|
try:
|
|
if started_callback:
|
|
started_callback()
|
|
self.continue_sniff = True
|
|
|
|
# Start timeout
|
|
if timeout is not None:
|
|
stoptime = time.time() + timeout
|
|
remain = None
|
|
|
|
while sniff_sockets and self.continue_sniff:
|
|
if timeout is not None:
|
|
remain = stoptime - time.time()
|
|
if remain <= 0:
|
|
break
|
|
sockets, read_func = select_func(sniff_sockets, remain)
|
|
read_func = read_func or _backup_read_func
|
|
dead_sockets = []
|
|
for s in sockets:
|
|
if s is close_pipe:
|
|
break
|
|
try:
|
|
p = read_func(s)
|
|
except EOFError:
|
|
# End of stream
|
|
try:
|
|
s.close()
|
|
except Exception:
|
|
pass
|
|
dead_sockets.append(s)
|
|
continue
|
|
except read_allowed_exceptions:
|
|
continue
|
|
except Exception as ex:
|
|
msg = " It was closed."
|
|
try:
|
|
# Make sure it's closed
|
|
s.close()
|
|
except Exception as ex:
|
|
msg = " close() failed with '%s'" % ex
|
|
warning(
|
|
"Socket %s failed with '%s'." % (s, ex) + msg
|
|
)
|
|
dead_sockets.append(s)
|
|
if conf.debug_dissector >= 2:
|
|
raise
|
|
continue
|
|
if p is None:
|
|
continue
|
|
if lfilter and not lfilter(p):
|
|
continue
|
|
p.sniffed_on = sniff_sockets[s]
|
|
# on_packet_received handles the prn/storage
|
|
session.on_packet_received(p)
|
|
# check
|
|
if (stop_filter and stop_filter(p)) or \
|
|
(0 < count <= session.count):
|
|
self.continue_sniff = False
|
|
break
|
|
# Removed dead sockets
|
|
for s in dead_sockets:
|
|
del sniff_sockets[s]
|
|
except KeyboardInterrupt:
|
|
pass
|
|
self.running = False
|
|
if opened_socket is None:
|
|
for s in sniff_sockets:
|
|
s.close()
|
|
elif close_pipe:
|
|
close_pipe.close()
|
|
self.results = session.toPacketList()
|
|
|
|
def start(self):
|
|
"""Starts AsyncSniffer in async mode"""
|
|
self._setup_thread()
|
|
self.thread.start()
|
|
|
|
def stop(self, join=True):
|
|
"""Stops AsyncSniffer if not in async mode"""
|
|
if self.running:
|
|
try:
|
|
self.stop_cb()
|
|
except AttributeError:
|
|
raise Scapy_Exception(
|
|
"Unsupported (offline or unsupported socket)"
|
|
)
|
|
if join:
|
|
self.join()
|
|
return self.results
|
|
else:
|
|
raise Scapy_Exception("Not started !")
|
|
|
|
def join(self, *args, **kwargs):
|
|
if self.thread:
|
|
self.thread.join(*args, **kwargs)
|
|
|
|
|
|
@conf.commands.register
|
|
def sniff(*args, **kwargs):
|
|
sniffer = AsyncSniffer()
|
|
sniffer._run(*args, **kwargs)
|
|
return sniffer.results
|
|
|
|
|
|
sniff.__doc__ = AsyncSniffer.__doc__
|
|
|
|
|
|
@conf.commands.register
|
|
def bridge_and_sniff(if1, if2, xfrm12=None, xfrm21=None, prn=None, L2socket=None, # noqa: E501
|
|
*args, **kargs):
|
|
"""Forward traffic between interfaces if1 and if2, sniff and return
|
|
the exchanged packets.
|
|
|
|
Arguments:
|
|
|
|
if1, if2: the interfaces to use (interface names or opened sockets).
|
|
|
|
xfrm12: a function to call when forwarding a packet from if1 to
|
|
if2. If it returns True, the packet is forwarded as it. If it
|
|
returns False or None, the packet is discarded. If it returns a
|
|
packet, this packet is forwarded instead of the original packet
|
|
one.
|
|
|
|
xfrm21: same as xfrm12 for packets forwarded from if2 to if1.
|
|
|
|
The other arguments are the same than for the function sniff(),
|
|
except for offline, opened_socket and iface that are ignored.
|
|
See help(sniff) for more.
|
|
|
|
"""
|
|
for arg in ['opened_socket', 'offline', 'iface']:
|
|
if arg in kargs:
|
|
log_runtime.warning("Argument %s cannot be used in "
|
|
"bridge_and_sniff() -- ignoring it.", arg)
|
|
del kargs[arg]
|
|
|
|
def _init_socket(iface, count):
|
|
if isinstance(iface, SuperSocket):
|
|
return iface, "iface%d" % count
|
|
else:
|
|
return (L2socket or conf.L2socket)(iface=iface), iface
|
|
sckt1, if1 = _init_socket(if1, 1)
|
|
sckt2, if2 = _init_socket(if2, 2)
|
|
peers = {if1: sckt2, if2: sckt1}
|
|
xfrms = {}
|
|
if xfrm12 is not None:
|
|
xfrms[if1] = xfrm12
|
|
if xfrm21 is not None:
|
|
xfrms[if2] = xfrm21
|
|
|
|
def prn_send(pkt):
|
|
try:
|
|
sendsock = peers[pkt.sniffed_on]
|
|
except KeyError:
|
|
return
|
|
if pkt.sniffed_on in xfrms:
|
|
try:
|
|
newpkt = xfrms[pkt.sniffed_on](pkt)
|
|
except Exception:
|
|
log_runtime.warning(
|
|
'Exception in transformation function for packet [%s] '
|
|
'received on %s -- dropping',
|
|
pkt.summary(), pkt.sniffed_on, exc_info=True
|
|
)
|
|
return
|
|
else:
|
|
if newpkt is True:
|
|
newpkt = pkt.original
|
|
elif not newpkt:
|
|
return
|
|
else:
|
|
newpkt = pkt.original
|
|
try:
|
|
sendsock.send(newpkt)
|
|
except Exception:
|
|
log_runtime.warning('Cannot forward packet [%s] received on %s',
|
|
pkt.summary(), pkt.sniffed_on, exc_info=True)
|
|
if prn is None:
|
|
prn = prn_send
|
|
else:
|
|
prn_orig = prn
|
|
|
|
def prn(pkt):
|
|
prn_send(pkt)
|
|
return prn_orig(pkt)
|
|
|
|
return sniff(opened_socket={sckt1: if1, sckt2: if2}, prn=prn,
|
|
*args, **kargs)
|
|
|
|
|
|
@conf.commands.register
|
|
def tshark(*args, **kargs):
|
|
"""Sniff packets and print them calling pkt.summary().
|
|
This tries to replicate what text-wireshark (tshark) would look like"""
|
|
|
|
if 'iface' in kargs:
|
|
iface = kargs.get('iface')
|
|
elif 'opened_socket' in kargs:
|
|
iface = kargs.get('opened_socket').iface
|
|
else:
|
|
iface = conf.iface
|
|
print("Capturing on '%s'" % iface)
|
|
|
|
# This should be a nonlocal variable, using a mutable object
|
|
# for Python 2 compatibility
|
|
i = [0]
|
|
|
|
def _cb(pkt):
|
|
print("%5d\t%s" % (i[0], pkt.summary()))
|
|
i[0] += 1
|
|
|
|
sniff(prn=_cb, store=False, *args, **kargs)
|
|
print("\n%d packet%s captured" % (i[0], 's' if i[0] > 1 else ''))
|