initial revision
This commit is contained in:
commit
4832744fd6
2 changed files with 352 additions and 0 deletions
124
README.md
Normal file
124
README.md
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# traceroute
|
||||
Traceroute is a Python script that allows you to get traceroute results with associated geolocation information for each hop for a specified host from geographically distant source(s).
|
||||
|
||||
## Installation
|
||||
|
||||
Save traceroute.py into a directory with its path stored in your PYTHONPATH environment variable.
|
||||
|
||||
## Usage
|
||||
|
||||
Try the following from your Python interpreter:
|
||||
|
||||
>>> from traceroute import Traceroute
|
||||
>>> traceroute = Traceroute(ip_address='8.8.8.8')
|
||||
>>> hops = traceroute.traceroute()
|
||||
>>> hops
|
||||
[{'latitude': 40.3756, 'rtt': '0.862 ms', 'ip_address': '128.112.128.114', 'longitude': -74.6597, 'hop_num': 1}, {'latitude': 40.3756, 'rtt': '0.462 ms', 'ip_address': '128.112.12.22', 'longitude': -74.6597, 'hop_num': 2}, {'latitude': 40.3265, 'rtt': '8.374 ms', 'ip_address': '24.104.128.89', 'longitude': -75.3697, 'hop_num': 3}, {'latitude': 38.0, 'rtt': '21.010 ms', 'ip_address': '68.86.84.177', 'longitude': -97.0, 'hop_num': 4}, {'latitude': 38.0, 'rtt': '12.218 ms', 'ip_address': '68.86.86.70', 'longitude': -97.0, 'hop_num': 5}, {'latitude': 38.0, 'rtt': '31.103 ms', 'ip_address': '75.149.231.62', 'longitude': -97.0, 'hop_num': 6}, {'latitude': 37.4192, 'rtt': '14.752 ms', 'ip_address': '209.85.252.80', 'longitude': -122.0574, 'hop_num': 7}, {'latitude': 37.4192, 'rtt': '18.437 ms', 'ip_address': '72.14.238.18', 'longitude': -122.0574, 'hop_num': 9}, {'latitude': 37.4192008972168, 'rtt': '18.610 ms', 'ip_address': '72.14.238.70', 'longitude': -122.05740356445312, 'hop_num': 9}, {'latitude': 37.4192, 'rtt': '16.696 ms', 'ip_address': '72.14.238.18', 'longitude': -122.0574, 'hop_num': 9}, {'latitude': 37.4192, 'rtt': '24.441 ms', 'ip_address': '72.14.232.21', 'longitude': -122.0574, 'hop_num': 10}, {'latitude': 37.4192, 'rtt': '13.925 ms', 'ip_address': '216.239.49.149', 'longitude': -122.0574, 'hop_num': 10}, {'latitude': 37.4192, 'rtt': '19.800 ms', 'ip_address': '72.14.232.21', 'longitude': -122.0574, 'hop_num': 10}, {'latitude': 37.4192, 'rtt': '14.144 ms', 'ip_address': '8.8.8.8', 'longitude': -122.0574, 'hop_num': 11}]
|
||||
>>> import json
|
||||
>>> json.dumps(hops, indent=2)
|
||||
'[\n {\n "latitude": 40.3756, \n "rtt": "0.862 ms", \n "ip_address": "128.112.128.114", \n "longitude": -74.6597, \n "hop_num": 1\n }, \n {\n "latitude": 40.3756, \n "rtt": "0.462 ms", \n "ip_address": "128.112.12.22", \n "longitude": -74.6597, \n "hop_num": 2\n }, \n {\n "latitude": 40.3265, \n "rtt": "8.374 ms", \n "ip_address": "24.104.128.89", \n "longitude": -75.3697, \n "hop_num": 3\n }, \n {\n "latitude": 38.0, \n "rtt": "21.010 ms", \n "ip_address": "68.86.84.177", \n "longitude": -97.0, \n "hop_num": 4\n }, \n {\n "latitude": 38.0, \n "rtt": "12.218 ms", \n "ip_address": "68.86.86.70", \n "longitude": -97.0, \n "hop_num": 5\n }, \n {\n "latitude": 38.0, \n "rtt": "31.103 ms", \n "ip_address": "75.149.231.62", \n "longitude": -97.0, \n "hop_num": 6\n }, \n {\n "latitude": 37.4192, \n "rtt": "14.752 ms", \n "ip_address": "209.85.252.80", \n "longitude": -122.0574, \n "hop_num": 7\n }, \n {\n "latitude": 37.4192, \n "rtt": "18.437 ms", \n "ip_address": "72.14.238.18", \n "longitude": -122.0574, \n "hop_num": 9\n }, \n {\n "latitude": 37.4192008972168, \n "rtt": "18.610 ms", \n "ip_address": "72.14.238.70", \n "longitude": -122.05740356445312, \n "hop_num": 9\n }, \n {\n "latitude": 37.4192, \n "rtt": "16.696 ms", \n "ip_address": "72.14.238.18", \n "longitude": -122.0574, \n "hop_num": 9\n }, \n {\n "latitude": 37.4192, \n "rtt": "24.441 ms", \n "ip_address": "72.14.232.21", \n "longitude": -122.0574, \n "hop_num": 10\n }, \n {\n "latitude": 37.4192, \n "rtt": "13.925 ms", \n "ip_address": "216.239.49.149", \n "longitude": -122.0574, \n "hop_num": 10\n }, \n {\n "latitude": 37.4192, \n "rtt": "19.800 ms", \n "ip_address": "72.14.232.21", \n "longitude": -122.0574, \n "hop_num": 10\n }, \n {\n "latitude": 37.4192, \n "rtt": "14.144 ms", \n "ip_address": "8.8.8.8", \n "longitude": -122.0574, \n "hop_num": 11\n }\n]'
|
||||
>>>
|
||||
|
||||
You can also run the script directly by passing in the --ip_address option:
|
||||
|
||||
$ python traceroute.py --ip_address=8.8.8.8
|
||||
[
|
||||
{
|
||||
"latitude": 40.3756,
|
||||
"rtt": "0.862 ms",
|
||||
"ip_address": "128.112.128.114",
|
||||
"longitude": -74.6597,
|
||||
"hop_num": 1
|
||||
},
|
||||
{
|
||||
"latitude": 40.3756,
|
||||
"rtt": "0.462 ms",
|
||||
"ip_address": "128.112.12.22",
|
||||
"longitude": -74.6597,
|
||||
"hop_num": 2
|
||||
},
|
||||
{
|
||||
"latitude": 40.3265,
|
||||
"rtt": "8.374 ms",
|
||||
"ip_address": "24.104.128.89",
|
||||
"longitude": -75.3697,
|
||||
"hop_num": 3
|
||||
},
|
||||
{
|
||||
"latitude": 38.0,
|
||||
"rtt": "21.010 ms",
|
||||
"ip_address": "68.86.84.177",
|
||||
"longitude": -97.0,
|
||||
"hop_num": 4
|
||||
},
|
||||
{
|
||||
"latitude": 38.0,
|
||||
"rtt": "12.218 ms",
|
||||
"ip_address": "68.86.86.70",
|
||||
"longitude": -97.0,
|
||||
"hop_num": 5
|
||||
},
|
||||
{
|
||||
"latitude": 38.0,
|
||||
"rtt": "31.103 ms",
|
||||
"ip_address": "75.149.231.62",
|
||||
"longitude": -97.0,
|
||||
"hop_num": 6
|
||||
},
|
||||
{
|
||||
"latitude": 37.4192,
|
||||
"rtt": "14.752 ms",
|
||||
"ip_address": "209.85.252.80",
|
||||
"longitude": -122.0574,
|
||||
"hop_num": 7
|
||||
},
|
||||
{
|
||||
"latitude": 37.4192,
|
||||
"rtt": "18.437 ms",
|
||||
"ip_address": "72.14.238.18",
|
||||
"longitude": -122.0574,
|
||||
"hop_num": 9
|
||||
},
|
||||
{
|
||||
"latitude": 37.4192008972168,
|
||||
"rtt": "18.610 ms",
|
||||
"ip_address": "72.14.238.70",
|
||||
"longitude": -122.05740356445312,
|
||||
"hop_num": 9
|
||||
},
|
||||
{
|
||||
"latitude": 37.4192,
|
||||
"rtt": "16.696 ms",
|
||||
"ip_address": "72.14.238.18",
|
||||
"longitude": -122.0574,
|
||||
"hop_num": 9
|
||||
},
|
||||
{
|
||||
"latitude": 37.4192,
|
||||
"rtt": "24.441 ms",
|
||||
"ip_address": "72.14.232.21",
|
||||
"longitude": -122.0574,
|
||||
"hop_num": 10
|
||||
},
|
||||
{
|
||||
"latitude": 37.4192,
|
||||
"rtt": "13.925 ms",
|
||||
"ip_address": "216.239.49.149",
|
||||
"longitude": -122.0574,
|
||||
"hop_num": 10
|
||||
},
|
||||
{
|
||||
"latitude": 37.4192,
|
||||
"rtt": "19.800 ms",
|
||||
"ip_address": "72.14.232.21",
|
||||
"longitude": -122.0574,
|
||||
"hop_num": 10
|
||||
},
|
||||
{
|
||||
"latitude": 37.4192,
|
||||
"rtt": "14.144 ms",
|
||||
"ip_address": "8.8.8.8",
|
||||
"longitude": -122.0574,
|
||||
"hop_num": 11
|
||||
}
|
||||
]
|
||||
228
traceroute.py
Normal file
228
traceroute.py
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
traceroute - Get traceroute results with associated geolocation information
|
||||
for each hop for a specified host from geographically distant source(s).
|
||||
"""
|
||||
|
||||
__author__ = 'Dazzlepod (info@dazzlepod.com)'
|
||||
__copyright__ = 'Copyright (c) 2013 Dazzlepod'
|
||||
__version__ = '$Revision: #7 $'
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib
|
||||
import urllib2
|
||||
from multiprocessing import Process
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
|
||||
class Traceroute(object):
|
||||
"""Traceroute instance."""
|
||||
def __init__(self, ip_address='8.8.8.8', debug=False):
|
||||
super(Traceroute, self).__init__()
|
||||
self.ip_address = ip_address
|
||||
self.debug = debug
|
||||
# cache geocoded IP addresses during the lifetime of this instance
|
||||
self.locations = {}
|
||||
|
||||
def traceroute(self):
|
||||
"""Instead of running the actual traceroute command, we will fetch
|
||||
standard traceroute results from several publicly available webpages
|
||||
that are listed at traceroute.org.
|
||||
|
||||
For each hop, we will then attach geolocation information to it."""
|
||||
self.print_debug("ip_address = %s" % self.ip_address)
|
||||
if not os.path.exists('%s.txt' % self.ip_address):
|
||||
(status_code, traceroute) = self.get_traceroute_output()
|
||||
f = open('%s.txt' % self.ip_address, 'w')
|
||||
f.write(traceroute)
|
||||
f.close()
|
||||
|
||||
traceroute = open('%s.txt' % self.ip_address, 'r').read()
|
||||
|
||||
# hops = dicts with keys: hop_num, hosts
|
||||
hops = self.get_hops(traceroute)
|
||||
|
||||
# hops = dicts with keys: hop_num, hostname, ip_address, rtt
|
||||
hops = self.get_formatted_hops(hops)
|
||||
|
||||
# hops = dicts with keys: hop_num, hostname, ip_address, rtt, location
|
||||
hops = self.get_geocoded_hops(hops)
|
||||
|
||||
# hops = dicts with keys: hop_num, ip_address, latitude, longitude, rtt
|
||||
hops = self.get_stripped_hops(hops)
|
||||
|
||||
return hops
|
||||
|
||||
def get_traceroute_output(self):
|
||||
"""Fetch traceroute output from a webpage."""
|
||||
|
||||
# Example from traceroute.org
|
||||
url = "http://www.net.princeton.edu/cgi-bin/traceroute.pl"
|
||||
(status_code, response) = self.urlopen(url, context = {'target': self.ip_address})
|
||||
|
||||
pattern = re.compile(r'<pre.*?>(?P<traceroute>.*?)</pre>', re.DOTALL|re.IGNORECASE)
|
||||
traceroute = re.findall(pattern, response)[0].strip()
|
||||
|
||||
return (status_code, traceroute)
|
||||
|
||||
def get_hops(self, traceroute):
|
||||
"""Get hops from a traceroute output and return the hops in an array
|
||||
of dicts each representing hop number and the associated hosts data."""
|
||||
hops = []
|
||||
|
||||
lines = traceroute.split('\n')
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
hop = {}
|
||||
if not line: continue
|
||||
try:
|
||||
hop = re.match(r'^(?P<hop_num>\d+)(?P<hosts>.*?)$', line).groupdict()
|
||||
except AttributeError:
|
||||
continue
|
||||
self.print_debug(hop)
|
||||
hops.append(hop)
|
||||
|
||||
return hops
|
||||
|
||||
def get_formatted_hops(self, hops):
|
||||
"""hosts data from get_hops() is represented in a single string.
|
||||
We use this function to better represent the hosts data in a dict."""
|
||||
formatted_hops = []
|
||||
|
||||
for hop in hops:
|
||||
hop_num = int(hop['hop_num'].strip())
|
||||
hosts = hop['hosts'].replace(' ', ' ').strip()
|
||||
|
||||
# Using re.findall(), first we split the hosts, then for each host we store a tuple containing hostname, IP address and the first round-trip time
|
||||
# [('<HOSTNAME>', '<IP_ADDRESS>', '<RTT1> ms'), ('<HOSTNAME_N>', '<IP_ADDRESS_N>', '<RTT1_N> ms')]
|
||||
hosts = re.findall(r'(?P<hostname>[\w.-]+) \((?P<ip_address>[\d.]+)\) (?P<rtt>\d{1,4}.\d{1,4} ms)', hosts)
|
||||
|
||||
for host in hosts:
|
||||
hop_context = {
|
||||
'hop_num': hop_num,
|
||||
'hostname': host[0],
|
||||
'ip_address': host[1],
|
||||
'rtt': host[2],
|
||||
}
|
||||
self.print_debug(hop_context)
|
||||
formatted_hops.append(hop_context)
|
||||
|
||||
return formatted_hops
|
||||
|
||||
def get_geocoded_hops(self, hops):
|
||||
"""Return hops from get_formatted_hops() with geolocation information
|
||||
for each hop."""
|
||||
for hop in hops:
|
||||
ip_address = hop['ip_address']
|
||||
location = None
|
||||
if self.locations.has_key(ip_address):
|
||||
location = self.locations[ip_address]
|
||||
else:
|
||||
location = self.get_location(ip_address)
|
||||
self.locations[ip_address] = location
|
||||
hop['location'] = location
|
||||
|
||||
return hops
|
||||
|
||||
def get_stripped_hops(self, hops):
|
||||
""" Return hops from get_geocoded_hops() with each dict containing
|
||||
data ready for plotting with e.g. Google Maps JavaScript API."""
|
||||
stripped_hops = []
|
||||
|
||||
for hop in hops:
|
||||
if hop['location']:
|
||||
lat = hop['location']['latitude']
|
||||
lon = hop['location']['longitude']
|
||||
stripped_hops.append({
|
||||
'hop_num': hop['hop_num'],
|
||||
'ip_address': hop['ip_address'],
|
||||
'latitude': lat,
|
||||
'longitude': lon,
|
||||
'rtt': hop['rtt'],
|
||||
})
|
||||
|
||||
return stripped_hops
|
||||
|
||||
def get_location(self, ip_address):
|
||||
"""Return geolocation information for the specified IP address, e.g.:
|
||||
{"ip": "75.126.24.77",
|
||||
"hostname": "web365.webfaction.com",
|
||||
"isp": "SoftLayer Technologies",
|
||||
"organization": "Client Intellect",
|
||||
"country": "United States",
|
||||
"region": "Texas",
|
||||
"city": "Dallas",
|
||||
"latitude": 32.9299,
|
||||
"longitude": -96.8353}
|
||||
"""
|
||||
location = None
|
||||
url = "https://dazzlepod.com/ip/%s.json" % ip_address
|
||||
(status_code, json_data) = self.urlopen(url)
|
||||
if status_code == 200 and json_data:
|
||||
tmp_location = json.loads(json_data)
|
||||
if tmp_location.has_key('latitude') and tmp_location.has_key('longitude'):
|
||||
location = tmp_location
|
||||
return location
|
||||
|
||||
def urlopen(self, url, context = None):
|
||||
"""Perform HTTP GET/POST on the specified URL and return the resultant
|
||||
status code and response.
|
||||
"""
|
||||
status_code = 200
|
||||
|
||||
request = urllib2.Request(url = url)
|
||||
|
||||
if context:
|
||||
data = urllib.urlencode(context)
|
||||
request.add_data(data)
|
||||
|
||||
response = ''
|
||||
try:
|
||||
response = urllib2.urlopen(request).read()
|
||||
except urllib2.HTTPError, e:
|
||||
status_code = e.code
|
||||
except urllib2.URLError:
|
||||
pass
|
||||
|
||||
try:
|
||||
self.urlopen_count += 1
|
||||
except AttributeError:
|
||||
self.urlopen_count = 1
|
||||
self.print_debug("[%d] url = %s, status_code = %d" % (self.urlopen_count, url, status_code))
|
||||
|
||||
return (status_code, response)
|
||||
|
||||
def print_debug(self, msg):
|
||||
"""Print debug message to standard output."""
|
||||
if self.debug:
|
||||
print "[DEBUG %s] %s" % (datetime.datetime.now(), msg)
|
||||
|
||||
|
||||
def main():
|
||||
usage = """%prog --ip_address=IP_ADDRESS"""
|
||||
cmdparser = optparse.OptionParser(usage, version=("traceroute " + __version__))
|
||||
cmdparser.add_option("-i", "--ip_address", type="string", default="", help="IP address of destination host")
|
||||
cmdparser.add_option("-d", "--debug", action="store_true", default=False, help="Show debug output")
|
||||
|
||||
(options, args) = cmdparser.parse_args()
|
||||
|
||||
if options.ip_address:
|
||||
traceroute = Traceroute(ip_address=options.ip_address, debug=options.debug)
|
||||
hops = traceroute.traceroute()
|
||||
hops = json.dumps(hops, indent=2)
|
||||
print hops
|
||||
else:
|
||||
cmdparser.print_usage()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue