mirror of
https://github.com/sileht/bird-lg.git
synced 2024-11-22 15:04:41 +01:00
Refactoring code
This commit is contained in:
parent
41aca54fba
commit
69ee07ffc2
148
bird.py
Normal file
148
bird.py
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
|
||||||
|
"9000" : "Command too long",
|
||||||
|
"9001" : "Parse error",
|
||||||
|
"9002" : "Invalid symbol type",
|
||||||
|
}
|
||||||
|
|
||||||
|
END_CODES = ERROR_CODES.keys() + SUCCESS_CODES.keys()
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.__host = host
|
||||||
|
self.__port = port
|
||||||
|
self.__sock = None
|
||||||
|
|
||||||
|
def __connect(self):
|
||||||
|
if self.__sock: return
|
||||||
|
|
||||||
|
self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.__sock.settimeout(3.0)
|
||||||
|
self.__sock.connect((self.__host, self.__port))
|
||||||
|
|
||||||
|
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")
|
||||||
|
return self.__read()
|
||||||
|
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' ]
|
||||||
|
|
||||||
|
|
||||||
|
|
8
lg.cfg
Normal file
8
lg.cfg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
|
||||||
|
HOST_MAPPING={
|
||||||
|
"gw": { "ipv4" : 9994, "ipv6": 9996 },
|
||||||
|
"h3": { "ipv4" : 9994, "ipv6": 9996 },
|
||||||
|
}
|
||||||
|
|
||||||
|
SESSION_KEY = '\xd77\xf9\xfa\xc2\xb5\xcd\x85)`+H\x9d\xeeW\\%\xbe/\xbaT\x89\xe8\xa7'
|
332
lg.py
332
lg.py
|
@ -1,211 +1,177 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
import sys, os, subprocess, re
|
import sys
|
||||||
from dns import resolver,reversename
|
import os
|
||||||
from flask import Flask, render_template, jsonify, redirect
|
import subprocess
|
||||||
|
import re
|
||||||
|
|
||||||
|
from toolbox import mask_is_valid, ipv6_is_valid, ipv4_is_valid, resolve
|
||||||
|
|
||||||
|
from bird import BirdSocketSingleton
|
||||||
|
from flask import Flask, render_template, jsonify, redirect, session, request
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.config.from_pyfile('lg.cfg')
|
||||||
import socket
|
|
||||||
|
|
||||||
def get_ip(n, q):
|
|
||||||
return str(resolver.query(n,q)[0])
|
|
||||||
|
|
||||||
def check_mask(n):
|
|
||||||
if not n:
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
mask = int(n)
|
|
||||||
return ( mask > 1 and mask < 128)
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def check_ipv4(n):
|
|
||||||
try:
|
|
||||||
socket.inet_pton(socket.AF_INET, n)
|
|
||||||
return True
|
|
||||||
except socket.error:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def check_ipv6(n):
|
|
||||||
try:
|
|
||||||
socket.inet_pton(socket.AF_INET6, n)
|
|
||||||
return True
|
|
||||||
except socket.error:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup_output(text):
|
def add_links(text):
|
||||||
return "\n".join([ add_link(re.sub(r'^[0-9]*-', r' ', line)) for line in text.split("\n") if not line.startswith("0000") ])
|
if type(text) in [ str, unicode ]:
|
||||||
|
text = text.split("\n")
|
||||||
|
|
||||||
def add_link(text):
|
ret_text = []
|
||||||
if text.strip().startswith("BGP.as_path:") or \
|
for line in text:
|
||||||
text.strip().startswith("Neighbor AS:") :
|
if line.strip().startswith("BGP.as_path:") or \
|
||||||
return re.sub(r'([0-9]*)',r'<a href="/whois/\1">\1</a>',text)
|
line.strip().startswith("Neighbor AS:") :
|
||||||
|
ret_text.append(re.sub(r'(\d+)',r'<a href="/whois/\1" class="whois">\1</a>',line))
|
||||||
else:
|
else:
|
||||||
return text
|
line = re.sub(r'AS(\d+)', r'<a href="/whois/\1" class="whois">AS\1</a>',line)
|
||||||
|
line = re.sub(r'(\d+\.\d+\.\d+\.\d+)', r'<a href="/whois/\1" class="whois">\1</a>',line)
|
||||||
|
ret_text.append(line)
|
||||||
|
return "\n".join(ret_text)
|
||||||
|
|
||||||
#@app.errorhandler(404)
|
def set_session(req_type, hosts, proto, request_args):
|
||||||
#def notfound(error):
|
session.update( {
|
||||||
# return redirect("/")
|
"req_type": req_type,
|
||||||
|
"hosts": hosts,
|
||||||
|
"proto": proto,
|
||||||
|
"request_args": request_args,
|
||||||
|
})
|
||||||
|
|
||||||
|
def bird_command(host, proto, command):
|
||||||
|
conf = app.config["HOST_MAPPING"].get(host, None)
|
||||||
|
port = conf.get(proto)
|
||||||
|
if not conf or not port:
|
||||||
|
return False, "Host/Proto not allowed"
|
||||||
|
else:
|
||||||
|
b = BirdSocketSingleton(host, port)
|
||||||
|
return b.cmd(command)
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_all_host():
|
||||||
|
return dict(all_hosts="+".join(app.config["HOST_MAPPING"].keys()))
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def hello():
|
def hello():
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
||||||
@app.route("/whois/<asnum>")
|
def error_page(text):
|
||||||
def whois(asnum):
|
return render_template('error.html', data = { "error": text } ), 500
|
||||||
output = "<h3> Whois as" + asnum + "</h3><pre>"
|
|
||||||
|
|
||||||
|
@app.route("/whois/<query>")
|
||||||
|
def whois(query):
|
||||||
try:
|
try:
|
||||||
asnum = int(asnum)
|
asnum = int(query)
|
||||||
|
query = "as%d"%asnum
|
||||||
except:
|
except:
|
||||||
output += "Failed to parse as%s"%asnum
|
m = re.match(r"[\w\d-]*\.(?P<domain>[\d\w-]+\.[\d\w-]+)$", query)
|
||||||
|
if m: query = query.groupdict()["domain"]
|
||||||
|
output = subprocess.Popen( [ 'whois', query], stdout=subprocess.PIPE).communicate()[0].decode('utf-8', 'ignore').replace("\n","<br>")
|
||||||
|
return jsonify(output=output, title=query)
|
||||||
|
|
||||||
|
SUMMARY_UNWANTED_PROTOS = ["Kernel", "Static", "Device"]
|
||||||
|
SUMMARY_RE_MATCH = r"(?P<name>[\w_]+)\s+(?P<proto>\w+)\s+(?P<table>\w+)\s+(?P<state>\w+)\s+(?P<since>((|\d\d\d\d-\d\d-\d\d\s)(|\d\d:)\d\d:\d\d|\w\w\w\d\d))(\s+(?P<info>.+)|)"
|
||||||
|
|
||||||
|
@app.route("/summary/<hosts>")
|
||||||
|
@app.route("/summary/<hosts>/<proto>")
|
||||||
|
def summary(hosts, proto="ipv4"):
|
||||||
|
set_session("summary", hosts, proto, "")
|
||||||
|
command = "show protocols"
|
||||||
|
|
||||||
|
summary = {}
|
||||||
|
for host in hosts.split("+"):
|
||||||
|
ret, res = bird_command(host, proto, command)
|
||||||
|
res = res.split("\n")
|
||||||
|
if ret:
|
||||||
|
data = []
|
||||||
|
for line in res[1:]:
|
||||||
|
line = line.strip()
|
||||||
|
if line and ( line.split() + [""] )[1] not in SUMMARY_UNWANTED_PROTOS:
|
||||||
|
m = re.match(SUMMARY_RE_MATCH ,line)
|
||||||
|
if m:
|
||||||
|
data.append(m.groupdict())
|
||||||
else:
|
else:
|
||||||
output += subprocess.Popen(['whois', 'as%d'%asnum], stdout=subprocess.PIPE).communicate()[0].decode('utf-8', 'ignore')
|
app.logger.warning("couldn't parse: %s" , line)
|
||||||
output += "</pre>"
|
|
||||||
return render_template('index.html', output=output, typ="whois", asnum=asnum)
|
|
||||||
|
|
||||||
@app.route("/prefix_detail/<host>/<proto>/")
|
summary[host] = data
|
||||||
@app.route("/prefix_detail/<host>/<proto>/<prefix>")
|
|
||||||
@app.route("/prefix_detail/<host>/<proto>/<prefix>/<mask>")
|
|
||||||
def show_route_for_prefix_detail(host, proto, prefix="", mask=""):
|
|
||||||
return show_route_for_prefix(host, proto=proto, prefix=prefix, mask=mask, all = True)
|
|
||||||
|
|
||||||
@app.route("/prefix/<host>/<proto>/")
|
|
||||||
@app.route("/prefix/<host>/<proto>/<prefix>")
|
|
||||||
@app.route("/prefix/<host>/<proto>/<prefix>/<mask>")
|
|
||||||
def show_route_for_prefix(host, proto, prefix="", mask="", all=False):
|
|
||||||
qprefix = prefix
|
|
||||||
|
|
||||||
|
|
||||||
# security check
|
|
||||||
allowed = True
|
|
||||||
if not prefix:
|
|
||||||
allowed = False
|
|
||||||
elif not check_mask(mask):
|
|
||||||
allowed = False
|
|
||||||
elif proto == "ipv6":
|
|
||||||
if not check_ipv6(prefix):
|
|
||||||
try:
|
|
||||||
qprefix = get_ip(prefix, "AAAA")
|
|
||||||
except:
|
|
||||||
qprefix = "unresolvable"
|
|
||||||
allowed = False
|
|
||||||
|
|
||||||
|
|
||||||
elif proto == "ipv4":
|
|
||||||
if not check_ipv4(prefix):
|
|
||||||
try:
|
|
||||||
qprefix = get_ip(prefix, "A")
|
|
||||||
except:
|
|
||||||
qprefix = "unresolvable"
|
|
||||||
allowed = False
|
|
||||||
else:
|
else:
|
||||||
allowed = False
|
summary[host] = { "error" : res }
|
||||||
|
|
||||||
output = '<h3>' + host + ' (' + proto + ') show route for ' + prefix + (prefix != qprefix and " (%s)"%qprefix or "") + (mask and '/' + mask or '' ) + (all and " all" or "") + '</h3>'
|
return render_template('summary.html', summary=summary, command=command)
|
||||||
|
|
||||||
if allowed:
|
@app.route("/detail/<hosts>/<proto>")
|
||||||
if mask: qprefix = qprefix +"/"+mask
|
def detail(hosts, proto):
|
||||||
if mask: prefix = prefix +"/"+mask
|
name = request.args.get('q', '')
|
||||||
ok, string = get_cmd_result(host , proto, "show route for " + qprefix + (all and " all" or ""))
|
set_session("detail", hosts, proto, name)
|
||||||
if ok:
|
command = "show protocols all %s" % name
|
||||||
output += '<pre>' + cleanup_output(string) + '</pre>'
|
|
||||||
|
detail = {}
|
||||||
|
for host in hosts.split("+"):
|
||||||
|
ret, res = bird_command(host, proto, command)
|
||||||
|
res = res.split("\n")
|
||||||
|
if ret:
|
||||||
|
detail[host] = { "status": res[1], "description": add_links(res[2:]) }
|
||||||
else:
|
else:
|
||||||
output += string
|
detail[host] = { "status": "bird error: %s" % "\n".join(res), "description": "" }
|
||||||
|
|
||||||
|
return render_template('detail.html', detail=detail, command=command)
|
||||||
|
|
||||||
|
@app.route("/where/<hosts>/<proto>")
|
||||||
|
def show_route_where(hosts, proto):
|
||||||
|
return show_route("where", hosts, proto)
|
||||||
|
|
||||||
|
@app.route("/where_detail/<hosts>/<proto>")
|
||||||
|
def show_route_where_detail(hosts, proto):
|
||||||
|
return show_route("where_detail", hosts, proto)
|
||||||
|
|
||||||
|
@app.route("/prefix/<hosts>/<proto>")
|
||||||
|
def show_route_for(hosts, proto):
|
||||||
|
return show_route("prefix", hosts, proto)
|
||||||
|
|
||||||
|
@app.route("/prefix_detail/<hosts>/<proto>")
|
||||||
|
def show_route_for_detail(hosts, proto):
|
||||||
|
return show_route("prefix_detail", hosts, proto)
|
||||||
|
|
||||||
|
def show_route(req_type, hosts, proto):
|
||||||
|
expression = request.args.get('q', '')
|
||||||
|
set_session(req_type, hosts, proto, expression)
|
||||||
|
|
||||||
|
all = (req_type.endswith("detail") and " all" or "" )
|
||||||
|
|
||||||
|
if req_type.startswith("where"):
|
||||||
|
command = "show route where net ~ [ " + expression + " ]" + all
|
||||||
else:
|
else:
|
||||||
if prefix and qprefix != "unresolvable":
|
mask = ""
|
||||||
output += prefix + ' not valid'
|
if len(expression.split("/")) > 1:
|
||||||
elif prefix:
|
expression, mask = (expression.split("/"))
|
||||||
output += prefix + ' unresolvable'
|
|
||||||
|
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 invalid" % mask)
|
||||||
|
if proto == "ipv6" and not ipv6_is_valid(expression):
|
||||||
|
try: expression = resolve(expression, "AAAA")
|
||||||
|
except: return error_page("%s unresolvable/invalid" % expression)
|
||||||
|
if proto == "ipv4" and not ipv4_is_valid(expression):
|
||||||
|
try: expression = resolve(expression, "A")
|
||||||
|
except: return error_page("%s unresolvable/invalid" % expression)
|
||||||
|
|
||||||
|
if mask: expression += "/" + mask
|
||||||
|
command = "show route for " + expression + all
|
||||||
|
|
||||||
|
detail = {}
|
||||||
|
for host in hosts.split("+"):
|
||||||
|
ret, res = bird_command(host, proto, command)
|
||||||
|
|
||||||
|
res = res.split("\n")
|
||||||
|
if ret:
|
||||||
|
detail[host] = add_links(res)
|
||||||
else:
|
else:
|
||||||
output += 'prefix missing'
|
detail[host] = "bird error: %s" % "\n".join(res)
|
||||||
|
|
||||||
return render_template('index.html', output=output, typ="prefix" + (all and "_detail" or ""), host=host+"/"+proto, prefix=prefix)
|
return render_template('route.html', detail=detail, command=command, expression=expression )
|
||||||
|
|
||||||
@app.route("/detail/<host>/<proto>/")
|
|
||||||
@app.route("/detail/<host>/<proto>/<name>")
|
|
||||||
def detail(host, proto, name=""):
|
|
||||||
output = '<h3>' + host + ' (' + proto + ') show protocols all ' + name + '</h3>'
|
|
||||||
|
|
||||||
if not name:
|
|
||||||
output += "name missing"
|
|
||||||
else:
|
|
||||||
ok, string = get_cmd_result(host , proto, "show protocols all " + name)
|
|
||||||
if ok:
|
|
||||||
output += '<pre>'
|
|
||||||
output += "\n".join([ add_link(s.strip()) for s in string.split("\n") if s.startswith(" ")])
|
|
||||||
output += '</pre>'
|
|
||||||
else:
|
|
||||||
output += string
|
|
||||||
|
|
||||||
return render_template('index.html', output=output, typ="detail", host=host+"/"+proto, name=name)
|
|
||||||
|
|
||||||
@app.route("/summary/<host>")
|
|
||||||
@app.route("/summary/<host>/<proto>")
|
|
||||||
def summary(host, proto="ipv4"):
|
|
||||||
output = '<h3>' + host + ' (' + proto + ') show protocols</h3>'
|
|
||||||
|
|
||||||
ok, string = get_cmd_result(host , proto, "show protocols")
|
|
||||||
if ok:
|
|
||||||
output += '<pre><table>'
|
|
||||||
for infos in string.split("\n"):
|
|
||||||
if not infos.startswith(" "): continue
|
|
||||||
d = infos.split()
|
|
||||||
name = d[0]
|
|
||||||
typ = d[1]
|
|
||||||
if typ == "BGP":
|
|
||||||
output += '<tr><td><a href="/detail/%s/%s/%s">%s</a><td><td>%s</td></tr>'%(host,proto,name,name,infos.replace(name,"").strip())
|
|
||||||
output += '</table></pre>'
|
|
||||||
else:
|
|
||||||
output += string
|
|
||||||
return render_template('index.html', output=output, typ="summary", host=host+"/"+proto)
|
|
||||||
|
|
||||||
@app.route("/<host>/<proto>/status")
|
|
||||||
def status(host, proto):
|
|
||||||
string = get_cmd_result(host, proto, "show status")
|
|
||||||
output = '<pre>' + string + '</pre>'
|
|
||||||
return render_template('index.html', output=output, host=host+"/"+proto)
|
|
||||||
|
|
||||||
def get_cmd_result(host, proto, cmd):
|
|
||||||
|
|
||||||
ret = True
|
|
||||||
if proto == "ipv4":
|
|
||||||
port = 9994
|
|
||||||
else:
|
|
||||||
port = 9996
|
|
||||||
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
sock.settimeout(3.0)
|
|
||||||
# sock.setblocking(0)
|
|
||||||
try:
|
|
||||||
sock.connect((host, port))
|
|
||||||
app.logger.debug("open socket on %s:%d", host, port)
|
|
||||||
|
|
||||||
sock.send(cmd + "\n")
|
|
||||||
app.logger.debug("send %s socket on %s:%d", cmd, host, port)
|
|
||||||
|
|
||||||
bufsize = 4096
|
|
||||||
data = sock.recv(bufsize)
|
|
||||||
string = data
|
|
||||||
app.logger.debug("read %s (%d)", data, len(data))
|
|
||||||
code = string.split("\n")[-2][0:4]
|
|
||||||
while not code[0] in ["0", "9", "8"]:
|
|
||||||
data = sock.recv(bufsize)
|
|
||||||
string = string + data
|
|
||||||
app.logger.debug("read %s (%d)", data, len(data))
|
|
||||||
code = string.strip()[len(string.strip())-4:]
|
|
||||||
|
|
||||||
if code[0] in [ "9", "8" ]:
|
|
||||||
ret = False
|
|
||||||
|
|
||||||
app.logger.debug("return %s",string)
|
|
||||||
except Exception as detail:
|
|
||||||
ret = False
|
|
||||||
string = "Failed connect to %s:%d (%s)"%(host, port, detail)
|
|
||||||
app.logger.error(string)
|
|
||||||
sock.close()
|
|
||||||
return (ret, string)
|
|
||||||
|
|
||||||
|
app.secret_key = app.config["SESSION_KEY"]
|
||||||
app.debug = True
|
app.debug = True
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run("0.0.0.0")
|
app.run("0.0.0.0")
|
||||||
|
|
5
lg.wsgi
Normal file
5
lg.wsgi
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0,"/var/www/lg.tetaneutral.net/")
|
||||||
|
|
||||||
|
from lg import app as application
|
13
static/jquery-impromptu.3.2.min.js
vendored
Normal file
13
static/jquery-impromptu.3.2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
154
static/style.css
154
static/style.css
|
@ -0,0 +1,154 @@
|
||||||
|
* {
|
||||||
|
font-family: Arial, Helvetica, Sans-Serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, ul, li { padding: 0 ; margin: 0; }
|
||||||
|
html, body {
|
||||||
|
height:100% ;
|
||||||
|
}
|
||||||
|
a { text-decoration: none; color:blue; }
|
||||||
|
|
||||||
|
h1, h2, h3 { }
|
||||||
|
h1 { font-size: 18px; }
|
||||||
|
h2 { font-size: 16px; }
|
||||||
|
h3 { font-size: 14px; }
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
padding:2px 5px;
|
||||||
|
border-left: 1px solid #AFAFAF;
|
||||||
|
}
|
||||||
|
tr.odd td{
|
||||||
|
background: #EFEFEF;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
border-bottom: 1px solid #AFAFAF;
|
||||||
|
background: #AFAFAF;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-top: 1px solid #AFAFAF;
|
||||||
|
border-right: 1px solid #AFAFAF;
|
||||||
|
border-bottom: 1px solid #AFAFAF;
|
||||||
|
}
|
||||||
|
#header {
|
||||||
|
float: left;
|
||||||
|
width: 180px;
|
||||||
|
padding:10px 20px;
|
||||||
|
height: 100%;
|
||||||
|
border-right: 1px solid #AFAFAF;
|
||||||
|
position:fixed;
|
||||||
|
}
|
||||||
|
#header h1{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#content {
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin-left: 230px;
|
||||||
|
}
|
||||||
|
div.summary,
|
||||||
|
div.detail {
|
||||||
|
font-size:12px;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
/* 140px */
|
||||||
|
border-top: 1px solid #AFAFAF;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li{
|
||||||
|
display: block;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
list-style-type: none;
|
||||||
|
background: #FFF;
|
||||||
|
border-bottom: 1px solid #AFAFAF;
|
||||||
|
}
|
||||||
|
ul li.current{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
ul li.selected{
|
||||||
|
background: #EFEFEF;
|
||||||
|
}
|
||||||
|
ul li:hover{
|
||||||
|
background: #AFAFAF;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
ul.hosts{
|
||||||
|
border-bottom: 1px solid #AFAFAF;
|
||||||
|
}
|
||||||
|
ul.hosts li{
|
||||||
|
width: 47px;
|
||||||
|
border-bottom: 0px solid #AFAFAF;
|
||||||
|
text-align: center;
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
ul.proto li{
|
||||||
|
text-align: center;
|
||||||
|
width: 78px;
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
select, input, { width: 100%;}
|
||||||
|
input[type=text],textarea { width: 97%; font-size:10px; }
|
||||||
|
|
||||||
|
|
||||||
|
/* POPUP */
|
||||||
|
.jqifade{
|
||||||
|
position: absolute;
|
||||||
|
background-color: #aaaaaa;
|
||||||
|
}
|
||||||
|
div.jqi{
|
||||||
|
width: 600px;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #ffffff;
|
||||||
|
font-size: 11px;
|
||||||
|
text-align: left;
|
||||||
|
border: solid 1px #eeeeee;
|
||||||
|
border-radius: 10px;
|
||||||
|
-moz-border-radius: 10px;
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
|
padding: 7px;
|
||||||
|
}
|
||||||
|
div.jqi .jqicontainer{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
div.jqi .jqiclose{
|
||||||
|
position: absolute;
|
||||||
|
top: 4px; right: -2px;
|
||||||
|
width: 18px;
|
||||||
|
cursor: default;
|
||||||
|
color: #bbbbbb;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
div.jqi .jqimessage{
|
||||||
|
padding: 10px;
|
||||||
|
color: #444444;
|
||||||
|
height: 400px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
div.jqi .jqibuttons{
|
||||||
|
text-align: right;
|
||||||
|
padding: 5px 0 5px 0;
|
||||||
|
border: solid 1px #eeeeee;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
div.jqi button{
|
||||||
|
padding: 3px 10px;
|
||||||
|
margin: 0 10px;
|
||||||
|
background-color: #2F6073;
|
||||||
|
border: solid 1px #f4f4f4;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
div.jqi button:hover{
|
||||||
|
background-color: #728A8C;
|
||||||
|
}
|
||||||
|
div.jqi button.jqidefaultbutton{
|
||||||
|
background-color: #BF5E26;
|
||||||
|
}
|
||||||
|
.jqiwarning .jqi .jqibuttons{
|
||||||
|
background-color: #BF5E26;
|
||||||
|
}
|
12
templates/detail.html
Normal file
12
templates/detail.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
{% for host in detail %}
|
||||||
|
<div class="detail">
|
||||||
|
<h2>{{host}}/{{session.proto}}: {{command}}</h2>
|
||||||
|
{{ detail[host].status }}
|
||||||
|
<pre>
|
||||||
|
{{ detail[host].description|safe }}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
4
templates/error.html
Normal file
4
templates/error.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
{{ data.error|safe }}
|
||||||
|
{% endblock %}
|
|
@ -1,119 +1,132 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<title>Tetaneutral.net looking glass</title>
|
<title>Tetaneutral.net looking glass</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
|
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
|
||||||
<script type="text/javascript" src="{{url_for('static', filename='jquery.js') }}"></script>
|
<script type="text/javascript" src="{{url_for('static', filename='jquery.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{url_for('static', filename='jquery-impromptu.3.2.min.js') }}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$( function() {
|
$( function() {
|
||||||
|
$("a.whois").click(function (event){
|
||||||
|
event.preventDefault();
|
||||||
|
link = $(this).attr('href')
|
||||||
|
$.getJSON(link, function(data) {
|
||||||
|
$.prompt("<h3>" + data.title + "</h3>" + data.output,{ show:'slideDown' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$("#typ option[value={{typ}}]").attr("selected", "selected")
|
previous_req_type = "{{session.req_type}}".replace("_detail","")
|
||||||
$("#host option[value='{{host}}']").attr("selected", "selected")
|
function update_view(){
|
||||||
$("#host").change()
|
$("#request_args").hide()
|
||||||
|
|
||||||
$("#typ").change(function(){
|
|
||||||
$("#name").hide()
|
|
||||||
$("#prefix").hide()
|
|
||||||
$("#submit").hide()
|
$("#submit").hide()
|
||||||
switch ($("#typ").val())
|
|
||||||
|
next_req_type = $("#req_type").val().replace("_detail","")
|
||||||
|
if (previous_req_type != next_req_type) {
|
||||||
|
$("#request_args").val("")
|
||||||
|
}
|
||||||
|
previous_req_type = next_req_type
|
||||||
|
|
||||||
|
switch(next_req_type)
|
||||||
{
|
{
|
||||||
case "summary":
|
case "summary":
|
||||||
if ($("#typ").val() != "{{typ}}")
|
|
||||||
$("#submit").click()
|
$("#submit").click()
|
||||||
break;
|
break;
|
||||||
case "detail":
|
default:
|
||||||
$("#name").show()
|
|
||||||
$("#submit").show()
|
$("#submit").show()
|
||||||
break;
|
$("#request_args").show()
|
||||||
case "prefix":
|
if ($("#request_args").val()) {
|
||||||
$("#submit").show()
|
|
||||||
$("#prefix").show()
|
|
||||||
if ($("#prefix").val()) {
|
|
||||||
$("#submit").click()
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "prefix_detail":
|
|
||||||
$("#submit").show()
|
|
||||||
$("#prefix").show()
|
|
||||||
if ($("#prefix").val()) {
|
|
||||||
$("#submit").click()
|
$("#submit").click()
|
||||||
|
} else {
|
||||||
|
$("#request_args").focus()
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$("ul li").click(function(){
|
||||||
|
// set hiddent field with class value
|
||||||
|
p = $(this).parent() // lu
|
||||||
|
c = p.attr("class")
|
||||||
|
|
||||||
|
$("#"+c).val($(this).attr("id"))
|
||||||
|
|
||||||
|
$("ul."+p.attr("class") + " li").removeClass('selected')
|
||||||
|
$(this).addClass('selected')
|
||||||
|
|
||||||
|
update_view()
|
||||||
});
|
});
|
||||||
$("#typ").change()
|
update_view()
|
||||||
|
|
||||||
|
$("ul.proto li#{{session.proto}}").addClass('selected')
|
||||||
|
$("ul.hosts li[id='{{session.hosts}}']").addClass('selected')
|
||||||
|
$("ul.req_type li#{{session.req_type}}").addClass('selected')
|
||||||
|
|
||||||
|
$("ul.proto li#{{session.proto}}").addClass('current')
|
||||||
|
$("ul.hosts li[id='{{session.hosts}}']").addClass('current')
|
||||||
|
$("ul.req_type li#{{session.req_type}}").addClass('current')
|
||||||
|
|
||||||
|
$("#request_args").keyup(function(event) {
|
||||||
|
if (event.which == 13) {
|
||||||
|
event.preventDefault();
|
||||||
|
$("#submit").click()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$("#submit").click( function(){
|
$("#submit").click( function(){
|
||||||
switch ($("#typ").val())
|
switch ($("#req_type").val())
|
||||||
{
|
{
|
||||||
case "summary":
|
case "summary":
|
||||||
document.location = "/summary/" + $("#host").val() ;
|
document.location = "/summary/" + $("#hosts").val() + "/" + $("#proto").val();
|
||||||
break;
|
break;
|
||||||
case "detail":
|
default:
|
||||||
document.location = "/detail/" + $("#host").val() + "/" + $("#name").val() ;
|
document.location = "/" + $("#req_type").val() + "/" + $("#hosts").val() + "/" + $("#proto").val() + "?q=" + $("#request_args").val() ;
|
||||||
break;
|
|
||||||
case "prefix":
|
|
||||||
document.location = "/prefix/" + $("#host").val() + "/" + $("#prefix").val() ;
|
|
||||||
break;
|
|
||||||
case "prefix_detail":
|
|
||||||
document.location = "/prefix_detail/" + $("#host").val() + "/" + $("#prefix").val() ;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
keypress_handler = function(e) {
|
|
||||||
if (e.which == 13) {
|
|
||||||
$("#submit").click()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
$("#prefix").keypress(keypress_handler)
|
|
||||||
$("#name").keypress(keypress_handler)
|
|
||||||
|
|
||||||
$("#host").change(function (){
|
|
||||||
switch ($("#typ").val())
|
|
||||||
{
|
|
||||||
case "summary":
|
|
||||||
$("#submit").click()
|
|
||||||
break;
|
|
||||||
case "detail":
|
|
||||||
if ($("#name").val()) {
|
|
||||||
$("#submit").click()
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "prefix":
|
|
||||||
if ($("#prefix").val()) {
|
|
||||||
$("#submit").click()
|
|
||||||
}
|
|
||||||
case "prefix_detail":
|
|
||||||
if ($("#prefix").val()) {
|
|
||||||
$("#submit").click()
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (document.location.pathname == "/"){
|
if (document.location.pathname == "/"){
|
||||||
$("#submit").click()
|
$("#submit").click()
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<div id="page">
|
<div id="page">
|
||||||
<h1>Tetaneutral.net Looking Glass</h1>
|
<div id="header">
|
||||||
|
<h1>Tetaneutral.net<br />Looking Glass</h1>
|
||||||
<form>
|
<form>
|
||||||
<select id="host">
|
<input id="hosts" type="hidden" value="{{session.hosts}}" />
|
||||||
<option value="gw/ipv4">gw (ipv4)</option>
|
<input id="proto" type="hidden" value="{{session.proto}}" />
|
||||||
<option value="gw/ipv6">gw (ipv6)</option>
|
<input id="req_type" type="hidden" value="{{session.req_type}}" />
|
||||||
<option value="h3/ipv4">h3 (ipv4)</option>
|
<ul class="proto">
|
||||||
<option value="h3/ipv6">h3 (ipv6)</option>
|
<li id="ipv4">ipv4</li>
|
||||||
</select>
|
<li id="ipv6">ipv6</li>
|
||||||
<select id="typ">
|
</ul>
|
||||||
<option value="summary">summary</option>
|
<ul class="hosts">
|
||||||
<option value="detail">detail</option>
|
<li id="{{all_hosts}}">all</li>
|
||||||
<option value="prefix">prefix</option>
|
{% for host in config.HOST_MAPPING %}
|
||||||
<option value="prefix_detail">prefix detail</option>
|
<li id="{{host}}">{{host}}</li>
|
||||||
</select>
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
<input type="text" id="name" style="display:none" value="{{ name }}"></input>
|
<ul class="req_type">
|
||||||
<input type="text" id="prefix" style="display:none" value="{{ prefix }}"></input>
|
<li id="summary">summary</li>
|
||||||
<input type="button" id="submit" value="submit"></input>
|
<li id="detail">detail</li>
|
||||||
|
<li id="prefix">prefix</li>
|
||||||
|
<li id="prefix_detail">prefix (detail)</li>
|
||||||
|
<li id="where">where net ~ [ ... ]</li>
|
||||||
|
<li id="where_detail">where net ~ [ ... ] (detail)</li>
|
||||||
|
</ul>
|
||||||
|
<br />
|
||||||
|
<!--textarea type="text" id="request_args" style="display:none">{{session.request_args}}</textarea-->
|
||||||
|
<input type="text" id="request_args" style="display:none" value="{{session.request_args}}" />
|
||||||
|
<br />
|
||||||
|
<input type="button" id="submit" value="submit" />
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
{% if errors %}
|
||||||
|
<div id="error">{{errors}}</div>
|
||||||
|
{% endif %}
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
14
templates/route.html
Normal file
14
templates/route.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
{% for host in detail %}
|
||||||
|
<div class="detail">
|
||||||
|
<h2>{{host}}/{{session.proto}}: {{command}}</h2>
|
||||||
|
{% if session.request_args != expression %}
|
||||||
|
DNS: <a href="/whois/{{session.request_args}}" class="whois">{{session.request_args}}</a> => <a href="/whois/{{ expression|replace("/32","") }}" class="whois">{{expression|replace("/32","")}}</a>
|
||||||
|
{% endif %}
|
||||||
|
<pre>
|
||||||
|
{{ detail[host]|safe }}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
16
templates/summary.html
Normal file
16
templates/summary.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
{% for host in summary %}
|
||||||
|
<div class="summary">
|
||||||
|
<h2>{{host}}/{{session.proto}}: {{command}}</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>Name</th><th>protocol</th><th>table</th><th>state</th><th>since</th><th>info</th></tr>
|
||||||
|
{% for row in summary[host] %}
|
||||||
|
<tr class="{{ loop.cycle('odd', 'even') }}"><td><a href="/detail/{{host}}/{{session.proto}}?q={{row.name}}">{{row.name}}</a></td><td>{{row.proto}}</td><td>{{row.table}}</td><td>{{row.state}}</td><td>{{row.since}}</td><td>{{row.info}}</td></tr>
|
||||||
|
{% else %}
|
||||||
|
<tr><td>{{summary[host].error}}</td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
6
test.py
Normal file
6
test.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
from bird import BirdSocketSingleton
|
||||||
|
|
||||||
|
s = BirdSocketSingleton("h3", 9994)
|
||||||
|
print s.cmd("show protocols")
|
||||||
|
print s.cmd("show protocols all TETANEUTRAL")
|
33
toolbox.py
Normal file
33
toolbox.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
from dns import resolver,reversename
|
||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
def resolve(n, q):
|
||||||
|
return str(resolver.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
|
||||||
|
|
||||||
|
def ipv4_is_valid(n):
|
||||||
|
try:
|
||||||
|
socket.inet_pton(socket.AF_INET, n)
|
||||||
|
return True
|
||||||
|
except socket.error:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def ipv6_is_valid(n):
|
||||||
|
try:
|
||||||
|
socket.inet_pton(socket.AF_INET6, n)
|
||||||
|
return True
|
||||||
|
except socket.error:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue