diff --git a/scapy/arch/__init__.py b/scapy/arch/__init__.py index d76ba5b49615bbe8901e78758d9cf1012cab0a22..f8cb4bf91633b122a8bd1579058dd10faaa13e3b 100644 --- a/scapy/arch/__init__.py +++ b/scapy/arch/__init__.py @@ -68,14 +68,6 @@ elif SOLARIS: from scapy.arch.solaris import * elif WINDOWS: from scapy.arch.windows import * - # import only if parent is not route.py - # because compatibility.py will require route.py to work (through sendrecv.py) - parents = parent_function() - if len(parents) >= 3: - if not parents[2][1].endswith("route.py"): - from scapy.arch.windows.compatibility import * - else: - from scapy.arch.windows.compatibility import * if scapy.config.conf.iface is None: scapy.config.conf.iface = LOOPBACK_NAME diff --git a/scapy/arch/bpf/supersocket.py b/scapy/arch/bpf/supersocket.py index 41865be43a2dcc7da4d55e26c12cc239893e4e1e..e3a6b6d003662f94adb6b59269469bcced12031d 100644 --- a/scapy/arch/bpf/supersocket.py +++ b/scapy/arch/bpf/supersocket.py @@ -363,22 +363,19 @@ def bpf_select(fds_list, timeout=None): for tmp_fd in fds_list: - # Specific BPF sockets - if isBPFSocket(tmp_fd): - # Get buffers status - if tmp_fd.buffered_frames(): - bpf_scks_buffered.append(tmp_fd) - continue + # Specific BPF sockets: get buffers status + if isBPFSocket(tmp_fd) and tmp_fd.buffered_frames(): + bpf_scks_buffered.append(tmp_fd) + continue # Regular file descriptors or empty BPF buffer select_fds.append(tmp_fd) - if len(select_fds): + if select_fds: # Call select for sockets with empty buffers if timeout is None: timeout = 0.05 ready_list, _, _ = select(select_fds, [], [], timeout) return bpf_scks_buffered + ready_list - else: return bpf_scks_buffered diff --git a/scapy/arch/pcapdnet.py b/scapy/arch/pcapdnet.py index 79c2a9291a81e3e0c502f55a3a29308435d67d4c..06a67a9ce6adc38bdf821777a7406c4e5a3b47f2 100644 --- a/scapy/arch/pcapdnet.py +++ b/scapy/arch/pcapdnet.py @@ -249,6 +249,10 @@ if conf.use_winpcapy: promisc = 0 self.promisc = promisc self.ins = open_pcap(iface, 1600, self.promisc, 100) + # We need to have a different interface open because of an + # access violation in Npcap that occurs in multi-threading + # (see https://github.com/nmap/nmap/issues/982) + self.outs = open_pcap(iface, 1600, self.promisc, 100) try: ioctl(self.ins.fileno(),BIOCIMMEDIATE,struct.pack("I",1)) except: @@ -275,7 +279,7 @@ if conf.use_winpcapy: sx = str(x) if hasattr(x, "sent_time"): x.sent_time = time.time() - return self.ins.send(sx) + return self.outs.send(sx) def recv(self,x=MTU): ll = self.ins.datalink() @@ -309,10 +313,12 @@ if conf.use_winpcapy: return p def close(self): - if hasattr(self, "ins"): - self.ins.close() - if hasattr(self, "outs"): - self.outs.close() + if not self.closed: + if hasattr(self, "ins"): + self.ins.close() + if hasattr(self, "outs"): + self.outs.close() + self.closed = True class L3pcapSocket(L2pcapSocket): desc = "read/write packets at layer 3 using only libpcap" @@ -408,8 +414,8 @@ if conf.use_pcap: return (s+0.000001*us), p __next__ = next def fileno(self): - warning("fileno: pcapy API does not permit to get capure file descriptor. Bugs ahead! Press Enter to trigger packet reading") - return 0 + raise RuntimeError("%s has no fileno. Please report this bug." % + self.__class__.__name__) def __getattr__(self, attr): return getattr(self.pcap, attr) def __del__(self): @@ -634,10 +640,12 @@ if conf.use_pcap and conf.use_dnet: return p def close(self): - if hasattr(self, "ins"): - del(self.ins) - if hasattr(self, "outs"): - del(self.outs) + if not self.closed: + if hasattr(self, "ins"): + del(self.ins) + if hasattr(self, "outs"): + del(self.outs) + self.closed = True class L2dnetSocket(SuperSocket): desc = "read/write packets at layer 2 using libdnet and libpcap" @@ -704,10 +712,12 @@ if conf.use_pcap and conf.use_dnet: return p def close(self): - if hasattr(self, "ins"): - del(self.ins) - if hasattr(self, "outs"): - del(self.outs) + if not self.closed: + if hasattr(self, "ins"): + del(self.ins) + if hasattr(self, "outs"): + del(self.outs) + self.closed = True conf.L3socket=L3dnetSocket conf.L2socket=L2dnetSocket diff --git a/scapy/arch/windows/__init__.py b/scapy/arch/windows/__init__.py index 43614e409b75b26d86ba03147dd9add7255efe04..154a667fdeb281e392d7678d3b1edaff55dd3f82 100755 --- a/scapy/arch/windows/__init__.py +++ b/scapy/arch/windows/__init__.py @@ -43,7 +43,7 @@ if not hasattr(socket, 'IPPROTO_GRE'): from scapy.arch import pcapdnet from scapy.arch.pcapdnet import * -from scapy.consts import LOOPBACK_NAME +import scapy.consts def is_new_release(ignoreVBS=False): if NEW_RELEASE and conf.prog.powershell is not None: @@ -348,7 +348,7 @@ class NetworkInterface(object): try: if not self.ip: self.ip=get_ip_from_name(data['name']) - if not self.ip and self.name == LOOPBACK_NAME: + if not self.ip and self.name == scapy.consts.LOOPBACK_NAME: self.ip = "127.0.0.1" if not self.ip: # No IP detected @@ -423,7 +423,7 @@ class NetworkInterfaceDict(UserDict): except (KeyError, PcapNameNotFoundError): pass - if len(self.data) == 0 and conf.use_winpcapy: + if not self.data and conf.use_winpcapy: _detect = pcap_service_status() def _ask_user(): if not conf.interactive: @@ -456,7 +456,9 @@ class NetworkInterfaceDict(UserDict): self.remove_invalid_ifaces() # Replace LOOPBACK_INTERFACE try: - scapy.consts.LOOPBACK_INTERFACE = self.dev_from_name(LOOPBACK_NAME) + scapy.consts.LOOPBACK_INTERFACE = self.dev_from_name( + scapy.consts.LOOPBACK_NAME, + ) except: pass @@ -683,14 +685,14 @@ def in6_getifaddr(): def _append_route6(routes, dpref, dp, nh, iface, lifaddr): cset = [] # candidate set (possible source addresses) - if iface.name == LOOPBACK_NAME: + if iface.name == scapy.consts.LOOPBACK_NAME: if dpref == '::': return cset = ['::1'] else: devaddrs = (x for x in lifaddr if x[2] == iface) cset = scapy.utils6.construct_source_candidate_set(dpref, dp, devaddrs) - if len(cset) == 0: + if not cset: return # APPEND (DESTINATION, NETMASK, NEXT HOP, IFACE, CANDIDATS) routes.append((dpref, dp, nh, iface, cset)) @@ -743,7 +745,7 @@ def _read_routes6_7(): index = 0 for l in stdout.split('\n'): if not l.strip(): - if len(current_object) == 0: + if not current_object: continue if len(current_object) == len(regex_list): @@ -834,10 +836,10 @@ def route_add_loopback(routes=None, ipv6=False, iflist=None): warning("This will completly mess up the routes. Testing purpose only !") # Add only if some adpaters already exist if ipv6: - if len(conf.route6.routes) == 0: + if not conf.route6.routes: return else: - if len(conf.route.routes) == 0: + if not conf.route.routes: return data = {} data['name'] = LOOPBACK_NAME @@ -882,3 +884,16 @@ def route_add_loopback(routes=None, ipv6=False, iflist=None): routes.append(loopback_route6_custom) else: routes.append(loopback_route) + + +if not conf.use_winpcapy: + + class NotAvailableSocket(SuperSocket): + desc = "wpcap.dll missing" + def __init__(self, *args, **kargs): + raise RuntimeError("Sniffing and sending packets is not available: " + "winpcap is not installed") + + conf.L2socket = NotAvailableSocket + conf.L2listen = NotAvailableSocket + conf.L3socket = NotAvailableSocket diff --git a/scapy/arch/windows/compatibility.py b/scapy/arch/windows/compatibility.py deleted file mode 100644 index 0b4700c026323341001fcbd38ecd3874e91e7b4c..0000000000000000000000000000000000000000 --- a/scapy/arch/windows/compatibility.py +++ /dev/null @@ -1,248 +0,0 @@ -## This file is part of Scapy -## See http://www.secdev.org/projects/scapy for more informations -## Copyright (C) Philippe Biondi <phil@secdev.org> -## This program is published under a GPLv2 license - -""" -Instanciate part of the customizations needed to support Microsoft Windows. -""" - -from __future__ import absolute_import -from __future__ import print_function -import itertools -import os -import re -import socket -import subprocess -import sys -import time - -from scapy.consts import LOOPBACK_NAME -from scapy.config import conf,ConfClass -from scapy.base_classes import Gen, SetGen -import scapy.plist as plist -from scapy.utils import PcapReader, tcpdump -from scapy.arch.pcapdnet import PcapTimeoutElapsed -from scapy.error import log_runtime -from scapy.data import MTU, ETH_P_ARP,ETH_P_ALL -import scapy.modules.six as six - -WINDOWS = True - -def sndrcv(pks, pkt, timeout = 2, inter = 0, verbose=None, chainCC=0, retry=0, multi=0): - if not isinstance(pkt, Gen): - pkt = SetGen(pkt) - - if verbose is None: - verbose = conf.verb - from scapy.sendrecv import debug - debug.recv = plist.PacketList([],"Unanswered") - debug.sent = plist.PacketList([],"Sent") - debug.match = plist.SndRcvList([]) - nbrecv=0 - ans = [] - # do it here to fix random fields, so that parent and child have the same - all_stimuli = tobesent = [p for p in pkt] - notans = len(tobesent) - - hsent={} - for i in tobesent: - h = i.hashret() - if h in hsent: - hsent[h].append(i) - else: - hsent[h] = [i] - if retry < 0: - retry = -retry - autostop=retry - else: - autostop=0 - - - while retry >= 0: - found=0 - - if timeout < 0: - timeout = None - - pid=1 - try: - if WINDOWS or pid == 0: - try: - try: - i = 0 - if verbose: - print("Begin emission:") - for p in tobesent: - pks.send(p) - i += 1 - time.sleep(inter) - if verbose: - print("Finished to send %i packets." % i) - except SystemExit: - pass - except KeyboardInterrupt: - pass - except: - log_runtime.exception("--- Error sending packets") - log_runtime.info("--- Error sending packets") - finally: - try: - sent_times = [p.sent_time for p in all_stimuli if p.sent_time] - except: - pass - if WINDOWS or pid > 0: - # Timeout starts after last packet is sent (as in Unix version) - if timeout: - stoptime = time.time()+timeout - else: - stoptime = 0 - remaintime = None - try: - try: - while True: - if stoptime: - remaintime = stoptime-time.time() - if remaintime <= 0: - break - r = pks.recv(MTU) - if r is None: - continue - ok = 0 - h = r.hashret() - if h in hsent: - hlst = hsent[h] - for i, sentpkt in enumerate(hlst): - if r.answers(sentpkt): - ans.append((sentpkt, r)) - if verbose > 1: - os.write(1, b"*") - ok = 1 - if not multi: - del hlst[i] - notans -= 1 - else: - if not hasattr(sentpkt, '_answered'): - notans -= 1 - sentpkt._answered = 1 - break - if notans == 0 and not multi: - break - if not ok: - if verbose > 1: - os.write(1, b".") - nbrecv += 1 - if conf.debug_match: - debug.recv.append(r) - except KeyboardInterrupt: - if chainCC: - raise - finally: - if WINDOWS: - for p,t in zip(all_stimuli, sent_times): - p.sent_time = t - finally: - pass - - remain = list(itertools.chain(*six.itervalues(hsent))) - if multi: - remain = [p for p in remain if not hasattr(p, '_answered')] - - if autostop and len(remain) > 0 and len(remain) != len(tobesent): - retry = autostop - - tobesent = remain - if len(tobesent) == 0: - break - retry -= 1 - - if conf.debug_match: - debug.sent=plist.PacketList(remain[:],"Sent") - debug.match=plist.SndRcvList(ans[:]) - - #clean the ans list to delete the field _answered - if (multi): - for s,r in ans: - if hasattr(s, '_answered'): - del(s._answered) - - if verbose: - print("\nReceived %i packets, got %i answers, remaining %i packets" % (nbrecv+len(ans), len(ans), notans)) - return plist.SndRcvList(ans),plist.PacketList(remain,"Unanswered") - - -import scapy.sendrecv as sendrecv -sendrecv.sndrcv = sndrcv - -def sniff(count=0, store=1, offline=None, prn = None, stop_filter=None, lfilter=None, L2socket=None, timeout=None, *arg, **karg): - """Sniff packets -sniff([count=0,] [prn=None,] [store=1,] [offline=None,] [lfilter=None,] + L2ListenSocket args) -> list of packets -Select interface to sniff by setting conf.iface. Use show_interfaces() to see interface names. - 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: - ex: prn = lambda x: x.summary() - filter: provide a BPF filter -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 to read packets from, instead of sniffing them -timeout: stop sniffing after a given time (default: None) -L2socket: use the provided L2socket -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) - """ - c = 0 - - if offline is None: - log_runtime.info('Sniffing on %s' % conf.iface) - if L2socket is None: - L2socket = conf.L2listen - s = L2socket(type=ETH_P_ALL, *arg, **karg) - else: - flt = karg.get('filter') - s = PcapReader(offline if flt is None else - tcpdump(offline, args=["-w", "-", flt], getfd=True)) - lst = [] - if timeout is not None: - stoptime = time.time()+timeout - remain = None - while True: - try: - if timeout is not None: - remain = stoptime-time.time() - if remain <= 0: - break - - try: - p = s.recv(MTU) - except PcapTimeoutElapsed: - continue - if p is None: - break - if lfilter and not lfilter(p): - continue - if store: - lst.append(p) - c += 1 - if prn: - r = prn(p) - if r is not None: - print(r) - if stop_filter and stop_filter(p): - break - if 0 < count <= c: - break - except KeyboardInterrupt: - break - s.close() - return plist.PacketList(lst,"Sniffed") - -import scapy.sendrecv -sendrecv.sniff = sniff - -# If wpcap.dll is not available -if not (conf.use_winpcapy or conf.use_pcap or conf.use_dnet): - from scapy.arch.windows.disable_sendrecv import * diff --git a/scapy/arch/windows/disable_sendrecv.py b/scapy/arch/windows/disable_sendrecv.py deleted file mode 100644 index e9d514c6f2a341982711b90cc921dce6423fcc4d..0000000000000000000000000000000000000000 --- a/scapy/arch/windows/disable_sendrecv.py +++ /dev/null @@ -1,57 +0,0 @@ -## This file is part of Scapy -## See http://www.secdev.org/projects/scapy for more informations -## Copyright (C) Philippe Biondi <phil@secdev.org> -## This program is published under a GPLv2 license - -""" -When wpcap.dll is not available, replace all sendrecv functions that won't work. -""" - -from scapy.error import log_runtime -import scapy.sendrecv as sendrecv -import scapy.config as conf -from scapy.supersocket import SuperSocket - -def log_warning(): - if conf.conf.interactive: - log_runtime.warning("Function not available (winpcap is not installed)") - else: - raise ImportError("Function not available (winpcap is not installed)") - -def not_available(*args, **kwargs): - log_warning() - return None - -class not_available_socket(SuperSocket): - desc = "wpcap.dll missing" - def __init__(self, type=None, promisc=None, filter=None, iface=None, nofilter=0): - log_warning() - return - def send(self, x): - return - def recv(self,x=None): - return - def nonblock_recv(self): - return - def close(self): - return - - -sendrecv.send = not_available -sendrecv.sendp = not_available -sendrecv.sendpfast = not_available -sendrecv.sr = not_available -sendrecv.sr1 = not_available -sendrecv.srflood = not_available -sendrecv.srloop = not_available -sendrecv.srp = not_available -sendrecv.srp1 = not_available -sendrecv.srpflood = not_available -sendrecv.srploop = not_available -sendrecv.sniff = not_available -sendrecv.sndrcv = not_available -sendrecv.sndrcvflood = not_available -sendrecv.tshark = not_available - -conf.L3socket=not_available_socket -conf.L2socket=not_available_socket diff --git a/scapy/config.py b/scapy/config.py index 3408648b7cad0f6870f0da8cee0628307962043e..871c182cfc8e4641be0ccc29fc1f056dfb781d9e 100755 --- a/scapy/config.py +++ b/scapy/config.py @@ -201,8 +201,12 @@ class CacheInstance(dict, object): self._timetable[item] = time.time() dict.__setitem__(self, item,v) def update(self, other): - dict.update(self, other) - self._timetable.update(other._timetable) + for key, value in other.iteritems(): + # We only update an element from `other` either if it does + # not exist in `self` or if the entry in `self` is older. + if key not in self or self._timetable[key] < other._timetable[key]: + dict.__setitem__(self, key, value) + self._timetable[key] = other._timetable[key] def iteritems(self): if self.timeout is None: return six.iteritems(self.__dict__) diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py index 5e7e7978fa9eec2a48d192ca561de83a976122ac..ae6dc5c0b8db7b12cc4e507b2c968bf221891fc3 100644 --- a/scapy/layers/l2.py +++ b/scapy/layers/l2.py @@ -468,7 +468,7 @@ arpcachepoison(target, victim, [interval=60]) -> None while True: sendp(p, iface_hint=target) if conf.verb > 1: - os.write(1,".") + os.write(1, b".") time.sleep(interval) except KeyboardInterrupt: pass diff --git a/scapy/plist.py b/scapy/plist.py index a066f49088e488099e15e0ec648e0b1dcb0e5413..3301ed030d0dc21a9b492be7b06ac1f0e8c7a646 100644 --- a/scapy/plist.py +++ b/scapy/plist.py @@ -416,7 +416,7 @@ lfilter: truth function to apply to each packet to decide whether it will be dis cbb = c.bbox() c.text(cbb.left(),cbb.top()+1,r"\font\cmssfont=cmss12\cmssfont{Frame %i/%i}" % (i,l),[pyx.text.size.LARGE]) if conf.verb >= 2: - os.write(1,".") + os.write(1, b".") d.append(pyx.document.page(c, paperformat=pyx.document.paperformat.A4, margin=1*pyx.unit.t_cm, fittosize=1)) diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py index b404f88fe2aa3d83490dfb69cf2f1cc747624125..4503abcac26c5526c0cbd2841ba4c59885f78022 100644 --- a/scapy/sendrecv.py +++ b/scapy/sendrecv.py @@ -7,24 +7,27 @@ Functions to send and receive packets. """ -from __future__ import absolute_import -from __future__ import print_function +from __future__ import absolute_import, print_function import errno -import os, sys, time, subprocess import itertools +import threading +import os from select import select, error as select_error +import subprocess +import time -from scapy.consts import DARWIN, FREEBSD, OPENBSD -from scapy.data import * +from scapy.consts import DARWIN, FREEBSD, OPENBSD, WINDOWS +from scapy.data import ETH_P_ALL, MTU from scapy.config import conf from scapy.packet import Gen from scapy.utils import get_temp_file, PcapReader, tcpdump, wrpcap from scapy import plist -from scapy.error import log_runtime, log_interactive, warning +from scapy.error import log_runtime, log_interactive from scapy.base_classes import SetGen from scapy.supersocket import StreamSocket import scapy.modules.six as six -from scapy.modules.six.moves import map, zip +from scapy.modules.six.moves import map +from scapy.modules.six import iteritems if conf.route is None: # unused import, only to initialize conf.route import scapy.route @@ -44,12 +47,41 @@ class debug: #################### +def _sndrcv_snd(pks, timeout, inter, verbose, tobesent, stopevent): + """Function used in the sending thread of sndrcv()""" + try: + i = 0 + if verbose: + print("Begin emission:") + for p in tobesent: + pks.send(p) + i += 1 + time.sleep(inter) + if verbose: + print("Finished to send %i packets." % i) + except SystemExit: + pass + except KeyboardInterrupt: + pass + except: + log_runtime.info("--- Error sending packets", exc_info=True) + if timeout is not None: + stopevent.wait(timeout) + stopevent.set() + +class _BreakException(Exception): + """A dummy exception used in _get_pkt() to get out of the infinite +loop -def sndrcv(pks, pkt, timeout = None, inter = 0, verbose=None, chainCC=0, retry=0, multi=0): + """ + pass + + +def sndrcv(pks, pkt, timeout=None, inter=0, verbose=None, chainCC=False, + retry=0, multi=False): if not isinstance(pkt, Gen): pkt = SetGen(pkt) - if verbose is None: verbose = conf.verb debug.recv = plist.PacketList([],"Unanswered") @@ -58,149 +90,103 @@ def sndrcv(pks, pkt, timeout = None, inter = 0, verbose=None, chainCC=0, retry=0 nbrecv=0 ans = [] # do it here to fix random fields, so that parent and child have the same - all_stimuli = tobesent = [p for p in pkt] + tobesent = [p for p in pkt] notans = len(tobesent) hsent={} for i in tobesent: h = i.hashret() - if h in hsent: - hsent[h].append(i) - else: - hsent[h] = [i] + hsent.setdefault(i.hashret(), []).append(i) + if retry < 0: retry = -retry - autostop=retry + autostop = retry else: - autostop=0 - + autostop = 0 + + if WINDOWS: + def _get_pkt(): + return pks.recv(MTU) + elif conf.use_bpf: + from scapy.arch.bpf.supersocket import bpf_select + def _get_pkt(): + if bpf_select([pks]): + return pks.recv() + elif conf.use_pcap or (not isinstance(pks, StreamSocket) + and (DARWIN or FREEBSD or OPENBSD)): + def _get_pkt(): + res = pks.nonblock_recv() + if res is None: + time.sleep(0.05) + return res + else: + def _get_pkt(): + try: + inp, _, _ = select([pks], [], [], 0.05) + except (IOError, select_error) as exc: + # select.error has no .errno attribute + if exc.args[0] != errno.EINTR: + raise + else: + if inp: + return pks.recv(MTU) + if stopevent.is_set(): + raise _BreakException() while retry >= 0: - found=0 - if timeout < 0: timeout = None - - rdpipe,wrpipe = os.pipe() - rdpipe=os.fdopen(rdpipe) - wrpipe=os.fdopen(wrpipe,"w") + stopevent = threading.Event() + + thread = threading.Thread( + target=_sndrcv_snd, + args=(pks, timeout, inter, verbose, tobesent, stopevent), + ) + thread.start() - pid=1 try: - pid = os.fork() - if pid == 0: - try: - sys.stdin.close() - rdpipe.close() - try: - i = 0 - if verbose: - print("Begin emission:") - for p in tobesent: - pks.send(p) - i += 1 - time.sleep(inter) - if verbose: - print("Finished to send %i packets." % i) - except SystemExit: - pass - except KeyboardInterrupt: - pass - except: - log_runtime.exception("--- Error in child %i" % os.getpid()) - log_runtime.info("--- Error in child %i" % os.getpid()) - finally: - try: - os.setpgrp() # Chance process group to avoid ctrl-C - sent_times = [p.sent_time for p in all_stimuli if p.sent_time] - six.moves.cPickle.dump( (conf.netcache,sent_times), wrpipe ) - wrpipe.close() - except: - pass - elif pid < 0: - log_runtime.error("fork error") - else: - wrpipe.close() - stoptime = 0 - remaintime = None - inmask = [rdpipe,pks] - try: - try: - while True: - if stoptime: - remaintime = stoptime-time.time() - if remaintime <= 0: - break - r = None - if conf.use_bpf: - from scapy.arch.bpf.supersocket import bpf_select - inp = bpf_select(inmask) - if pks in inp: - r = pks.recv() - elif not isinstance(pks, StreamSocket) and (FREEBSD or DARWIN or OPENBSD): - inp, out, err = select(inmask,[],[], 0.05) - if len(inp) == 0 or pks in inp: - r = pks.nonblock_recv() - else: - inp = [] - try: - inp, out, err = select(inmask,[],[], remaintime) - except (IOError, select_error) as exc: - # select.error has no .errno attribute - if exc.args[0] != errno.EINTR: - raise - if len(inp) == 0: - break - if pks in inp: - r = pks.recv(MTU) - if rdpipe in inp: - if timeout: - stoptime = time.time()+timeout - del(inmask[inmask.index(rdpipe)]) - if r is None: - continue - ok = 0 - h = r.hashret() - if h in hsent: - hlst = hsent[h] - for i, sentpkt in enumerate(hlst): - if r.answers(sentpkt): - ans.append((sentpkt, r)) - if verbose > 1: - os.write(1, b"*") - ok = 1 - if not multi: - del hlst[i] - notans -= 1 - else: - if not hasattr(sentpkt, '_answered'): - notans -= 1 - sentpkt._answered = 1 - break - if notans == 0 and not multi: - break - if not ok: + try: + while True: + r = _get_pkt() + if r is None: + if stopevent.is_set(): + break + continue + ok = False + h = r.hashret() + if h in hsent: + hlst = hsent[h] + for i, sentpkt in enumerate(hlst): + if r.answers(sentpkt): + ans.append((sentpkt, r)) if verbose > 1: - os.write(1, b".") - nbrecv += 1 - if conf.debug_match: - debug.recv.append(r) - except KeyboardInterrupt: - if chainCC: - raise - finally: - try: - nc,sent_times = six.moves.cPickle.load(rdpipe) - except EOFError: - warning("Child died unexpectedly. Packets may have not been sent %i"%os.getpid()) - else: - conf.netcache.update(nc) - for p,t in zip(all_stimuli, sent_times): - p.sent_time = t - os.waitpid(pid,0) + os.write(1, b"*") + ok = True + if not multi: + del hlst[i] + notans -= 1 + else: + if not hasattr(sentpkt, '_answered'): + notans -= 1 + sentpkt._answered = 1 + break + if notans == 0 and not multi: + break + if not ok: + if verbose > 1: + os.write(1, b".") + nbrecv += 1 + if conf.debug_match: + debug.recv.append(r) + except KeyboardInterrupt: + if chainCC: + raise + except _BreakException: + pass finally: - if pid == 0: - os._exit(0) + stopevent.set() + thread.join() + pks.close() remain = list(itertools.chain(*six.itervalues(hsent))) if multi: @@ -213,20 +199,20 @@ def sndrcv(pks, pkt, timeout = None, inter = 0, verbose=None, chainCC=0, retry=0 if len(tobesent) == 0: break retry -= 1 - + if conf.debug_match: - debug.sent=plist.PacketList(remain[:],"Sent") + debug.sent=plist.PacketList(remain[:], "Sent") debug.match=plist.SndRcvList(ans[:]) - #clean the ans list to delete the field _answered - if (multi): - for s,r in ans: - if hasattr(s, '_answered'): - del(s._answered) - + # Clean the ans list to delete the field _answered + if multi: + for snd, _ in ans: + if hasattr(snd, '_answered'): + del snd._answered + if verbose: print("\nReceived %i packets, got %i answers, remaining %i packets" % (nbrecv+len(ans), len(ans), notans)) - return plist.SndRcvList(ans),plist.PacketList(remain,"Unanswered") + return plist.SndRcvList(ans), plist.PacketList(remain, "Unanswered") def __gen_send(s, x, inter=0, loop=0, count=None, verbose=None, realtime=None, return_packets=False, *args, **kargs): @@ -352,9 +338,9 @@ iface: listen answers only on the given interface""" if "timeout" not in kargs: kargs["timeout"] = -1 s = conf.L3socket(promisc=promisc, filter=filter, iface=iface, nofilter=nofilter) - a,b=sndrcv(s,x,*args,**kargs) + result = sndrcv(s, x, *args, **kargs) s.close() - return a,b + return result @conf.commands.register def sr1(x, promisc=None, filter=None, iface=None, nofilter=0, *args,**kargs): @@ -370,10 +356,10 @@ iface: listen answers only on the given interface""" if "timeout" not in kargs: kargs["timeout"] = -1 s=conf.L3socket(promisc=promisc, filter=filter, nofilter=nofilter, iface=iface) - a,b=sndrcv(s,x,*args,**kargs) + ans, _ = sndrcv(s, x, *args, **kargs) s.close() - if len(a) > 0: - return a[0][1] + if len(ans) > 0: + return ans[0][1] else: return None @@ -393,9 +379,9 @@ iface: work 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, iface=iface, filter=filter, nofilter=nofilter, type=type) - a,b=sndrcv(s ,x,*args,**kargs) + result = sndrcv(s, x, *args, **kargs) s.close() - return a,b + return result @conf.commands.register def srp1(*args,**kargs): @@ -410,9 +396,9 @@ filter: provide a BPF filter iface: work only on the given interface""" if "timeout" not in kargs: kargs["timeout"] = -1 - a,b=srp(*args,**kargs) - if len(a) > 0: - return a[0][1] + ans, _ = srp(*args, **kargs) + if len(ans) > 0: + return ans[0][1] else: return None @@ -438,7 +424,7 @@ def __sr_loop(srfunc, pkts, prn=lambda x:x[1].summary(), prnfail=lambda x:x.summ start = time.time() if verbose > 1: print("\rsend...\r", end=' ') - res = srfunc(pkts, timeout=timeout, verbose=0, chainCC=1, *args, **kargs) + res = srfunc(pkts, timeout=timeout, verbose=0, chainCC=True, *args, **kargs) n += len(res[0])+len(res[1]) r += len(res[0]) if verbose > 1 and prn and len(res[0]) > 0: @@ -574,94 +560,171 @@ iface: listen answers only on the given interface""" @conf.commands.register -def sniff(count=0, store=1, offline=None, prn=None, lfilter=None, +def sniff(count=0, store=True, offline=None, prn=None, lfilter=None, L2socket=None, timeout=None, opened_socket=None, stop_filter=None, iface=None, *arg, **karg): - """Sniff packets -sniff([count=0,] [prn=None,] [store=1,] [offline=None,] -[lfilter=None,] + L2ListenSocket args) -> list of packets + """ + +Sniff packets and return a list of packets. + +Arguments: + + count: number of packets to capture. 0 means infinity. - 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: - ex: prn = lambda x: x.summary() - filter: provide a BPF filter -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 to read packets from, instead of sniffing them -timeout: stop sniffing after a given time (default: None) -L2socket: use the provided L2socket -opened_socket: provide an object 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) + + prn: function to apply to each packet. If something is returned, it + is displayed. + + Ex: prn = lambda x: x.summary() + + 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). + +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: + + >>> sniff(filter="arp") + + >>> 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())) + """ c = 0 - label = {} - sniff_sockets = [] + sniff_sockets = {} # socket: label dict if opened_socket is not None: - sniff_sockets = [opened_socket] - else: - if offline is None: - if L2socket is None: - L2socket = conf.L2listen - if isinstance(iface, list): - for i in iface: - s = L2socket(type=ETH_P_ALL, iface=i, *arg, **karg) - label[s] = i - sniff_sockets.append(s) - else: - sniff_sockets = [L2socket(type=ETH_P_ALL, iface=iface, *arg, - **karg)] + 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 iteritems(opened_socket)) + else: + sniff_sockets[opened_socket] = "socket0" + if offline is not None: + flt = karg.get('filter') + if isinstance(offline, list): + 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 iteritems(offline)) else: - flt = karg.get('filter') - sniff_sockets = [PcapReader( + 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 iteritems(iface) + ) + else: + sniff_sockets[L2socket(type=ETH_P_ALL, iface=iface, + *arg, **karg)] = iface lst = [] if timeout is not None: stoptime = time.time()+timeout remain = None + read_allowed_exceptions = () + if conf.use_bpf: + from scapy.arch.bpf.supersocket import bpf_select + def _select(sockets): + return bpf_select(sockets, remain) + elif WINDOWS: + from scapy.arch.pcapdnet import PcapTimeoutElapsed + read_allowed_exceptions = (PcapTimeoutElapsed,) + def _select(sockets): + try: + return sockets + except PcapTimeoutElapsed: + return [] + else: + def _select(sockets): + try: + return select(sockets, [], [], remain)[0] + except select_error as exc: + # Catch 'Interrupted system call' errors + if exc[0] == errno.EINTR: + return [] + raise try: - stop_event = False - while not stop_event: + while sniff_sockets: if timeout is not None: remain = stoptime-time.time() if remain <= 0: break - if conf.use_bpf: - from scapy.arch.bpf.supersocket import bpf_select - ins = bpf_select(sniff_sockets, remain) - else: - ins, _, _ = select(sniff_sockets, [], [], remain) + ins = _select(sniff_sockets) for s in ins: - p = s.recv() - if p is None and offline is not None: - stop_event = True + try: + p = s.recv() + except read_allowed_exceptions: + continue + if p is None: + del sniff_sockets[s] + break + if lfilter and not lfilter(p): + continue + p.sniffed_on = sniff_sockets[s] + if store: + lst.append(p) + c += 1 + if prn: + r = prn(p) + if r is not None: + print(r) + if stop_filter and stop_filter(p): + sniff_sockets = [] + break + if 0 < count <= c: + sniff_sockets = [] break - elif p is not None: - if lfilter and not lfilter(p): - continue - if s in label: - p.sniffed_on = label[s] - if store: - lst.append(p) - c += 1 - if prn: - r = prn(p) - if r is not None: - print(r) - if stop_filter and stop_filter(p): - stop_event = True - break - if 0 < count <= c: - stop_event = True - break except KeyboardInterrupt: pass if opened_socket is None: @@ -671,76 +734,48 @@ interfaces) @conf.commands.register -def bridge_and_sniff(if1, if2, count=0, store=1, offline=None, prn=None, - lfilter=None, L2socket=None, timeout=None, - stop_filter=None, *args, **kargs): - """Forward traffic between two interfaces and sniff packets exchanged -bridge_and_sniff([count=0,] [prn=None,] [store=1,] [offline=None,] -[lfilter=None,] + L2Socket args) -> list of packets - - 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: - ex: prn = lambda x: x.summary() -lfilter: python function applied to each packet to determine - if further action may be done - ex: lfilter = lambda x: x.haslayer(Padding) -timeout: stop sniffing after a given time (default: None) -L2socket: use the provided L2socket -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) +def bridge_and_sniff(if1, if2, prn=None, L2socket=None, *args, **kargs): + """Forward traffic between interfaces if1 and if2, sniff and return the +exchanged packets. + +Arguments: + + if1, if2: the interfaces to use + + The other arguments are the same than for the function sniff(), + except for opened_socket, offline and iface that are ignored. + See help(sniff) for more. + """ - c = 0 + 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] if L2socket is None: L2socket = conf.L2socket s1 = L2socket(iface=if1) s2 = L2socket(iface=if2) - peerof={s1:s2,s2:s1} - label={s1:if1, s2:if2} - - lst = [] - if timeout is not None: - stoptime = time.time()+timeout - remain = None - try: - stop_event = False - while not stop_event: - if timeout is not None: - remain = stoptime-time.time() - if remain <= 0: - break - if conf.use_bpf: - from scapy.arch.bpf.supersocket import bpf_select - ins = bpf_select([s1, s2], remain) - else: - ins, _, _ = select([s1, s2], [], [], remain) + peers = {if1: s2, if2: s1} + def prn_send(pkt): + try: + sendsock = peers[pkt.sniffed_on] + except KeyError: + return + try: + sendsock.send(pkt.original) + except: + log_runtime.warning('Cannot forward packet [%s] received from %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) - for s in ins: - p = s.recv() - if p is not None: - peerof[s].send(p.original) - if lfilter and not lfilter(p): - continue - if store: - p.sniffed_on = label[s] - lst.append(p) - c += 1 - if prn: - r = prn(p) - if r is not None: - print(r) - if stop_filter and stop_filter(p): - stop_event = True - break - if 0 < count <= c: - stop_event = True - break - except KeyboardInterrupt: - pass - finally: - return plist.PacketList(lst,"Sniffed") + return sniff(opened_socket={s1: if1, s2: if2}, prn=prn, *args, **kargs) @conf.commands.register diff --git a/scapy/utils6.py b/scapy/utils6.py index 0759d6446afb37354548cbf4f66690ed3d1f075f..31bdb66c787d5b6a0e597c7789458eb1b52079f3 100644 --- a/scapy/utils6.py +++ b/scapy/utils6.py @@ -15,6 +15,7 @@ import socket import struct from scapy.config import conf +import scapy.consts from scapy.data import * from scapy.utils import * from scapy.pton_ntop import *