A Visual Traceroute Program

I was having trouble with my internet connection the other day. A few router restarts later and the connection was still acting up, so I fired up a terminal and tracerouted a few IP’s and noticed some odd hostnames in the output. I went to www.ipinfodb.com and looked a few of them up. One of the IP’s I tracerouted was for a server here in Texas and traceroute was indicating packets were going all the way to Europe. I decided to look for a visual traceroute program so I could figure out the route the packets were actually taking, but I couldn’t find anything. So, I decided to come up with something on my own.

Below are two Python files, vistraceroute.py and pyipinfodb.py. vistraceroute.py is the actual traceroute program. pyinfodb.py is a Python module with a class for retrieving location information based on an IP or hostname. This Python module was originally written by Westly Ward and is available here, but the class was written for version 2 of the IPInfoDB API, and the current version is version 3, so the pyipinfodb.py below reflects changes made by me to use the latest API.

I’ve tested vistraceroute.py on both Linux (Ubuntu 12.10) and Windows 7 with Python 2.7.3. You’ll need to get an IPInfoDB API key from here and place it in vistraceroute.py in order to access the location data for the IP’s generated by traceroute. You can download an archive of these files here.

Installation/Usage

Linux:

  1. Extract the files to your hard drive
  2. Open a terminal and cd to the directory containing the files
  3. Make the file executable (chmod +x vistraceroute.py)
  4. Run “vistraceroute.py <ip_address/hostname>”
  5. The program will output a Google Map URL. Copy and paste this into your web browser.

Windows:

  1. Extract the files to your hard drive (7-zip is great for .tar.gz on Windows)
  2. Open a command prompt and change to the directory containing the extracted files
  3. Make sure the python executable is on your system’s path
  4. Run “python vistraceroute.py <ip_address/hostname>”
  5. The program will output a Google Map URL. Copy and paste this into your web browser. (Right click -> Mark in the command prompt to copy)

After you run the program and open the URL you’ll get something like this:

Vistraceroute output
Map generated by link from vistraceroute output

I hope to eventually integrate this all into a GUI application and allow greater control of the map and more traceroute options, but for now, this will do for my purposes. If you have trouble running the program or notice any bugs (of which I’m sure there are a few), let me know.

vistraceroute.py


#!/usr/bin/env python

# (C) Kyle Flanagan 2012
# vistraceroute
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from pyipinfodb import *
import subprocess, urllib, urllib2, sys, os

# api key for ipinfodb.com
API_KEY = "YOUR API KEY HERE"
# URL for Google Static Map API
google_map_base_url = "http://maps.googleapis.com/maps/api/staticmap?"

def getURLDict(ips):
 """
 Returns a dictionary consisting of URL paramenters to pass to the Google
 static map api. The IP addresses in ips are looked up using the IPInfo
 class from pyipinfodb and constructed into the url_dict.
 """
 url_dict = {'zoom' : '',
 'maptype':'roadmap',
 'sensor':'false',
 'size':'1200x600',
 'markers':[],
 'path':'color:0x0000ff|weight:3',
 'center':''}
 for x, ip in enumerate(ips):
 ipi = IPInfo(API_KEY, ip, city=True) # get city, state
 location = ipi.getCity() + ',' + ipi.getRegion()
 print "IP: " + ip + " Location: " + location

# the IPInfo class returns '-' if it cannot find a city or
 # state, so ignore these values
 if '-' in location:
 continue

# we could use the heuristic below to find an approximate center
 # for the map, but since we're passing markers and a path, Google
 # will find the center for us
 ##if len(ips) / 2 <= x and url_dict['center'] == None:
 ## url_dict['center'] = ipi.getCity() + ',' + ipi.getRegion()

# append markers
 if x == len(ips)-1: # end with red marker
 url_dict['markers'].append('color:red|label:' + str(x) + '|' + location)
 else: # else use a green one
 url_dict['markers'].append('color:green|label:' + str(x) + '|' + location)

# append to the path route
 url_dict['path'] = url_dict['path'] + "|" + location
 return url_dict

def main():
 """
 Usage: vistraceroute ip_address
 ---
 vistraceroute uses data from traceroute to query locations of IP addresses.
 Using these locations, it constructs a Google Static Map URL to plot the
 locations and also draws the path from location to location so that the
 user can see a visual represenation of the traceroute data.
 """
 if len(sys.argv) < 2:
 print "Usage: vistraceroute <ip_address>"
 return
 IP = sys.argv[1]
 print "Visual traceroute to IP: " + IP

# determine system
 posix_system = True
 traceroute = 'traceroute'
 args = [IP]
 if (os.name != "posix"):
 # assume windows
 posix_system = False
 traceroute = 'tracert'
 args.insert(0, '-d') # for windows traceroute to jsut get IP

args.insert(0, traceroute)
 # args now looks like: traceroute, [flag,] IP

# start traceroute
 print "Starting traceroute..."
 process = subprocess.Popen(args,
 shell=False,
 stdout=subprocess.PIPE)

# wait for traceroute to finish
 print "Traceroute running. Please wait..."
 if process.wait() != 0:
 print "Traceroute error. Exiting."
 #print process.communicate()[1]
 return

 # read data from traceroute and split it into lines
 lines = process.communicate()[0].split('\n')
 # print out traceroute data for user
 for line in lines:
 print line

print "Traceroute finished. Looking up IP addresses. Please wait..."
 # first line is traceroute info output, remove it
 lines.pop(0)
 # and there are extra lines on windows output
 if not posix_system:
 lines.pop(0)
 lines.pop(0)
 lines.pop(0)
 lines.pop()
 lines.pop()
 lines.pop()
 # now get hostname, ip from each line
 ips = []
 for line in lines:
 if line != "":
 # if we didn't get a reply from a gateway, ignore that line in the
 # traceroute output
 if '*' in line:
 continue

 # Split the line and extract the hostname and IP address
 if posix_system:
 split_line = line.split(' ')
 ips.append(split_line[1].split(' ')[1][1:-1])
 else:
 line = line.lstrip()
 line = line.rstrip()
 split_line = line.split(' ')
 ips.append(split_line[-1])

print "IP addresses to be looked up:"
 for ip in ips:
 print ip

url_dict = getURLDict(ips)

urldata = urllib.urlencode(url_dict, True)
 print "Google Map URL (copy and paste into your web browser):"
 url = google_map_base_url + urldata
 print url

if __name__ == "__main__":
 main()

pyipinfodb.py


#!/usr/bin/env python

# Copyright (c) 2010, Westly Ward
# 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 pyipinfodb 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 Westly Ward ''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 Westly Ward 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.
#
#
# Modifications made 2012 by Kyle Flanagan to update this class to version 3
#
#

import json, urllib, urllib2, socket
CITY_URL = "http://api.ipinfodb.com/v3/ip-city/"
COUNTRY_URL = "http://api.ipinfodb.com/v3/ip-country/"
class IPInfo() :
 def __init__(self, apikey, ip=None, city=False, country=False) :
 if ip == None:
 print "Error: No IP address specified."
 return None
 self.apikey = apikey
 if city:
 self.data = self.GetIPInfo(CITY_URL, ip)
 elif country:
 self.data = self.GetIPInfo(COUNTRY_URL, ip)
 else:
 print "Error: No precision specified. Must call with either city=True or country=True"
 return None

def GetIPInfo(self, baseurl, ip=None, timezone=False) :
 passdict = {"format":"json", "key":self.apikey}
 if ip :
 try :
 passdict["ip"] = socket.gethostbyaddr(ip)[2][0]
 except : passdict["ip"] = ip
 if timezone :
 passdict["timezone"] = "true"
 else :
 passdict["timezone"] = "false"
 urldata = urllib.urlencode(passdict)
 url = baseurl + "?" + urldata
 urlobj = urllib2.urlopen(url)
 #print urlobj
 data = urlobj.read()
 #print '*' * 80
 #print data
 #print '*' * 80
 urlobj.close()
 datadict = json.loads(data)
 return datadict

def getDict(self):
 return self.data

def getStatusCode(self):
 return self.data['statusCode']

def getStatusMessage(self):
 return self.data['statusMessage']

def getIP(self):
 return self.data['ipAddress']

def getCountryCode(self):
 return self.data['countryCode']

def getCountry(self) :
 return self.data['countryName']

def getRegion(self):
 return self.data['regionName']

def getCity(self) :
 return self.data['cityName']

def getZip(self):
 return self.data['zipCode']

def getLat(self):
 return self.data['latitude']

def getLong(self):
 return self.data['longitude']

def getTimezone(self):
 return self.data['timeZone']

Author: kyleflan

Kyle is a software developer by day and a husband and father by night. He enjoys cooking, a good craft beer, and tinkering on various software/hardware projects. Current interests are data science, containerization, Raspberry Pi projects and Node.js.

3 thoughts on “A Visual Traceroute Program”

Leave a comment