pep8 compliant

This commit is contained in:
Mehdi Abaakouk 2016-10-09 09:16:19 +02:00
parent d6a82367f8
commit a52edffc2c
5 changed files with 388 additions and 307 deletions

View File

@ -22,156 +22,156 @@
import socket import socket
import sys import sys
__all__ = ['BirdSocketSingleton', 'BirdSocket']
BUFSIZE = 4096 BUFSIZE = 4096
SUCCESS_CODES = { SUCCESS_CODES = {
"0000" : "OK", "0000": "OK",
"0001" : "Welcome", "0001": "Welcome",
"0002" : "Reading configuration", "0002": "Reading configuration",
"0003" : "Reconfigured", "0003": "Reconfigured",
"0004" : "Reconfiguration in progress", "0004": "Reconfiguration in progress",
"0005" : "Reconfiguration already in progress, queueing", "0005": "Reconfiguration already in progress, queueing",
"0006" : "Reconfiguration ignored, shutting down", "0006": "Reconfiguration ignored, shutting down",
"0007" : "Shutdown ordered", "0007": "Shutdown ordered",
"0008" : "Already disabled", "0008": "Already disabled",
"0009" : "Disabled", "0009": "Disabled",
"0010" : "Already enabled", "0010": "Already enabled",
"0011" : "Enabled", "0011": "Enabled",
"0012" : "Restarted", "0012": "Restarted",
"0013" : "Status report", "0013": "Status report",
"0014" : "Route count", "0014": "Route count",
"0015" : "Reloading", "0015": "Reloading",
"0016" : "Access restricted", "0016": "Access restricted",
} }
TABLES_ENTRY_CODES = { TABLES_ENTRY_CODES = {
"1000" : "BIRD version", "1000": "BIRD version",
"1001" : "Interface list", "1001": "Interface list",
"1002" : "Protocol list", "1002": "Protocol list",
"1003" : "Interface address", "1003": "Interface address",
"1004" : "Interface flags", "1004": "Interface flags",
"1005" : "Interface summary", "1005": "Interface summary",
"1006" : "Protocol details", "1006": "Protocol details",
"1007" : "Route list", "1007": "Route list",
"1008" : "Route details", "1008": "Route details",
"1009" : "Static route list", "1009": "Static route list",
"1010" : "Symbol list", "1010": "Symbol list",
"1011" : "Uptime", "1011": "Uptime",
"1012" : "Route extended attribute list", "1012": "Route extended attribute list",
"1013" : "Show ospf neighbors", "1013": "Show ospf neighbors",
"1014" : "Show ospf", "1014": "Show ospf",
"1015" : "Show ospf interface", "1015": "Show ospf interface",
"1016" : "Show ospf state/topology", "1016": "Show ospf state/topology",
"1017" : "Show ospf lsadb", "1017": "Show ospf lsadb",
"1018" : "Show memory", "1018": "Show memory",
} }
ERROR_CODES = { ERROR_CODES = {
"8000" : "Reply too long", "8000": "Reply too long",
"8001" : "Route not found", "8001": "Route not found",
"8002" : "Configuration file error", "8002": "Configuration file error",
"8003" : "No protocols match", "8003": "No protocols match",
"8004" : "Stopped due to reconfiguration", "8004": "Stopped due to reconfiguration",
"8005" : "Protocol is down => cannot dump", "8005": "Protocol is down => cannot dump",
"8006" : "Reload failed", "8006": "Reload failed",
"8007" : "Access denied", "8007": "Access denied",
"9000" : "Command too long", "9000": "Command too long",
"9001" : "Parse error", "9001": "Parse error",
"9002" : "Invalid symbol type", "9002": "Invalid symbol type",
} }
END_CODES = ERROR_CODES.keys() + SUCCESS_CODES.keys() END_CODES = ERROR_CODES.keys() + SUCCESS_CODES.keys()
global bird_sockets global bird_sockets
bird_sockets = {} bird_sockets = {}
def BirdSocketSingleton(host, port): def BirdSocketSingleton(host, port):
global bird_sockets global bird_sockets
s = bird_sockets.get((host,port), None) s = bird_sockets.get((host, port), None)
if not s: if not s:
s = BirdSocket(host,port) s = BirdSocket(host, port)
bird_sockets[(host,port)] = s bird_sockets[(host, port)] = s
return s return s
class BirdSocket:
def __init__(self, host="", port="", file=""):
self.__file = file
self.__host = host
self.__port = port
self.__sock = None
def __connect(self):
if self.__sock: return
if not file:
self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.__sock.settimeout(3.0)
self.__sock.connect((self.__host, self.__port))
else:
self.__sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.__sock.settimeout(3.0)
self.__sock.connect(self.__file)
# read welcome message
self.__sock.recv(1024)
self.cmd("restrict")
def close(self):
if self.__sock:
try: self.__sock.close()
except: pass
self.__sock = None
def cmd(self, cmd):
try:
self.__connect()
self.__sock.send(cmd + "\n")
data = self.__read()
return data
except socket.error:
why = sys.exc_info()[1]
self.close()
return False, "Bird connection problem: %s" % why
def __read(self):
code = "7000" # Not used in bird
parsed_string = ""
lastline = ""
while code not in END_CODES:
data = self.__sock.recv(BUFSIZE)
lines = (lastline + data).split("\n")
if len(data) == BUFSIZE:
lastline = lines[-1]
lines = lines[:-1]
for line in lines:
code = line[0:4]
if not line.strip():
continue
elif code == "0000":
return True, parsed_string
elif code in SUCCESS_CODES.keys():
return True, SUCCESS_CODES.get(code)
elif code in ERROR_CODES.keys():
return False, ERROR_CODES.get(code)
elif code[0] in [ "1", "2"] :
parsed_string += line[5:] + "\n"
elif code[0] == " ":
parsed_string += line[1:] + "\n"
elif code[0] == "+":
parsed_string += line[1:]
else:
parsed_string += "<<<unparsable_string(%s)>>>\n"%line
return True, parsed_string
__all__ = ['BirdSocketSingleton' , 'BirdSocket' ] class BirdSocket(object):
def __init__(self, host="", port="", file=""):
self.__file = file
self.__host = host
self.__port = port
self.__sock = None
def __connect(self):
if self.__sock:
return
if not file:
self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.__sock.settimeout(3.0)
self.__sock.connect((self.__host, self.__port))
else:
self.__sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.__sock.settimeout(3.0)
self.__sock.connect(self.__file)
# read welcome message
self.__sock.recv(1024)
self.cmd("restrict")
def close(self):
if self.__sock:
try:
self.__sock.close()
except Exception:
pass
self.__sock = None
def cmd(self, cmd):
try:
self.__connect()
self.__sock.send(cmd + "\n")
data = self.__read()
return data
except socket.error:
why = sys.exc_info()[1]
self.close()
return False, "Bird connection problem: %s" % why
def __read(self):
code = "7000" # Not used in bird
parsed_string = ""
lastline = ""
while code not in END_CODES:
data = self.__sock.recv(BUFSIZE)
lines = (lastline + data).split("\n")
if len(data) == BUFSIZE:
lastline = lines[-1]
lines = lines[:-1]
for line in lines:
code = line[0:4]
if not line.strip():
continue
elif code == "0000":
return True, parsed_string
elif code in SUCCESS_CODES.keys():
return True, SUCCESS_CODES.get(code)
elif code in ERROR_CODES.keys():
return False, ERROR_CODES.get(code)
elif code[0] in ["1", "2"]:
parsed_string += line[5:] + "\n"
elif code[0] == " ":
parsed_string += line[1:] + "\n"
elif code[0] == "+":
parsed_string += line[1:]
else:
parsed_string += "<<<unparsable_string(%s)>>>\n" % line
return True, parsed_string

View File

@ -21,48 +21,53 @@
### ###
from datetime import datetime from datetime import datetime
import memcache
import subprocess
import logging
from logging.handlers import TimedRotatingFileHandler
import re
from urllib2 import urlopen
from urllib import quote, unquote
import json import json
import logging
from logging import handlers
import memcache
import random import random
import re
import subprocess
import urllib
from urllib2 import urlopen
from toolbox import mask_is_valid, ipv6_is_valid, ipv4_is_valid, resolve, save_cache_pickle, load_cache_pickle, unescape from dns import exception as dns_exc
#from xml.sax.saxutils import escape import flask
import pydot import pydot
from flask import Flask, render_template, jsonify, redirect, session, request, abort, Response, Markup # from flask import Flask, render_template, jsonify, redirect, session, request
# from flask import abort, Response, Markup
app = Flask(__name__) import toolbox
app = flask.Flask(__name__)
app.config.from_pyfile('lg.cfg') app.config.from_pyfile('lg.cfg')
app.secret_key = app.config["SESSION_KEY"] app.secret_key = app.config["SESSION_KEY"]
app.debug = app.config["DEBUG"] app.debug = app.config["DEBUG"]
file_handler = TimedRotatingFileHandler(filename=app.config["LOG_FILE"], when="midnight") file_handler = handlers.TimedRotatingFileHandler(
filename=app.config["LOG_FILE"], when="midnight")
file_handler.setLevel(getattr(logging, app.config["LOG_LEVEL"].upper())) file_handler.setLevel(getattr(logging, app.config["LOG_LEVEL"].upper()))
app.logger.addHandler(file_handler) app.logger.addHandler(file_handler)
# NOTE(sileht): 15 days by default
memcache_server = app.config.get("MEMCACHE_SERVER", "127.0.0.1:11211") memcache_server = app.config.get("MEMCACHE_SERVER", "127.0.0.1:11211")
memcache_expiration = int(app.config.get("MEMCACHE_EXPIRATION", "1296000")) # 15 days by default memcache_expiration = int(app.config.get("MEMCACHE_EXPIRATION", "1296000"))
mc = memcache.Client([memcache_server]) mc = memcache.Client([memcache_server])
def get_asn_from_as(n): def get_asn_from_as(n):
asn_zone = app.config.get("ASN_ZONE", "asn.cymru.com") asn_zone = app.config.get("ASN_ZONE", "asn.cymru.com")
try: try:
data = resolve("AS%s.%s" % (n, asn_zone) ,"TXT").replace("'","").replace('"','') data = toolbox.resolve("AS%s.%s" % (n, asn_zone), "TXT")
except: data = data.replace("'", "").replace('"', '')
return " "*5 except dns_exc.DNSException:
return [ field.strip() for field in data.split("|") ] return " " * 5
return [field.strip() for field in data.split("|")]
def add_links(text): def add_links(text):
"""Browser a string and replace ipv4, ipv6, as number, with a """Browser a string and replace ipv4, ipv6, as number, with a whois link"""
whois link """
if type(text) in [str, unicode]: if type(text) in [str, unicode]:
text = text.split("\n") text = text.split("\n")
@ -71,49 +76,64 @@ def add_links(text):
for line in text: for line in text:
# Some heuristic to create link # Some heuristic to create link
if line.strip().startswith("BGP.as_path:") or \ if line.strip().startswith("BGP.as_path:") or \
line.strip().startswith("Neighbor AS:"): line.strip().startswith("Neighbor AS:"):
ret_text.append(re.sub(r'(\d+)', r'<a href="/whois?q=\1" class="whois">\1</a>', line)) ret_text.append(
re.sub(r'(\d+)', r'<a href="/whois?q=\1" class="whois">\1</a>',
line))
else: 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'([a-zA-Z0-9\-]*\.([a-zA-Z]{2,3}){1,2})(\s|$)',
line = re.sub(r'AS(\d+)', r'<a href="/whois?q=\1" class="whois">AS\1</a>', line) r'<a href="/whois?q=\1" class="whois">\1</a>\3',
line = re.sub(r'(\d+\.\d+\.\d+\.\d+)', r'<a href="/whois?q=\1" class="whois">\1</a>', line) line)
if len(request.path) >= 2: line = re.sub(r'AS(\d+)',
hosts = "/".join(request.path.split("/")[2:]) 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)
if len(flask.request.path) >= 2:
hosts = "/".join(flask.request.path.split("/")[2:])
else: else:
hosts = "/" hosts = "/"
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'\[(\w+)\s+((|\d\d\d\d-\d\d-\d\d\s)'
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) r'(|\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)
ret_text.append(line) ret_text.append(line)
return "\n".join(ret_text) return "\n".join(ret_text)
def set_session(request_type, hosts, proto, request_args): def set_session(request_type, hosts, proto, request_args):
""" Store all data from user in the user session """ """Store all data from user in the user session"""
session.permanent = True flask.session.permanent = True
session.update({ flask.session.update({
"request_type": request_type, "request_type": request_type,
"hosts": hosts, "hosts": hosts,
"proto": proto, "proto": proto,
"request_args": request_args, "request_args": request_args,
}) })
history = session.get("history", []) history = flask.session.get("history", [])
# erase old format history # erase old format history
if type(history) != type(list()): if not isinstance(history, list):
history = [] history = []
t = (hosts, proto, request_type, request_args) t = (hosts, proto, request_type, request_args)
if t in history: if t in history:
del history[history.index(t)] del history[history.index(t)]
history.insert(0, t) history.insert(0, t)
session["history"] = history[:20] flask.session["history"] = history[:20]
def whois_command(query): def whois_command(query):
server = [] server = []
if app.config.get("WHOIS_SERVER", ""): if app.config.get("WHOIS_SERVER", ""):
server = [ "-h", 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') p = subprocess.Popen(['whois'] + server + [query],
stdout=subprocess.PIPE)
return p.communicate()[0].decode('utf-8', 'ignore')
def bird_command(host, proto, query): def bird_command(host, proto, query):
@ -144,7 +164,8 @@ def bird_proxy(host, proto, service, query):
elif not path: elif not path:
return False, 'Proto "%s" invalid' % proto return False, 'Proto "%s" invalid' % proto
else: else:
url = "http://%s.%s:%d/%s?q=%s" % (host, app.config["DOMAIN"], port, path, quote(query)) url = "http://%s.%s:%d/%s?q=%s" % (host, app.config["DOMAIN"], port,
path, urllib.quote(query))
try: try:
f = urlopen(url) f = urlopen(url)
resultat = f.read() resultat = f.read()
@ -183,46 +204,58 @@ def inject_all_host():
@app.route("/") @app.route("/")
def hello(): def hello():
return redirect("/summary/%s/ipv4" % "+".join(app.config["PROXY"].keys())) proxy = "+".join(app.config["PROXY"].keys())
return flask.redirect("/summary/%s/ipv4" % proxy)
def error_page(text): def error_page(text):
return render_template('error.html', errors=[text]), 500 return flask.render_template('error.html', errors=[text]), 500
@app.errorhandler(400) @app.errorhandler(400)
def incorrect_request(e): def incorrect_request(e):
return render_template('error.html', warnings=["The server could not understand the request"]), 400 warn = ["The server could not understand the request"]
return flask.render_template('error.html', warnings=warn), 400
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
return render_template('error.html', warnings=["The requested URL was not found on the server."]), 404 warn = ["The requested URL was not found on the server."]
return flask.render_template('error.html', warnings=warn), 404
def get_query(): def get_query():
q = unquote(request.args.get('q', '').strip()) q = urllib.unquote(flask.request.args.get('q', '').strip())
return q return q
@app.route("/whois") @app.route("/whois")
def whois(): def whois():
query = get_query() query = get_query()
if not query: if not query:
abort(400) flask.abort(400)
try: try:
asnum = int(query) asnum = int(query)
query = "as%d" % asnum query = "as%d" % asnum
except: except ValueError:
m = re.match(r"[\w\d-]*\.(?P<domain>[\d\w-]+\.[\d\w-]+)$", query) m = re.match(r"[\w\d-]*\.(?P<domain>[\d\w-]+\.[\d\w-]+)$", query)
if m: if m:
query = query.groupdict()["domain"] query = query.groupdict()["domain"]
output = whois_command(query).replace("\n", "<br>") output = whois_command(query).replace("\n", "<br>")
return jsonify(output=output, title=query) return flask.jsonify(output=output, title=query)
SUMMARY_UNWANTED_PROTOS = ["Kernel", "Static", "Device"] 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>.*))" SUMMARY_RE_MATCH = (
r"(?P<name>[\w_]+)\s+"
r"(?P<proto>\w+)\s+"
r"(?P<table>\w+)\s+"
r"(?P<state>\w+)\s+"
r"(?P<since>((\d\d\d\d-\d\d-\d\d\s)|(\d\d:)\d\d:\d\d|\w\w\w\d\d))"
r"($|\s+(?P<info>.*))"
)
@app.route("/summary/<hosts>") @app.route("/summary/<hosts>")
@ -243,13 +276,16 @@ def summary(hosts, proto="ipv4"):
continue continue
if len(res) <= 1: if len(res) <= 1:
errors.append("%s: bird command failed with error, %s" % (host, "\n".join(res))) errors.append("%s: bird command failed with error, %s" %
(host, "\n".join(res)))
continue continue
data = [] data = []
for line in res[1:]: for line in res[1:]:
line = line.strip() line = line.strip()
if line and (line.split() + [""])[1] not in SUMMARY_UNWANTED_PROTOS: if not line:
continue
if (line.split() + [""])[1] not in SUMMARY_UNWANTED_PROTOS:
m = re.match(SUMMARY_RE_MATCH, line) m = re.match(SUMMARY_RE_MATCH, line)
if m: if m:
data.append(m.groupdict()) data.append(m.groupdict())
@ -258,7 +294,9 @@ def summary(hosts, proto="ipv4"):
summary[host] = data summary[host] = data
return render_template('summary.html', summary=summary, command=command, errors=errors) return flask.render_template('summary.html', summary=summary,
command=command,
errors=errors)
@app.route("/detail/<hosts>/<proto>") @app.route("/detail/<hosts>/<proto>")
@ -266,7 +304,7 @@ def detail(hosts, proto):
name = get_query() name = get_query()
if not name: if not name:
abort(400) flask.abort(400)
set_session("detail", hosts, proto, name) set_session("detail", hosts, proto, name)
command = "show protocols all %s" % name command = "show protocols all %s" % name
@ -282,12 +320,15 @@ def detail(hosts, proto):
continue continue
if len(res) <= 1: if len(res) <= 1:
errors.append("%s: bird command failed with error, %s" % (host, "\n".join(res))) errors.append("%s: bird command failed with error, %s" %
(host, "\n".join(res)))
continue continue
detail[host] = {"status": res[1], "description": add_links(res[2:])} detail[host] = {"status": res[1], "description": add_links(res[2:])}
return render_template('detail.html', detail=detail, command=command, errors=errors) return flask.render_template('detail.html', detail=detail,
command=command,
errors=errors)
@app.route("/traceroute/<hosts>/<proto>") @app.route("/traceroute/<hosts>/<proto>")
@ -295,20 +336,18 @@ def traceroute(hosts, proto):
q = get_query() q = get_query()
if not q: if not q:
abort(400) flask.abort(400)
set_session("traceroute", hosts, proto, q) set_session("traceroute", hosts, proto, q)
if proto == "ipv6" and not ipv6_is_valid(q): if proto == "ipv6" and not toolbox.ipv6_is_valid(q):
try: qtype = "AAAA"
q = resolve(q, "AAAA") elif proto == "ipv4" and not toolbox.ipv4_is_valid(q):
except: qtype = "A"
return error_page("%s is unresolvable or invalid for %s" % (q, proto)) try:
if proto == "ipv4" and not ipv4_is_valid(q): q = toolbox.resolve(q, qtype)
try: except dns_exc.DNSException:
q = resolve(q, "A") return error_page("%s is unresolvable or invalid for %s" % (q, proto))
except:
return error_page("%s is unresolvable or invalid for %s" % (q, proto))
errors = [] errors = []
infos = {} infos = {}
@ -317,10 +356,9 @@ def traceroute(hosts, proto):
if status is False: if status is False:
errors.append("%s" % resultat) errors.append("%s" % resultat)
continue continue
infos[host] = add_links(resultat) infos[host] = add_links(resultat)
return render_template('traceroute.html', infos=infos, errors=errors)
return flask.render_template('traceroute.html', infos=infos, errors=errors)
@app.route("/adv/<hosts>/<proto>") @app.route("/adv/<hosts>/<proto>")
@ -367,7 +405,8 @@ def get_as_name(_as):
"""return a string that contain the as number following by the as name """return a string that contain the as number following by the as name
It's the use whois database informations It's the use whois database informations
# Warning, the server can be blacklisted from ripe is too many requests are done Warning, the server can be blacklisted from ripe is too many requests
are done
""" """
if not _as: if not _as:
return "AS?????" return "AS?????"
@ -378,7 +417,7 @@ def get_as_name(_as):
name = mc.get(str('lg_%s' % _as)) name = mc.get(str('lg_%s' % _as))
if not name: if not name:
app.logger.info("asn for as %s not found in memcache", _as) app.logger.info("asn for as %s not found in memcache", _as)
name = get_asn_from_as(_as)[-1].replace(" ","\r",1) name = get_asn_from_as(_as)[-1].replace(" ", "\r", 1)
if name: if name:
mc.set(str("lg_%s" % _as), str(name), memcache_expiration) mc.set(str("lg_%s" % _as), str(name), memcache_expiration)
return "AS%s | %s" % (_as, name) return "AS%s | %s" % (_as, name)
@ -399,7 +438,7 @@ def show_bgpmap():
data = get_query() data = get_query()
if not data: if not data:
abort(400) flask.abort(400)
data = json.loads(data) data = json.loads(data)
@ -417,8 +456,14 @@ def show_bgpmap():
def add_node(_as, **kwargs): def add_node(_as, **kwargs):
if _as not in nodes: 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>>" label = escape(kwargs.get("label", get_as_name(_as)))
nodes[_as] = pydot.Node(_as, style="filled", fontsize="10", **kwargs) kwargs["label"] = ('<<TABLE CELLBORDER="0" BORDER="0" '
'CELLPADDING="0" CELLSPACING="0">'
'<TR><TD ALIGN="CENTER">' +
label.replace("\r", "<BR/>") +
"</TD></TR></TABLE>>")
nodes[_as] = pydot.Node(_as, style="filled", fontsize="10",
**kwargs)
graph.add_node(nodes[_as]) graph.add_node(nodes[_as])
return nodes[_as] return nodes[_as]
@ -437,15 +482,19 @@ def show_bgpmap():
label_without_star = kwargs["label"].replace("*", "") label_without_star = kwargs["label"].replace("*", "")
labels = e.get_label().split("\r") labels = e.get_label().split("\r")
if "%s*" % label_without_star not in labels: if "%s*" % label_without_star not in labels:
labels = [ kwargs["label"] ] + [ l for l in labels if not l.startswith(label_without_star) ] labels = [kwargs["label"]] + [
labels = sorted(labels, cmp=lambda x,y: x.endswith("*") and -1 or 1) l for l in labels if not l.startswith(label_without_star)]
labels = sorted(labels,
cmp=lambda x, y: x.endswith("*") and -1 or 1)
label = escape("\r".join(labels)) label = escape("\r".join(labels))
e.set_label(label) e.set_label(label)
return edges[edge_tuple] return edges[edge_tuple]
for host, asmaps in data.iteritems(): for host, asmaps in data.iteritems():
add_node(host, label= "%s\r%s" % (host.upper(), app.config["DOMAIN"].upper()), shape="box", fillcolor="#F5A9A9") add_node(host, label="%s\r%s" % (host.upper(),
app.config["DOMAIN"].upper()),
shape="box", fillcolor="#F5A9A9")
as_number = app.config["AS_NUMBER"].get(host, None) as_number = app.config["AS_NUMBER"].get(host, None)
if as_number: if as_number:
@ -454,7 +503,8 @@ def show_bgpmap():
edge.set_color("red") edge.set_color("red")
edge.set_style("bold") edge.set_style("bold")
#colors = [ "#009e23", "#1a6ec1" , "#d05701", "#6f879f", "#939a0e", "#0e9a93", "#9a0e85", "#56d8e1" ] # colors = ["#009e23", "#1a6ec1" , "#d05701", "#6f879f",
# "#939a0e", "#0e9a93", "#9a0e85", "#56d8e1"]
previous_as = None previous_as = None
hosts = data.keys() hosts = data.keys()
for host, asmaps in data.iteritems(): for host, asmaps in data.iteritems():
@ -480,12 +530,13 @@ def show_bgpmap():
else: else:
hop_label = "" hop_label = ""
add_node(_as, fillcolor=(first and "#F5A9A9" or "white")) add_node(_as, fillcolor=(first and "#F5A9A9" or "white"))
if hop_label: if hop_label:
edge = add_edge(nodes[previous_as], nodes[_as], label=hop_label, fontsize="7") edge = add_edge(nodes[previous_as], nodes[_as],
label=hop_label, fontsize="7")
else: else:
edge = add_edge(nodes[previous_as], nodes[_as], fontsize="7") edge = add_edge(nodes[previous_as], nodes[_as],
fontsize="7")
hop_label = "" hop_label = ""
@ -504,18 +555,21 @@ def show_bgpmap():
node.set_shape("box") node.set_shape("box")
for _as in prepend_as: for _as in prepend_as:
graph.add_edge(pydot.Edge(*(_as, _as), label=" %dx" % prepend_as[_as], color="grey", fontcolor="grey")) graph.add_edge(pydot.Edge(*(_as, _as), label=" %dx" % prepend_as[_as],
color="grey", fontcolor="grey"))
#response = Response("<pre>" + graph.create_dot() + "</pre>") # response = Response("<pre>" + graph.create_dot() + "</pre>")
response = Response(graph.create_png(), mimetype='image/png') response = flask.Response(graph.create_png(), mimetype='image/png')
response.headers['Last-Modified'] = datetime.now() 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['Cache-Control'] = ('no-store, no-cache, '
'must-revalidate, '
'post-check=0, pre-check=0, '
'max-age=0')
response.headers['Pragma'] = 'no-cache' response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '-1' response.headers['Expires'] = '-1'
return response return response
def build_as_tree_from_raw_bird_ouput(host, proto, text): def build_as_tree_from_raw_bird_ouput(host, proto, text):
"""Extract the as path from the raw bird "show route all" command""" """Extract the as path from the raw bird "show route all" command"""
@ -545,8 +599,10 @@ def build_as_tree_from_raw_bird_ouput(host, proto, text):
break break
else: else:
# ugly hack for good printing # ugly hack for good printing
path = [ peer_protocol_name ] 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)))] # path = ["%s\r%s" % (peer_protocol_name,
# get_as_name(get_as_number_from_protocol_name(
# host, proto, peer_protocol_name)))]
expr2 = re.search(r'(.*)unreachable\s+\[(\w+)\s+', line) expr2 = re.search(r'(.*)unreachable\s+\[(\w+)\s+', line)
if expr2: if expr2:
@ -571,7 +627,7 @@ def build_as_tree_from_raw_bird_ouput(host, proto, text):
def show_route(request_type, hosts, proto): def show_route(request_type, hosts, proto):
expression = get_query() expression = get_query()
if not expression: if not expression:
abort(400) flask.abort(400)
set_session(request_type, hosts, proto, expression) set_session(request_type, hosts, proto, expression)
@ -596,19 +652,19 @@ def show_route(request_type, hosts, proto):
mask = "32" mask = "32"
if not mask and proto == "ipv6": if not mask and proto == "ipv6":
mask = "128" mask = "128"
if not mask_is_valid(mask): if not toolbox.mask_is_valid(mask):
return error_page("mask %s is invalid" % mask) return error_page("mask %s is invalid" % mask)
if proto == "ipv6" and not ipv6_is_valid(expression): if proto == "ipv6" and not toolbox.ipv6_is_valid(expression):
try: qtype = "AAAA"
expression = resolve(expression, "AAAA") elif proto == "ipv4" and not toolbox.ipv4_is_valid(expression):
except: qtype = "A"
return error_page("%s is unresolvable or invalid for %s" % (expression, proto))
if proto == "ipv4" and not ipv4_is_valid(expression): try:
try: expression = toolbox.resolve(expression, qtype)
expression = resolve(expression, "A") except dns_exc.DNSException:
except: return error_page("%s is unresolvable or invalid for %s" %
return error_page("%s is unresolvable or invalid for %s" % (expression, proto)) (expression, proto))
if mask: if mask:
expression += "/" + mask expression += "/" + mask
@ -626,7 +682,8 @@ def show_route(request_type, hosts, proto):
continue continue
if len(res) <= 1: if len(res) <= 1:
errors.append("%s: bird command failed with error, %s" % (host, "\n".join(res))) errors.append("%s: bird command failed with error, %s" %
(host, "\n".join(res)))
continue continue
if bgpmap: if bgpmap:
@ -637,8 +694,11 @@ def show_route(request_type, hosts, proto):
if bgpmap: if bgpmap:
detail = json.dumps(detail) detail = json.dumps(detail)
return render_template((bgpmap and 'bgpmap.html' or 'route.html'), detail=detail, command=command, expression=expression, errors=errors) return flask.render_template((bgpmap and 'bgpmap.html' or 'route.html'),
detail=detail, command=command,
expression=expression, errors=errors)
if __name__ == "__main__": if __name__ == "__main__":
app.run(app.config.get("BIND_IP", "0.0.0.0"), app.config.get("BIND_PORT", 5000)) app.run(app.config.get("BIND_IP", "0.0.0.0"),
app.config.get("BIND_PORT", 5000))

View File

@ -20,96 +20,107 @@
### ###
import sys
import logging import logging
from logging.handlers import TimedRotatingFileHandler from logging import handlers
from logging import FileHandler
import subprocess import subprocess
from urllib import unquote import sys
import urllib
from bird import BirdSocket import flask
from flask import Flask, request, abort import bird
app = Flask(__name__) app = flask.Flask(__name__)
app.debug = app.config["DEBUG"] app.debug = app.config["DEBUG"]
app.config.from_pyfile('lgproxy.cfg') app.config.from_pyfile('lgproxy.cfg')
file_handler = TimedRotatingFileHandler(filename=app.config["LOG_FILE"], when="midnight") file_handler = handlers.TimedRotatingFileHandler(
filename=app.config["LOG_FILE"], when="midnight")
app.logger.setLevel(getattr(logging, app.config["LOG_LEVEL"].upper())) app.logger.setLevel(getattr(logging, app.config["LOG_LEVEL"].upper()))
app.logger.addHandler(file_handler) app.logger.addHandler(file_handler)
@app.before_request @app.before_request
def access_log_before(*args, **kwargs): def access_log_before(*args, **kwargs):
app.logger.info("[%s] request %s, %s", request.remote_addr, request.url, "|".join(["%s:%s"%(k,v) for k,v in request.headers.items()])) app.logger.info("[%s] flash.request %s, %s",
flask.request.remote_addr,
flask.request.url, "|".join(
["%s:%s" % (k, v) for k, v in
flask.request.headers.items()])
)
@app.after_request @app.after_request
def access_log_after(response, *args, **kwargs): def access_log_after(response, *args, **kwargs):
app.logger.info("[%s] reponse %s, %s", request.remote_addr, request.url, response.status_code) app.logger.info("[%s] reponse %s, %s", flask.request.remote_addr,
flask.request.url, response.status_code)
return response return response
def check_accesslist(): def check_accesslist():
if app.config["ACCESS_LIST"] and request.remote_addr not in app.config["ACCESS_LIST"]: if (app.config["ACCESS_LIST"] and
abort(401) flask.request.remote_addr not in app.config["ACCESS_LIST"]):
flask.abort(401)
@app.route("/traceroute") @app.route("/traceroute")
@app.route("/traceroute6") @app.route("/traceroute6")
def traceroute(): def traceroute():
check_accesslist() check_accesslist()
if sys.platform.startswith('freebsd') or sys.platform.startswith('netbsd') or sys.platform.startswith('openbsd'): if (sys.platform.startswith('freebsd') or sys.platform.startswith('netbsd')
traceroute4 = [ 'traceroute' ] or sys.platform.startswith('openbsd')):
traceroute6 = [ 'traceroute6' ] traceroute4 = ['traceroute']
else: # For Linux traceroute6 = ['traceroute6']
traceroute4 = [ 'traceroute', '-4' ] else: # For Linux
traceroute6 = [ 'traceroute', '-6' ] traceroute4 = ['traceroute', '-4']
traceroute6 = ['traceroute', '-6']
src = [] src = []
if request.path == '/traceroute6': if flask.request.path == '/traceroute6':
traceroute = traceroute6 traceroute = traceroute6
if app.config.get("IPV6_SOURCE",""): if app.config.get("IPV6_SOURCE", ""):
src = [ "-s", app.config.get("IPV6_SOURCE") ] src = ["-s", app.config.get("IPV6_SOURCE")]
else:
traceroute = traceroute4
if app.config.get("IPV4_SOURCE", ""):
src = ["-s", app.config.get("IPV4_SOURCE")]
else: query = flask.request.args.get("q", "")
traceroute = traceroute4 query = urllib.unquote(query)
if app.config.get("IPV4_SOURCE",""):
src = [ "-s", app.config.get("IPV4_SOURCE") ]
query = request.args.get("q","")
query = unquote(query)
if sys.platform.startswith('freebsd') or sys.platform.startswith('netbsd'): if sys.platform.startswith('freebsd') or sys.platform.startswith('netbsd'):
options = [ '-a', '-q1', '-w1', '-m15' ] options = ['-a', '-q1', '-w1', '-m15']
elif sys.platform.startswith('openbsd'): elif sys.platform.startswith('openbsd'):
options = [ '-A', '-q1', '-w1', '-m15' ] options = ['-A', '-q1', '-w1', '-m15']
else: # For Linux else: # For Linux
options = [ '-A', '-q1', '-N32', '-w1', '-m15' ] options = ['-A', '-q1', '-N32', '-w1', '-m15']
command = traceroute + src + options + [ query ] command = traceroute + src + options + [query]
result = subprocess.Popen( command , stdout=subprocess.PIPE).communicate()[0].decode('utf-8', 'ignore').replace("\n","<br>") p = subprocess.Popen(command, stdout=subprocess.PIPE)
return p.communicate()[0].decode('utf-8', 'ignore').replace("\n", "<br>")
return result
@app.route("/bird") @app.route("/bird")
@app.route("/bird6") @app.route("/bird6")
def bird(): def bird_query():
check_accesslist() check_accesslist()
if request.path == "/bird": b = BirdSocket(file=app.config.get("BIRD_SOCKET")) if flask.request.path == "/bird":
elif request.path == "/bird6": b = BirdSocket(file=app.config.get("BIRD6_SOCKET")) b = bird.BirdSocket(file=app.config.get("BIRD_SOCKET"))
else: return "No bird socket selected" elif flask.request.path == "/bird6":
b = bird.BirdSocket(file=app.config.get("BIRD6_SOCKET"))
else:
return "No bird socket selected"
query = request.args.get("q","") query = flask.request.args.get("q", "")
query = unquote(query) query = urllib.unquote(query)
status, result = b.cmd(query) status, result = b.cmd(query)
b.close() b.close()
# FIXME: use status # FIXME: use status
return result return result
if __name__ == "__main__": if __name__ == "__main__":
app.logger.info("lgproxy start") app.logger.info("lgproxy start")
app.run("0.0.0.0") app.run("0.0.0.0")

View File

@ -20,56 +20,66 @@
### ###
from dns import resolver from dns import resolver
import socket
import pickle import pickle
import socket
import xml.parsers.expat import xml.parsers.expat
resolv = resolver.Resolver() resolv = resolver.Resolver()
resolv.timeout = 0.5 resolv.timeout = 0.5
resolv.lifetime = 1 resolv.lifetime = 1
def resolve(n, q): def resolve(n, q):
return str(resolv.query(n,q)[0]) return str(resolv.query(n, q)[0])
def mask_is_valid(n): def mask_is_valid(n):
if not n: if not n:
return True return True
try: try:
mask = int(n) mask = int(n)
return ( mask >= 1 and mask <= 128) except ValueError:
except: return False
return False else:
return (mask >= 1 and mask <= 128)
def ipv4_is_valid(n): def ipv4_is_valid(n):
try: try:
socket.inet_pton(socket.AF_INET, n) socket.inet_pton(socket.AF_INET, n)
return True
except socket.error: except socket.error:
return False return False
else:
return True
def ipv6_is_valid(n): def ipv6_is_valid(n):
try: try:
socket.inet_pton(socket.AF_INET6, n) socket.inet_pton(socket.AF_INET6, n)
return True
except socket.error: except socket.error:
return False return False
else:
return True
def save_cache_pickle(filename, data): def save_cache_pickle(filename, data):
output = open(filename, 'wb') output = open(filename, 'wb')
pickle.dump(data, output) pickle.dump(data, output)
output.close() output.close()
def load_cache_pickle(filename, default=None):
try:
pkl_file = open(filename, 'rb')
except IOError:
return default
try:
data = pickle.load(pkl_file)
except pickle.UnpicklingError:
data = default
pkl_file.close()
return data
def load_cache_pickle(filename, default = None):
try:
pkl_file = open(filename, 'rb')
except IOError:
return default
try:
data = pickle.load(pkl_file)
except:
data = default
pkl_file.close()
return data
def unescape(s): def unescape(s):
want_unicode = False want_unicode = False

View File

@ -15,7 +15,7 @@ deps =
flake8 flake8
doc8 doc8
commands = commands =
doc8 --ignore-path doc/source/rest.rst doc/source # doc8 doc/source
flake8 {posargs} flake8 {posargs}
[testenv:venv] [testenv:venv]