Skip to content

Commit

Permalink
* new filter options (-f and -F)
Browse files Browse the repository at this point in the history
* text based output (-t txt)
* fix for ASN lookups
* timestamps in output
  • Loading branch information
teunvink committed Oct 27, 2015
1 parent a8b915e commit 864bbd5
Showing 1 changed file with 145 additions and 22 deletions.
167 changes: 145 additions & 22 deletions scripts/ring-trace
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
Changelog:
v1.8 Minor fix for ASN lookup
Added -f and -F flag for filtering results on IP and ASN
Added plain text output format (-t txt)
Added timestamp to output
v1.7.3 Enforce output type PNG when setting transparency
Fix error message when unable to do API query
v1.7.2 Add flag -R for tranparent backgrounds (issue #59)
Expand Down Expand Up @@ -43,7 +47,7 @@
"""

import sys, os, threading, tempfile, time, random, re, operator, itertools
import urllib, urllib2, socket, Queue, types, simplejson
import urllib, urllib2, socket, Queue, types, simplejson, time

try:
from dns.resolver import query
Expand All @@ -58,7 +62,7 @@ except:
print "On Ubuntu: apt-get install python-dnspython python-argparse python-ipaddr"
sys.exit(1)

VERSION="1.7.1"
VERSION="1.8"
DEBUG=0
PASTEBIN="https://ring.nlnog.net/paste/"
API_NODES="https://ring.nlnog.net/api/1.0/nodes/active"
Expand Down Expand Up @@ -618,7 +622,7 @@ def traceroutes(hosts, destination, user, proto, udp, timeout, sshcommand):
for host in hosts:
r = threads[host].get_result()
if len(r) > 1:
result[host] = threads[host].get_result()
result[host] = threads[host].get_result()[2:]
else:
print "Host %s returned no trace." % host

Expand Down Expand Up @@ -735,8 +739,8 @@ digraph G {
fontcolor="#777777"
labeljust=r
bgcolor="%s"
label="ring-trace v%s to %s - https://ring.nlnog.net"
""" % ("transparent" if transparent else "white", VERSION, destination)
label="ring-trace v%s to %s - https://ring.nlnog.net\ngenerated at %s"
""" % ("transparent" if transparent else "white", VERSION, destination, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
count = 0
# using dicts to make nodes and edges unique
nodes = {}
Expand Down Expand Up @@ -854,6 +858,66 @@ digraph G {
return result


def make_text(traces, tracedata, outfile, pastebin, asn, dest):
"""Create a text based output of the traces.
"""

out = []
out.append("traceroutes to %s generated by ring-trace %s at %s\n" % (dest, VERSION,
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
if asn:
for trace in traces:
out.append("Node: %s\n%s" % (trace, "-" * (len(trace) + 6)))
prev = None
count = 1
for line in traces[trace]:
ip = line.split(" ")[1]
if tracedata.get(ip, "?")["asn"] != prev:
out.append("%d.|-- AS%s (%s)" % (count, tracedata[ip].get("asn", "?"), tracedata[ip].get("desc", "?")))
count += 1
prev = tracedata[ip]["asn"]

out.append("\n")
else:
for trace in traces:
out.append("Node: %s\n%s" % (trace, "-" * (len(trace) + 6)))
out.append("\n".join(traces[trace]))
out.append("\n")


if ns.outfile:
try:
fh = open(outfile, 'w')
fh.write("\n".join(out))
fh.close()
print "Wrote output to file '%s'. " % outfile
except:
print "Failed to save output to '%s'." % outfile

if pastebin:
debug("sending file to pastebin...")

postarray = [
("content", "\n".join(out)),
("mimetype", "text/html"),
("ttl", 604800)
]
postdata = urllib.urlencode(postarray)
try:
req = urllib2.Request(url=PASTEBIN, data=postdata)
result = urllib2.urlopen(req)
if result.url == PASTEBIN:
print "\nFailed to upload the output to the pastebin."
sys.exit(1)
else:
print "\nOutput uploaded to %s" % result.url
except:
print "failed to upload the image to the pastebin."
sys.exit(1)
else:
print "\n".join(out)


def make_image(dot, layout, outfile, outtype, pastebin, dest, traces):
"""Make the actual image by running 'dot' on the generated graphviz code
"""
Expand Down Expand Up @@ -925,6 +989,33 @@ def make_image(dot, layout, outfile, outtype, pastebin, dest, traces):
print "Created file: %s" % outfile


def filter(traces, tracedata, filter_ip, filter_asn):
"""Filter traced based on lists of IPs and ASNs.
"""

remove = {}
for trace in traces:
found = False
for line in traces[trace]:
trace_ip = line.split(" ")[1]
for ip in filter_ip:
if ip == trace_ip:
found = True
if tracedata[trace_ip]["asn"] in filter_asn:
found = True

if found:
debug("trace from %s matches IP and/or ASN filter." % trace)
else:
debug("Removing trace from host %s since it does not match IP and/or ASN filter." % trace)
remove[trace] = trace

for rem in remove:
del traces[rem]

debug("Removed %d traces based on IP and/or ASN filter criteria." % len(remove))
return (traces, tracedata)


def debug(msg):
"""Print a string only if debugging is enabled.
Expand All @@ -948,6 +1039,8 @@ def read_config():
"showcountry": False, # flag: -c, --show-country
"removeduplicate": False, # flag: -d, --remove-duplicate-edges
"excludehosts": [], # flag: -e, --exclude
"via-ip": [], # flag: -f, --filter-ip
"via-asn": [], # flag: -F, --filter-asn
"includehosts": [], # flag: -i, --include
"layout": "dot", # flag: -l, --layout
"usenodes": -1, # flag: -n, --random
Expand Down Expand Up @@ -982,15 +1075,17 @@ def read_config():
"topnodes": "int",
"resolve": "bool",
"sshcommand": "str",
"filetype": "enum:dot,gif,jpg,pdf,png,ps,svg",
"filetype": "enum:dot,gif,jpg,pdf,png,ps,svg,txt",
"sshtimeout": "int",
"transparent": "bool",
"user": "str",
"udp": "bool",
"verbose": "int",
"excludeixp": "bool",
"ipv4": "bool",
"ipv6": "bool"
"ipv6": "bool",
"via-ip": "str",
"via-asn": "str",
}

try:
Expand Down Expand Up @@ -1103,6 +1198,22 @@ if __name__ == "__main__":
default=conf['excludehosts'],
metavar="HOST"
)
parser.add_argument(
"-f", "--filter-ip",
action="append",
dest="filter_ip",
help="only use paths via given IP address",
default=conf['via-ip'],
metavar="IP"
)
parser.add_argument(
"-F", "--filter-asn",
action="append",
dest="filter_asn",
help="only use paths via given ASN address",
default=conf['via-asn'],
metavar="ASN"
)
parser.add_argument(
"-i", "--include",
action="append",
Expand Down Expand Up @@ -1170,7 +1281,7 @@ if __name__ == "__main__":
"-t", "--type",
action="store",
dest="outtype",
choices=("dot", "gif", "pdf", "png", "jpg", "ps", "svg"),
choices=("dot", "gif", "pdf", "png", "jpg", "ps", "svg", "txt"),
help="output filetype (jpg by default)",
default=conf["filetype"]
)
Expand Down Expand Up @@ -1299,6 +1410,13 @@ if __name__ == "__main__":
debug('analysing traces.')
tracedata = analyse(traces, ns.resolve and not ns.asn)

if len(ns.filter_ip) > 0 or len(ns.filter_asn) > 0:
debug("filtering traces.")
(traces, tracedata) = filter(traces, tracedata, ns.filter_ip, ns.filter_asn)
if len(traces) == 0:
print "No traces found which match the filter criteria."
sys.exit(1)

if (ns.hops > 0) and (ns.hops * 2 < len(traces)):
print "Picking top and bottom %d hosts based on hop count." % ns.hops
# sort based on trace length
Expand All @@ -1314,24 +1432,29 @@ if __name__ == "__main__":
for t in ts:
newtraces[t[0]] = traces[t[0]]
traces = newtraces


debug('generating color table.')
for c in range(0x333333, 0xdddddd, 0xaaaaaa/len(hosts)):
colors.append("#%x" % c)
if ns.outtype == "txt":
make_text(traces, tracedata, ns.outfile, ns.pastebin, ns.asn, destination)
else:
debug('generating output file.')
outfile = ns.outfile

if not outfile:
outfile = "trace-%s.%s" % (ns.destination, ns.outtype)

debug('generating graphs.')
dot = graph(traces, tracedata, destination, ns.resolve, ns.asn, ns.no_ixp,
ns.show_country, ns.remove_broken, ns.highlight_ixp, ns.remove_duplicate, ns.transparent)
debug('generating color table.')
for c in range(0x333333, 0xdddddd, 0xaaaaaa/len(hosts)):
colors.append("#%x" % c)

if ns.transparent and ns.outtype != "png":
print "Transparent background requested, setting file type to PNG."
ns.outtype = "png"
debug('generating graphs.')
dot = graph(traces, tracedata, destination, ns.resolve, ns.asn, ns.no_ixp,
ns.show_country, ns.remove_broken, ns.highlight_ixp, ns.remove_duplicate, ns.transparent)

debug('generating output file.')
outfile = ns.outfile
if ns.transparent and ns.outtype != "png":
print "Transparent background requested, setting file type to PNG."
ns.outtype = "png"

if not outfile:
outfile = "trace-%s.%s" % (ns.destination, ns.outtype)
make_image(dot, ns.layout, outfile, ns.outtype, ns.pastebin, destination, traces)
make_image(dot, ns.layout, outfile, ns.outtype, ns.pastebin, destination, traces)

print "Done in %.1f seconds." % (time.time() - start)

0 comments on commit 864bbd5

Please sign in to comment.