From ffd8158c25e4dcf65cad269c8ee1f32d29e5487b Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 27 Jul 2015 22:40:40 -0400 Subject: [PATCH] - basics of init-script working - basic shell script which enables virtualenv environment and calls traceroute.py at intervals - added stubs to only call webhook based on a specified latency - still need to store offline data in redis - coming soon --- README.md | 1 + init-script/traceroute | 64 +++++++++++++++++++++++++++++++++ init-script/traceroute.sh | 19 ++++++++++ traceroute.py | 75 +++++++++++++++++++++++++++++++++++---- 4 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 init-script/traceroute create mode 100755 init-script/traceroute.sh diff --git a/README.md b/README.md index 946d8c6..e2a08df 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Multi-source traceroute with geolocation information. Demo: [IP Address Lookup]( 1. Install dependencies listed in requirements.txt file. Pip is recommended. You could also use virtualenv to keep things isolated. 2. Save traceroute.py into a directory with its path stored in your PYTHONPATH environment variable. (if using virtualenv, copy it here) +3. Install Redis-Server ( may change ) ## Usage diff --git a/init-script/traceroute b/init-script/traceroute new file mode 100644 index 0000000..096e1da --- /dev/null +++ b/init-script/traceroute @@ -0,0 +1,64 @@ +#!/bin/bash +# traceroute.py daemon +# chkconfig: 345 20 80 +# description: traceroute.py daemon +# processname: traceroute.py + +DAEMON_PATH="/var/lib/python/traceroute" + +DAEMON=traceroute.sh +DAEMONOPTS="" + +NAME=traceroute.py +DESC="Traceroute Python Tool" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +case "$1" in +start) + printf "%-50s" "Starting $NAME..." + cd $DAEMON_PATH + PID=`$DAEMON $DAEMONOPTS > /dev/null 2>&1 & echo $!` + #echo "Saving PID" $PID " to " $PIDFILE + if [ -z $PID ]; then + printf "%s\n" "Fail" + else + echo $PID > $PIDFILE + printf "%s\n" "Ok" + fi +;; +status) + printf "%-50s" "Checking $NAME..." + if [ -f $PIDFILE ]; then + PID=`cat $PIDFILE` + if [ -z "`ps axf | grep ${PID} | grep -v grep`" ]; then + printf "%s\n" "Process dead but pidfile exists" + else + echo "Running" + fi + else + printf "%s\n" "Service not running" + fi +;; +stop) + printf "%-50s" "Stopping $NAME" + PID=`cat $PIDFILE` + cd $DAEMON_PATH + if [ -f $PIDFILE ]; then + kill -HUP $PID + printf "%s\n" "Ok" + rm -f $PIDFILE + else + printf "%s\n" "pidfile not found" + fi +;; + +restart) + $0 stop + $0 start +;; + +*) + echo "Usage: $0 {status|start|stop|restart}" + exit 1 +esac diff --git a/init-script/traceroute.sh b/init-script/traceroute.sh new file mode 100755 index 0000000..1db088e --- /dev/null +++ b/init-script/traceroute.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +EXEC_DIR=/var/lib/python/traceroute +VIRTUALENV_DIR=/var/lib/python/traceroute/env + +# Repeat every 5 seconds +INTERVAL=5 # + +source $VIRTUALENV_DIR/bin/activate + +cd $EXEC_DIR + +# TODO add some logging later + +while true; +do + python ./traceroute.py --ip_address=8.8.8.8 -c LO --webhook=http://localhost:8081/test; + sleep $INTERVAL; +done diff --git a/traceroute.py b/traceroute.py index ed2e735..6eb6ac1 100755 --- a/traceroute.py +++ b/traceroute.py @@ -27,7 +27,7 @@ class Traceroute(object): Multi-source traceroute instance. """ def __init__(self, ip_address, source=None, country="US", tmp_dir="/tmp", - no_geo=False, timeout=120, debug=False): + no_geo=False, timeout=120, debug=False, max_latency=5): super(Traceroute, self).__init__() self.ip_address = ip_address self.source = source @@ -37,6 +37,9 @@ class Traceroute(object): self.source = sources[country] self.tmp_dir = tmp_dir + self.LATENCY_THRESHOLD = float(max_latency) + + self.no_geo = no_geo self.timeout = timeout self.debug = debug @@ -44,6 +47,9 @@ class Traceroute(object): self.hops = {} self.country = country + # flag to determine if webhook alert is warranted + self.latency_exceeded = False + # Localhost Specific operations happen here if self.country == 'LO': self.local_mode = True @@ -59,6 +65,11 @@ class Traceroute(object): self.__run_traceroute() self.probe_end = time.time() * 1000 + def pingLatencyThresholdExceeded(self): + """public method to query state of Traceroute calls""" + + return self.latency_exceeded + def __run_traceroute(self): """ Instead of running the actual traceroute command, we will fetch @@ -134,6 +145,7 @@ class Traceroute(object): hop_element_pattern = '([\d\w.-]+)\s+\((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\)\s+(\d+\.\d+ ms)' hp = re.compile(hop_element_pattern) + alertTriggered = False for entry in traceroute.split('\n'): entry = entry.strip() result = re.match(hop_pattern,entry) @@ -146,9 +158,17 @@ class Traceroute(object): hop_hosts = re.findall(host_pattern, hop['hosts']) self.hops[hop_num] = [] + for host in hop_hosts: m = hp.search(host) (hostname, ip, ping_time) = m.groups() + + # Check ping time to see if it exceeds threshold. Once one is found, don't need any more info from other hops + if alertTriggered is False: + if self._exceeds_hop_latency(ping_time): + self.latency_exceeded = True + alertTriggered = True + if self.no_geo: self.hops[hop_num].append( { @@ -207,6 +227,15 @@ class Traceroute(object): if 'latitude' in tmp_location and 'longitude' in tmp_location: location = tmp_location return location + def _exceeds_hop_latency(self,ping_time): + """return true if hop time exceeds specified latency threshold""" + # remote ' ms' from ping time + ping_as_float = float(ping_time.replace(" ms","")) + return ping_as_float >= self.LATENCY_THRESHOLD + + + + def execute_cmd(self, cmd): """ @@ -360,6 +389,18 @@ def post_result(webhook_url, report, timeout=120): """ return requests.post(webhook_url, data=json.dumps(report), timeout=timeout) +def webhook_available(webhook_url): + """ + Function to check if a webhook host is responding. + Not 100% sure this will work... + """ + try: + data = urllib.urlopen(webhook_url) + return True + except Exception,e: + return False + + def main(): cmdparser = optparse.OptionParser("%prog --ip_address=IP_ADDRESS") @@ -393,6 +434,11 @@ def main(): cmdparser.add_option( "-w", "--webhook", type="string", default="", help="Specify URL to POST report payload rather than stdout") + + cmdparser.add_option( + "--max_latency", type="int", default="5", + help="Maximum latency whereby the system will trigger the webhook ( if requested ). ") + options, _ = cmdparser.parse_args() json_file = open(options.json_file, "r").read() @@ -406,17 +452,32 @@ def main(): tmp_dir=options.tmp_dir, no_geo=options.no_geo, timeout=options.timeout, - debug=options.debug) + debug=options.debug, max_latency = options.max_latency) # pull complete report -> Hop data plus meta info about the network report = traceroute.get_report() + + + if options.webhook != "": - try: - result = post_result(options.webhook, report, options.timeout) - print "Webhook POST Result: {}".format(result) - except Exception,e: - print "Provided webhook {0} is invalid. Message was: {1}".format(options.webhook, e) + # check if remote host is available + if webhook_available(options.webhook): + + # if available, check if there are any outstanding reports that should be sent + # if traceroute::backlog == true => Purge results + + if traceroute.pingLatencyThresholdExceeded(): + try: + result = post_result(options.webhook, report, options.timeout) + print "Webhook POST Result: {}".format(result) + except Exception,e: + print "Provided webhook {0} is invalid. Message was: {1}".format(options.webhook, e) + else: + print "Webhook unavailable, caching" + pass + # Dump Result into Redis + # Set redis flag traceroute::backlog => true else: print(json.dumps(report, indent=4)) return 0