1#!/usr/bin/env python3 2# 3# Copyright (c) 2016, The OpenThread Authors. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 1. Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# 2. Redistributions in binary form must reproduce the above copyright 11# notice, this list of conditions and the following disclaimer in the 12# documentation and/or other materials provided with the distribution. 13# 3. Neither the name of the copyright holder nor the 14# names of its contributors may be used to endorse or promote products 15# derived from this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27# POSSIBILITY OF SUCH DAMAGE. 28# 29 30import collections 31import io 32import logging 33import os 34import sys 35import time 36 37import pcap 38import threading 39import traceback 40 41try: 42 import Queue 43except ImportError: 44 import queue as Queue 45 46import message 47import sniffer_transport 48 49 50class Sniffer: 51 """ Class representing the Sniffing node, whose main task is listening 52 and logging message exchange performed by other nodes. 53 """ 54 55 logger = logging.getLogger("sniffer.Sniffer") 56 57 RECV_BUFFER_SIZE = 4096 58 59 def __init__(self, message_factory): 60 """ 61 Args: 62 message_factory (MessageFactory): Class producing messages from data bytes. 63 """ 64 65 self._message_factory = message_factory 66 67 self._pcap = pcap.PcapCodec(os.getenv('TEST_NAME', 'current')) 68 if __name__ == '__main__': 69 sys.stdout.buffer.write(self._pcap.encode_header()) 70 sys.stdout.buffer.flush() 71 72 # Create transport 73 transport_factory = sniffer_transport.SnifferTransportFactory() 74 self._transport = transport_factory.create_transport() 75 76 self._thread = None 77 self._thread_alive = threading.Event() 78 self._thread_alive.clear() 79 80 self._buckets = collections.defaultdict(Queue.Queue) 81 82 def _sniffer_main_loop(self): 83 """ Sniffer main loop. """ 84 85 self.logger.debug("Sniffer started.") 86 87 while self._thread_alive.is_set(): 88 data, nodeid = self._transport.recv(self.RECV_BUFFER_SIZE) 89 90 pkt = self._pcap.append(data) 91 if __name__ == '__main__': 92 try: 93 sys.stdout.buffer.write(pkt) 94 sys.stdout.flush() 95 except BrokenPipeError: 96 self._thread_alive.clear() 97 break 98 99 # Ignore any exceptions 100 if self._message_factory is not None: 101 try: 102 messages = self._message_factory.create(io.BytesIO(data)) 103 self.logger.debug("Received messages: {}".format(messages)) 104 for msg in messages: 105 self._buckets[nodeid].put(msg) 106 107 except Exception as e: 108 # Just print the exception to the console 109 self.logger.error("EXCEPTION: %s" % e) 110 traceback.print_exc() 111 112 self.logger.debug("Sniffer stopped.") 113 114 def start(self): 115 """ Start sniffing. """ 116 117 self._thread = threading.Thread(target=self._sniffer_main_loop) 118 self._thread.daemon = True 119 120 self._transport.open() 121 122 self._thread_alive.set() 123 self._thread.start() 124 125 def stop(self): 126 """ Stop sniffing. """ 127 128 self._thread_alive.clear() 129 130 self._transport.close() 131 132 self._thread.join(timeout=1) 133 self._thread = None 134 135 def set_lowpan_context(self, cid, prefix): 136 self._message_factory.set_lowpan_context(cid, prefix) 137 138 def get_messages_sent_by(self, nodeid): 139 """ Get sniffed messages. 140 141 Note! This method flushes the message queue so calling this 142 method again will return only the newly logged messages. 143 144 Args: 145 nodeid (int): node id 146 147 Returns: 148 MessagesSet: a set with received messages. 149 """ 150 bucket = self._buckets[nodeid] 151 messages = [] 152 153 while not bucket.empty(): 154 messages.append(bucket.get_nowait()) 155 156 return message.MessagesSet(messages) 157 158 159def run_sniffer(): 160 sniffer = Sniffer(None) 161 sniffer.start() 162 while sniffer._thread_alive.is_set(): 163 try: 164 time.sleep(1) 165 except KeyboardInterrupt: 166 break 167 168 sniffer.stop() 169 170 171if __name__ == '__main__': 172 run_sniffer() 173