Compare commits

..

No commits in common. "master" and "2012.1" have entirely different histories.

30 changed files with 406 additions and 13077 deletions

2
.gitignore vendored
View File

@ -1,4 +1,2 @@
*.pyc
*.pyo
lg.cfg
lgproxy.cfg

View File

@ -3,9 +3,9 @@
Copyright (c) 2006 Mehdi Abaakouk
This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
the Free Software Foundation; either version 3 of the License
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
@ -13,4 +13,5 @@
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA

View File

@ -1,116 +1,42 @@
BIRD-LG
=======
Overview
--------
this is a looking glass for the Internet Routing Daemon "Bird"
This is a looking glass for the Internet Routing Daemon "Bird".
Software is split in two parts:
- lgproxy.py:
It must be installed and started on all bird nodes. It act as a proxy to make traceroute and bird query on the node.
Access restriction to this web service can be done in file "lgproxy.cfg". Two access restriction methods can be configured:
based on source IP address or based on a shared secret. Both methods can be used at the same time.
software is splited onto two parts:
- lg-proxy.py:
It must be install and started on all bird node. It act as a proxy to make traceroute and bird query on the node
Also the access restriction to this web service can be done is file "lg-proxy.cfg" (only ip based restriction for now)
- lg.py:
This is the frontend, a web based UI that request information to all lg-proxy.py nodes
The domain and the list of all bird node can be done
This is the frontend, a web based UI that request informations to all lgproxy.py nodes.
The domain and the list of all bird nodes can be done.
```
***************
+--> * lgproxy.py *
+--> * lg-proxy.py *
| ***************
|
******** ******************* | ***************
* USER * ----> * webserver/lg.py *--+--> * lgproxy.py *
* USER * ----> * webserver/lg.py *--+--> * lg-proxy.py *
******** ******************* | ***************
|
| ***************
+--> * lgproxy.py *
+--> * lg-proxy.py *
***************
```
Installation
------------
The web service (lg.py) depends on:
bird-lg depend on :
- python-flask >= 0.8
- python-dnspython
- python-pydot
- graphviz
- whois
The proxy running on routers (lgproxy.py) depends on:
Each service can by embeded in any webserver by follow regular python-flask configuration
- python-flask >= 0.8
- traceroute
- ping
Only tested with bird 1.2.5
Each service can be embedded in any webserver by following regular python-flask configuration.
It is also possible to run the services directly with python for developping / testing:
source code under GPL 3.0, powered by Flask, jQuery and Bootstrap
python2 lg.py
python2 lgproxy.py
Systemd unit files are provided in the `init/` subdirectory.
Configuration
-------------
On your routers, copy `lgproxy.cfg.example` to `lgproxy.cfg` and edit the values.
On the web host, copy `lg.cfg.example` to `lg.cfg` and edit the values.
License
-------
Source code is under GPL 3.0, powered by Flask, jQuery and Bootstrap.
Copyright © 2012 Mehdi Abaakouk <sileht@sileht.net>
Happy users
-----------
* https://lg.ovh.net/
* http://lg.beta.as6453.net/
* https://lg.hamburg.freifunk.net/start
* http://lg.ring.nlnog.net/
* https://lg.tetaneutral.net/
* https://lg.gitoyen.net/
* http://lg.as5580.net/
* https://lg.ldn-fai.net/
* http://lg.arn-fai.net
* https://lg.grenode.net/
* http://lg.dataix.ru/
* https://lg.blix.com/
* https://lg.man-da.de/
* http://route-server.belwue.net/
* https://lg.exn.uk/
* https://meerblick.io/
* https://lg.as49697.net/
* http://lg.netnation.com/
* http://lg.edxnetwork.eu/
* https://lg.hivane.net/
* https://atw.hu/looking-glass
* http://lg.sibir-ix.ru/
* http://lg.interlan.ro/
* http://lg.as35266.net/
* https://lg.atw.co.hu/
* http://lg.as60362.net/
* http://lg.stuttgart-ix.de/
* http://www.bet3000.tv/
* https://lg.franceix.net/
* https://lg.fullsave.net/
* http://lg.catnix.net/
* https://lg.worldstream.nl/
* https://lg.angolacables.co.ao/
Copyright (c) 2012 Mehdi Abaakouk <sileht@sileht.net>

241
bird.py
View File

@ -25,150 +25,153 @@ import sys
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
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 __init__(self, host="", port="", file=""):
self.__file = file
self.__host = host
self.__port = port
self.__sock = None
def __connect(self):
if self.__sock: return
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)
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")
# 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 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 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 = ""
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]
while code not in END_CODES:
data = self.__sock.recv(BUFSIZE)
for line in lines:
code = line[0:4]
lines = (lastline + data).split("\n")
if len(data) == BUFSIZE:
lastline = lines[-1]
lines = lines[:-1]
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
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
return True, parsed_string
__all__ = ['BirdSocketSingleton' , 'BirdSocket' ]
__all__ = ['BirdSocketSingleton', 'BirdSocket']

View File

@ -1,18 +0,0 @@
# bird-lg init
Systemd unit files for the bird-lg webservice, and for the proxy service running on routers.
You need to adapt the exact command used to start the service (`ExecStart`) and the `User`
under which it should run. Don't run the services as root!
## Installation
Copy the init file under `/etc/systemd/system/` and run:
systemctl daemon-reload
systemctl start bird-lg-proxy
systemctl enable bird-lg-proxy
## Credits
Adapted from <http://gitlab.netlib.re/arn/arn-confs/tree/master/routing/looking-glass>

View File

@ -1,31 +0,0 @@
# Copyright (C) 2015-2018 Alsace Réseau Neutre
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Debian GNU/Linux: store this in /etc/systemd/system/
[Unit]
Description=BIRD Looking-Glass proxy
After=bird.service bird6.service
[Service]
Type=simple
ExecStart=/usr/local/lookingglass/lgproxy.py
User=lgproxy
Restart=on-failure
[Install]
WantedBy=multi-user.target

View File

@ -1,32 +0,0 @@
# Copyright (C) 2015-2018 Alsace Réseau Neutre
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Debian GNU/Linux: store this in /etc/systemd/system/
[Unit]
Description=BIRD Looking-Glass service
After=apache2.service
[Service]
Type=simple
User=lookingglass
ExecStart=/usr/local/lookingglass/lg.py
Restart=on-failure
[Install]
WantedBy=multi-user.target

4
lg-proxy.cfg Normal file
View File

@ -0,0 +1,4 @@
ACCESS_LIST = ["91.224.149.206", "178.33.111.110"]
IPV4_SOURCE="91.224.148.1"
IPV6_SOURCE="2a01:6600:8000::1"

84
lg-proxy.py Normal file
View File

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# vim: ts=4
###
#
# Copyright (c) 2006 Mehdi Abaakouk
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#
###
import subprocess
from urllib import unquote
from bird import BirdSocket
from flask import Flask, request, abort
app = Flask(__name__)
app.config.from_pyfile('lg-proxy.cfg')
def check_accesslist():
if app.config["ACCESS_LIST"] and request.remote_addr not in app.config["ACCESS_LIST"]:
abort(401)
@app.route("/traceroute")
@app.route("/traceroute6")
def traceroute():
check_accesslist()
src = []
if request.path == '/traceroute6':
o = "-6"
if app.config.get("IPV6_SOURCE",""):
src = [ "-s", app.config.get("IPV6_SOURCE") ]
else:
o = "-4"
if app.config.get("IPV4_SOURCE",""):
src = [ "-s", app.config.get("IPV4_SOURCE") ]
query = request.args.get("q","")
query = unquote(query)
command = [ 'traceroute' , o ] + src + [ '-A', '-q1', '-N32', '-w1', '-m15', query ]
result = subprocess.Popen( command , stdout=subprocess.PIPE).communicate()[0].decode('utf-8', 'ignore').replace("\n","<br>")
return result
@app.route("/bird")
@app.route("/bird6")
def bird():
check_accesslist()
if request.path == "/bird": b = BirdSocket(file="/var/run/bird.ctl")
elif request.path == "/bird6": b = BirdSocket(file="/var/run/bird6.ctl")
else: return "No bird socket selected"
query = request.args.get("q","")
query = unquote(query)
status, result = b.cmd(query)
b.close()
# FIXME: use status
return result
if __name__ == "__main__":
app.debug = True
app.run("0.0.0.0")

20
lg.cfg Normal file
View File

@ -0,0 +1,20 @@
DOMAIN = "tetaneutral.net"
PROXY = {
"gw": 5000,
"h3": 5000,
}
# Used for bgpmap
ROUTER_IP = {
"gw" : [ "91.224.148.2", "2a01:6600:8000::175" ],
"h3" : [ "91.224.148.3", "2a01:6600:8000::131" ]
}
AS_NUMBER = {
"gw" : "197422",
"h3" : "197422"
}
SESSION_KEY = '\xd77\xf9\xfa\xc2\xb5\xcd\x85)`+H\x9d\xeeW\\%\xbe/\xbaT\x89\xe8\xa7'

View File

@ -1,42 +0,0 @@
# Configuration file example for lg.py
# Adapt and copy to lg.cfg
WEBSITE_TITLE="Bird-LG / Looking Glass"
DEBUG = False
LOG_FILE="/var/log/lg.log"
LOG_LEVEL="WARNING"
# Keep log history indefinitely by default.
LOG_NUM_DAYS=0
DOMAIN = "tetaneutral.net"
# Used to optionally restrict access to lgproxy based on a shared secret.
# Empty string or unset = no shared secret is used to run queries on lgproxies.
SHARED_SECRET="ThisTokenIsNotSecret"
BIND_IP = "0.0.0.0"
BIND_PORT = 5000
PROXY = {
"gw": 5000,
"h3": 5000,
}
# Used for bgpmap
ROUTER_IP = {
"gw" : [ "91.224.148.2", "2a01:6600:8000::175" ],
"h3" : [ "91.224.148.3", "2a01:6600:8000::131" ]
}
AS_NUMBER = {
"gw" : "197422",
"h3" : "197422"
}
#WHOIS_SERVER = "whois.foo.bar"
# DNS zone to query for ASN -> name mapping
ASN_ZONE = "asn.cymru.com"
# Used for secure session storage, change this
SESSION_KEY = '\xd77\xf9\xfa\xc2\xb5\xcd\x85)`+H\x9d\xeeW\\%\xbe/\xbaT\x89\xe8\xa7'

365
lg.py Normal file → Executable file
View File

@ -20,45 +20,20 @@
#
###
import base64
from datetime import datetime
import subprocess
import logging
from logging.handlers import TimedRotatingFileHandler
import re
from urllib2 import urlopen
from urllib import quote, unquote
import json
import random
import argparse
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 toolbox import mask_is_valid, ipv6_is_valid, ipv4_is_valid, resolve, save_cache_pickle, load_cache_pickle
import pydot
from flask import Flask, render_template, jsonify, redirect, session, request, abort, Response, Markup
parser = argparse.ArgumentParser()
parser.add_argument('-c', dest='config_file', help='path to config file', default='lg.cfg')
args = parser.parse_args()
from flask import Flask, render_template, jsonify, redirect, session, request, abort, Response
app = Flask(__name__)
app.config.from_pyfile(args.config_file)
app.secret_key = app.config["SESSION_KEY"]
app.debug = app.config["DEBUG"]
file_handler = TimedRotatingFileHandler(filename=app.config["LOG_FILE"], when="midnight", backupCount=app.config.get("LOG_NUM_DAYS", 0))
file_handler.setLevel(getattr(logging, app.config["LOG_LEVEL"].upper()))
app.logger.addHandler(file_handler)
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("|") ]
app.config.from_pyfile('lg.cfg')
def add_links(text):
@ -73,17 +48,14 @@ def add_links(text):
# Some heuristic to create link
if line.strip().startswith("BGP.as_path:") or \
line.strip().startswith("Neighbor AS:"):
ret_text.append(re.sub(r'(\d+)', r'<a href="/whois?q=\1" class="whois">\1</a>', line))
ret_text.append(re.sub(r'(\d+)', r'<a href="/whois/\1" class="whois">\1</a>', line))
else:
line = re.sub(r'([a-zA-Z0-9\-]*\.([a-zA-Z]{2,3}){1,2})(\s|$)', r'<a href="/whois?q=\1" class="whois">\1</a>\3', line)
line = re.sub(r'(?<=\[)AS(\d+)', r'<a href="/whois?q=\1" class="whois">AS\1</a>', line)
line = re.sub(r'(\d+\.\d+\.\d+\.\d+)', r'<a href="/whois?q=\1" class="whois">\1</a>', line)
if len(request.path) >= 2:
hosts = "/".join(request.path.split("/")[2:])
else:
hosts = "/"
line = re.sub(r'([a-zA-Z0-9\-]*\.([a-zA-Z]{2,3}){1,2})(\s|$)', r'<a href="/whois/\1" class="whois">\1</a>\3', line)
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)
hosts = "/".join(request.path.split("/")[2:])
line = re.sub(r'\[(\w+)\s+((|\d\d\d\d-\d\d-\d\d\s)(|\d\d:)\d\d:\d\d|\w\w\w\d\d)', r'[<a href="/detail/%s?q=\1">\1</a> \2' % hosts, line)
line = re.sub(r'(^|\s+)(([a-f\d]{0,4}:){3,10}[a-f\d]{0,4})', r'\1<a href="/whois?q=\2" class="whois">\2</a>', line, re.I)
line = re.sub(r'(^|\s+)(([a-f\d]{0,4}:){3,10}[a-f\d]{0,4})', r'\1<a href="/whois/\2" class="whois">\2</a>', line, re.I)
ret_text.append(line)
return "\n".join(ret_text)
@ -111,10 +83,7 @@ def set_session(request_type, hosts, proto, request_args):
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')
return subprocess.Popen(['whois', query], stdout=subprocess.PIPE).communicate()[0].decode('utf-8', 'ignore')
def bird_command(host, proto, query):
@ -123,9 +92,9 @@ def bird_command(host, proto, query):
def bird_proxy(host, proto, service, query):
"""Retreive data of a service from a running lgproxy on a remote node
"""Retreive data of a service from a running lg-proxy on a remote node
First and second arguments are the node and the port of the running lgproxy
First and second arguments are the node and the port of the running lg-proxy
Third argument is the service, can be "traceroute" or "bird"
Last argument, the query to pass to the service
@ -140,29 +109,18 @@ def bird_proxy(host, proto, service, query):
port = app.config["PROXY"].get(host, "")
if not port:
return False, 'Host "%s" invalid' % host
elif not path:
return False, 'Proto "%s" invalid' % proto
url = "http://%s" % (host)
if "DOMAIN" in app.config:
url = "%s.%s" % (url, app.config["DOMAIN"])
url = "%s:%d/%s?" % (url, port, path)
if "SHARED_SECRET" in app.config:
url = "%ssecret=%s&" % (url, app.config["SHARED_SECRET"])
url = "%sq=%s" % (url, quote(query))
try:
f = urlopen(url)
resultat = f.read()
status = True # retreive remote status
except IOError:
resultat = "Failed to retrieve URL for host %s" % host
app.logger.warning("Failed to retrieve URL for host %s: %s", host, url)
status = False
return status, resultat
if not port or not path:
return False, "Host/Proto not allowed"
else:
url = "http://%s.%s:%d/%s?q=%s" % (host, app.config["DOMAIN"], port, path, quote(query))
try:
f = urlopen(url)
resultat = f.read()
status = True # retreive remote status
except IOError:
resultat = "Failed retreive url: %s" % url
status = False
return status, resultat
@app.context_processor
@ -197,26 +155,22 @@ def hello():
def error_page(text):
return render_template('error.html', errors=[text]), 500
return render_template('error.html', error=text), 500
@app.errorhandler(400)
def incorrect_request(e):
return render_template('error.html', warnings=["The server could not understand the request"]), 400
return render_template('error.html', warning="The server could not understand the request"), 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
return render_template('error.html', warning="The requested URL was not found on the server."), 404
def get_query():
q = unquote(request.args.get('q', '').strip())
return q
@app.route("/whois")
def whois():
query = get_query()
if not query:
@app.route("/whois/<query>")
def whois(query):
if not query.strip():
abort(400)
try:
@ -227,59 +181,46 @@ def whois():
if m:
query = query.groupdict()["domain"]
output = whois_command(query)
output = whois_command(query).replace("\n", "<br>")
return jsonify(output=output, title=query)
SUMMARY_UNWANTED_PROTOS = ["Kernel", "Static", "Device", "Direct"]
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 = {}
errors = []
error = []
for host in hosts.split("+"):
ret, res = bird_command(host, proto, command)
res = res.split("\n")
if len(res) > 1:
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)
if ret is False:
errors.append("%s" % res)
continue
summary[host] = data
else:
error.append("%s: bird command failed with error, %s" % (host, "\n".join(res)))
if len(res) <= 1:
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:
split = line.split()
if len(split) >= 5:
props = dict()
props["name"] = split[0]
props["proto"] = split[1]
props["table"] = split[2]
props["state"] = split[3]
props["since"] = split[4]
props["info"] = ' '.join(split[5:]) if len(split) > 5 else ""
data.append(props)
else:
app.logger.warning("couldn't parse: %s", line)
summary[host] = data
return render_template('summary.html', summary=summary, command=command, errors=errors)
return render_template('summary.html', summary=summary, command=command, error="<br>".join(error))
@app.route("/detail/<hosts>/<proto>")
def detail(hosts, proto):
name = get_query()
name = request.args.get('q', '').strip()
if not name:
abort(400)
@ -287,28 +228,21 @@ def detail(hosts, proto):
command = "show protocols all %s" % name
detail = {}
errors = []
error = []
for host in hosts.split("+"):
ret, res = bird_command(host, proto, command)
res = res.split("\n")
if len(res) > 1:
detail[host] = {"status": res[1], "description": add_links(res[2:])}
else:
error.append("%s: bird command failed with error, %s" % (host, "\n".join(res)))
if ret is False:
errors.append("%s" % res)
continue
if len(res) <= 1:
errors.append("%s: bird command failed with error, %s" % (host, "\n".join(res)))
continue
detail[host] = {"status": res[1], "description": add_links(res[2:])}
return render_template('detail.html', detail=detail, command=command, errors=errors)
return render_template('detail.html', detail=detail, command=command, error="<br>".join(error))
@app.route("/traceroute/<hosts>/<proto>")
def traceroute(hosts, proto):
q = get_query()
q = request.args.get('q', '').strip()
if not q:
abort(400)
@ -325,17 +259,11 @@ def traceroute(hosts, proto):
except:
return error_page("%s is unresolvable or invalid for %s" % (q, proto))
errors = []
infos = {}
for host in hosts.split("+"):
status, resultat = bird_proxy(host, proto, "traceroute", q)
if status is False:
errors.append("%s" % resultat)
continue
infos[host] = add_links(resultat)
return render_template('traceroute.html', infos=infos, errors=errors)
return render_template('traceroute.html', infos=infos)
@app.route("/adv/<hosts>/<proto>")
@ -378,18 +306,31 @@ def show_route_for_bgpmap(hosts, proto):
return show_route("prefix_bgpmap", hosts, proto)
ASNAME_CACHE_FILE = "/tmp/asname_cache.pickle"
ASNAME_CACHE = load_cache_pickle(ASNAME_CACHE_FILE, {})
def get_as_name(_as):
"""Returns 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
# Warning, the server can be blacklisted from ripe is too many requests are done
"""
if not _as:
return "AS?????"
if not _as.isdigit():
return _as.strip()
name = get_asn_from_as(_as)[-1].replace(" ", "\r", 1)
return "AS%s | %s" % (_as, name)
if _as not in ASNAME_CACHE:
whois_answer = whois_command("as%s" % _as)
as_name = re.search('(as-name|ASName): (.*)', whois_answer)
if as_name:
ASNAME_CACHE[_as] = as_name.group(2).strip()
else:
ASNAME_CACHE[_as] = _as
save_cache_pickle(ASNAME_CACHE_FILE, ASNAME_CACHE)
if ASNAME_CACHE[_as] == _as:
return "AS%s" % _as
else:
return "AS%s\r%s" % (_as, ASNAME_CACHE[_as])
def get_as_number_from_protocol_name(host, proto, protocol):
ret, res = bird_command(host, proto, "show protocols all %s" % protocol)
@ -404,28 +345,20 @@ def get_as_number_from_protocol_name(host, proto, protocol):
def show_bgpmap():
"""return a bgp map in a png file, from the json tree in q argument"""
data = get_query()
data = request.args.get('q', '').strip()
if not data:
abort(400)
data = base64.b64decode(data)
data = json.loads(data)
data = json.loads(unquote(data))
graph = pydot.Dot('BGPMAP', graph_type='digraph')
nodes = {}
edges = {}
prepend_as = {}
def escape(label):
label = label.replace("&", "&amp;")
label = label.replace(">", "&gt;")
label = label.replace("<", "&lt;")
return label
def add_node(_as, **kwargs):
if _as not in nodes:
kwargs["label"] = '<<TABLE CELLBORDER="0" BORDER="0" CELLPADDING="0" CELLSPACING="0"><TR><TD ALIGN="CENTER">' + escape(kwargs.get("label", get_as_name(_as))).replace("\r","<BR/>") + "</TD></TR></TABLE>>"
kwargs["label"] = '<<TABLE CELLBORDER="0" BORDER="0" CELLPADDING="0" CELLSPACING="0"><TR><TD ALIGN="CENTER">' + kwargs.get("label", get_as_name(_as)).replace("\r","<BR/>") + "</TD></TR></TABLE>>"
nodes[_as] = pydot.Node(_as, style="filled", fontsize="10", **kwargs)
graph.add_node(nodes[_as])
return nodes[_as]
@ -441,24 +374,11 @@ def show_bgpmap():
edges[edge_tuple] = edge
elif "label" in kwargs and kwargs["label"]:
e = edges[edge_tuple]
label_without_star = kwargs["label"].replace("*", "")
if e.get_label() is not None:
labels = e.get_label().split("\r")
else:
return edges[edge_tuple]
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)
label = escape("\r".join(labels))
e.set_label(label)
e.set_label(e.get_label() + "\r" + kwargs["label"])
return edges[edge_tuple]
for host, asmaps in data.iteritems():
if "DOMAIN" in app.config:
add_node(host, label= "%s\r%s" % (host.upper(), app.config["DOMAIN"].upper()), shape="box", fillcolor="#F5A9A9")
else:
add_node(host, label= "%s" % (host.upper()), shape="box", fillcolor="#F5A9A9")
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:
@ -466,9 +386,9 @@ def show_bgpmap():
edge = add_edge(as_number, nodes[host])
edge.set_color("red")
edge.set_style("bold")
#colors = [ "#009e23", "#1a6ec1" , "#d05701", "#6f879f", "#939a0e", "#0e9a93", "#9a0e85", "#56d8e1" ]
previous_as = None
hosts = data.keys()
for host, asmaps in data.iteritems():
first = True
@ -480,64 +400,39 @@ def show_bgpmap():
hop_label = ""
for _as in asmap:
if _as == previous_as:
if not prepend_as.get(_as, None):
prepend_as[_as] = {}
if not prepend_as[_as].get(host, None):
prepend_as[_as][host] = {}
if not prepend_as[_as][host].get(asmap[0], None):
prepend_as[_as][host][asmap[0]] = 1
prepend_as[_as][host][asmap[0]] += 1
continue
if not hop:
hop = True
if _as not in hosts:
hop_label = _as
hop_label = _as
if first:
hop_label = hop_label + "*"
continue
else:
hop_label = ""
if _as == asmap[-1]:
add_node(_as, fillcolor="#F5A9A9", shape="box", )
else:
add_node(_as, fillcolor=(first and "#F5A9A9" or "white"), )
if hop_label:
edge = add_edge(nodes[previous_as], nodes[_as], label=hop_label, fontsize="7")
else:
edge = add_edge(nodes[previous_as], nodes[_as], fontsize="7")
add_node(_as, fillcolor=(first and "#F5A9A9" or "white"))
edge = add_edge(nodes[previous_as], nodes[_as] , label=hop_label, fontsize="7")
hop_label = ""
if first or _as == asmap[-1]:
if first:
edge.set_style("bold")
edge.set_color("red")
elif edge.get_style() != "bold":
elif edge.get_color() != "red":
edge.set_style("dashed")
edge.set_color(color)
previous_as = _as
first = False
for _as in prepend_as:
for n in set([ n for h, d in prepend_as[_as].iteritems() for p, n in d.iteritems() ]):
graph.add_edge(pydot.Edge(*(_as, _as), label=" %dx" % n, color="grey", fontcolor="grey"))
fmt = request.args.get('fmt', 'png')
#response = Response("<pre>" + graph.create_dot() + "</pre>")
if fmt == "png":
response = Response(graph.create_png(), mimetype='image/png')
elif fmt == "svg":
response = Response(graph.create_svg(), mimetype='image/svg+xml')
else:
abort(400, "Incorrect format")
response.headers['Last-Modified'] = datetime.now()
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '-1'
return response
node = add_node(previous_as)
node.set_shape("box")
#return Response("<pre>" + graph.create_dot() + "</pre>")
return Response(graph.create_png(), mimetype='image/png')
def build_as_tree_from_raw_bird_ouput(host, proto, text):
@ -546,29 +441,21 @@ def build_as_tree_from_raw_bird_ouput(host, proto, text):
path = None
paths = []
net_dest = None
peer_protocol_name = ""
for line in text:
line = line.strip()
expr = re.search(r'(.*)unicast\s+\[(\w+)\s+', line)
expr = re.search(r'(.*)via\s+([0-9a-fA-F:\.]+)\s+on.*\[(\w+)\s+', line)
if expr:
if expr.group(1).strip():
net_dest = expr.group(1).strip()
peer_protocol_name = expr.group(2).strip()
expr2 = re.search(r'(.*)via\s+([0-9a-fA-F:\.]+)\s+on\s+\S+(\s+\[(\w+)\s+)?', line)
if expr2:
if path:
path.append(net_dest)
paths.append(path)
path = None
if expr2.group(1).strip():
net_dest = expr2.group(1).strip()
if expr.group(1).strip():
net_dest = expr.group(1).strip()
peer_ip = expr2.group(2).strip()
if expr2.group(4):
peer_protocol_name = expr2.group(4).strip()
peer_ip = expr.group(2).strip()
peer_protocol_name = expr.group(3).strip()
# Check if via line is a internal route
for rt_host, rt_ips in app.config["ROUTER_IP"].iteritems():
# Special case for internal routing
@ -580,26 +467,9 @@ def build_as_tree_from_raw_bird_ouput(host, proto, text):
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)))]
expr3 = re.search(r'(.*)unreachable\s+\[(\w+)\s+', line)
if expr3:
if path:
path.append(net_dest)
paths.append(path)
path = None
if path is None:
path = [ expr3.group(2).strip() ]
if expr3.group(1).strip():
net_dest = expr3.group(1).strip()
if line.startswith("BGP.as_path:"):
ASes = line.replace("BGP.as_path:", "").strip().split(" ")
if path:
path.extend(ASes)
else:
path = ASes
path.extend(line.replace("BGP.as_path:", "").strip().split(" "))
if path:
path.append(net_dest)
paths.append(path)
@ -608,7 +478,7 @@ def build_as_tree_from_raw_bird_ouput(host, proto, text):
def show_route(request_type, hosts, proto):
expression = get_query()
expression = unquote(request.args.get('q', '')).strip()
if not expression:
abort(400)
@ -628,7 +498,7 @@ def show_route(request_type, hosts, proto):
command = "show route where net ~ [ " + expression + " ]" + all
else:
mask = ""
if len(expression.split("/")) == 2:
if len(expression.split("/")) > 1:
expression, mask = (expression.split("/"))
if not mask and proto == "ipv4":
@ -655,29 +525,26 @@ def show_route(request_type, hosts, proto):
command = "show route for " + expression + all
detail = {}
errors = []
error = []
for host in hosts.split("+"):
ret, res = bird_command(host, proto, command)
res = res.split("\n")
if ret is False:
errors.append("%s" % res)
continue
if len(res) <= 1:
errors.append("%s: bird command failed with error, %s" % (host, "\n".join(res)))
continue
if bgpmap:
detail[host] = build_as_tree_from_raw_bird_ouput(host, proto, res)
if len(res) > 1:
if bgpmap:
detail[host] = build_as_tree_from_raw_bird_ouput(host, proto, res)
else:
detail[host] = add_links(res)
else:
detail[host] = add_links(res)
error.append("%s: bird command failed with error, %s" % (host, "\n".join(res)))
if bgpmap:
detail = base64.b64encode(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 render_template((bgpmap and 'bgpmap.html' or 'route.html'), detail=detail, command=command, expression=expression, error="<br>".join(error))
app.secret_key = app.config["SESSION_KEY"]
app.debug = True
if __name__ == "__main__":
app.run(app.config.get("BIND_IP", "0.0.0.0"), app.config.get("BIND_PORT", 5000))
app.run("0.0.0.0")

View File

@ -1,8 +1,5 @@
import sys
import os
sitepath = os.path.realpath(os.path.dirname(__file__))
sys.path.insert(0, sitepath)
sys.path.insert(0,"/var/www/lg2.tetaneutral.net/")
from lg import app as application

View File

@ -1,28 +0,0 @@
# Configuration file example for lgproxy.py
# Adapt and copy to lgproxy.cfg
DEBUG=False
LOG_FILE="/var/log/lg-proxy/lg-proxy.log"
LOG_LEVEL="WARNING"
# Keep log history indefinitely by default.
LOG_NUM_DAYS=0
BIND_IP = "0.0.0.0"
BIND_PORT = 5000
# Used to restrict access to lgproxy based on source IP address.
# Empty list = any IP is allowed to run queries.
ACCESS_LIST = ["91.224.149.206", "178.33.111.110", "2a01:6600:8081:ce00::1"]
# Used to restrict access to lgproxy based on a shared secret (must also be configured in lg.cfg)
# Empty string or unset = no shared secret is required to run queries.
SHARED_SECRET="ThisTokenIsNotSecret"
# Used as source address when running traceroute (optional)
IPV4_SOURCE="198.51.100.42"
IPV6_SOURCE="2001:db8:42::1"
BIRD_SOCKET="/var/run/bird/bird.ctl"
BIRD6_SOCKET="/var/run/bird/bird6.ctl"

View File

@ -1,122 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# vim: ts=4
###
#
# Copyright (c) 2006 Mehdi Abaakouk
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#
###
import sys
import logging
from logging.handlers import TimedRotatingFileHandler
from logging import FileHandler
import subprocess
from urllib import unquote
import argparse
from bird import BirdSocket
from flask import Flask, request, abort
parser = argparse.ArgumentParser()
parser.add_argument('-c', dest='config_file', help='path to config file', default='lgproxy.cfg')
args = parser.parse_args()
app = Flask(__name__)
app.debug = app.config["DEBUG"]
app.config.from_pyfile(args.config_file)
file_handler = TimedRotatingFileHandler(filename=app.config["LOG_FILE"], when="midnight", backupCount=app.config.get("LOG_NUM_DAYS", 0))
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.after_request
def access_log_after(response, *args, **kwargs):
app.logger.info("[%s] reponse %s, %s", request.remote_addr, request.url, response.status_code)
return response
def check_security():
if app.config["ACCESS_LIST"] and request.remote_addr not in app.config["ACCESS_LIST"]:
app.logger.info("Your remote address is not valid")
abort(401)
if app.config.get('SHARED_SECRET') and request.args.get("secret") != app.config["SHARED_SECRET"]:
app.logger.info("Your shared secret is not valid")
abort(401)
@app.route("/traceroute")
@app.route("/traceroute6")
def traceroute():
check_security()
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") ]
else:
traceroute = traceroute4
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'):
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","<br>")
return result
@app.route("/bird")
@app.route("/bird6")
def bird():
check_security()
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"
query = request.args.get("q","")
query = unquote(query)
status, result = b.cmd(query)
b.close()
# FIXME: use status
return result
if __name__ == "__main__":
app.logger.info("lgproxy start")
app.run(app.config.get("BIND_IP", "0.0.0.0"), app.config.get("BIND_PORT", 5000))

View File

@ -1,8 +0,0 @@
import sys
import os
sitepath = os.path.realpath(os.path.dirname(__file__))
sys.path.insert(0, sitepath)
from lgproxy import app as application

View File

@ -1,47 +0,0 @@
div.dataTables_length label {
float: left;
text-align: left;
}
div.dataTables_length select {
width: 75px;
}
div.dataTables_filter label {
float: right;
}
div.dataTables_info {
padding-top: 8px;
}
div.dataTables_paginate {
float: right;
margin: 0;
}
table.table {
clear: both;
margin-bottom: 6px !important;
}
table.table thead .sorting,
table.table thead .sorting_asc,
table.table thead .sorting_desc,
table.table thead .sorting_asc_disabled,
table.table thead .sorting_desc_disabled {
cursor: pointer;
*cursor: hand;
}
table.table thead .sorting { background: url('../img/sort_both.png') no-repeat center right; }
table.table thead .sorting_asc { background: url('../img/sort_asc.png') no-repeat center right; }
table.table thead .sorting_desc { background: url('../img/sort_desc.png') no-repeat center right; }
table.table thead .sorting_asc_disabled { background: url('../img/sort_asc_disabled.png') no-repeat center right; }
table.table thead .sorting_desc_disabled { background: url('../img/sort_desc_disabled.png') no-repeat center right; }
table.dataTable th:active {
outline: none;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,96 +0,0 @@
/* Default class modification */
$.extend( $.fn.dataTableExt.oStdClasses, {
"sWrapper": "dataTables_wrapper form-inline"
} );
/* API method to get paging information */
$.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
{
return {
"iStart": oSettings._iDisplayStart,
"iEnd": oSettings.fnDisplayEnd(),
"iLength": oSettings._iDisplayLength,
"iTotal": oSettings.fnRecordsTotal(),
"iFilteredTotal": oSettings.fnRecordsDisplay(),
"iPage": Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ),
"iTotalPages": Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength )
};
}
/* Bootstrap style pagination control */
$.extend( $.fn.dataTableExt.oPagination, {
"bootstrap": {
"fnInit": function( oSettings, nPaging, fnDraw ) {
var oLang = oSettings.oLanguage.oPaginate;
var fnClickHandler = function ( e ) {
e.preventDefault();
if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) {
fnDraw( oSettings );
}
};
$(nPaging).addClass('pagination').append(
'<ul>'+
'<li class="prev disabled"><a href="#">&larr; '+oLang.sPrevious+'</a></li>'+
'<li class="next disabled"><a href="#">'+oLang.sNext+' &rarr; </a></li>'+
'</ul>'
);
var els = $('a', nPaging);
$(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler );
$(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler );
},
"fnUpdate": function ( oSettings, fnDraw ) {
var iListLength = 5;
var oPaging = oSettings.oInstance.fnPagingInfo();
var an = oSettings.aanFeatures.p;
var i, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2);
if ( oPaging.iTotalPages < iListLength) {
iStart = 1;
iEnd = oPaging.iTotalPages;
}
else if ( oPaging.iPage <= iHalf ) {
iStart = 1;
iEnd = iListLength;
} else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) {
iStart = oPaging.iTotalPages - iListLength + 1;
iEnd = oPaging.iTotalPages;
} else {
iStart = oPaging.iPage - iHalf + 1;
iEnd = iStart + iListLength - 1;
}
for ( i=0, iLen=an.length ; i<iLen ; i++ ) {
// Remove the middle elements
$('li:gt(0)', an[i]).filter(':not(:last)').remove();
// Add the new list items and their event handlers
for ( j=iStart ; j<=iEnd ; j++ ) {
sClass = (j==oPaging.iPage+1) ? 'class="active"' : '';
$('<li '+sClass+'><a href="#">'+j+'</a></li>')
.insertBefore( $('li:last', an[i])[0] )
.bind('click', function (e) {
e.preventDefault();
oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength;
fnDraw( oSettings );
} );
}
// Add / remove disabled classes from the static elements
if ( oPaging.iPage === 0 ) {
$('li:first', an[i]).addClass('disabled');
} else {
$('li:first', an[i]).removeClass('disabled');
}
if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
$('li:last', an[i]).addClass('disabled');
} else {
$('li:last', an[i]).removeClass('disabled');
}
}
}
}
} );

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
const noArgReqs = ["summary"];
$(window).unload(function(){
$(".progress").show()
@ -12,9 +12,9 @@ function change_url(loc){
function reload(){
loc = "/" + request_type + "/" + hosts + "/" + proto;
if (!noArgReqs.includes(request_type)){
if (request_type != "summary" ){
if( request_args != undefined && request_args != ""){
loc = loc + "?q=" + encodeURIComponent(request_args);
loc = loc + "?q=" + request_args;
change_url(loc)
}
} else {
@ -22,7 +22,7 @@ function reload(){
}
}
function update_view(){
if (noArgReqs.includes(request_type))
if (request_type == "summary")
$(".navbar-search").hide();
else
$(".navbar-search").show();
@ -58,7 +58,7 @@ $(function(){
link = $(this).attr('href');
$.getJSON(link, function(data) {
$(".modal h3").html(data.title);
$(".modal .modal-body > p").css("white-space", "pre-line").text(data.output);
$(".modal .modal-body > p").html(data.output);
$(".modal").modal('show');
});
});
@ -94,12 +94,6 @@ $(function(){
});
$('.request_args').val(request_args);
update_view();
t = $('.table-summary')
if (t) t.dataTable( {
"bPaginate": false,
} );
});

View File

@ -1,13 +1,9 @@
{% extends "layout.html" %}
{% block body %}
<div style="float:right">
<a href="/bgpmap/?killcache=1&q={{detail}}&fmt=svg" target="_blank">SVG</a>
<a href="/bgpmap/?killcache=1&q={{detail}}&fmt=png" target="_blank">PNG</a>
</div>
<h3>{{session.hosts}}: {{command}}</h3>
{% if session.request_args != expression|replace("/32","")|replace("/128","") %}
<i>DNS: <a href="/whois/{{session.request_args}}" class="whois">{{session.request_args}}</a> => <a href="/whois/{{ expression|replace("/32","")|replace("/128","") }}" class="whois">{{expression|replace("/32","")|replace("/128","")}}</a></i><br />
{% endif %}<br />
<a href="/bgpmap/?killcache=1&q={{detail}}"><img src="/bgpmap/?q={{detail}}" /></a>
<a href="/bgpmap/?q={{detail}}"><img src="/bgpmap/?q={{detail}}" /></a>
<br />
{% endblock %}

View File

@ -1,12 +1,11 @@
<!doctype html>
<html lang="en">
<title>Tetaneutral.net looking glass</title>
<head>
<title>{{config.WEBSITE_TITLE|default("Bird-LG / Looking Glass") }}</title>
<meta charset="UTF-8">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/bootstrap-responsive.min.css') }}">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/docs.css') }}">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/DT_bootstrap.css') }}">
</head>
<body>
<div class="navbar navbar-fixed-top">
@ -18,7 +17,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="/">{{config.WEBSITE_TITLE|default("Bird-LG / Looking Glass") }}</a>
<a class="brand" href="/">{{config.DOMAIN|capitalize}} / Looking Glass</a>
<div class="navbar nav-collapse">
<ul class="nav nav-pills">
<li class="navbar-text">Nodes:&nbsp;&nbsp;</li>
@ -64,15 +63,11 @@
<div class="container-fluid">
<div class="row-fluid">
<div class="span8">
{% if warnings %}
<div class="alert alert-warning">
{% for warning in warnings %}{{warning}}<br />{% endfor %}
</div>
{% if warning %}
<div class="alert alert-warning">{{warning}}</div>
{% endif %}
{% if errors %}
<div class="alert alert-error">
{% for error in errors %}{{error}}<br />{% endfor %}
</div>
{% if error %}
<div class="alert alert-error">{{error}}</div>
{% endif %}
{% block body %}{% endblock %}
@ -84,7 +79,7 @@
<li class="nav-header">Request history</li>
{% for hosts, proto, request_type, request_args in session.history %}
<li{% if loop.first %} class="active"{% endif %}>
<a href="/{{ [request_type, hosts, proto]|join("/") }}{% if request_args %}?q={{request_args|urlencode}}{% endif %}">
<a href="/{{ [request_type, hosts, proto]|join("/") }}{% if request_args %}?q={{request_args}}{% endif %}">
{{hosts}}/{{proto}}: {{ commands_dict[request_type]|replace("...", request_args) }}
</a>
</li>
@ -116,8 +111,7 @@
</div>
<script type="text/javascript" src="{{url_for('static', filename='js/jquery.js') }}"></script>
<script type="text/javascript" src="{{url_for('static', filename='js/bootstrap.min.js') }}"></script>
<script type="text/javascript" src="{{url_for('static', filename='js/jquery.dataTables.js') }}"></script>
<script type="text/javascript" src="{{url_for('static', filename='js/DT_bootstrap.js') }}"></script>
<script type="text/javascript" src="{{url_for('static', filename='js/jquery-impromptu.3.2.min.js') }}"></script>
<script type="text/javascript">
request_type = "{{session.request_type}}";
request_args = "{{session.request_args}}";

View File

@ -3,7 +3,7 @@
{% for host in detail %}
<h3>
{{host}}: {{command}}
<small><a class="pull-right" href="/{{session.request_type|replace("_detail","")}}_bgpmap/{{session.hosts}}/{{session.proto}}?q={{session.request_args|urlencode}}">View the BGP map</a></small>
<small><a class="pull-right" href="/{{session.request_type|replace("_detail","")}}_bgpmap/{{session.hosts}}/{{session.proto}}?q={{session.request_args}}">View the BGP map</a></small>
</h3>
{% if session.request_args != expression|replace("/32","")|replace("/128","") %}
<i>DNS: <a href="/whois/{{session.request_args}}" class="whois">{{session.request_args}}</a> => <a href="/whois/{{ expression|replace("/32","")|replace("/128","") }}" class="whois">{{expression|replace("/32","")|replace("/128","")}}</a></i><br />

View File

@ -1,21 +1,14 @@
{% extends "layout.html" %}
{% block body %}
{% for host in summary %}
<h3 style="float:left">{{host}}: {{command}}</h3>
<table class="table table-striped table-bordered table-condensed table-summary">
<h3>{{host}}: {{command}}</h3><br />
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr><th>Name</th><th>protocol</th><th>table</th><th>state</th><th>since</th><th>info</th></tr>
</thead>
<tbody>
{% 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><span class="label label-{% if row.state == "up" %}success{% elif row.state == "down" %}default{% else %}important{% endif %}">{{row.state}}</span></td>
<td>{{row.since}}</td>
<td>{{row.info}}</td>
</tr>
<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 %}

View File

@ -22,25 +22,19 @@
from dns import resolver
import socket
import pickle
import xml.parsers.expat
dns_cache = resolver.LRUCache(max_size=10000)
resolv = resolver.Resolver()
resolv.timeout = 0.5
resolv.lifetime = 1
resolv.cache = dns_cache
def resolve(n, q):
return str(resolv.query(n,q)[0])
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
if not n:
return True
try:
mask = int(n)
return ( mask >= 1 and mask <= 128)
except:
return False
def ipv4_is_valid(n):
try:
@ -57,45 +51,19 @@ def ipv6_is_valid(n):
return False
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:
data = default
pkl_file.close()
return data
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
if isinstance(s, unicode):
s = s.encode("utf-8")
want_unicode = True
# the rest of this assumes that `s` is UTF-8
list = []
# create and initialize a parser object
p = xml.parsers.expat.ParserCreate("utf-8")
p.buffer_text = True
p.returns_unicode = want_unicode
p.CharacterDataHandler = list.append
# parse the data wrapped in a dummy element
# (needed so the "document" is well-formed)
p.Parse("<e>", 0)
p.Parse(s, 0)
p.Parse("</e>", 1)
# join the extracted strings and return
es = ""
if want_unicode:
es = u""
return es.join(list)