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:
- Extract the files to your hard drive
- Open a terminal and cd to the directory containing the files
- Make the file executable (chmod +x vistraceroute.py)
- Run “vistraceroute.py <ip_address/hostname>”
- The program will output a Google Map URL. Copy and paste this into your web browser.
Windows:
- Extract the files to your hard drive (7-zip is great for .tar.gz on Windows)
- Open a command prompt and change to the directory containing the extracted files
- Make sure the python executable is on your system’s path
- Run “python vistraceroute.py <ip_address/hostname>”
- 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:
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']
We built similar tool and put it online at https://dazzlepod.com/ip/ (see the visual traceroute tab) using Python, Django GeoIP with MaxMind DB. The traceroute.py is available on GitHub at https://github.com/ayeowch/traceroute. Have you compared the accuracy of IPInfoDB vs. MaxMind?
Hey,
I was looking for something similar to xtraceroute or similar (see a colection here: http://personalpages.manchester.ac.uk/staff/m.dodge/cybergeography/atlas/routes.html ) and of what I could find this is the closest resemblance that works. Integrating it into a GUI based program would do wonders.
There is certainly a lack of such a tool nowadays (at least in GNU/Linux environments).
Keep up the good work!
Nice article!
Alternatively you can check the following library for traceroute and related functionality…its very handy!
https://github.com/hardikvasa/webb