Tor Circuit Visualization

I am currently doing some research on how different internet routing techniques impact network throughput and latency. Tor is an interesting routing technique.

Here I will describe how to write a program to visualize what circuts Tor is currently using on a map. We will use a Python library by the Tor project called Stem to collect the data and gnuplot to make the plots.

demo plot


Install the dependencies. This assumes that you have already have Tor running on your machine. For an example tor setup see Sending all outbound tcp traffic through tor.

python3 -m pip install stem
sudo apt install gnuplot
curl https://raw.githubusercontent.com/gnuplot/gnuplot/master/demo/world.dat > world.dat

In addition to those software dependencies you’ll also need some way to get the geolocation of an ip address. I’ve been using the IP geolocation API from abstractapi.com.

Getting circuits

First you’ll need to set up Stem. Documentation on that can be found here.

In a file get_circuits.py add the following python code:

from stem import CircStatus
from stem.control import Controller

def get_circuits():
  circuits = []
  with Controller.from_port(port = 9051) as controller:
    controller.authenticate("your password")
    for circ in sorted(controller.get_circuits()):
      if circ.status != CircStatus.BUILT:
      circuit = ["circ-{}".format(circ.id)]
      for i, entry in enumerate(circ.path):
        fingerprint, nickname = entry

        desc = controller.get_network_status(fingerprint, None)
        address = desc.address if desc else 'unknown'
  return circuits

This get_circuits function will collect all of the circuits currently in use on the machine and for each one return a list containing its id followed by all of the addresses in it.


In another file we can write a function locate to make a call to the ip geolocation api.

import requests
import json

API_KEY="" # put your key here

def locate(address):
    request_url = "https://ipgeolocation.abstractapi.com/v1/?api_key={}&ip_address={}".format(API_KEY, address)
    response = requests.get(request_url)
    return response.json()

Putting together the python

With that done we can write a main function that collects all of the circuits, geolocates them, and then writes them out to disk so that we can plot them later.

Note that I have hardcoded the coordinates of the AWS box that I am running these tests from. I prepend that to each list of coordinates so that when we visualize our circuits they will appear to begin there.

from geolocate import locate
from get_circuits import get_circuits

AWS_COORDS="-83.0061 39.9625\n"

def get_coords(address):
    res = locate(address)
    if "latitude" in res and "longitude" in res:
        return (res["latitude"], res["longitude"])
    return None

circuits = get_circuits()
for c in circuits:
    nickname = c[0]
    addresses = c[1:]
    with open("{}.cor".format(nickname), "w") as out_file:
        for a in addresses:
            cords = get_coords(a)
            if cords != None:
                if cords[0] != "unknown" and cords[1] != "unknown":
                    out_file.write("{} {}\n".format(cords[1], cords[0]))


Next we need a gnuplot script to generate these graphs. This one will do:

set terminal svg
set title "Current TOR Circuits"
set xrange [-180:180]
set yrange [-90:90]
set yzeroaxis
set xtics geographic
set ytics geographic
set format x "%D %E"
set format y "%D %N"

FILES = system("ls -1 *.cor")
LABEL = system("ls -1 *.cor | sed -e 's/.cor//'")

plot 'world.dat' with lines notitle, for [i=1:words(FILES)] word(FILES,i) with lines title word(LABEL,i)

Note that if you didn’t follow the setup instructions earlier and download world.dat you’ll have a hard time with this. This script is based on a demo script from the gnuplot folks here.

We can combine the script and the python programs with a simple shell script.


rm *.cor
python3 main.py
gnuplot world.gnuplot