2008-05-29 14:32 UTC TraceViz: Visualizing traceroute output with graphivz

Update: Belorussian translation.

At Edgeio we had a fairly complicated network setup, and at one point I quickly hacked together a Ruby script to merge the paths generated by multiple traceroute runs together into directed graphs, showing the routing from a few selected host in our environment to all our other hosts. I generated dot-files suitable for Graphviz from it.

It was a helpful way of looking for weird inconsistencies in routing, in particular between our two locations.

Unfortunately, when Edgeio closed down I think the script was lost, and in any case if it isn't I wouldn't be able to get permission to release it without more hassle than it'd take me to recreate it from scratch. So recreate it from scratch is exactly what I did.

Here's an example of a traceroute from www.hokstad.com to www.gmail.com, docs.google.com and www.google.com (scaled down):

(Gradients and shadows courtesy of my XSL transform to make graphviz output prettier)

There's a couple of caveats: I just strip out failed probes, and I don't try to reconcile the names of the endpoints (which I preferred to include for readability) with the IP addresses of the trace, so the first/last grey nodes before the named/blue nodes may be redundant.

This script by default runs traceroute 3 times for each target, and that's the reason why there are more possible paths than endpoints, and it illustrates failover and/or load balancing mostly, but can also be affected by fluctuations in dynamic routing. It's usually fairly stable, and in fact at Edgeio I found several network problems by re-running the script when something was up and looking at how the routing had changed. 3 runs seemed sufficient for my use, but for large networks adding more may give a better picture of the routing.

The Ruby script

You can find the full script here, but here are the guts:

First I defined a convenience method to run traceroute and capture the output. This is intended for a POSIX OS (Linux / Unix / BSD's), but mainly requires a working traceroute where the output is a number of lines starting with a hop count and then the ip address. TRACEROUTE must be set to a valid traceroute command.

TRACEROUTE=`which traceroute`.chomp

def traceroute host `#{TRACEROUTE} -n #{host}` end

The TraceViz class does the gruntwork. @edges contains the edges of the graph, in other words which pairs of ip addresses represent a hop further in the network. @nodes contains a set of the ip addresses found. @targets contains the hostnames of the start and end-points - it's used only to style them differently:

class TraceViz
  def initialize(times,timeout)
    @times,@timeout = times,timeout
    @edges = Set.new
    @nodes = Set.new
    @targets = Set.new
    @this_host = Socket.gethostname
    @targets << @this_host
  end

The #trace method executes the traceroutes, and enforces a timeout:

  def trace host
    @times.times do |i|
      STDERR.puts "Trace ##{i+1} for #{host}"
      Timeout::timeout(@timeout) do
        process_trace(host,traceroute(host))
      end rescue nil
    end
  end

#process_trace handles the parsing of the trace, by splitting the output into lines, extracting the IP addresses, and then adding each of them to @nodes, and adding each pair to @edges. I don't care if we've seen them before, since I use Set's so the previous (identical) nodes/edges will just overwrite the same values:

  def process_trace host,trace
    @targets << host
    trace = [@this_host] + trace.collect do |line|
      line=line.split
      line[0].to_i > 0 && line[1] != "*" ? line[1] : nil
    end.compact
    trace << host
    trace.each {|h| @nodes << h }
    trace.each_cons(2) {|h1,h2| @edges << [h1,h2] }
  end

Finally #to_dot generates a graphviz compatible directed graph:

 def to_dot
    res = "digraph G {"
    @edges.each { |h1,h2| res << "   \"#{h1}\"->\"#{h2}\"\n" }
    @nodes.each do |n|
      color = @targets.member?(n) ? "lightblue" : "lightgrey"
      res << "  \"#{n}\" [style=filled fillcolor=#{color}]\n"
    end
    res << "}"
  end
end

Running it and generating the image

First run the script to generate the dot-files, and then generate an SVG file from it:

ruby traceviz.rb www.google.com www.gmail.com docs.google.com >trace.dot
dot -Tsvg trace.dot >trace.svg

Optionally, process the script with my XSL transform to make it prettier (adding the gradients from above etc) - I'm using xsltproc from libxslt:

xsltproc notugly.xsl trace.svg >trace-notugly.svg

Then I used "rsvg" from librsvg2 to turn it into a PNG:

rsvg trace-notugly.svg traceviz.png

Of course these steps are easily enough wrapped into a script.



Comments - Newest first

2010-03-23 22:08 UTC
Hi Vidar,

I'm a relative newcomer to coding and have never worked with Pearl before.

Would it be relatively easy to modify this program to read previously collected traceroute data?

I have several .txt log files which have been collecting traceroute information 7 times every four hours for a whole week.

I was hoping I could use this method to display the data.

2010-03-09 17:01 UTC
Vidar Hokstad
Mike: No, it gathers the traceroute itself by executing "traceroute" and parsing the output.

2010-03-04 14:44 UTC
this is the vizualization of a Tracerout gained by Scappy? it is a wondeful tool, since it has unlimited usage, hence you can use it for whatever purpose. However the most best thing about it is that you can almost discover new way of using Scapy and utilize it.
2009-12-14 10:28 UTC
Hey man, don't know if you ever came across it, but I did something similar in 2004 with Python. I wrote a blog post about it here:

http://oubiwann.blogspot.com/2005/01/traceroute-games.html

I've recently put the original 2004 image files up on flickr as well as some new ones that show the results off better. The old source code is available here, and the new (updated) code is here

2009-06-05 17:23 UTC
I don't know what you're on about Trek, it's working just fine for me in 1.8.

And yes, thanks for the wonderful graphviz example!

2009-03-25 15:42 UTC
Vidar Hokstad
Presumably you mean it won't work with 1.9? I haven't upgraded to 1.9 yet, so all my testing has been done on 1.8.5 and 1.8.6...

Anyway, I'm about to upgrade, so I'll post an update if I find any problems.

(.. and Javascript may be evil, but I haven't had a _single_ spam comment get through since I added that check, out of several hundred attempted posts a day, so the tradeoff is worth it...)

2009-03-17 17:56 UTC
Trek
Wonderful script! Thanks! It do not works with ruby 1.8, but with the 1.9 version.

P.S.: Javascript is evil and disabled by Tor anonymous users.

About me

E-mail: vidar@hokstad.com Skype: vhokstad
Twitter: vhokstad
View my LinkedIn profile.

I was born April 21st, 1975, in Oslo, Norway. Since 2000 I've been living in London, UK. I'm married and we just had our first child, Tristan Ikemefuna Hokstad.

I'm working for Aardvark Media as Director of Technology. I'm also currently on the board of SpatialQ, a startup in the GIS space, and an advisor to Skoach, a startup doing a time management app for people with ADD.

Twitter Updates

    follow me on Twitter