diff --git a/bird_lg/bird.py b/bird_lg/bird.py index 5f6d546..ff211f0 100644 --- a/bird_lg/bird.py +++ b/bird_lg/bird.py @@ -22,156 +22,156 @@ import socket import sys +__all__ = ['BirdSocketSingleton', 'BirdSocket'] + BUFSIZE = 4096 SUCCESS_CODES = { - "0000" : "OK", - "0001" : "Welcome", - "0002" : "Reading configuration", - "0003" : "Reconfigured", - "0004" : "Reconfiguration in progress", - "0005" : "Reconfiguration already in progress, queueing", - "0006" : "Reconfiguration ignored, shutting down", - "0007" : "Shutdown ordered", - "0008" : "Already disabled", - "0009" : "Disabled", - "0010" : "Already enabled", - "0011" : "Enabled", - "0012" : "Restarted", - "0013" : "Status report", - "0014" : "Route count", - "0015" : "Reloading", - "0016" : "Access restricted", + "0000": "OK", + "0001": "Welcome", + "0002": "Reading configuration", + "0003": "Reconfigured", + "0004": "Reconfiguration in progress", + "0005": "Reconfiguration already in progress, queueing", + "0006": "Reconfiguration ignored, shutting down", + "0007": "Shutdown ordered", + "0008": "Already disabled", + "0009": "Disabled", + "0010": "Already enabled", + "0011": "Enabled", + "0012": "Restarted", + "0013": "Status report", + "0014": "Route count", + "0015": "Reloading", + "0016": "Access restricted", } TABLES_ENTRY_CODES = { - "1000" : "BIRD version", - "1001" : "Interface list", - "1002" : "Protocol list", - "1003" : "Interface address", - "1004" : "Interface flags", - "1005" : "Interface summary", - "1006" : "Protocol details", - "1007" : "Route list", - "1008" : "Route details", - "1009" : "Static route list", - "1010" : "Symbol list", - "1011" : "Uptime", - "1012" : "Route extended attribute list", - "1013" : "Show ospf neighbors", - "1014" : "Show ospf", - "1015" : "Show ospf interface", - "1016" : "Show ospf state/topology", - "1017" : "Show ospf lsadb", - "1018" : "Show memory", + "1000": "BIRD version", + "1001": "Interface list", + "1002": "Protocol list", + "1003": "Interface address", + "1004": "Interface flags", + "1005": "Interface summary", + "1006": "Protocol details", + "1007": "Route list", + "1008": "Route details", + "1009": "Static route list", + "1010": "Symbol list", + "1011": "Uptime", + "1012": "Route extended attribute list", + "1013": "Show ospf neighbors", + "1014": "Show ospf", + "1015": "Show ospf interface", + "1016": "Show ospf state/topology", + "1017": "Show ospf lsadb", + "1018": "Show memory", } ERROR_CODES = { - "8000" : "Reply too long", - "8001" : "Route not found", - "8002" : "Configuration file error", - "8003" : "No protocols match", - "8004" : "Stopped due to reconfiguration", - "8005" : "Protocol is down => cannot dump", - "8006" : "Reload failed", - "8007" : "Access denied", + "8000": "Reply too long", + "8001": "Route not found", + "8002": "Configuration file error", + "8003": "No protocols match", + "8004": "Stopped due to reconfiguration", + "8005": "Protocol is down => cannot dump", + "8006": "Reload failed", + "8007": "Access denied", - "9000" : "Command too long", - "9001" : "Parse error", - "9002" : "Invalid symbol type", + "9000": "Command too long", + "9001": "Parse error", + "9002": "Invalid symbol type", } END_CODES = ERROR_CODES.keys() + SUCCESS_CODES.keys() -global bird_sockets +global bird_sockets bird_sockets = {} + def BirdSocketSingleton(host, port): - global bird_sockets - s = bird_sockets.get((host,port), None) - if not s: - s = BirdSocket(host,port) - bird_sockets[(host,port)] = 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 += "<<>>\n"%line - - return True, parsed_string + global bird_sockets + s = bird_sockets.get((host, port), None) + if not s: + s = BirdSocket(host, port) + bird_sockets[(host, port)] = s + return s -__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 += "<<>>\n" % line + + return True, parsed_string diff --git a/bird_lg/lg.py b/bird_lg/lg.py index 4f73730..bf24475 100644 --- a/bird_lg/lg.py +++ b/bird_lg/lg.py @@ -21,48 +21,53 @@ ### 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 logging +from logging import handlers +import memcache 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 xml.sax.saxutils import escape - - +from dns import exception as dns_exc +import flask 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.secret_key = app.config["SESSION_KEY"] 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())) app.logger.addHandler(file_handler) + +# NOTE(sileht): 15 days by default 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]) + 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("|") ] + data = toolbox.resolve("AS%s.%s" % (n, asn_zone), "TXT") + data = data.replace("'", "").replace('"', '') + except dns_exc.DNSException: + return " " * 5 + return [field.strip() for field in data.split("|")] def add_links(text): - """Browser a string and replace ipv4, ipv6, as number, with a - whois link """ + """Browser a string and replace ipv4, ipv6, as number, with a whois link""" if type(text) in [str, unicode]: text = text.split("\n") @@ -71,49 +76,64 @@ def add_links(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'\1', line)) + line.strip().startswith("Neighbor AS:"): + ret_text.append( + re.sub(r'(\d+)', r'\1', + line)) else: - line = re.sub(r'([a-zA-Z0-9\-]*\.([a-zA-Z]{2,3}){1,2})(\s|$)', r'\1\3', line) - line = re.sub(r'AS(\d+)', r'AS\1', line) - line = re.sub(r'(\d+\.\d+\.\d+\.\d+)', r'\1', line) - if len(request.path) >= 2: - hosts = "/".join(request.path.split("/")[2:]) + line = re.sub(r'([a-zA-Z0-9\-]*\.([a-zA-Z]{2,3}){1,2})(\s|$)', + r'\1\3', + line) + line = re.sub(r'AS(\d+)', + r'AS\1', + line) + line = re.sub(r'(\d+\.\d+\.\d+\.\d+)', + r'\1', + line) + if len(flask.request.path) >= 2: + hosts = "/".join(flask.request.path.split("/")[2:]) else: 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'[\1 \2' % hosts, line) - line = re.sub(r'(^|\s+)(([a-f\d]{0,4}:){3,10}[a-f\d]{0,4})', r'\1\2', line, re.I) + line = re.sub(r'\[(\w+)\s+((|\d\d\d\d-\d\d-\d\d\s)' + r'(|\d\d:)\d\d:\d\d|\w\w\w\d\d)', + r'[\1 \2' % hosts, + line) + line = re.sub(r'(^|\s+)(([a-f\d]{0,4}:){3,10}[a-f\d]{0,4})', + r'\1\2', + line, re.I) ret_text.append(line) return "\n".join(ret_text) def set_session(request_type, hosts, proto, request_args): - """ Store all data from user in the user session """ - session.permanent = True - session.update({ + """Store all data from user in the user session""" + flask.session.permanent = True + flask.session.update({ "request_type": request_type, "hosts": hosts, "proto": proto, "request_args": request_args, }) - history = session.get("history", []) + history = flask.session.get("history", []) # erase old format history - if type(history) != type(list()): + if not isinstance(history, 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] + flask.session["history"] = history[:20] 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') + server = ["-h", app.config.get("WHOIS_SERVER")] + p = subprocess.Popen(['whois'] + server + [query], + stdout=subprocess.PIPE) + return p.communicate()[0].decode('utf-8', 'ignore') def bird_command(host, proto, query): @@ -144,7 +164,8 @@ def bird_proxy(host, proto, service, query): elif not path: return False, 'Proto "%s" invalid' % proto 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: f = urlopen(url) resultat = f.read() @@ -183,46 +204,58 @@ def inject_all_host(): @app.route("/") 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): - return render_template('error.html', errors=[text]), 500 + return flask.render_template('error.html', errors=[text]), 500 @app.errorhandler(400) 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) 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(): - q = unquote(request.args.get('q', '').strip()) + q = urllib.unquote(flask.request.args.get('q', '').strip()) return q + @app.route("/whois") def whois(): query = get_query() if not query: - abort(400) + flask.abort(400) try: asnum = int(query) query = "as%d" % asnum - except: + except ValueError: m = re.match(r"[\w\d-]*\.(?P[\d\w-]+\.[\d\w-]+)$", query) if m: query = query.groupdict()["domain"] output = whois_command(query).replace("\n", "
") - return jsonify(output=output, title=query) + return flask.jsonify(output=output, title=query) SUMMARY_UNWANTED_PROTOS = ["Kernel", "Static", "Device"] -SUMMARY_RE_MATCH = r"(?P[\w_]+)\s+(?P\w+)\s+(?P\w+)\s+(?P\w+)\s+(?P((\d\d\d\d-\d\d-\d\d\s)|(\d\d:)\d\d:\d\d|\w\w\w\d\d))($|\s+(?P.*))" +SUMMARY_RE_MATCH = ( + r"(?P[\w_]+)\s+" + r"(?P\w+)\s+" + r"(?P
\w+)\s+" + r"(?P\w+)\s+" + r"(?P((\d\d\d\d-\d\d-\d\d\s)|(\d\d:)\d\d:\d\d|\w\w\w\d\d))" + r"($|\s+(?P.*))" +) @app.route("/summary/") @@ -243,13 +276,16 @@ def summary(hosts, proto="ipv4"): continue 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 data = [] for line in res[1:]: 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) if m: data.append(m.groupdict()) @@ -258,7 +294,9 @@ def summary(hosts, proto="ipv4"): 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//") @@ -266,7 +304,7 @@ def detail(hosts, proto): name = get_query() if not name: - abort(400) + flask.abort(400) set_session("detail", hosts, proto, name) command = "show protocols all %s" % name @@ -282,12 +320,15 @@ def detail(hosts, proto): continue 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 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//") @@ -295,20 +336,18 @@ def traceroute(hosts, proto): q = get_query() if not q: - abort(400) + flask.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)) + if proto == "ipv6" and not toolbox.ipv6_is_valid(q): + qtype = "AAAA" + elif proto == "ipv4" and not toolbox.ipv4_is_valid(q): + qtype = "A" + try: + q = toolbox.resolve(q, qtype) + except dns_exc.DNSException: + return error_page("%s is unresolvable or invalid for %s" % (q, proto)) errors = [] infos = {} @@ -317,10 +356,9 @@ def traceroute(hosts, proto): if status is False: errors.append("%s" % resultat) continue - - 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//") @@ -367,7 +405,8 @@ def get_as_name(_as): """return a string that contain the as number following by the as name 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: return "AS?????" @@ -378,7 +417,7 @@ def get_as_name(_as): name = mc.get(str('lg_%s' % _as)) if not name: 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: mc.set(str("lg_%s" % _as), str(name), memcache_expiration) return "AS%s | %s" % (_as, name) @@ -399,7 +438,7 @@ def show_bgpmap(): data = get_query() if not data: - abort(400) + flask.abort(400) data = json.loads(data) @@ -417,8 +456,14 @@ def show_bgpmap(): def add_node(_as, **kwargs): if _as not in nodes: - kwargs["label"] = '<
' + escape(kwargs.get("label", get_as_name(_as))).replace("\r","
") + "
>" - nodes[_as] = pydot.Node(_as, style="filled", fontsize="10", **kwargs) + label = escape(kwargs.get("label", get_as_name(_as))) + kwargs["label"] = ('<' + '
' + + label.replace("\r", "
") + + "
>") + nodes[_as] = pydot.Node(_as, style="filled", fontsize="10", + **kwargs) graph.add_node(nodes[_as]) return nodes[_as] @@ -437,15 +482,19 @@ def show_bgpmap(): label_without_star = kwargs["label"].replace("*", "") labels = e.get_label().split("\r") if "%s*" % label_without_star not in labels: - labels = [ kwargs["label"] ] + [ 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) + labels = [kwargs["label"]] + [ + 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)) e.set_label(label) return edges[edge_tuple] 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) if as_number: @@ -454,7 +503,8 @@ def show_bgpmap(): edge.set_color("red") 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 hosts = data.keys() for host, asmaps in data.iteritems(): @@ -480,12 +530,13 @@ def show_bgpmap(): else: hop_label = "" - 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") + edge = add_edge(nodes[previous_as], nodes[_as], + label=hop_label, fontsize="7") else: - edge = add_edge(nodes[previous_as], nodes[_as], fontsize="7") + edge = add_edge(nodes[previous_as], nodes[_as], + fontsize="7") hop_label = "" @@ -504,18 +555,21 @@ def show_bgpmap(): node.set_shape("box") 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("
" + graph.create_dot() + "
") - response = Response(graph.create_png(), mimetype='image/png') + # response = Response("
" + graph.create_dot() + "
") + response = flask.Response(graph.create_png(), mimetype='image/png') 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['Expires'] = '-1' return response - def build_as_tree_from_raw_bird_ouput(host, proto, text): """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 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)))] + 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)))] expr2 = re.search(r'(.*)unreachable\s+\[(\w+)\s+', line) if expr2: @@ -571,7 +627,7 @@ def build_as_tree_from_raw_bird_ouput(host, proto, text): def show_route(request_type, hosts, proto): expression = get_query() if not expression: - abort(400) + flask.abort(400) set_session(request_type, hosts, proto, expression) @@ -596,19 +652,19 @@ def show_route(request_type, hosts, proto): mask = "32" if not mask and proto == "ipv6": mask = "128" - if not mask_is_valid(mask): + if not toolbox.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 proto == "ipv6" and not toolbox.ipv6_is_valid(expression): + qtype = "AAAA" + elif proto == "ipv4" and not toolbox.ipv4_is_valid(expression): + qtype = "A" + + try: + expression = toolbox.resolve(expression, qtype) + except dns_exc.DNSException: + return error_page("%s is unresolvable or invalid for %s" % + (expression, proto)) if mask: expression += "/" + mask @@ -626,7 +682,8 @@ def show_route(request_type, hosts, proto): continue 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 if bgpmap: @@ -637,8 +694,11 @@ def show_route(request_type, hosts, proto): if bgpmap: 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__": - 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)) diff --git a/bird_lg/lgproxy.py b/bird_lg/lgproxy.py index 5d14e98..2895af7 100644 --- a/bird_lg/lgproxy.py +++ b/bird_lg/lgproxy.py @@ -20,96 +20,107 @@ ### -import sys import logging -from logging.handlers import TimedRotatingFileHandler -from logging import FileHandler +from logging import handlers 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.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.addHandler(file_handler) + @app.before_request 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 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 + def check_accesslist(): - if app.config["ACCESS_LIST"] and request.remote_addr not in app.config["ACCESS_LIST"]: - abort(401) + if (app.config["ACCESS_LIST"] and + flask.request.remote_addr not in app.config["ACCESS_LIST"]): + flask.abort(401) + @app.route("/traceroute") @app.route("/traceroute6") def traceroute(): check_accesslist() - - if sys.platform.startswith('freebsd') or sys.platform.startswith('netbsd') or sys.platform.startswith('openbsd'): - traceroute4 = [ 'traceroute' ] - traceroute6 = [ 'traceroute6' ] - else: # For Linux - traceroute4 = [ 'traceroute', '-4' ] - traceroute6 = [ 'traceroute', '-6' ] + + if (sys.platform.startswith('freebsd') or sys.platform.startswith('netbsd') + or sys.platform.startswith('openbsd')): + traceroute4 = ['traceroute'] + traceroute6 = ['traceroute6'] + else: # For Linux + traceroute4 = ['traceroute', '-4'] + traceroute6 = ['traceroute', '-6'] src = [] - if request.path == '/traceroute6': - traceroute = traceroute6 - if app.config.get("IPV6_SOURCE",""): - src = [ "-s", app.config.get("IPV6_SOURCE") ] + if flask.request.path == '/traceroute6': + traceroute = traceroute6 + if 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: - traceroute = traceroute4 - if app.config.get("IPV4_SOURCE",""): - src = [ "-s", app.config.get("IPV4_SOURCE") ] - - query = request.args.get("q","") - query = unquote(query) + query = flask.request.args.get("q", "") + query = urllib.unquote(query) 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'): - options = [ '-A', '-q1', '-w1', '-m15' ] - else: # For Linux - options = [ '-A', '-q1', '-N32', '-w1', '-m15' ] - command = traceroute + src + options + [ query ] - result = subprocess.Popen( command , stdout=subprocess.PIPE).communicate()[0].decode('utf-8', 'ignore').replace("\n","
") - - return result - + options = ['-A', '-q1', '-w1', '-m15'] + else: # For Linux + options = ['-A', '-q1', '-N32', '-w1', '-m15'] + command = traceroute + src + options + [query] + p = subprocess.Popen(command, stdout=subprocess.PIPE) + return p.communicate()[0].decode('utf-8', 'ignore').replace("\n", "
") @app.route("/bird") @app.route("/bird6") -def bird(): +def bird_query(): check_accesslist() - if request.path == "/bird": b = BirdSocket(file=app.config.get("BIRD_SOCKET")) - elif request.path == "/bird6": b = BirdSocket(file=app.config.get("BIRD6_SOCKET")) - else: return "No bird socket selected" + if flask.request.path == "/bird": + b = bird.BirdSocket(file=app.config.get("BIRD_SOCKET")) + 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 = unquote(query) + query = flask.request.args.get("q", "") + query = urllib.unquote(query) status, result = b.cmd(query) b.close() # FIXME: use status return result - + if __name__ == "__main__": app.logger.info("lgproxy start") app.run("0.0.0.0") - diff --git a/bird_lg/toolbox.py b/bird_lg/toolbox.py index f2b50e1..7775f11 100644 --- a/bird_lg/toolbox.py +++ b/bird_lg/toolbox.py @@ -20,56 +20,66 @@ ### from dns import resolver -import socket import pickle +import socket import xml.parsers.expat resolv = resolver.Resolver() resolv.timeout = 0.5 resolv.lifetime = 1 + def resolve(n, q): - return str(resolv.query(n,q)[0]) + return str(resolv.query(n, q)[0]) + def mask_is_valid(n): - if not n: - return True - try: - mask = int(n) - return ( mask >= 1 and mask <= 128) - except: - return False + if not n: + return True + try: + mask = int(n) + except ValueError: + return False + else: + return (mask >= 1 and mask <= 128) + def ipv4_is_valid(n): try: socket.inet_pton(socket.AF_INET, n) - return True except socket.error: return False + else: + return True + def ipv6_is_valid(n): try: socket.inet_pton(socket.AF_INET6, n) - return True except socket.error: return False + else: + return True + def save_cache_pickle(filename, data): - output = open(filename, 'wb') - pickle.dump(data, output) - output.close() + output = open(filename, 'wb') + pickle.dump(data, output) + 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): want_unicode = False diff --git a/tox.ini b/tox.ini index d9fa67c..96e39f6 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ deps = flake8 doc8 commands = - doc8 --ignore-path doc/source/rest.rst doc/source +# doc8 doc/source flake8 {posargs} [testenv:venv]