123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- #!/usr/bin/env python3
- import io
- import sys
- import json
- import time
- from netaddr import IPNetwork, IPSet
- from utils import read_json, write_json
- from registry import Inetnum, AutNum
- DBFILE = "/srv/http/dn42/tower-bird.json"
- REGISTRY = "/home/zorun/net.dn42.registry"
- HTMLOUT = "/srv/http/dn42/lastseen/index.html"
- # Where the data comes from
- ASN = 76142
- #TIMEFMT = '%F %H:%M'
- TIMEFMT = '%c UTC'
- DN42 = IPSet(["172.22.0.0/15"])
- def prefix_components(prefix):
- """Can be used as a key for sorting collections of prefixes"""
- ip = prefix.split("/")[0]
- cidr = prefix.split("/")[1]
- return tuple(int(part) for part in ip.split('.')) + (cidr,)
- class LastSeen():
- # Database of all previously seen prefixes, with dates
- prefixes_db = dict()
- # Prefixes currently announced
- current_prefixes = list()
- # From the registry
- autnums = dict()
- inetnums = dict()
- # Optimised version for inclusion testing
- networks = list()
- def __init__(self, db_filename):
- self.prefixes_db = read_json(db_filename)
- self.current_prefixes = [prefix for prefix in self.prefixes_db if self.prefixes_db[prefix]["current"]]
- # Registry
- self.inetnums = Inetnum(REGISTRY).data
- self.autnums = AutNum(REGISTRY).data
- # Precompute this
- self.networks = [IPNetwork(net) for net in self.inetnums]
- def stats(self):
- known = IPSet(self.prefixes_db)
- current = IPSet(self.current_prefixes)
- ratio_known = float(known.size) / float(DN42.size)
- ratio_current = float(current.size) / float(DN42.size)
- return {"known": ratio_known, "active": ratio_current}
- def whois(self, prefix):
- """Returns the name associated to a prefix, according to the registry. We
- look for the most specific prefix containing the argument.
- """
- prefix = IPNetwork(prefix)
- relevant_nets = [net for net in self.networks if prefix in net]
- if relevant_nets:
- final_net = str(max(relevant_nets, key=(lambda p: p.prefixlen)))
- if "netname" in self.inetnums[final_net]:
- netname = self.inetnums[final_net]["netname"][0]
- #print("{} -> {} [{}]".format(prefix, final_net, netname))
- return netname
- else:
- return None
- else:
- #print("No whois for {}".format(prefix))
- return None
- def as_name(self, asn):
- """Returns a tuple (as-name, descr), any of which can be the empty string,
- or None if the AS is not found in the registry.
- """
- if isinstance(asn, int):
- query = 'AS' + str(asn)
- elif isinstance(asn, str):
- asn = asn.upper()
- if not asn.startswith('AS'):
- query = 'AS' + asn
- else:
- return None
- if query in self.autnums:
- return (self.autnums[query].get('as-name', [""])[0], self.autnums[query].get('descr', [""])[0])
- def gen_html(self, out):
- stats = self.stats()
- out.write("<html><head><style type='text/css'>table { text-align: center} tr td.good { background-color: #00AA00 } tr td.bad { background-color: #AA0000 }</style>")
- out.write("<h1>Last seen in dn42 BGP (from AS {})</h1>".format(ASN))
- out.write("<p><a href='../tower-bird.json'>Raw JSON data</a>, <a href='../scripts/bgp-lastseen.py'>Python script</a></p>")
- out.write("<p><strong>Last update:</strong> {} (data collection started on 26th January 2014)</p>".format(time.strftime('%c UTC', time.gmtime())))
- out.write("<p><strong>Number of prefixes currently announced:</strong> {} (totalizing {:.2%} of dn42 address space)</p>".format(len(self.current_prefixes), stats['active']))
- out.write("<p><strong>Number of known prefixes since January 2014:</strong> {} (totalizing {:.2%} of dn42 address space)</p>".format(len(self.prefixes_db), stats['known']))
- out.write("<p><em>Data comes from BGP (AS {}) and is sampled every 10 minutes. \"UP\" means that the prefix is currently announced in dn42. \"DOWN\" means that the prefix has been announced at some point, but not anymore</em></p>".format(ASN))
- out.write("<p><table border=1>"
- "<tr>"
- "<th>Status</th>"
- "<th>Prefix</th>"
- "<th>Netname</th>"
- "<th>Origin</th>"
- "<th>Last seen</th>"
- # "<th>First seen</th>"
- "</tr>")
- for (prefix, data) in sorted(self.prefixes_db.items(), key=(lambda d : (d[1]["last_updated"],) + prefix_components(d[0]))):
- out.write("<tr>")
- good = data["current"]
- netname = self.whois(prefix)
- asn = data["origin_as"]
- as_string = 'AS' + str(asn)
- as_name = self.as_name(asn)
- if as_name:
- as_string += ' | ' + as_name[0]
- last_seen = data["last_updated"]
- # first_seen = time.strftime(TIMEFMT,
- # time.gmtime(int(data["first_seen"]))) \
- # if "first_seen" in data else "?"
- out.write("<td style='font-weight: bold' {}>{}</td>".format("class='good'" if good else "class='bad'",
- "UP" if good else "DOWN"))
- out.write("<td>{}</td>".format(prefix))
- out.write("<td>{}</td>".format(netname if netname else "?"))
- out.write("<td>{}</td>".format(as_string))
- out.write("<td>{}</td>".format(time.strftime(TIMEFMT, time.gmtime(int(last_seen)))))
- # out.write("<td>{}</td>".format(first_seen))
- out.write("</tr>")
- out.write("</table></p></html>")
- if __name__ == '__main__':
- l = LastSeen(DBFILE)
- buf = io.StringIO()
- l.gen_html(buf)
- with open(HTMLOUT, "w+") as htmlfile:
- htmlfile.write(buf.getvalue())
|