From 940430828f9af97f6164049d7ecf02176ea54e82 Mon Sep 17 00:00:00 2001 From: Jeroen Pluimers Date: Sun, 9 Jul 2017 11:17:04 +0200 Subject: [PATCH] Fixes #11 and use semantic versioning - when there is monitoring, but no capture, indicate so in `fritzcap.py` main script - new threading base class `ExceptionLoggingThread` that logs exceptions during `run`: all thread classes now descend from `ExceptionLoggingThread` - in `call_monitor`, when there is no `capture_monitor`: prevent exceptions and ensure the intended logic still works - semantic versioning http://semver.org/ ``` 2017-07-09 10:33:21,889 - FritzCap version 2.3.1 started. 2017-07-09 10:33:21,889 - Note: -m or --monitor_calls without -c or --capture_files does monitoring without capture. 2017-07-09 10:33:21,890 - Connect to the call monitor service on 192.168.124.23:1012. 2017-07-09 10:33:21,897 - Connected to the call monitor service on 192.168.124.23:1012. 2017-07-09 10:33:37,816 - Ring (ID:0, ActiveCalls.:1, Caller:xxxxxxxxxx, DialedNumber:yyyyyyyyyy, LinePort:SIP0) 2017-07-09 10:33:43,075 - Disconnect (ID:0, ActiveCalls.:0, Caller:xxxxxxxxxx, DialedNumber:yyyyyyyyyy, LinePort:SIP0) 2017-07-09 10:33:58,084 - Connection to the call monitor service on 192.168.124.23:1012 stopped. 2017-07-09 10:33:58,084 - FritzCap version 2.3.1 finished. ``` --- core/call_monitor.py | 87 +++++++++++++++++++------------- core/capfile_worker.py | 7 +-- core/capture_monitor.py | 7 +-- core/exception_logging_thread.py | 51 +++++++++++++++++++ core/interfaces_dumper.py | 7 +-- core/string_helper.py | 2 +- core/sysinput_reader.py | 7 +-- core/tracer.py | 10 ++-- fritzcap.py | 11 ++-- repair_cap_file.py | 2 +- 10 files changed, 132 insertions(+), 59 deletions(-) create mode 100644 core/exception_logging_thread.py diff --git a/core/call_monitor.py b/core/call_monitor.py index 4ce8617..30c4640 100644 --- a/core/call_monitor.py +++ b/core/call_monitor.py @@ -42,11 +42,12 @@ from log import Log from capture_monitor import CaptureMonitor +from exception_logging_thread import ExceptionLoggingThread -class CallMonitor(threading.Thread): +class CallMonitor(ExceptionLoggingThread): def __init__(self, capture_monitor, box_name, call_service_port): - threading.Thread.__init__(self) + ExceptionLoggingThread.__init__(self) self._stop = threading.Event() self.capture_monitor = capture_monitor @@ -70,7 +71,7 @@ def init_connection(self): self.logger.info("Connected to the call monitor service on %s:%s." % (self.box_name,self.call_service_port)) - def run(self): + def run_logic(self): self.logger.debug("Thread started.") callers_count = 0 # Set the count of currently led calls to 0. call_id_map = {} @@ -103,8 +104,8 @@ def run(self): sline = line.split(";") event_time = datetime.datetime.now() - # parse the oryginal telnet time. Example: 13.01.11 21:48:31 - oryginal_event_time = time.strptime(sline[0], "%d.%m.%y %H:%M:%S") # 13.01.11 21:48:31 + # parse the original telnet time. Example: 13.01.11 21:48:31 + original_event_time = time.strptime(sline[0], "%d.%m.%y %H:%M:%S") # 13.01.11 21:48:31 self.logger.debug("Telnet:'"+line+"'") @@ -120,19 +121,25 @@ def run(self): if not me: me = "Unknown" - self.capture_monitor.set_data("callevent.name", command) - self.capture_monitor.set_data("acalls.number", callers_count) - self.capture_monitor.set_data("lineport.name", sline[5]) + if (self.capture_monitor is None): + me_numbername = me + callpartner_numbername = call_partner - self.capture_monitor.set_data("tocall", oryginal_event_time) - self.capture_monitor.set_data("tcall", event_time) - self.capture_monitor.set_callnumber("caller", call_partner) - self.capture_monitor.set_callnumber("dialed", me) - self.capture_monitor.set_callnumber("me", me) - self.capture_monitor.set_callnumber("callpartner", call_partner) + else: + self.capture_monitor.set_data("callevent.name", command) + self.capture_monitor.set_data("acalls.number", callers_count) + self.capture_monitor.set_data("lineport.name", sline[5]) + + self.capture_monitor.set_data("tocall", original_event_time) + self.capture_monitor.set_data("tcall", event_time) + self.capture_monitor.set_callnumber("caller", call_partner) + self.capture_monitor.set_callnumber("dialed", me) + self.capture_monitor.set_callnumber("me", me) + self.capture_monitor.set_callnumber("callpartner", call_partner) + + me_numbername = self.capture_monitor.get_call_numbername(me) + callpartner_numbername = self.capture_monitor.get_call_numbername(call_partner) - me_numbername = self.capture_monitor.get_call_numbername(me) - callpartner_numbername = self.capture_monitor.get_call_numbername(call_partner) self.logger.info("Ring (ID:%s, ActiveCalls.:%s, Caller:%s, DialedNumber:%s, LinePort:%s)" % (sline[2],callers_count,callpartner_numbername,me_numbername,sline[5])) call_id_map[sline[2]] = [callpartner_numbername,me_numbername,sline[5]] @@ -147,37 +154,45 @@ def run(self): if not call_partner: call_partner = "Unknown" - self.capture_monitor.set_data("callevent.name", command) - self.capture_monitor.set_data("acalls.number", callers_count) - self.capture_monitor.set_data("lineport.name", sline[6]) - self.capture_monitor.set_data("tocall", oryginal_event_time) - self.capture_monitor.set_data("tcall", event_time) - self.capture_monitor.set_callnumber("caller", me) - self.capture_monitor.set_callnumber("dialed", call_partner) - self.capture_monitor.set_callnumber("me", me) - self.capture_monitor.set_callnumber("callpartner", call_partner) - - me_numbername = self.capture_monitor.get_call_numbername(me) - callpartner_numbername = self.capture_monitor.get_call_numbername(call_partner) + if (self.capture_monitor is None): + me_numbername = self.capture_monitor.get_call_numbername(me) + callpartner_numbername = self.capture_monitor.get_call_numbername(call_partner) + else: + self.capture_monitor.set_data("callevent.name", command) + self.capture_monitor.set_data("acalls.number", callers_count) + self.capture_monitor.set_data("lineport.name", sline[6]) + self.capture_monitor.set_data("tocall", original_event_time) + self.capture_monitor.set_data("tcall", event_time) + self.capture_monitor.set_callnumber("caller", me) + self.capture_monitor.set_callnumber("dialed", call_partner) + self.capture_monitor.set_callnumber("me", me) + self.capture_monitor.set_callnumber("callpartner", call_partner) + + me_numbername = self.capture_monitor.get_call_numbername(me) + callpartner_numbername = self.capture_monitor.get_call_numbername(call_partner) + self.logger.info("Call (ID:%s, ActiveCalls.:%s, Caller:%s, DialedNumber:%s, LinePort:%s)" % (sline[2],callers_count,me_numbername,callpartner_numbername,sline[6])) call_id_map[sline[2]] = [me_numbername,callpartner_numbername,sline[6]] elif command == "CONNECT": # Conversation started. - self.capture_monitor.set_data("toconn", oryginal_event_time) - self.capture_monitor.set_data("tconn", event_time) + if (self.capture_monitor is not None): + self.capture_monitor.set_data("toconn", original_event_time) + self.capture_monitor.set_data("tconn", event_time) self.logger.info("Connect (ID:%s, ActiveCalls.:%s, Caller:%s, DialedNumber:%s, LinePort:%s)" % (sline[2],callers_count,call_id_map[sline[2]][0],call_id_map[sline[2]][1],call_id_map[sline[2]][2])) continue # Don't increase currently led calls, because already done with CALL/RING. elif command == "DISCONNECT": # Call was ended. callers_count -= 1 # Decrease the count of currently led calls. - self.capture_monitor.set_data("todisc", oryginal_event_time) - self.capture_monitor.set_data("tdisc", event_time) - self.capture_monitor.set_data("acalls.number", callers_count) + if (self.capture_monitor is not None): + self.capture_monitor.set_data("todisc", original_event_time) + self.capture_monitor.set_data("tdisc", event_time) + self.capture_monitor.set_data("acalls.number", callers_count) self.logger.info("Disconnect (ID:%s, ActiveCalls.:%s, Caller:%s, DialedNumber:%s, LinePort:%s)" % (sline[2],callers_count,call_id_map[sline[2]][0],call_id_map[sline[2]][1],call_id_map[sline[2]][2])) if (callers_count < 0): self.logger.warning("There is more stopped calls than started. Data corrupt or program started while calling. Normalize ActiveCalls to '0'") callers_count = 0; - self.capture_monitor.set_data("acalls.number", callers_count) + if (self.capture_monitor is not None): + self.capture_monitor.set_data("acalls.number", callers_count) else: continue @@ -190,10 +205,10 @@ def run(self): self.capture_monitor.stop_capture(); self._stop.set() - self.capture_monitor.stop() + if (self.capture_monitor is not None): + self.capture_monitor.stop() self.logger.debug("Thread stopped.") - def stop (self): self.logger.debug("Received signal to stop the thread.") self._stop.set() diff --git a/core/capfile_worker.py b/core/capfile_worker.py index 54c9d09..684fc77 100644 --- a/core/capfile_worker.py +++ b/core/capfile_worker.py @@ -43,11 +43,12 @@ from log import Log from pcap_parse import PcapParser from g711_decoder import G711Decoder +from exception_logging_thread import ExceptionLoggingThread -class CapfileWorker(threading.Thread): +class CapfileWorker(ExceptionLoggingThread): def __init__(self, worker_id, decode_work_queue): - threading.Thread.__init__(self) + ExceptionLoggingThread.__init__(self) self.decode_work_queue = decode_work_queue self.worker_id = worker_id self._stop = threading.Event() @@ -55,7 +56,7 @@ def __init__(self, worker_id, decode_work_queue): self.logger = Log().getLogger() self.logger.debug("CapfileWorker(worker_id:%s, decode_work_queue:%s)." % (worker_id, decode_work_queue)) - def run(self): + def run_logic(self): self.logger.debug("Thread started.") while not self._stop.isSet(): try: diff --git a/core/capture_monitor.py b/core/capture_monitor.py index 3377f0a..f6c9204 100644 --- a/core/capture_monitor.py +++ b/core/capture_monitor.py @@ -46,15 +46,16 @@ from log import Log from string_helper import StringHelper from tracer import Tracer +from exception_logging_thread import ExceptionLoggingThread -class CaptureMonitor(threading.Thread): +class CaptureMonitor(ExceptionLoggingThread): state_started = False next_stop_time = 0 next_start_time = 0 cap_file_path = "" def __init__(self, decode_work_queue, data_map, box_name, username, password, protocol, cap_folder, cap_file, cap_interface, login_required, default_login, sid_challenge, sid_login, start_str, stop_str, after_capture_time): - threading.Thread.__init__(self) + ExceptionLoggingThread.__init__(self) self._stop = threading.Event() self.decode_work_queue = decode_work_queue @@ -82,7 +83,7 @@ def __init__(self, decode_work_queue, data_map, box_name, username, password, pr self.logger = Log().getLogger() self.logger.debug("CaptureMonitor(decode_work_queue:'%s', data_map:'%s', box_name:'%s', username:'%s', password:'%s', protocol:'%s', cap_folder:'%s', cap_file:'%s', cap_interface:'%s', login_required:'%s', default_login:'%s', sid_challenge:'%s', sid_login:'%s', start_str:'%s', stop_str:'%s', after_capture_time:'%s')" % (decode_work_queue,data_map,box_name,username,password,protocol,cap_folder,cap_file,cap_interface,login_required,default_login,sid_challenge,sid_login,start_str,stop_str,after_capture_time)) - def run(self): + def run_logic(self): self.logger.debug("Thread started.") if self.login_required: diff --git a/core/exception_logging_thread.py b/core/exception_logging_thread.py new file mode 100644 index 0000000..fddeab3 --- /dev/null +++ b/core/exception_logging_thread.py @@ -0,0 +1,51 @@ +#!/usr/bin/python +# -*- coding: iso-8859-1 -*- +################################################################################# +# Simple thread that logs exceptions in the run for issue resolving purposes. +################################################################################## +# Copyright (c) 2017, Jeroen Wiert Pluimers (https://wiert.me) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +################################################################################## + +import threading + +from log import Log + +class ExceptionLoggingThread(threading.Thread): + + def __init__(self): + threading.Thread.__init__(self) + self.logger = Log().getLogger() + self.logger.debug("ExceptionLoggingThread().") + + def run_logic(self): + self.logger.debug("Thread started.") + + def run(self): + try: + self.run_logic() + except: + self.logger.exception('Exception in `run`') + raise diff --git a/core/interfaces_dumper.py b/core/interfaces_dumper.py index 8b7a263..eb0a3c4 100644 --- a/core/interfaces_dumper.py +++ b/core/interfaces_dumper.py @@ -47,6 +47,7 @@ from HTMLParser import HTMLParser from htmlentitydefs import name2codepoint +from exception_logging_thread import ExceptionLoggingThread ## https://docs.python.org/2/library/htmlparser.html class CaptureLuaHtmlParser(HTMLParser): @@ -120,10 +121,10 @@ def handle_decl(self, data): pass -class InterfacesDumper(threading.Thread): +class InterfacesDumper(ExceptionLoggingThread): def __init__(self, box_name, username, password, protocol, login_required, default_login, sid_challenge, sid_login): - threading.Thread.__init__(self) + ExceptionLoggingThread.__init__(self) self._stop = threading.Event() self.box_name = box_name @@ -139,7 +140,7 @@ def __init__(self, box_name, username, password, protocol, login_required, defau self.logger = Log().getLogger() self.logger.debug("InterfacesDumper(box_name:'%s', username:'%s', password:'%s', protocol:'%s', login_required:'%s', default_login:'%s', sid_challenge:'%s', sid_login:'%s')" % (box_name,username,password,protocol,login_required,default_login,sid_challenge,sid_login)) - def run(self): + def run_logic(self): self.logger.debug("Thread started.") if self.login_required: diff --git a/core/string_helper.py b/core/string_helper.py index 9ef65e0..c4eb101 100644 --- a/core/string_helper.py +++ b/core/string_helper.py @@ -58,7 +58,7 @@ class StringHelper(): def __init__(self): self.logger = Log().getLogger() - self.logger.debug("SytemInputFileReader(decode_work_queue:'%s')" % (decode_work_queue)) + self.logger.debug("StringHelper()") def parse_string(data_str, data_map): datetime_parse_str = "" diff --git a/core/sysinput_reader.py b/core/sysinput_reader.py index 0347e70..5b6ec62 100644 --- a/core/sysinput_reader.py +++ b/core/sysinput_reader.py @@ -41,18 +41,19 @@ import logging from log import Log +from exception_logging_thread import ExceptionLoggingThread -class SytemInputFileReader(threading.Thread): +class SytemInputFileReader(ExceptionLoggingThread): def __init__(self, decode_work_queue): - threading.Thread.__init__(self) + ExceptionLoggingThread.__init__(self) self._stop = threading.Event() self.decode_work_queue = decode_work_queue self.logger = Log().getLogger() self.logger.debug("SytemInputFileReader(decode_work_queue:'%s')" % (decode_work_queue)) - def run(self): + def run_logic(self): self.logger.debug("Thread started.") for line in sys.stdin: line = line.strip() diff --git a/core/tracer.py b/core/tracer.py index 367994d..3b95315 100644 --- a/core/tracer.py +++ b/core/tracer.py @@ -33,21 +33,21 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ################################################################################## -import threading, urllib, ctypes, platform +import urllib, ctypes, platform from log import Log +from exception_logging_thread import ExceptionLoggingThread THREAD_SET_INFORMATION = 0x20 THREAD_PRIORITY_ABOVE_NORMAL = 1 - ''' Tracer runs in a separate thread''' -class Tracer(threading.Thread): +class Tracer(ExceptionLoggingThread): def __init__(self, url, filename): self.url = url self.i = 0 self.filename = filename - threading.Thread.__init__(self) + ExceptionLoggingThread.__init__(self) self.logger = Log().getLogger() @@ -57,7 +57,7 @@ def monitor(self, n1, n2, n3): self.i += 1 self.i %= 4 - def run(self): + def run_logic(self): if platform.system() == "Windows": w32 = ctypes.windll.kernel32 tid = w32.GetCurrentThreadId() diff --git a/fritzcap.py b/fritzcap.py index c370513..730322b 100644 --- a/fritzcap.py +++ b/fritzcap.py @@ -121,7 +121,7 @@ def signal_handler(signum, stack): main_args = parser.add_argument_group('main arguments') ext_args = parser.add_argument_group('extended defaults arguments') - fritzcap_version = '2.3' + fritzcap_version = '2.3.1' parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + fritzcap_version) main_args.add_argument('-c', '--capture_files', default=None, action='store_true', help='capture file/s. If the monitor option is not set, only one file will be captured') @@ -179,10 +179,10 @@ def signal_handler(signum, stack): if (args.__contains__(key)): value = args.__getattribute__(key) - if value is None and config.has_option("settings", key) and config.get("settings", key): + if (value is None) and config.has_option("settings", key) and config.get("settings", key): value = config.get("settings", key) - if value is None and default_value: + if (value is None) and default_value: value = default_value args.__setattr__(key,value) @@ -213,7 +213,7 @@ def signal_handler(signum, stack): # take the password data from the command line - if (args.capture_files or args.show_interfaces) and args.password is None and login_required: + if (args.capture_files or args.show_interfaces) and (args.password is None) and login_required: platform_system = platform.system() if (platform_system == "Windows"): signal.signal(signal.SIGINT, signal_handler) @@ -275,6 +275,9 @@ def signal_handler(signum, stack): ###################################### if (args.monitor_calls): nothing_to_do = False + if (capture_monitor is None): + logger.info("Note: -m or --monitor_calls without -c or --capture_files does monitoring without capture.") + call_monitor = CallMonitor(capture_monitor, args.box_name, args.call_service_port) all_threads.insert(0, call_monitor) elif (args.capture_files): diff --git a/repair_cap_file.py b/repair_cap_file.py index 152d07e..278f00b 100644 --- a/repair_cap_file.py +++ b/repair_cap_file.py @@ -192,7 +192,7 @@ def check_data(data, filepos, last_time_sec): printed_line2 = "counter:%-10s, byte_counter:0x%-6X = %-6s, stime:%s, ts_usec:%-7s, incl_len:0x%-6X = %-6s, orig_len:0x%-6X = %-6s, data:" % (counter,nextpos,nextpos,cap_time2, ts_usec2, incl_len2, incl_len2, orig_len2, orig_len2) - print "The Oryginal is OK, but the next of oryginal seems not to be correct. Go Back..." + print "The original is OK, but the next of original seems not to be correct. Go Back..." print "\t\t"+printed_line print "\t\t"+printed_line2 print ""