1
0
Fork 0
mirror of https://github.com/sileht/bird-lg.git synced 2024-12-22 12:34:42 +01:00

Refactoring code

This commit is contained in:
Mehdi Abaakouk 2012-01-20 00:12:19 +01:00 committed by root
parent 41aca54fba
commit 69ee07ffc2
13 changed files with 677 additions and 285 deletions

148
bird.py Normal file
View 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
View 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
View file

@ -1,211 +1,177 @@
#!/usr/bin/python
import sys, os, subprocess, re
from dns import resolver,reversename
from flask import Flask, render_template, jsonify, redirect
import sys
import os
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__)
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
app.config.from_pyfile('lg.cfg')
def cleanup_output(text):
return "\n".join([ add_link(re.sub(r'^[0-9]*-', r' ', line)) for line in text.split("\n") if not line.startswith("0000") ])
def add_links(text):
if type(text) in [ str, unicode ]:
text = text.split("\n")
def add_link(text):
if text.strip().startswith("BGP.as_path:") or \
text.strip().startswith("Neighbor AS:") :
return re.sub(r'([0-9]*)',r'<a href="/whois/\1">\1</a>',text)
ret_text = []
for line in text:
if line.strip().startswith("BGP.as_path:") or \
line.strip().startswith("Neighbor AS:") :
ret_text.append(re.sub(r'(\d+)',r'<a href="/whois/\1" class="whois">\1</a>',line))
else:
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)
def set_session(req_type, hosts, proto, request_args):
session.update( {
"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:
return text
#@app.errorhandler(404)
#def notfound(error):
# return redirect("/")
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("/")
def hello():
return render_template('index.html')
@app.route("/whois/<asnum>")
def whois(asnum):
output = "<h3> Whois as" + asnum + "</h3><pre>"
def error_page(text):
return render_template('error.html', data = { "error": text } ), 500
@app.route("/whois/<query>")
def whois(query):
try:
asnum = int(asnum)
asnum = int(query)
query = "as%d"%asnum
except:
output += "Failed to parse as%s"%asnum
else:
output += subprocess.Popen(['whois', 'as%d'%asnum], stdout=subprocess.PIPE).communicate()[0].decode('utf-8', 'ignore')
output += "</pre>"
return render_template('index.html', output=output, typ="whois", asnum=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)
@app.route("/prefix_detail/<host>/<proto>/")
@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)
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("/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
@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:
app.logger.warning("couldn't parse: %s" , line)
# 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:
allowed = False
output = '<h3>' + host + ' (' + proto + ') show route for ' + prefix + (prefix != qprefix and " (%s)"%qprefix or "") + (mask and '/' + mask or '' ) + (all and " all" or "") + '</h3>'
if allowed:
if mask: qprefix = qprefix +"/"+mask
if mask: prefix = prefix +"/"+mask
ok, string = get_cmd_result(host , proto, "show route for " + qprefix + (all and " all" or ""))
if ok:
output += '<pre>' + cleanup_output(string) + '</pre>'
summary[host] = data
else:
output += string
else:
if prefix and qprefix != "unresolvable":
output += prefix + ' not valid'
elif prefix:
output += prefix + ' unresolvable'
summary[host] = { "error" : res }
return render_template('summary.html', summary=summary, command=command)
@app.route("/detail/<hosts>/<proto>")
def detail(hosts, proto):
name = request.args.get('q', '')
set_session("detail", hosts, proto, name)
command = "show protocols all %s" % name
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:
output += 'prefix missing'
detail[host] = { "status": "bird error: %s" % "\n".join(res), "description": "" }
return render_template('detail.html', detail=detail, command=command)
return render_template('index.html', output=output, typ="prefix" + (all and "_detail" or ""), host=host+"/"+proto, prefix=prefix)
@app.route("/where/<hosts>/<proto>")
def show_route_where(hosts, proto):
return show_route("where", hosts, proto)
@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>'
@app.route("/where_detail/<hosts>/<proto>")
def show_route_where_detail(hosts, proto):
return show_route("where_detail", hosts, proto)
if not name:
output += "name missing"
@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:
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>'
mask = ""
if len(expression.split("/")) > 1:
expression, mask = (expression.split("/"))
if not mask and proto == "ipv4" : mask = "32"
if not mask and proto == "ipv6" : mask = "128"
if not mask_is_valid(mask):
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:
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)
detail[host] = "bird error: %s" % "\n".join(res)
return render_template('route.html', detail=detail, command=command, expression=expression )
app.secret_key = app.config["SESSION_KEY"]
app.debug = True
if __name__ == "__main__":
app.run("0.0.0.0")

5
lg.wsgi Normal file
View 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

File diff suppressed because one or more lines are too long

View file

@ -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
View 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
View file

@ -0,0 +1,4 @@
{% extends "layout.html" %}
{% block body %}
{{ data.error|safe }}
{% endblock %}

View file

@ -1,119 +1,132 @@
<!doctype html>
<title>Tetaneutral.net looking glass</title>
<meta charset="UTF-8">
<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-impromptu.3.2.min.js') }}"></script>
<script type="text/javascript">
$(function() {
$("#typ option[value={{typ}}]").attr("selected", "selected")
$("#host option[value='{{host}}']").attr("selected", "selected")
$("#host").change()
$("#typ").change(function(){
$("#name").hide()
$("#prefix").hide()
$("#submit").hide()
switch ($("#typ").val())
{
case "summary":
if ($("#typ").val() != "{{typ}}")
$("#submit").click()
break;
case "detail":
$("#name").show()
$("#submit").show()
break;
case "prefix":
$("#submit").show()
$("#prefix").show()
if ($("#prefix").val()) {
$("#submit").click()
}
break;
case "prefix_detail":
$("#submit").show()
$("#prefix").show()
if ($("#prefix").val()) {
$("#submit").click()
}
break;
}
$( 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").change()
$("#submit").click( function(){
switch ($("#typ").val())
{
case "summary":
document.location = "/summary/" + $("#host").val() ;
break;
case "detail":
document.location = "/detail/" + $("#host").val() + "/" + $("#name").val() ;
break;
case "prefix":
document.location = "/prefix/" + $("#host").val() + "/" + $("#prefix").val() ;
break;
case "prefix_detail":
document.location = "/prefix_detail/" + $("#host").val() + "/" + $("#prefix").val() ;
break;
}
});
});
keypress_handler = function(e) {
if (e.which == 13) {
$("#submit").click()
}
};
$("#prefix").keypress(keypress_handler)
$("#name").keypress(keypress_handler)
previous_req_type = "{{session.req_type}}".replace("_detail","")
function update_view(){
$("#request_args").hide()
$("#submit").hide()
$("#host").change(function (){
switch ($("#typ").val())
{
case "summary":
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":
$("#submit").click()
break;
default:
$("#submit").show()
$("#request_args").show()
if ($("#request_args").val()) {
$("#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 == "/"){
$("#submit").click()
} else {
$("#request_args").focus()
}
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()
});
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(){
switch ($("#req_type").val())
{
case "summary":
document.location = "/summary/" + $("#hosts").val() + "/" + $("#proto").val();
break;
default:
document.location = "/" + $("#req_type").val() + "/" + $("#hosts").val() + "/" + $("#proto").val() + "?q=" + $("#request_args").val() ;
break;
}
});
if (document.location.pathname == "/"){
$("#submit").click()
}
});
</script>
<div id="page">
<h1>Tetaneutral.net Looking Glass</h1>
<div id="header">
<h1>Tetaneutral.net<br />Looking Glass</h1>
<form>
<select id="host">
<option value="gw/ipv4">gw (ipv4)</option>
<option value="gw/ipv6">gw (ipv6)</option>
<option value="h3/ipv4">h3 (ipv4)</option>
<option value="h3/ipv6">h3 (ipv6)</option>
</select>
<select id="typ">
<option value="summary">summary</option>
<option value="detail">detail</option>
<option value="prefix">prefix</option>
<option value="prefix_detail">prefix detail</option>
</select>
<input type="text" id="name" style="display:none" value="{{ name }}"></input>
<input type="text" id="prefix" style="display:none" value="{{ prefix }}"></input>
<input type="button" id="submit" value="submit"></input>
<input id="hosts" type="hidden" value="{{session.hosts}}" />
<input id="proto" type="hidden" value="{{session.proto}}" />
<input id="req_type" type="hidden" value="{{session.req_type}}" />
<ul class="proto">
<li id="ipv4">ipv4</li>
<li id="ipv6">ipv6</li>
</ul>
<ul class="hosts">
<li id="{{all_hosts}}">all</li>
{% for host in config.HOST_MAPPING %}
<li id="{{host}}">{{host}}</li>
{% endfor %}
</ul>
<ul class="req_type">
<li id="summary">summary</li>
<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>
</div>
<div id="content">
{% if errors %}
<div id="error">{{errors}}</div>
{% endif %}
{% block body %}{% endblock %}
</div>
</div>

14
templates/route.html Normal file
View 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
View 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
View 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
View 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