1
0
Fork 0
mirror of https://github.com/sileht/bird-lg.git synced 2024-11-05 07:34:42 +01:00
bird-lg/lg.py

361 lines
12 KiB
Python
Raw Normal View History

2011-12-16 11:02:24 +01:00
#!/usr/bin/python
2012-01-27 19:12:59 +01:00
# -*- coding: utf-8 -*-
# vim: ts=4
###
#
2012-02-08 22:05:28 +01:00
# Copyright (c) 2012 Mehdi Abaakouk
2012-01-27 19:12:59 +01:00
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#
###
2011-12-16 11:02:24 +01:00
2012-01-20 00:12:19 +01:00
import subprocess
import re
2012-01-26 17:12:18 +01:00
from urllib2 import urlopen
2012-01-27 00:15:46 +01:00
from urllib import quote, unquote
2012-05-27 14:30:31 +02:00
import json
2012-01-20 00:12:19 +01:00
from toolbox import mask_is_valid, ipv6_is_valid, ipv4_is_valid, resolve, save_cache_pickle, load_cache_pickle
2012-01-20 00:12:19 +01:00
2012-05-27 14:30:31 +02:00
import pydot
from flask import Flask, render_template, jsonify, redirect, session, request, abort, Response
2012-01-20 00:12:19 +01:00
2011-12-16 11:02:24 +01:00
app = Flask(__name__)
2012-01-20 00:12:19 +01:00
app.config.from_pyfile('lg.cfg')
2011-12-16 11:02:24 +01:00
2012-01-20 00:12:19 +01:00
def add_links(text):
2012-02-08 22:05:28 +01:00
"""Browser a string and replace ipv4, ipv6, as number, with a whois link """
2012-01-20 00:12:19 +01:00
if type(text) in [ str, unicode ]:
text = text.split("\n")
2011-12-16 13:19:48 +01:00
2012-01-20 00:12:19 +01:00
ret_text = []
for line in text:
2012-01-26 23:20:59 +01:00
# Some heuristic to create link
2012-01-20 00:12:19 +01:00
if line.strip().startswith("BGP.as_path:") or \
line.strip().startswith("Neighbor AS:") :
ret_text.append(re.sub(r'(\d+)',r'<a href="/whois/\1" class="whois">\1</a>',line))
else:
2012-02-08 22:05:28 +01:00
line = re.sub(r'([a-zA-Z0-9\-]*\.([a-zA-Z]{2,3}){1,2})(\s|$)', r'<a href="/whois/\1" class="whois">\1</a>\3',line)
2012-01-20 00:12:19 +01:00
line = re.sub(r'AS(\d+)', r'<a href="/whois/\1" class="whois">AS\1</a>',line)
line = re.sub(r'(\d+\.\d+\.\d+\.\d+)', r'<a href="/whois/\1" class="whois">\1</a>',line)
2012-01-26 23:20:59 +01:00
hosts = "/".join(request.path.split("/")[2:])
2012-01-26 23:24:15 +01:00
line = re.sub(r'\[(\w+)\s+((|\d\d\d\d-\d\d-\d\d\s)(|\d\d:)\d\d:\d\d|\w\w\w\d\d)', r'[<a href="/detail/%s?q=\1">\1</a> \2' % hosts, line)
2012-01-27 00:17:11 +01:00
line = re.sub(r'(^|\s+)(([a-f\d]{0,4}:){3,10}[a-f\d]{0,4})', r'\1<a href="/whois/\2" class="whois">\2</a>',line , re.I)
2012-01-20 00:12:19 +01:00
ret_text.append(line)
return "\n".join(ret_text)
2012-05-27 14:30:31 +02:00
def extract_paths(text):
paths = []
for line in text:
line = line.strip()
if line.startswith("BGP.as_path:"):
paths.append(line.replace("BGP.as_path:", "").strip().split(" "))
return paths
2012-02-05 14:30:32 +01:00
def set_session(request_type, hosts, proto, request_args):
2012-02-08 22:05:28 +01:00
""" Store all data from user in the user session """
2012-01-22 23:13:11 +01:00
session.permanent = True
2012-01-20 00:12:19 +01:00
session.update( {
2012-02-05 14:30:32 +01:00
"request_type": request_type,
2012-01-20 00:12:19 +01:00
"hosts": hosts,
"proto": proto,
"request_args": request_args,
})
2012-02-05 14:30:32 +01:00
history = session.get("history", [])
# erase old format history
if type(history) != type(list()): history = []
t = (hosts, proto, request_type, request_args)
if t in history:
del history[history.index(t)]
history.insert(0, t)
session["history"] = history[:20]
2012-01-20 00:12:19 +01:00
2012-05-27 14:30:31 +02:00
def whois_command(query):
return subprocess.Popen( [ 'whois', query], stdout=subprocess.PIPE).communicate()[0].decode('utf-8', 'ignore')
2012-01-26 17:12:18 +01:00
def bird_command(host, proto, query):
2012-02-08 22:05:28 +01:00
"""Alias to bird_proxy for bird service"""
2012-01-26 17:12:18 +01:00
return bird_proxy(host, proto, "bird", query)
def bird_proxy(host, proto, service, query):
2012-02-08 22:05:28 +01:00
"""Retreive data of a service from a running lg-proxy on a remote node
First and second arguments are the node and the port of the running lg-proxy
Third argument is the service, can be "traceroute" or "bird"
Last argument, the query to pass to the service
return tuple with the success of the command and the returned data
"""
2012-01-26 17:12:18 +01:00
path = ""
if proto == "ipv6": path = service + "6"
elif proto == "ipv4": path = service
port = app.config["PROXY"].get(host,"")
if not port or not path:
2012-01-20 00:12:19 +01:00
return False, "Host/Proto not allowed"
2012-01-16 14:13:49 +01:00
else:
2012-01-26 17:12:18 +01:00
url = "http://%s.%s:%d/%s?q=%s" % (host, app.config["DOMAIN"], port, path, quote(query))
try:
f = urlopen(url)
resultat = f.read()
status = True # retreive remote status
except IOError:
resultat = "Failed retreive url: %s" % url
status = False
return status, resultat
2012-01-20 00:12:19 +01:00
2012-02-05 14:30:32 +01:00
@app.context_processor
def inject_commands():
commands = [
("traceroute","traceroute ..."),
("summary","show protocols"),
("detail","show protocols ... all"),
("prefix","show route for ..."),
("prefix_detail","show route for ... all"),
("where","show route where net ~ [ ... ]"),
("where_detail","show route where net ~ [ ... ] all"),
("adv","show route ..."),
]
commands_dict = {}
for id, text in commands:
commands_dict[id] = text
return dict(commands=commands, commands_dict=commands_dict)
2012-01-20 00:12:19 +01:00
@app.context_processor
def inject_all_host():
2012-01-26 17:12:18 +01:00
return dict(all_hosts="+".join(app.config["PROXY"].keys()))
2012-01-16 14:13:49 +01:00
2011-12-16 11:02:24 +01:00
@app.route("/")
def hello():
2012-01-26 17:12:18 +01:00
return redirect("/summary/%s/ipv4" % "+".join(app.config["PROXY"].keys()) )
2011-12-16 11:02:24 +01:00
2012-01-20 00:12:19 +01:00
def error_page(text):
2012-02-05 14:30:32 +01:00
return render_template('error.html', error = text ), 500
@app.errorhandler(400)
2012-02-08 22:05:28 +01:00
def incorrect_request(e):
2012-02-05 14:30:32 +01:00
return render_template('error.html', warning="The server could not understand the request"), 400
2011-12-16 19:05:16 +01:00
2012-02-05 14:30:32 +01:00
@app.errorhandler(404)
def page_not_found(e):
return render_template('error.html', warning="The requested URL was not found on the server."), 404
2012-01-16 14:13:49 +01:00
2012-01-20 00:12:19 +01:00
@app.route("/whois/<query>")
def whois(query):
2012-02-05 14:30:32 +01:00
if not query.strip(): abort(400)
2012-01-20 00:12:19 +01:00
try:
asnum = int(query)
query = "as%d"%asnum
except:
m = re.match(r"[\w\d-]*\.(?P<domain>[\d\w-]+\.[\d\w-]+)$", query)
if m: query = query.groupdict()["domain"]
2012-05-27 14:30:31 +02:00
output = whois_command(query).replace("\n","<br>")
2012-01-20 00:12:19 +01:00
return jsonify(output=output, title=query)
SUMMARY_UNWANTED_PROTOS = ["Kernel", "Static", "Device"]
SUMMARY_RE_MATCH = r"(?P<name>[\w_]+)\s+(?P<proto>\w+)\s+(?P<table>\w+)\s+(?P<state>\w+)\s+(?P<since>((|\d\d\d\d-\d\d-\d\d\s)(|\d\d:)\d\d:\d\d|\w\w\w\d\d))($|\s+(?P<info>.*))"
2012-01-20 00:12:19 +01:00
@app.route("/summary/<hosts>")
@app.route("/summary/<hosts>/<proto>")
def summary(hosts, proto="ipv4"):
set_session("summary", hosts, proto, "")
command = "show protocols"
summary = {}
2012-02-05 14:30:32 +01:00
error = []
2012-01-20 00:12:19 +01:00
for host in hosts.split("+"):
ret, res = bird_command(host, proto, command)
res = res.split("\n")
if len(res) > 1: #if ret:
2012-01-20 00:12:19 +01:00
data = []
for line in res[1:]:
line = line.strip()
if line and ( line.split() + [""] )[1] not in SUMMARY_UNWANTED_PROTOS:
m = re.match(SUMMARY_RE_MATCH ,line)
if m:
data.append(m.groupdict())
else:
app.logger.warning("couldn't parse: %s" , line)
summary[host] = data
2011-12-16 11:02:24 +01:00
else:
2012-02-05 14:30:32 +01:00
error.append("%s: bird command failed with error, %s" % (host,"\n".join(res)))
2012-01-20 00:12:19 +01:00
2012-02-05 14:30:32 +01:00
return render_template('summary.html', summary=summary, command=command, error="<br>".join(error))
2012-01-20 00:12:19 +01:00
@app.route("/detail/<hosts>/<proto>")
def detail(hosts, proto):
name = request.args.get('q', '')
2012-02-05 14:30:32 +01:00
if not name.strip(): abort(400)
2012-01-27 11:32:50 +01:00
2012-01-20 00:12:19 +01:00
set_session("detail", hosts, proto, name)
command = "show protocols all %s" % name
detail = {}
2012-02-05 14:30:32 +01:00
error = []
2012-01-20 00:12:19 +01:00
for host in hosts.split("+"):
ret, res = bird_command(host, proto, command)
res = res.split("\n")
if len(res) > 1 : #if ret:
2012-01-20 00:12:19 +01:00
detail[host] = { "status": res[1], "description": add_links(res[2:]) }
2011-12-16 19:05:16 +01:00
else:
2012-02-05 14:30:32 +01:00
error.append("%s: bird command failed with error, %s" % (host,"\n".join(res)))
2012-01-20 00:12:19 +01:00
2012-02-05 14:30:32 +01:00
return render_template('detail.html', detail=detail, command=command, error="<br>".join(error))
2011-12-16 13:19:48 +01:00
2012-01-22 23:06:52 +01:00
@app.route("/traceroute/<hosts>/<proto>")
def traceroute(hosts, proto):
q = request.args.get('q', '')
2012-02-05 14:30:32 +01:00
if not q.strip(): abort(400)
2012-01-22 23:06:52 +01:00
set_session("traceroute", hosts, proto, q)
2012-02-05 14:30:32 +01:00
if proto == "ipv6" and not ipv6_is_valid(q):
try: q = resolve(q, "AAAA")
except: return error_page("%s is unresolvable or invalid for %s" % (q, proto))
if proto == "ipv4" and not ipv4_is_valid(q):
try: q = resolve(q, "A")
except: return error_page("%s is unresolvable or invalid for %s" % (q, proto))
2012-01-22 23:06:52 +01:00
infos = {}
for host in hosts.split("+"):
2012-01-26 17:12:18 +01:00
status, resultat = bird_proxy(host, proto, "traceroute", q)
infos[host] = add_links(resultat)
2012-01-22 23:06:52 +01:00
return render_template('traceroute.html', infos=infos)
2012-01-27 00:15:46 +01:00
@app.route("/adv/<hosts>/<proto>")
def show_route_filter(hosts, proto):
2012-01-27 00:22:30 +01:00
return show_route("adv", hosts, proto)
2012-01-27 00:15:46 +01:00
2012-01-20 00:12:19 +01:00
@app.route("/where/<hosts>/<proto>")
def show_route_where(hosts, proto):
return show_route("where", hosts, proto)
2011-12-16 11:02:24 +01:00
2012-01-20 00:12:19 +01:00
@app.route("/where_detail/<hosts>/<proto>")
def show_route_where_detail(hosts, proto):
return show_route("where_detail", hosts, proto)
2011-12-16 11:02:24 +01:00
2012-01-20 00:12:19 +01:00
@app.route("/prefix/<hosts>/<proto>")
def show_route_for(hosts, proto):
return show_route("prefix", hosts, proto)
2011-12-16 11:02:24 +01:00
2012-01-20 00:12:19 +01:00
@app.route("/prefix_detail/<hosts>/<proto>")
def show_route_for_detail(hosts, proto):
return show_route("prefix_detail", hosts, proto)
2011-12-16 11:02:24 +01:00
ASNAME_CACHE_FILE = "/tmp/asname_cache.pickle"
ASNAME_CACHE = load_cache_pickle(ASNAME_CACHE_FILE, {})
2012-05-27 14:30:31 +02:00
def get_as_name(_as):
2012-05-27 14:52:51 +02:00
if not ASNAME_CACHE.has_key(_as):
2012-05-27 14:30:31 +02:00
whois_answer = whois_command("as%s" % _as)
2012-05-28 18:19:19 +02:00
as_name = re.search('(as-name|ASName): (.*)', whois_answer)
2012-05-27 14:30:31 +02:00
if as_name:
2012-05-28 18:19:19 +02:00
ASNAME_CACHE[_as] = as_name.group(2).strip()
2012-05-27 14:30:31 +02:00
else:
ASNAME_CACHE[_as] = _as
save_cache_pickle(ASNAME_CACHE_FILE, ASNAME_CACHE)
2012-05-27 14:30:31 +02:00
if ASNAME_CACHE[_as] == _as:
return "AS%s" % _as
else:
return "AS%s\r%s" % (_as, ASNAME_CACHE[_as])
@app.route("/bgpmap/<data>")
def show_bgpmap(data):
data = json.loads(unquote(data))
graph = pydot.Dot('BGPMAP', graph_type='digraph')
nodes = {}
edges = {}
for host, asmaps in data.iteritems():
nodes[host] = pydot.Node(host, shape="box", style="filled", fillcolor="#F5A9A9")
graph.add_node(nodes[host])
for host, asmaps in data.iteritems():
first = True
for asmap in asmaps:
previous_as = host
for _as in asmap:
_as = get_as_name(_as)
if _as == previous_as:
continue
if not nodes.has_key(_as):
2012-05-27 14:52:51 +02:00
nodes[_as] = pydot.Node(_as, label=_as, style="filled", fillcolor=(first and "#F5A9A9" or "white"))
2012-05-27 14:30:31 +02:00
graph.add_node(nodes[_as])
edge_tuple = (nodes[previous_as], nodes[_as])
if not edges.has_key(edge_tuple):
edge = pydot.Edge(*edge_tuple)
graph.add_edge(edge)
edges[edge_tuple] = edge
if edge.get_color() != "red" and first:
edge.set_color("red")
previous_as = _as
first = False
#return Response("<pre>" + graph.create_dot() + "</pre>")
return Response(graph.create_png(), mimetype='image/png')
2012-02-05 14:30:32 +01:00
def show_route(request_type, hosts, proto):
2012-01-27 00:15:46 +01:00
expression = unquote(request.args.get('q', ''))
2012-02-05 14:30:32 +01:00
if not expression.strip(): abort(400)
set_session(request_type, hosts, proto, expression)
2011-12-16 11:02:24 +01:00
2012-02-05 14:30:32 +01:00
all = (request_type.endswith("detail") and " all" or "" )
2011-12-16 11:02:24 +01:00
2012-02-05 14:30:32 +01:00
if request_type.startswith("adv"):
2012-01-27 00:15:46 +01:00
command = "show route " + expression
2012-02-05 14:30:32 +01:00
elif request_type.startswith("where"):
2012-01-20 00:12:19 +01:00
command = "show route where net ~ [ " + expression + " ]" + all
else:
mask = ""
if len(expression.split("/")) > 1:
expression, mask = (expression.split("/"))
if not mask and proto == "ipv4" : mask = "32"
if not mask and proto == "ipv6" : mask = "128"
if not mask_is_valid(mask):
2012-02-05 14:30:32 +01:00
return error_page("mask %s is invalid" % mask)
2012-01-20 00:12:19 +01:00
if proto == "ipv6" and not ipv6_is_valid(expression):
try: expression = resolve(expression, "AAAA")
2012-02-05 14:30:32 +01:00
except: return error_page("%s is unresolvable or invalid for %s" % (expression, proto))
2012-01-20 00:12:19 +01:00
if proto == "ipv4" and not ipv4_is_valid(expression):
try: expression = resolve(expression, "A")
2012-02-05 14:30:32 +01:00
except: return error_page("%s is unresolvable or invalid for %s" % (expression, proto))
2012-01-20 00:12:19 +01:00
if mask: expression += "/" + mask
command = "show route for " + expression + all
detail = {}
2012-02-05 14:30:32 +01:00
error = []
2012-05-27 14:30:31 +02:00
bgpmap = {}
2012-01-20 00:12:19 +01:00
for host in hosts.split("+"):
ret, res = bird_command(host, proto, command)
res = res.split("\n")
if len(res) > 1 : #if ret:
2012-01-20 00:12:19 +01:00
detail[host] = add_links(res)
2012-05-27 14:30:31 +02:00
bgpmap[host] = extract_paths(res)
2012-01-20 00:12:19 +01:00
else:
2012-02-05 14:30:32 +01:00
error.append("%s: bird command failed with error, %s" % (host,"\n".join(res)))
2012-01-20 00:12:19 +01:00
2012-05-27 14:30:31 +02:00
return render_template('route.html', detail=detail, command=command, expression=expression, bgpmap=json.dumps(bgpmap), error="<br>".join(error) )
2011-12-16 11:02:24 +01:00
2012-01-20 00:12:19 +01:00
app.secret_key = app.config["SESSION_KEY"]
2012-01-16 14:13:49 +01:00
app.debug = True
2011-12-16 11:02:24 +01:00
if __name__ == "__main__":
2012-01-16 14:13:49 +01:00
app.run("0.0.0.0")