Table of Content

Introduction

IP packet has a TTL field where TTL is an acronym for time-to-live. A forwarding node decrements the TTL value in the packet and then forward it, or drop it when the TTL value becomes 0 and reply the source with a ICMP Time-Exceeded message.

Trace Route

Knowning how a router decrements the TTL value and response to a packet with the 0 TTL value, we can design a trace route program to figure out routers between a source and a destination node. You can actually implement it using with a few different protocols. If you are sending UDP datagrams, you have a UDP traceroute; if you are sending out a TCP SYN segments, you have a TCP traceroute; and if you are sending out ICMP packet, you have a ICMP traceroute.

Below is an ICMP traceroute program in Python using ScaPy. Let’s call this program icmptracert.py

icmptracert.py

import sys
from scapy.layers.inet import IP
from scapy.layers.inet import ICMP
from scapy.sendrecv import sr1
from socket import gethostbyname

def print_route(hops, reply):
    nonemsg = '*****None******'
    unreachmsg = '*****None******'
    hopmsg = 'hops'
    typemsg = 'ICMP msg type '
    if hops == 1:
        hopmsg = 'hop'
    if reply is None:
        src = '*****None******'
        rtype = 'OOPS'
        typemsg = ''
    elif reply.type == 3:
        src = '**Unreachable**'
        rtype = reply.type
    else:
        src = reply.src
        rtype = reply.type

    print(f'{hops:2d} {hopmsg:4} away from {src:15} [{typemsg}{rtype:2}]')

    if not reply is None and reply.type == 0:
        print ('Desitnation reached')
        
def icmp_trace_route(dst):
    dstaddr = gethostbyname(dst)
    noops = 0
    print(f'Traceroute to {dst} at {dstaddr}:')
    for i in range(1, 65): # try 1 hops to 64 hops
        # prepare a ICMP packet (what type of ICMP message is it)?
        pkt = IP(dst=dstaddr, ttl=i) / ICMP()
        # Send the packet and get a reply
        reply = sr1(pkt, verbose=0, timeout=5)
        if reply is None:
            noops = noops + 1
            print_route(i, reply)
            if noops >= 5:
                break
        elif reply.type == 0:
            print_route(i, reply)
            break
        elif reply.type == 3:  # Destination Unreachable  (RFC 792)
            print_route(i, reply)
        elif reply.type == 11: # Time Exceeded (RFC 792)
            print_route(i, reply)
        else:  # Hi friend, How are you configured? Why am I getting this?
            print_route(i, reply)
        

def main(argv):
    if len(argv) < 2:
        print('Usage: ' + argv[0] + ' destination')
        sys.exit(1)
    dst = sys.argv[1]
    nodes = icmp_trace_route(dst)

if __name__=='__main__':
    main(sys.argv)

Running icmptracert.py

Below are some examples.

  1. Trace route to www.brooklyn.cuny.edu
    sudo python icmptracert.py www.brooklyn.cuny.edu
    
  2. Trace route to www.google.com
    sudo python icmptracert.py www.google.com
    
  3. Trace route to www.facebook.com
    sudo python icmptracert.py www.facebook.com
    

Exercise and Exploration

  1. Trace route to a few of your favorite web sites. Is there anything notable that you observe?
  2. How do we revise the program to do a UDP tracerroute instead? What have you tried? What do you observe from your exploration?

Appendix I. Guessing Hosting Locations from IP Addresses

Knowing location of a host is useful to an array of application, such as, positioning you to a nearest geographical point of interest, like a supermarket or a coffee shop when you visit the supermarket’s website or use the coffee shop’s mobile app. There have emerged vendors providing service to determine host locations of IP addresses. A host’s geographical location and its IP address assignment are dynamic and proprietary. The locations have accuracy varying across a number of dimensions, such as, ISP and countries. So, unless you are the ISP or the ISP shares with you the locations, these are a best effort guess.

The following example is to guess the geographical locations of the IP addresses returned from the traceroute experiments, which may provide you with an insight on the scale of the global Internet.

The example uses ipgeolocation.io’s RESTful Web service to guess the locations of the IP addresses. So, your first step is to sign up for a free developer account for an API KEY at https://ipgeolocation.io/pricing.html. With the API KEY, one can issue 1,000 requests a day at present. To run the following program replace the string YOUR_API_KEY with your actually API KEY.

It is worthy of comparing the get_geo_loc() function in the following program with the main() function in helloclient.py, which reveals that that we write the program by revising the helloclient.py program since the following program is actually a Web client to a RESTful Web service.

ip2loc.py

from http import client as httpclient
import json
import sys

HOST = 'api.ipgeolocation.io'
API_KEY = 'YOUR_API_KEY'

def build_request(ip):
    request = '/ipgeo?apiKey=' + API_KEY + '&ip=' + ip + '&output=json'
    return request
    
def get_geo_loc(ip):  
    request = build_request(ip);
    conn = httpclient.HTTPSConnection(HOST)
    
    conn.request('GET', request)  
    response = conn.getresponse()  
    if response.status != 200:
        location = (False, response.status, json.loads(response.read()))
    else:
        location = (True, response.status, json.loads(response.read()))
    conn.close()  
    return location

def main():
    ips = [line.strip() for line in sys.stdin]
    for ip in ips:
        success,_,location = get_geo_loc(ip)
        if not success:
            print(ip, location['message'])
        else:
            print(ip, location['latitude'], location['longitude'])
            


if __name__ == "__main__":    
    # execute only if run as a script
    main()

Running ip2loc.py

The ip2loc.py takes in a list of IP addresses one IP address a line from the Stadndard Input while the icmptracert.py prints out the routes on the Standard Output. Given these, we run icmptracert.py and ip2loc.py in a pipelined fashion as in the following example where we trace route to www.facebook.com,

python icmptracert.py www.facebook.com > facebook_route.txt
grep "hop" facebook_route.txt | grep hop tr.txt | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]" > facebook_route_ip.txt
python ip2loc.py < facebook_route_ip.txt > facebook_route_loc.txt

You should take a look at the output files, like facebook_route_loc.txt.

Appendix 2. Mapping Routes

Next step is to put the routes on the map if we know each node’s latitutde and longitude. Python has a few packages to plot maps. The loc2gmap.py is to plot the route on a map generated using the gmplot package, a Python wrapper of the Google Map API, a JavaScript API.

Preparing Environment

On the virtual machine you’ve beening doing the trace route experiment and getting the route’s locations, install pip3 and gmplot by following the steps below,

sudo apt-get install --no-install-recommends python3-pip
sudo pip3 install gmplot --upgrade

loc2gmap.py

import sys
from gmplot import GoogleMapPlotter 

def get_ip_locs():
    locs = [line.split() for line in sys.stdin]

    lat_list = [float(loc[1]) for loc in locs]
    lon_list = [float(loc[2]) for loc in locs]
    lbl_list = [loc[0] for loc in locs]

    lat_center = sum(lat_list)/len(lat_list)
    lon_center = sum(lon_list)/len(lon_list)

    return lat_list,lon_list,lbl_list,lat_center,lon_center

def main(map_html_fn):
    lat_list,lon_list,lbl_list,lat_center,lon_center = get_ip_locs()


    gmap = GoogleMapPlotter(lat_center, lon_center, 13)

    gmap.scatter(lat_list, lon_list, '#3B0B39', size=200, marker=False ) 
    for lat,lon,lbl in zip(lat_list,lon_list,lbl_list):
        gmap.text(lat, lon, color='#3B0B39', text=lbl)
  
    gmap.plot(lat_list, lon_list,  'cornflowerblue', edge_width = 2.5)
  
    gmap.draw(map_html_fn) 

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('Usage: loc2gmap.py YOUR_MAP_NAME.html')
        sys.exit(1)
    main(sys.argv[1])

Running loc2gmap.py and Viewing the Map

  1. First, we generate the map using the output obtained by running ip2loc.py in Appendix I as follows,
    grep -v bogon facebook_route_loc.txt | python loc2gmap.py facebook_route_gmap.html
    

    where facebook_route_gmap.html contains the map.

  2. How do we view the map? We rely on Python’s built-in Web server. First, take a note of the IP address of the virtual machine,
    ip address show
    

    Let’s assume the IP address is 192.168.56.101. Then we proceed to the next step.

  3. In the directory where the map html file is, run
    python -m http.server
    

    Below is an example including output,

    brooklyn@midwood:~$ python -m http.server
    Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
    

    which shows that the Web server listens to incoming connections at end point 0.0.0.0:8000.

  4. Finally, on the host (e.g., Windows or Mac), point your favorite Web browser, like Firefox, Chrome, or Safari to something like http://192.168.56.101/facebook_route_gmap.html, where the host in the URL is 192.168.56.101 because it is the IP address of the virtual machine we took note of, the map file is the file we generated using loc2gmap.py.

Ignore whatever Google Map complains. Zoom out or in the map. Do you see anything interesting?