PK!pcapfilter/__init__.py# -*- coding: utf-8 -*- """Top-level package for PCap Filter.""" __author__ = """Nahuel Defossé""" __email__ = "nahuel.defosse+pip@gmail.com" __version__ = "__version__ = '0.2.1'" PK!Q~ypcapfilter/cli.py# -*- coding: utf-8 -*- """Console script for pcapfilter.""" import logging import os import sys import click from .template import FILTER_TEMPLATE LOGGER = logging.getLogger() LOGGER.setLevel(logging.DEBUG) STREAM_HANDLER = logging.StreamHandler(sys.stderr) STREAM_HANDLER.setLevel(logging.DEBUG) STREAM_HANDLER.setFormatter( logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ) def show_docker_help(): click.echo( click.style( "To run pcapfilter inside docker and use your module function you\n" "must ensure that you're not using some python package that was\n" "bundled in the image.\n" "\n", fg="red", ) ) click.echo( "tcpdump -i en0 -s0 -w - | docker run --rm -i -v $(pwd):/shared pcapfilter " "pcapfilter -vm /shared/main.py | wireshark -k -i -" "\n\n" "The main.py should contain a function like:\n" ) click.echo( click.style( "from scapy.all import *\n" "from logging import getLogger\n" "\n" "LOG = getLogger(__name__)\n\n" "def packet_filter(packet):\n" " # Return None to filter, or (un)modified packet with scapy\n" " return packet\n", fg="green", ) ) click.echo("\n") @click.command( help="Read packet capture data (pcap) stream from stdin, apply a function and write " "to stdout. Example (capture from INTERFACE and display in Wireshark): " "tcpdump -i INTERFACE -s0 -w - | pcapfilter -m myfiltermodule.py | wireshark -k -i -", epilog="In normal operation stdin and stdout should work with pcap data. Log messages" " are written to stderr.", ) @click.option( "-m", "--module", type=str, default="", help="A python module name that contains a packet_filter(packet). You can create one " "with -w example.py", ) @click.option("-s", "--silent", is_flag=False, help="Hide log messages from STDERR)") @click.option("-o", "--oldpcap", is_flag=True, help="Use old pcap for input") @click.option("-r", "--reload", is_flag=True, help="Reloads the module upon changes") @click.option("-w", "--create-template", type=str, help="Creates an example file") @click.option( "-d", "--docker-help", is_flag=True, help="Shows help when running from docker" ) def main(module, silent, oldpcap, reload, create_template, docker_help): LOGGER.debug(__name__) # Delay scapy import until it's necessary, since it takes some time if docker_help: show_docker_help() return if not silent: LOGGER.addHandler(STREAM_HANDLER) if create_template: if reload: LOGGER.warning("Cannot use reload when file creation is requested") return if module: LOGGER.warning("The module name argument is expected after -w") if os.path.exists(create_template): LOGGER.critical("Will not overwrite %s", create_template) return 4 LOGGER.info("Creating example file named {}".format(module)) with open(create_template, "w") as fp: fp.write(FILTER_TEMPLATE) return 0 if not oldpcap: LOGGER.info("Using PcapNgReader") from scapy.all import PcapNgReader as Reader else: LOGGER.info("Using PcapReader") from scapy.all import PcapReader as Reader from scapy.all import PcapWriter as Writer # Delay scapy import from .pcapfilter import run_filter run_filter( _input=sys.stdin, _output=sys.stdout, reader_class=Reader, writer_class=Writer, module=module, reload=reload, ) if __name__ == "__main__": sys.exit(main()) # pragma: no cover PK!a6pcapfilter/pcapfilter.py# -*- coding: utf-8 -*- """ tcpdump -i en0 -s0 -w - | pcapfilter -m mymodule.py | wireshark -k -i - In a OpenWRT enabled router something like this could be used: ssh router "tcpdump -i eth1.2 -i br-lan -s0 -w - " | pcapfilter -m mymodule.py | wireshark -k -i - """ import imp import logging import os from pathlib import Path from queue import Queue import sys from typing import Any, Callable from datetime import datetime, timedelta from scapy.error import Scapy_Exception from scapy.all import PcapNgReader, PcapWriter import io try: from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer WATCHDOG_ENABLED = True except ImportError: WATCHDOG_ENABLED = False LOGGER = logging.getLogger() NOTIFICATION_QUEUE = Queue(maxsize=1) def start_observer(module): """ Starts the file observer if available and injects notifies the main loop though the NOTIFICATION_QUEUE. """ if not WATCHDOG_ENABLED: LOGGER.info("Cannot start observer, watchdog module not available") return module_path = Path(module.__file__) module_abspath = module_path.absolute() module_dir = module_path.cwd().absolute() class ModuleEventHandler(FileSystemEventHandler): def on_modified(self, event): if Path(event.src_path).absolute() == module_abspath: if not NOTIFICATION_QUEUE.full(): LOGGER.info("Notifying reload of: %s", module_abspath) NOTIFICATION_QUEUE.put(1) file_event_handler = ModuleEventHandler() observer = Observer() observer.schedule(file_event_handler, module_dir, recursive=False) observer.start() return observer, file_event_handler def _extract(mod, name): callback = getattr(mod, name, None) if not callable(callback): LOGGER.warning("{} does not have a callable {}".format(mod, name)) return callback def import_module_and_callback( module_name: str, callback_name: str = "packet_filter" ) -> (Any, Callable): """ Returns the module and the callback. The module is used for autoreload. """ module, callback = None, None if module_name: if module_name.endswith(".py") and os.path.exists(module_name): LOGGER.info("Loading plain file {}".format(module_name)) sys.path.append(os.path.dirname(module_name)) module_name, _ = os.path.splitext(os.path.basename(module_name)) if callable(module_name): callback = module_name else: try: module = __import__(module_name) callback = _extract(module, callback_name) except ImportError: LOGGER.info("Could not import {}".format(module_name)) return module, callback def reload_module_and_callback( module: Any, callback_name: str = "packet_filter" ) -> Callable: new_mod = imp.reload(module) callback = _extract(module, callback_name) return new_mod, callback def run_filter( _input: io.BytesIO, _output: io.BytesIO, module: str = None, reader_class: object = PcapNgReader, writer_class: object = PcapWriter, reload: bool = False, ): """ Process packets inserted from _input through a function and wirte them to _output """ module, callback = import_module_and_callback(module) if reload and module: _reloader = start_observer(module) # noqa try: LOGGER.info("Creating reader") reader = reader_class(_input) writer = writer_class(_output) LOGGER.info("Creating writer") except KeyboardInterrupt: LOGGER.info("Existed before fully opening the stream") return -1 except Scapy_Exception: LOGGER.error("Could not read pcap form stdin.") return -2 # Counters last = current = datetime.now() delta = timedelta(seconds=1) count = 0 for source in reader: try: output = callback(source) if callable(callback) else source if not output: continue writer.write(output) if reload and NOTIFICATION_QUEUE.full(): module, callback = reload_module_and_callback(module) NOTIFICATION_QUEUE.get() # Stats count += 1 current = datetime.now() if current - last >= delta: LOGGER.info("Packets processed in last second: {}".format(count)) count = 0 last = current except (KeyboardInterrupt, BrokenPipeError): LOGGER.info("Stopping...") break except Scapy_Exception: LOGGER.error("Could not read pcap form stdin") return -2 LOGGER.info("%s packets processed." % count) return 0 PK!c[ljXXpcapfilter/template.py# -*- coding: utf-8 -*- FILTER_TEMPLATE = """\ from scapy.all import * from logging import getLogger LOG = getLogger(__name__) def packet_filter(packet): #if IP in packet and UDP in packet: # LOG.info('Package has UDP data') #if IP in packet and TCP in packet: # LOG.info('Package has TCP data') return packet """ PK!H)%+2+pcapfilter-0.2.1.dist-info/entry_points.txtN+I/N.,()*HN,H)I-E0s2r3PK!R22"pcapfilter-0.2.1.dist-info/LICENSEMIT License Copyright (c) 2018, Nahuel Defossé Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HڽTU pcapfilter-0.2.1.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!HM'#pcapfilter-0.2.1.dist-info/METADATAKN0>,AjV-Ҋ.(#gXsp17 Xa셜W+V&ľV] ~ܜ.j(`.=LP8Ђ`{a]+ R/7>Vߑo9cwVScZ?k6JU uJl#uzgQuɥfP=[:fc>ńFŔCU(:8Z6hSMk)bQqO>ĘOGd >PK!HH!pcapfilter-0.2.1.dist-info/RECORD}9@| b ** r W/Ȭ$3\~C+L \bN~^v7KMz5Voq8ax yH:OK < \IAbwfGHR}/ ]wwY}1H/\3ЂSSЄ)>m X<z젆)U(A9HPY1A3y&mz@o-'4<_瘱t‚F3 n.RWU`d$j)O uM.]2lïƪSo DSS7,ڣ1Y8Hl:C/3 GP,8`!Zvsz bpn̤7e"p㝒O |e[N3tјӍsi_qK6^Ӂ;[;e