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

725 lines
24 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
2017-08-16 22:22:42 +02:00
import base64
2016-03-02 09:46:51 +01:00
from datetime import datetime
2012-01-20 00:12:19 +01:00
import subprocess
2012-08-21 11:12:58 +02:00
import logging
from logging.handlers import TimedRotatingFileHandler
2012-01-20 00:12:19 +01:00
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-05-29 17:05:12 +02:00
import random
2019-12-28 11:32:00 +01:00
import argparse
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, unescape
#from xml.sax.saxutils import escape
2012-10-16 08:00:10 +02:00
2012-01-20 00:12:19 +01:00
2012-05-27 14:30:31 +02:00
import pydot
from flask import Flask, render_template, render_template_string, jsonify, redirect, session, request, abort, Response, Markup
2019-12-28 11:32:00 +01:00
parser = argparse.ArgumentParser()
parser.add_argument('-c', dest='config_file', help='path to config file', default='lg.cfg')
args = parser.parse_args()
2012-01-20 00:12:19 +01:00
2011-12-16 11:02:24 +01:00
app = Flask(__name__)
2019-12-28 11:32:00 +01:00
app.config.from_pyfile(args.config_file)
2012-08-21 11:12:58 +02:00
app.secret_key = app.config["SESSION_KEY"]
app.debug = app.config["DEBUG"]
file_handler = TimedRotatingFileHandler(filename=app.config["LOG_FILE"], when="midnight", backupCount=app.config.get("LOG_NUM_DAYS", 0))
2012-08-21 11:12:58 +02:00
file_handler.setLevel(getattr(logging, app.config["LOG_LEVEL"].upper()))
app.logger.addHandler(file_handler)
2011-12-16 11:02:24 +01:00
2012-05-29 16:53:19 +02:00
def get_asn_from_as(n):
asn_zone = app.config.get("ASN_ZONE", "asn.cymru.com")
try:
data = resolve("AS%s.%s" % (n, asn_zone) ,"TXT").replace("'","").replace('"','')
except:
return " "*5
return [ field.strip() for field in data.split("|") ]
2012-01-20 00:12:19 +01:00
def add_links(text):
2012-05-29 16:53:19 +02:00
"""Browser a string and replace ipv4, ipv6, as number, with a
whois link """
if type(text) in [str, unicode]:
text = text.split("\n")
ret_text = []
for line in text:
# Some heuristic to create link
if line.strip().startswith("BGP.as_path:") or \
line.strip().startswith("Neighbor AS:"):
ret_text.append(re.sub(r'(\d+)', r'<a href="/whois?q=\1" class="whois">\1</a>', line))
2012-05-29 16:53:19 +02:00
else:
line = re.sub(r'([a-zA-Z0-9\-]*\.([a-zA-Z]{2,3}){1,2})(\s|$)', r'<a href="/whois?q=\1" class="whois">\1</a>\3', line)
line = re.sub(r'(?<=\[)AS(\d+)', r'<a href="/whois?q=\1" class="whois">AS\1</a>', line)
line = re.sub(r'(\d+\.\d+\.\d+\.\d+)', r'<a href="/whois?q=\1" class="whois">\1</a>', line)
2012-10-16 08:07:30 +02:00
if len(request.path) >= 2:
hosts = "/".join(request.path.split("/")[2:])
else:
hosts = "/"
2012-05-29 16:53:19 +02: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)
line = re.sub(r'(^|\s+)(([a-f\d]{0,4}:){3,10}[a-f\d]{0,4})', r'\1<a href="/whois?q=\2" class="whois">\2</a>', line, re.I)
2012-05-29 16:53:19 +02:00
ret_text.append(line)
return "\n".join(ret_text)
2012-05-27 14:30:31 +02:00
2012-02-05 14:30:32 +01:00
def set_session(request_type, hosts, proto, request_args):
2012-05-29 16:53:19 +02:00
""" Store all data from user in the user session """
session.permanent = True
session.update({
"request_type": request_type,
"hosts": hosts,
"proto": proto,
"request_args": request_args,
})
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):
server = []
if app.config.get("WHOIS_SERVER", ""):
server = [ "-h", app.config.get("WHOIS_SERVER") ]
return subprocess.Popen(['whois'] + server + [query], stdout=subprocess.PIPE).communicate()[0].decode('utf-8', 'ignore')
2012-05-29 16:53:19 +02:00
2012-05-27 14:30:31 +02:00
2012-01-26 17:12:18 +01:00
def bird_command(host, proto, query):
2012-05-29 16:53:19 +02:00
"""Alias to bird_proxy for bird service"""
return bird_proxy(host, proto, "bird", query)
2012-01-26 17:12:18 +01:00
def bird_proxy(host, proto, service, query):
"""Retreive data of a service from a running lgproxy on a remote node
2012-05-29 16:53:19 +02:00
First and second arguments are the node and the port of the running lgproxy
2012-05-29 16:53:19 +02:00
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
"""
path = ""
if proto == "ipv6":
path = service + "6"
elif proto == "ipv4":
path = service
port = app.config["PROXY"].get(host, "")
2012-10-17 16:33:02 +02:00
if not port:
return False, 'Host "%s" invalid' % host
elif not path:
return False, 'Proto "%s" invalid' % proto
2020-06-15 09:57:59 +02:00
url = "http://%s" % (host)
if "DOMAIN" in app.config:
url = "%s.%s" % (url, app.config["DOMAIN"])
url = "%s:%d/%s?" % (url, port, path)
2020-06-15 13:27:26 +02:00
if "SHARED_SECRET" in app.config:
url = "%ssecret=%s&" % (url, app.config["SHARED_SECRET"])
2020-06-15 09:57:59 +02:00
url = "%sq=%s" % (url, quote(query))
try:
f = urlopen(url)
resultat = f.read()
status = True # retreive remote status
except IOError:
resultat = "Failed to retrieve URL for host %s" % host
app.logger.warning("Failed to retrieve URL for host %s: %s", host, url)
2020-06-15 09:57:59 +02:00
status = False
return status, resultat
2012-05-29 16:53:19 +02:00
2012-02-05 14:30:32 +01:00
@app.context_processor
def inject_commands():
2012-05-29 16:53:19 +02:00
commands = [
("traceroute", "traceroute ..."),
("summary", "show protocols"),
("detail", "show protocols ... all"),
("prefix", "show route for ..."),
("prefix_detail", "show route for ... all"),
("prefix_bgpmap", "show route for ... (bgpmap)"),
("where", "show route where net ~ [ ... ]"),
("where_detail", "show route where net ~ [ ... ] all"),
("where_bgpmap", "show route where net ~ [ ... ] (bgpmap)"),
("adv", "show route ..."),
("adv_bgpmap", "show route ... (bgpmap)"),
]
commands = [i for i in commands if i[0] not in app.config.get("BLACKLIST_COMMANDS", [])]
2012-05-29 16:53:19 +02:00
commands_dict = {}
for id, text in commands:
commands_dict[id] = text
return dict(commands=commands, commands_dict=commands_dict)
2012-02-05 14:30:32 +01:00
2012-01-20 00:12:19 +01:00
@app.context_processor
def inject_all_host():
2012-05-29 16:53:19 +02: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():
if app.config.get("DEFAULT_TEMPLATE", False):
first_command = next(iter(inject_commands()['commands_dict']))
set_session(first_command, "+".join(app.config["PROXY"].keys()), "ipv4", "")
with open(app.config.get("DEFAULT_TEMPLATE"), 'r') as filehandle:
filecontent = filehandle.read()
return render_template_string(filecontent)
2012-05-29 16:53:19 +02: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):
return render_template('error.html', errors=[text]), 500
2012-05-29 16:53:19 +02:00
2012-02-05 14:30:32 +01:00
@app.errorhandler(400)
2012-02-08 22:05:28 +01:00
def incorrect_request(e):
return render_template('error.html', warnings=["The server could not understand the request"]), 400
2012-05-29 16:53:19 +02:00
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', warnings=["The requested URL was not found on the server."]), 404
2012-05-29 16:53:19 +02:00
def get_query():
q = unquote(request.args.get('q', '').strip())
return q
2012-01-16 14:13:49 +01:00
@app.route("/whois")
def whois():
query = get_query()
if not query:
2012-05-29 16:53:19 +02:00
abort(400)
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"]
output = whois_command(query)
2012-05-29 16:53:19 +02:00
return jsonify(output=output, title=query)
2012-01-20 00:12:19 +01:00
SUMMARY_UNWANTED_PROTOS = ["Kernel", "Static", "Device", "Direct"]
2012-05-29 16:53:19 +02:00
2012-01-20 00:12:19 +01:00
@app.route("/summary/<hosts>")
@app.route("/summary/<hosts>/<proto>")
def summary(hosts, proto="ipv4"):
if 'summary' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
2012-10-16 08:00:10 +02:00
2012-05-29 16:53:19 +02:00
set_session("summary", hosts, proto, "")
command = "show protocols"
summary = {}
errors = []
2012-05-29 16:53:19 +02:00
for host in hosts.split("+"):
ret, res = bird_command(host, proto, command)
res = res.split("\n")
2012-10-17 16:33:02 +02:00
if ret is False:
errors.append("%s" % res)
continue
if len(res) <= 1:
errors.append("%s: bird command failed with error, %s" % (host, "\n".join(res)))
2012-10-17 16:33:02 +02:00
continue
data = []
for line in res[1:]:
line = line.strip()
if line and (line.split() + [""])[1] not in SUMMARY_UNWANTED_PROTOS:
split = line.split()
if len(split) >= 5:
props = dict()
props["name"] = split[0]
props["proto"] = split[1]
props["table"] = split[2]
props["state"] = split[3]
props["since"] = split[4]
props["info"] = ' '.join(split[5:]) if len(split) > 5 else ""
data.append(props)
2012-10-17 16:33:02 +02:00
else:
app.logger.warning("couldn't parse: %s", line)
summary[host] = data
2012-05-29 16:53:19 +02:00
return render_template('summary.html', summary=summary, command=command, errors=errors)
2012-05-29 16:53:19 +02:00
2012-01-20 00:12:19 +01:00
@app.route("/detail/<hosts>/<proto>")
def detail(hosts, proto):
if 'detail' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
name = get_query()
2012-10-16 08:00:10 +02:00
2012-05-29 16:53:19 +02:00
if not name:
abort(400)
set_session("detail", hosts, proto, name)
command = "show protocols all %s" % name
detail = {}
errors = []
2012-05-29 16:53:19 +02:00
for host in hosts.split("+"):
ret, res = bird_command(host, proto, command)
res = res.split("\n")
2012-10-17 16:33:02 +02:00
if ret is False:
errors.append("%s" % res)
continue
if len(res) <= 1:
errors.append("%s: bird command failed with error, %s" % (host, "\n".join(res)))
2012-10-17 16:33:02 +02:00
continue
detail[host] = {"status": res[1], "description": add_links(res[2:])}
2012-05-29 16:53:19 +02:00
return render_template('detail.html', detail=detail, command=command, errors=errors)
2012-05-29 16:53:19 +02:00
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):
if 'traceroute' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
q = get_query()
2012-10-16 08:00:10 +02:00
2012-05-29 16:53:19 +02:00
if not q:
abort(400)
set_session("traceroute", hosts, proto, q)
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-10-17 16:33:02 +02:00
errors = []
2012-05-29 16:53:19 +02:00
infos = {}
for host in hosts.split("+"):
status, resultat = bird_proxy(host, proto, "traceroute", q)
2012-10-17 16:33:02 +02:00
if status is False:
errors.append("%s" % resultat)
continue
2012-05-29 16:53:19 +02:00
infos[host] = add_links(resultat)
2012-10-17 16:33:02 +02:00
return render_template('traceroute.html', infos=infos, errors=errors)
2012-05-29 16:53:19 +02:00
2012-01-22 23:06:52 +01:00
2012-01-27 00:15:46 +01:00
@app.route("/adv/<hosts>/<proto>")
def show_route_filter(hosts, proto):
if 'adv' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
2012-05-29 16:53:19 +02:00
return show_route("adv", hosts, proto)
@app.route("/adv_bgpmap/<hosts>/<proto>")
def show_route_filter_bgpmap(hosts, proto):
if 'adv_bgpmap' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
2012-05-29 16:53:19 +02:00
return show_route("adv_bgpmap", 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):
if 'where' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
2012-05-29 16:53:19 +02:00
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):
if 'where_detail' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
2012-05-29 16:53:19 +02:00
return show_route("where_detail", hosts, proto)
@app.route("/where_bgpmap/<hosts>/<proto>")
def show_route_where_bgpmap(hosts, proto):
if 'where_bgpmap' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
2012-05-29 16:53:19 +02:00
return show_route("where_bgpmap", 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):
if 'prefix' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
2012-05-29 16:53:19 +02:00
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):
if 'prefix_detail' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
2012-05-29 16:53:19 +02:00
return show_route("prefix_detail", hosts, proto)
@app.route("/prefix_bgpmap/<hosts>/<proto>")
def show_route_for_bgpmap(hosts, proto):
if 'prefix_bgpmap' not in iter(inject_commands()['commands_dict']):
return render_template('error.html', errors=["Access denied"]), 403
2012-05-29 16:53:19 +02:00
return show_route("prefix_bgpmap", hosts, proto)
2011-12-16 11:02:24 +01:00
2012-05-27 14:30:31 +02:00
def get_as_name(_as):
"""Returns a string that contain the as number following by the as name
2012-05-29 16:53:19 +02:00
"""
if not _as:
return "AS?????"
2012-05-29 16:53:19 +02:00
if not _as.isdigit():
2012-05-30 09:16:58 +02:00
return _as.strip()
2012-05-29 16:53:19 +02:00
name = get_asn_from_as(_as)[-1].replace(" ", "\r", 1)
return "AS%s | %s" % (_as, name)
2012-05-29 16:53:19 +02:00
2012-05-30 09:16:58 +02:00
def get_as_number_from_protocol_name(host, proto, protocol):
ret, res = bird_command(host, proto, "show protocols all %s" % protocol)
re_asnumber = re.search("Neighbor AS:\s*(\d*)", res)
if re_asnumber:
return re_asnumber.group(1)
else:
return "?????"
2012-05-29 16:53:19 +02:00
@app.route("/bgpmap/")
def show_bgpmap():
"""return a bgp map in a png file, from the json tree in q argument"""
data = get_query()
2012-05-29 16:53:19 +02:00
if not data:
abort(400)
2017-08-16 22:22:42 +02:00
data = base64.b64decode(data)
data = json.loads(data)
2012-05-29 16:53:19 +02:00
graph = pydot.Dot('BGPMAP', graph_type='digraph')
nodes = {}
edges = {}
2016-03-02 09:46:51 +01:00
prepend_as = {}
2012-05-29 16:53:19 +02:00
def escape(label):
label = label.replace("&", "&amp;")
label = label.replace(">", "&gt;")
label = label.replace("<", "&lt;")
return label
2012-05-29 16:53:19 +02:00
def add_node(_as, **kwargs):
if _as not in nodes:
kwargs["label"] = '<<TABLE CELLBORDER="0" BORDER="0" CELLPADDING="0" CELLSPACING="0"><TR><TD ALIGN="CENTER">' + escape(kwargs.get("label", get_as_name(_as))).replace("\r","<BR/>") + "</TD></TR></TABLE>>"
2012-05-29 16:53:19 +02:00
nodes[_as] = pydot.Node(_as, style="filled", fontsize="10", **kwargs)
graph.add_node(nodes[_as])
return nodes[_as]
def add_edge(_previous_as, _as, **kwargs):
kwargs["splines"] = "true"
force = kwargs.get("force", False)
edge_tuple = (_previous_as, _as)
if force or edge_tuple not in edges:
edge = pydot.Edge(*edge_tuple, **kwargs)
graph.add_edge(edge)
edges[edge_tuple] = edge
2012-05-30 09:16:58 +02:00
elif "label" in kwargs and kwargs["label"]:
e = edges[edge_tuple]
2012-07-19 17:28:32 +02:00
label_without_star = kwargs["label"].replace("*", "")
if e.get_label() is not None:
labels = e.get_label().split("\r")
else:
return edges[edge_tuple]
2012-07-20 17:52:06 +02:00
if "%s*" % label_without_star not in labels:
labels = [ kwargs["label"] ] + [ l for l in labels if not l.startswith(label_without_star) ]
2012-07-19 17:28:32 +02:00
labels = sorted(labels, cmp=lambda x,y: x.endswith("*") and -1 or 1)
label = escape("\r".join(labels))
e.set_label(label)
2012-05-29 16:53:19 +02:00
return edges[edge_tuple]
for host, asmaps in data.iteritems():
2020-06-15 09:57:59 +02:00
if "DOMAIN" in app.config:
add_node(host, label= "%s\r%s" % (host.upper(), app.config["DOMAIN"].upper()), shape="box", fillcolor="#F5A9A9")
else:
add_node(host, label= "%s" % (host.upper()), shape="box", fillcolor="#F5A9A9")
2012-05-29 16:53:19 +02:00
as_number = app.config["AS_NUMBER"].get(host, None)
if as_number:
node = add_node(as_number, fillcolor="#F5A9A9")
edge = add_edge(as_number, nodes[host])
edge.set_color("red")
edge.set_style("bold")
2012-05-29 17:05:12 +02:00
#colors = [ "#009e23", "#1a6ec1" , "#d05701", "#6f879f", "#939a0e", "#0e9a93", "#9a0e85", "#56d8e1" ]
previous_as = None
2012-05-29 17:05:12 +02:00
hosts = data.keys()
2012-05-29 16:53:19 +02:00
for host, asmaps in data.iteritems():
first = True
for asmap in asmaps:
previous_as = host
2012-05-29 17:05:12 +02:00
color = "#%x" % random.randint(0, 16777215)
2012-05-30 09:16:58 +02:00
hop = False
hop_label = ""
2012-05-29 16:53:19 +02:00
for _as in asmap:
if _as == previous_as:
if not prepend_as.get(_as, None):
prepend_as[_as] = {}
if not prepend_as[_as].get(host, None):
prepend_as[_as][host] = {}
if not prepend_as[_as][host].get(asmap[0], None):
prepend_as[_as][host][asmap[0]] = 1
prepend_as[_as][host][asmap[0]] += 1
2012-05-29 16:53:19 +02:00
continue
2012-05-30 09:16:58 +02:00
if not hop:
hop = True
if _as not in hosts:
hop_label = _as
2012-05-30 09:28:07 +02:00
if first:
hop_label = hop_label + "*"
2012-05-30 09:16:58 +02:00
continue
else:
hop_label = ""
if _as == asmap[-1]:
add_node(_as, fillcolor="#F5A9A9", shape="box", )
else:
add_node(_as, fillcolor=(first and "#F5A9A9" or "white"), )
if hop_label:
edge = add_edge(nodes[previous_as], nodes[_as], label=hop_label, fontsize="7")
else:
edge = add_edge(nodes[previous_as], nodes[_as], fontsize="7")
2012-05-30 09:16:58 +02:00
hop_label = ""
2012-05-29 16:53:19 +02:00
if first or _as == asmap[-1]:
2012-05-29 16:53:19 +02:00
edge.set_style("bold")
edge.set_color("red")
elif edge.get_style() != "bold":
2012-05-29 16:53:19 +02:00
edge.set_style("dashed")
2012-05-29 17:05:12 +02:00
edge.set_color(color)
2012-05-29 16:53:19 +02:00
previous_as = _as
first = False
2016-03-02 09:46:51 +01:00
for _as in prepend_as:
for n in set([ n for h, d in prepend_as[_as].iteritems() for p, n in d.iteritems() ]):
graph.add_edge(pydot.Edge(*(_as, _as), label=" %dx" % n, color="grey", fontcolor="grey"))
2016-03-02 09:46:51 +01:00
2017-08-16 22:43:52 +02:00
fmt = request.args.get('fmt', 'png')
2016-03-02 09:46:51 +01:00
#response = Response("<pre>" + graph.create_dot() + "</pre>")
2017-08-16 22:38:35 +02:00
if fmt == "png":
response = Response(graph.create_png(), mimetype='image/png')
elif fmt == "svg":
response = Response(graph.create_svg(), mimetype='image/svg+xml')
else:
abort(400, "Incorrect format")
2016-03-02 09:46:51 +01:00
response.headers['Last-Modified'] = datetime.now()
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '-1'
return response
2012-05-29 16:53:19 +02:00
def build_as_tree_from_raw_bird_ouput(host, proto, text):
"""Extract the as path from the raw bird "show route all" command"""
path = None
paths = []
net_dest = None
peer_protocol_name = ""
2012-05-29 16:53:19 +02:00
for line in text:
line = line.strip()
Correctly parse aspath for both bird 1.x and 2.0. Due to the change in the output of `show route for <prefix> all` since bird-2.0, this patch is necessary to correctly parse aspaths. Without this patch, `build_as_tree_from_raw_bird_ouput()` fails with the following exception if the backend is bird-2.0. ``` Traceback (most recent call last): File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1836, in __call__ return self.wsgi_app(environ, start_response) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1820, in wsgi_app response = self.make_response(self.handle_exception(e)) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1403, in handle_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1817, in wsgi_app response = self.full_dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1477, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1381, in handle_user_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1475, in full_dispatch_request rv = self.dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1461, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "./lg.py", line 522, in show_bgpmap response = Response(graph.create_png(), mimetype='image/png') File "/usr/lib/python2.7/dist-packages/pydot.py", line 1735, in <lambda> lambda f=frmt, prog=self.prog: self.create(format=f, prog=prog) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1905, in create self.write(tmp_name) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1830, in write data = self.to_string() File "/usr/lib/python2.7/dist-packages/pydot.py", line 1600, in to_string graph.append(node.to_string() + '\n') File "/usr/lib/python2.7/dist-packages/pydot.py", line 865, in to_string node += ' [' + node_attr + ']' TypeError: unsupported operand type(s) for +=: 'NoneType' and 'str' ``` Consequently, `show route for <prefix> (bgpmap)` shows an error icon.
2019-06-09 11:35:20 +02:00
expr = re.search(r'(.*)unicast\s+\[(\w+)\s+', line)
2012-05-29 16:53:19 +02:00
if expr:
Correctly parse aspath for both bird 1.x and 2.0. Due to the change in the output of `show route for <prefix> all` since bird-2.0, this patch is necessary to correctly parse aspaths. Without this patch, `build_as_tree_from_raw_bird_ouput()` fails with the following exception if the backend is bird-2.0. ``` Traceback (most recent call last): File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1836, in __call__ return self.wsgi_app(environ, start_response) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1820, in wsgi_app response = self.make_response(self.handle_exception(e)) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1403, in handle_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1817, in wsgi_app response = self.full_dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1477, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1381, in handle_user_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1475, in full_dispatch_request rv = self.dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1461, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "./lg.py", line 522, in show_bgpmap response = Response(graph.create_png(), mimetype='image/png') File "/usr/lib/python2.7/dist-packages/pydot.py", line 1735, in <lambda> lambda f=frmt, prog=self.prog: self.create(format=f, prog=prog) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1905, in create self.write(tmp_name) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1830, in write data = self.to_string() File "/usr/lib/python2.7/dist-packages/pydot.py", line 1600, in to_string graph.append(node.to_string() + '\n') File "/usr/lib/python2.7/dist-packages/pydot.py", line 865, in to_string node += ' [' + node_attr + ']' TypeError: unsupported operand type(s) for +=: 'NoneType' and 'str' ``` Consequently, `show route for <prefix> (bgpmap)` shows an error icon.
2019-06-09 11:35:20 +02:00
if expr.group(1).strip():
net_dest = expr.group(1).strip()
peer_protocol_name = expr.group(2).strip()
expr2 = re.search(r'(.*)via\s+([0-9a-fA-F:\.]+)\s+on\s+\S+(\s+\[(\w+)\s+)?', line)
Correctly parse aspath for both bird 1.x and 2.0. Due to the change in the output of `show route for <prefix> all` since bird-2.0, this patch is necessary to correctly parse aspaths. Without this patch, `build_as_tree_from_raw_bird_ouput()` fails with the following exception if the backend is bird-2.0. ``` Traceback (most recent call last): File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1836, in __call__ return self.wsgi_app(environ, start_response) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1820, in wsgi_app response = self.make_response(self.handle_exception(e)) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1403, in handle_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1817, in wsgi_app response = self.full_dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1477, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1381, in handle_user_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1475, in full_dispatch_request rv = self.dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1461, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "./lg.py", line 522, in show_bgpmap response = Response(graph.create_png(), mimetype='image/png') File "/usr/lib/python2.7/dist-packages/pydot.py", line 1735, in <lambda> lambda f=frmt, prog=self.prog: self.create(format=f, prog=prog) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1905, in create self.write(tmp_name) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1830, in write data = self.to_string() File "/usr/lib/python2.7/dist-packages/pydot.py", line 1600, in to_string graph.append(node.to_string() + '\n') File "/usr/lib/python2.7/dist-packages/pydot.py", line 865, in to_string node += ' [' + node_attr + ']' TypeError: unsupported operand type(s) for +=: 'NoneType' and 'str' ``` Consequently, `show route for <prefix> (bgpmap)` shows an error icon.
2019-06-09 11:35:20 +02:00
if expr2:
2012-05-29 16:53:19 +02:00
if path:
path.append(net_dest)
paths.append(path)
path = None
Correctly parse aspath for both bird 1.x and 2.0. Due to the change in the output of `show route for <prefix> all` since bird-2.0, this patch is necessary to correctly parse aspaths. Without this patch, `build_as_tree_from_raw_bird_ouput()` fails with the following exception if the backend is bird-2.0. ``` Traceback (most recent call last): File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1836, in __call__ return self.wsgi_app(environ, start_response) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1820, in wsgi_app response = self.make_response(self.handle_exception(e)) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1403, in handle_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1817, in wsgi_app response = self.full_dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1477, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1381, in handle_user_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1475, in full_dispatch_request rv = self.dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1461, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "./lg.py", line 522, in show_bgpmap response = Response(graph.create_png(), mimetype='image/png') File "/usr/lib/python2.7/dist-packages/pydot.py", line 1735, in <lambda> lambda f=frmt, prog=self.prog: self.create(format=f, prog=prog) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1905, in create self.write(tmp_name) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1830, in write data = self.to_string() File "/usr/lib/python2.7/dist-packages/pydot.py", line 1600, in to_string graph.append(node.to_string() + '\n') File "/usr/lib/python2.7/dist-packages/pydot.py", line 865, in to_string node += ' [' + node_attr + ']' TypeError: unsupported operand type(s) for +=: 'NoneType' and 'str' ``` Consequently, `show route for <prefix> (bgpmap)` shows an error icon.
2019-06-09 11:35:20 +02:00
if expr2.group(1).strip():
net_dest = expr2.group(1).strip()
2012-05-29 16:53:19 +02:00
Correctly parse aspath for both bird 1.x and 2.0. Due to the change in the output of `show route for <prefix> all` since bird-2.0, this patch is necessary to correctly parse aspaths. Without this patch, `build_as_tree_from_raw_bird_ouput()` fails with the following exception if the backend is bird-2.0. ``` Traceback (most recent call last): File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1836, in __call__ return self.wsgi_app(environ, start_response) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1820, in wsgi_app response = self.make_response(self.handle_exception(e)) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1403, in handle_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1817, in wsgi_app response = self.full_dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1477, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1381, in handle_user_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1475, in full_dispatch_request rv = self.dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1461, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "./lg.py", line 522, in show_bgpmap response = Response(graph.create_png(), mimetype='image/png') File "/usr/lib/python2.7/dist-packages/pydot.py", line 1735, in <lambda> lambda f=frmt, prog=self.prog: self.create(format=f, prog=prog) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1905, in create self.write(tmp_name) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1830, in write data = self.to_string() File "/usr/lib/python2.7/dist-packages/pydot.py", line 1600, in to_string graph.append(node.to_string() + '\n') File "/usr/lib/python2.7/dist-packages/pydot.py", line 865, in to_string node += ' [' + node_attr + ']' TypeError: unsupported operand type(s) for +=: 'NoneType' and 'str' ``` Consequently, `show route for <prefix> (bgpmap)` shows an error icon.
2019-06-09 11:35:20 +02:00
peer_ip = expr2.group(2).strip()
if expr2.group(4):
peer_protocol_name = expr2.group(4).strip()
2012-05-29 16:53:19 +02:00
# Check if via line is a internal route
for rt_host, rt_ips in app.config["ROUTER_IP"].iteritems():
2012-05-30 09:16:58 +02:00
# Special case for internal routing
2012-05-29 16:53:19 +02:00
if peer_ip in rt_ips:
path = [rt_host]
break
2012-05-30 09:16:58 +02:00
else:
# ugly hack for good printing
path = [ peer_protocol_name ]
# path = ["%s\r%s" % (peer_protocol_name, get_as_name(get_as_number_from_protocol_name(host, proto, peer_protocol_name)))]
Correctly parse aspath for both bird 1.x and 2.0. Due to the change in the output of `show route for <prefix> all` since bird-2.0, this patch is necessary to correctly parse aspaths. Without this patch, `build_as_tree_from_raw_bird_ouput()` fails with the following exception if the backend is bird-2.0. ``` Traceback (most recent call last): File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1836, in __call__ return self.wsgi_app(environ, start_response) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1820, in wsgi_app response = self.make_response(self.handle_exception(e)) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1403, in handle_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1817, in wsgi_app response = self.full_dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1477, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1381, in handle_user_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1475, in full_dispatch_request rv = self.dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1461, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "./lg.py", line 522, in show_bgpmap response = Response(graph.create_png(), mimetype='image/png') File "/usr/lib/python2.7/dist-packages/pydot.py", line 1735, in <lambda> lambda f=frmt, prog=self.prog: self.create(format=f, prog=prog) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1905, in create self.write(tmp_name) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1830, in write data = self.to_string() File "/usr/lib/python2.7/dist-packages/pydot.py", line 1600, in to_string graph.append(node.to_string() + '\n') File "/usr/lib/python2.7/dist-packages/pydot.py", line 865, in to_string node += ' [' + node_attr + ']' TypeError: unsupported operand type(s) for +=: 'NoneType' and 'str' ``` Consequently, `show route for <prefix> (bgpmap)` shows an error icon.
2019-06-09 11:35:20 +02:00
expr3 = re.search(r'(.*)unreachable\s+\[(\w+)\s+', line)
if expr3:
if path:
path.append(net_dest)
paths.append(path)
path = None
Correctly parse aspath for both bird 1.x and 2.0. Due to the change in the output of `show route for <prefix> all` since bird-2.0, this patch is necessary to correctly parse aspaths. Without this patch, `build_as_tree_from_raw_bird_ouput()` fails with the following exception if the backend is bird-2.0. ``` Traceback (most recent call last): File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1836, in __call__ return self.wsgi_app(environ, start_response) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1820, in wsgi_app response = self.make_response(self.handle_exception(e)) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1403, in handle_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1817, in wsgi_app response = self.full_dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1477, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1381, in handle_user_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1475, in full_dispatch_request rv = self.dispatch_request() File "/usr/lib/python2.7/dist-packages/flask/app.py", line 1461, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "./lg.py", line 522, in show_bgpmap response = Response(graph.create_png(), mimetype='image/png') File "/usr/lib/python2.7/dist-packages/pydot.py", line 1735, in <lambda> lambda f=frmt, prog=self.prog: self.create(format=f, prog=prog) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1905, in create self.write(tmp_name) File "/usr/lib/python2.7/dist-packages/pydot.py", line 1830, in write data = self.to_string() File "/usr/lib/python2.7/dist-packages/pydot.py", line 1600, in to_string graph.append(node.to_string() + '\n') File "/usr/lib/python2.7/dist-packages/pydot.py", line 865, in to_string node += ' [' + node_attr + ']' TypeError: unsupported operand type(s) for +=: 'NoneType' and 'str' ``` Consequently, `show route for <prefix> (bgpmap)` shows an error icon.
2019-06-09 11:35:20 +02:00
if path is None:
path = [ expr3.group(2).strip() ]
if expr3.group(1).strip():
net_dest = expr3.group(1).strip()
2012-05-29 16:53:19 +02:00
if line.startswith("BGP.as_path:"):
ASes = line.replace("BGP.as_path:", "").strip().split(" ")
if path:
path.extend(ASes)
else:
path = ASes
2012-05-29 16:53:19 +02:00
if path:
path.append(net_dest)
paths.append(path)
return paths
2012-05-27 14:30:31 +02:00
2012-02-05 14:30:32 +01:00
def show_route(request_type, hosts, proto):
expression = get_query()
2012-05-29 16:53:19 +02:00
if not expression:
abort(400)
set_session(request_type, hosts, proto, expression)
bgpmap = request_type.endswith("bgpmap")
all = (request_type.endswith("detail") and " all" or "")
if bgpmap:
all = " all"
if request_type.startswith("adv"):
command = "show route " + expression.strip()
if bgpmap and not command.endswith("all"):
command = command + " all"
elif request_type.startswith("where"):
command = "show route where net ~ [ " + expression + " ]" + all
else:
mask = ""
2012-10-16 08:07:30 +02:00
if len(expression.split("/")) == 2:
2012-05-29 16:53:19 +02:00
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):
return error_page("mask %s is invalid" % mask)
if proto == "ipv6" and not ipv6_is_valid(expression):
try:
expression = resolve(expression, "AAAA")
except:
return error_page("%s is unresolvable or invalid for %s" % (expression, proto))
if proto == "ipv4" and not ipv4_is_valid(expression):
try:
expression = resolve(expression, "A")
except:
return error_page("%s is unresolvable or invalid for %s" % (expression, proto))
if mask:
expression += "/" + mask
command = "show route for " + expression + all
detail = {}
errors = []
2012-05-29 16:53:19 +02:00
for host in hosts.split("+"):
ret, res = bird_command(host, proto, command)
res = res.split("\n")
2012-10-17 16:33:02 +02:00
if ret is False:
errors.append("%s" % res)
continue
if len(res) <= 1:
errors.append("%s: bird command failed with error, %s" % (host, "\n".join(res)))
2012-10-17 16:33:02 +02:00
continue
if bgpmap:
detail[host] = build_as_tree_from_raw_bird_ouput(host, proto, res)
else:
detail[host] = add_links(res)
2012-05-29 16:53:19 +02:00
if bgpmap:
2017-08-16 22:22:42 +02:00
detail = base64.b64encode(json.dumps(detail))
2012-05-29 16:53:19 +02:00
return render_template((bgpmap and 'bgpmap.html' or 'route.html'), detail=detail, command=command, expression=expression, errors=errors)
2012-05-29 16:53:19 +02:00
2011-12-16 11:02:24 +01:00
if __name__ == "__main__":
app.run(app.config.get("BIND_IP", "0.0.0.0"), app.config.get("BIND_PORT", 5000))