示例#1
0
def dumpling_emitter(
    kitchen_name: str,
    hub: str,
    dumpling_queue: multiprocessing.Queue,
    kitchen_info: Dict,
):
    """
    Starts an async event loop to manage funneling dumplings from the queue to
    the dumpling hub.

    This function is intended to be invoked as a Python process.

    :param kitchen_name: The name of the kitchen that the dumplings will be
        coming from.
    :param hub: The address where ``nd-hub`` is receiving dumplings.
    :param dumpling_queue: Queue to get dumplings from.
    :param kitchen_info: Information on the kitchen.
    """
    configure_logging()
    log = logging.getLogger('netdumplings.console.sniff')
    log.info("{0}: Starting dumpling emitter process".format(kitchen_name))

    try:
        asyncio.run(
            send_dumplings_from_queue_to_hub(kitchen_name, hub, dumpling_queue,
                                             kitchen_info, log))
    except KeyboardInterrupt:
        log.warning(f"Keyboard interrupt detected")
示例#2
0
    def test_logging_formatter(self):
        """
        Test that the logging formatter gets configured like we expect.
        """
        configure_logging()

        assert logging.Formatter.converter == time.gmtime
        assert logging.Formatter.default_time_format == '%Y-%m-%dT%H:%M:%S'
        assert logging.Formatter.default_msec_format == '%s.%03d'
示例#3
0
    def test_invalid_logging_config_file(self, monkeypatch, mocker):
        """
        Test NETDUMPLINGS_LOGGING_CONFIG environment variable for setting the
        logging config with an invalid file. Should fall back on basicConfig().
        """
        # We still want the Formatter to be configured.
        assert logging.Formatter.converter == time.gmtime
        assert logging.Formatter.default_time_format == '%Y-%m-%dT%H:%M:%S'
        assert logging.Formatter.default_msec_format == '%s.%03d'

        # Set NETDUMPLINGS_LOGGING_CONFIG to point to a test logging config.
        logging_config_file = 'tests/data/logging_bogus.json'
        monkeypatch.setenv('NETDUMPLINGS_LOGGING_CONFIG', logging_config_file)
        spy_basic_config = mocker.spy(logging, 'basicConfig')

        configure_logging(logging.DEBUG)

        spy_basic_config.assert_called_once_with(level=logging.DEBUG)
示例#4
0
def hub_cli(address, in_port, out_port, status_freq):
    """
    The dumpling hub.

    Sends dumplings received from all kitchens (usually any running instances
    of nd-sniff) to all dumpling eaters. All kitchens and eaters need to
    connect to the nd-hub --in-port or --out-port respectively.
    """
    configure_logging()

    dumpling_hub = netdumplings.DumplingHub(
        address=address,
        in_port=in_port,
        out_port=out_port,
        status_freq=status_freq,
    )

    try:
        dumpling_hub.run()
    except NetDumplingsError as e:
        print('hub error: {0}'.format(e))
        sys.exit(1)
示例#5
0
    def test_logging_config_file(self, monkeypatch):
        """
        Test NETDUMPLINGS_LOGGING_CONFIG environment variable for setting the
        logging config.
        """
        # We still want the Formatter to be configured.
        assert logging.Formatter.converter == time.gmtime
        assert logging.Formatter.default_time_format == '%Y-%m-%dT%H:%M:%S'
        assert logging.Formatter.default_msec_format == '%s.%03d'

        # Set NETDUMPLINGS_LOGGING_CONFIG to point to a test logging config.
        logging_config_file = 'tests/data/logging.json'
        monkeypatch.setenv('NETDUMPLINGS_LOGGING_CONFIG', logging_config_file)

        configure_logging()

        # The test config file sets all the loggers to ERROR.
        assert logging.getLogger('netdumplings').level == logging.ERROR
        assert logging.getLogger(
            'netdumplings.dumplinghub').level == logging.ERROR
        assert logging.getLogger(
            'netdumplings.dumplingkitchen').level == logging.ERROR
        assert logging.getLogger(
            'netdumplings.dumplingeater').level == logging.ERROR
示例#6
0
def sniff_cli(kitchen_name, hub, interface, pkt_filter, chef_module, chef,
              chef_list, poke_interval):
    """
    A dumpling sniffer kitchen.

    Sniffs network packets matching the given PCAP-style filter and sends them
    to chefs for processing into dumplings. Dumplings are then sent to nd-hub
    for distribution to the dumpling eaters.
    """
    # NOTE: Since the --chef-module and --chef flags can be specified multiple
    #   times, the associated 'chef_module' and 'chef' parameters are tuples of
    #   zero or more modules/chefs respectively.
    chef = True if len(chef) == 0 else chef

    # Display the chef list and exit if that's all the user wanted.
    if chef_list:
        list_chefs(chef_module)
        sys.exit(0)

    # now do the following:
    #
    # * Create a queue for a network-sniffing kitchen process to put dumplings
    #   onto
    # * Start a kitchen process, which will be putting dumplings onto the queue
    # * Start a dumpling emitter process which takes dumplings from the queue
    #   and sends them to nd-hub over a websocket

    configure_logging()
    logger = logging.getLogger('netdumplings.console.sniff')

    # A queue for passing dumplings from the sniffer kitchen to the
    # dumpling-emitter process.
    dumpling_emitter_queue = multiprocessing.Queue()

    # Determine what chefs we'll be sending packets to.
    valid_chefs = get_valid_chefs(kitchen_name, chef_module, chef, logger)

    if not valid_chefs:
        logger.error('{}: No valid chefs found. Not starting sniffer.'.format(
            kitchen_name
        ))
        sys.exit(1)

    # Generate list of module.class names for all the seemingly-valid chefs
    # we'll be instantiating.  This is for use in the status dumplings.
    valid_chef_list = []
    for chef_module_name in sorted(valid_chefs.keys()):
        for chef_class_name in sorted(valid_chefs[chef_module_name]):
            valid_chef_list.append('{}.{}'.format(
                chef_module_name, chef_class_name)
            )

    # Start the sniffer kitchen and dumpling-emitter processes.
    sniffer_process = multiprocessing.Process(
        target=network_sniffer,
        args=(
            kitchen_name, interface, chef, chef_module, valid_chefs,
            pkt_filter, poke_interval, dumpling_emitter_queue,
        )
    )

    kitchen_info = {
        'kitchen_name': kitchen_name,
        'interface': interface,
        'filter': pkt_filter,
        'chefs': valid_chef_list,
        'poke_interval': poke_interval,
    }

    dumpling_emitter_process = multiprocessing.Process(
        target=dumpling_emitter,
        args=(kitchen_name, hub, dumpling_emitter_queue, kitchen_info),
    )

    sniffer_process.start()
    dumpling_emitter_process.start()

    try:
        while True:
            if (sniffer_process.is_alive() and
                    dumpling_emitter_process.is_alive()):
                sleep(1)
            else:
                if sniffer_process.is_alive():
                    logger.error(
                        "{0}: Dumpling emitter process died; exiting.".format(
                            kitchen_name))
                    sniffer_process.terminate()

                if dumpling_emitter_process.is_alive():
                    logger.error(
                        "{0}: Network sniffer process died; exiting.".format(
                            kitchen_name))
                    dumpling_emitter_process.terminate()

                break
    except KeyboardInterrupt:
        logger.warning(
            "{0}: Caught keyboard interrupt; exiting.".format(
                kitchen_name))
示例#7
0
def sniff_cli(kitchen_name, hub, interface, pkt_filter, chef_module, chef,
              chef_list, poke_interval):
    """
    A dumpling sniffer kitchen.

    Sniffs network packets matching the given PCAP-style filter and sends them
    to chefs for processing into dumplings. Dumplings are then sent to nd-hub
    for distribution to the dumpling eaters.

    This tool likely needs to be run as root, or as an Administrator user.
    """
    # NOTE: Since the --chef-module and --chef flags can be specified multiple
    #   times, the associated 'chef_module' and 'chef' parameters are tuples of
    #   zero or more modules/chefs respectively.
    chef = True if len(chef) == 0 else chef

    # Display the chef list and exit if that's all the user wanted.
    if chef_list:
        list_chefs(chef_module)
        sys.exit(0)

    # now do the following:
    #
    # * Create a queue for a network-sniffing kitchen process to put dumplings
    #   onto
    # * Start a kitchen process, which will be putting dumplings onto the queue
    # * Start a dumpling emitter process which takes dumplings from the queue
    #   and sends them to nd-hub over a websocket

    configure_logging()
    logger = logging.getLogger('netdumplings.console.sniff')
    logger.info("Initializing sniffer...")

    # A queue for passing dumplings from the sniffer kitchen to the
    # dumpling-emitter process.
    dumpling_emitter_queue = multiprocessing.Queue()

    # Determine what chefs we'll be sending packets to.
    valid_chefs = get_valid_chefs(kitchen_name, chef_module, chef, logger)

    if not valid_chefs:
        logger.error('{}: No valid chefs found. Not starting sniffer.'.format(
            kitchen_name))
        sys.exit(1)

    # Generate list of module.class names for all the seemingly-valid chefs
    # we'll be instantiating.  This is for use in the status dumplings.
    valid_chef_list = []
    for chef_module_name in sorted(valid_chefs.keys()):
        for chef_class_name in sorted(valid_chefs[chef_module_name]):
            valid_chef_list.append('{}.{}'.format(chef_module_name,
                                                  chef_class_name))

    # Start the sniffer kitchen and dumpling-emitter processes.
    sniffer_process = multiprocessing.Process(target=network_sniffer,
                                              args=(
                                                  kitchen_name,
                                                  interface,
                                                  chef,
                                                  chef_module,
                                                  valid_chefs,
                                                  pkt_filter,
                                                  poke_interval,
                                                  dumpling_emitter_queue,
                                              ))

    kitchen_info = {
        'kitchen_name': kitchen_name,
        'interface': interface,
        'filter': pkt_filter,
        'chefs': valid_chef_list,
        'poke_interval': poke_interval,
    }

    dumpling_emitter_process = multiprocessing.Process(
        target=dumpling_emitter,
        args=(kitchen_name, hub, dumpling_emitter_queue, kitchen_info),
        daemon=True,
    )

    sniffer_process.start()
    dumpling_emitter_process.start()

    try:
        while True:
            sniffer_process.join(0.5)
            dumpling_emitter_process.join(0.5)

            if not sniffer_process.is_alive():
                logger.error(
                    f"{kitchen_name}: Network sniffer process died; exiting.")
                break

            if not dumpling_emitter_process.is_alive():
                logger.error(
                    f"{kitchen_name}: Dumpling emitter process died; exiting.")
                break
    except KeyboardInterrupt:
        logger.warning(f"{kitchen_name}: Caught keyboard interrupt; exiting.")

    for process in multiprocessing.active_children():
        process.terminate()
示例#8
0
def network_sniffer(
    kitchen_name: str,
    interface: str,
    chefs: Union[List[str], bool],
    chef_modules: List[str],
    valid_chefs: Dict,
    sniffer_filter: str,
    chef_poke_interval: int,
    dumpling_queue: multiprocessing.Queue,
):
    """
    The main network sniffing management function, responsible for:

    * Instantiating a dumpling kitchen (which does the actual sniffing) and
      providing it with a queue to put chef-created dumplings on
    * Instantiating the dumpling chefs and registering them with the kitchen
    * Running the kitchen

    This function is intended to be invoked as a Python process.

    A dumpling chef will only be instantiated if its ``assignable_to_kitchen``
    class attribute is ``True``.

    :param kitchen_name: Name of the sniffer kitchen.
    :param interface: Network interface to sniff (``all`` sniffs all
        interfaces).
    :param chefs: List of chefs to send packets to. Used for display only.
    :param chef_modules: List of Python module names in which to find chefs.
        Used for display only.
    :param valid_chefs: Dict of module+chef combinations we plan on importing.
    :param sniffer_filter: PCAP-compliant sniffer filter.
    :param chef_poke_interval: Interval (in secs) to poke chefs.
    :param dumpling_queue: Queue to pass to the kitchen to put dumplings on.
    """
    configure_logging()
    log = logging.getLogger('netdumplings.console.sniff')
    log.info("{0}: Starting network sniffer process".format(kitchen_name))
    log.info("{0}: Interface: {1}".format(kitchen_name, interface))
    log.info("{0}: Requested chefs: {1}".format(
        kitchen_name, "all" if chefs is True else ", ".join(chefs)))
    log.info("{0}: Chef modules: {1}".format(kitchen_name,
                                             ", ".join(chef_modules)))
    log.info("{0}: Filter: {1}".format(
        kitchen_name,
        "<all packets>" if not sniffer_filter else sniffer_filter))
    log.info("{0}: Chef poke interval (secs): {1}".format(
        kitchen_name, chef_poke_interval))

    sniffer_kitchen = netdumplings.DumplingKitchen(
        dumpling_queue=dumpling_queue,
        name=kitchen_name,
        interface=interface,
        sniffer_filter=sniffer_filter,
        chef_poke_interval=chef_poke_interval,
    )

    # Instantiate all the valid DumplingChef classes and register them with
    # the kitchen.
    for chef_module in valid_chefs:
        chef_class_names = valid_chefs[chef_module]

        if os.path.isfile(chef_module):
            spec = importlib.util.spec_from_file_location('chefs', chef_module)
            mod = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(mod)
        else:
            # TODO: Investigate replacing __import__ with
            #   importlib.import_module
            mod = __import__(chef_module, fromlist=chef_class_names)

        for chef_class_name in chef_class_names:
            log.info("{0}: Registering {1}.{2} with kitchen".format(
                kitchen_name, chef_module, chef_class_name))
            klass = getattr(mod, chef_class_name)
            klass(kitchen=sniffer_kitchen)

    sniffer_kitchen.run()