Commit a9b79b43 authored by Tomas Hlavacek's avatar Tomas Hlavacek

Add BGP graph for Cisco.

parent 6cfdc78a
......@@ -21,7 +21,7 @@ from ulgcisco import *
from ulgbird import *
routers = [
CiscoRouter(host='testrouter1.core.company.com', user='xyz', password='xyz'),
CiscoRouter(host='testrouter2.core.company.com', user='xyz', password='xyz'),
CiscoRouter(host='testrouter1.core.company.com', user='xyz', password='xyz', asn=1234),
CiscoRouter(host='testrouter2.core.company.com', user='xyz', password='xyz', asn=2345),
BirdRouterLocal('/var/run/bird.ctl')
]
......@@ -229,6 +229,9 @@ class Session(object):
self.range=resrange
self.save()
def showRange(self):
return self.getCommand().showRange()
def getMaxRange(self):
return self.resultlines
......@@ -264,6 +267,9 @@ class DecoratorHelper:
def getErrorURL(self,parameters={}):
return self.getURL('error',parameters)
def getSpecialContentURL(self,sessionid,parameters={}):
return self.getURL('getfile',dict({'sessionid':sessionid},**parameters))
def getRouterID(self,router):
for ridx,r in enumerate(config.routers):
if(r == router):
......@@ -287,6 +293,12 @@ class DecoratorHelper:
def copy_session(self,session):
return Session(copy=session)
def img(self,url,alternative_text=None):
if(alternative_text):
return ('<img src="%s" alt="%s">' % (url,alternative_text))
else:
return ('<img src="%s">' % url)
class ULGCgi:
def __init__(self):
......@@ -297,6 +309,11 @@ class ULGCgi:
self.decorator_helper = DecoratorHelper()
def print_text_html(self):
print "Content-Type: text/html\n"
def increaseUsage(self):
u = 0
try:
......@@ -465,6 +482,9 @@ class ULGCgi:
def renderULGResult(self,sessionid=None,resrange=0):
def getRangeStepURLs(session,decorator_helper):
if(not session.showRange()):
return None
cur_range = session.getRange()
max_range = session.getMaxRange()
......@@ -573,28 +593,34 @@ class ULGCgi:
if(session == None):
return self.HTTPRedirect(self.decorator_helper.getErrorURL())
return session.getCommand().getSpecialContent(session,**params)
# speciality here: the function is responsible for printing the output itself
session.getCommand().getSpecialContent(session,**params)
def index(self, **params):
self.print_text_html()
if('sessionid' in params.keys()):
print self.renderULGIndex(sessionid=params['sessionid'])
else:
print self.renderULGIndex()
def runcommand(self,routerid=0,commandid=0,sessionid=None,**params):
self.print_text_html()
print self.renderULGAction(routerid,commandid,sessionid,**params)
def display(self,sessionid=None,**params):
self.print_text_html()
print self.renderULGResult(sessionid,**params)
def getfile(self,sessionid=None,**params):
print self.getULGSpecialContent(sessionid,**params)
self.getULGSpecialContent(sessionid,**params)
def error(self,sessionid=None,**params):
self.print_text_html()
print self.renderULGError(sessionid,**params)
def debug(self,**params):
self.print_text_html()
print self.renderULGDebug(**params)
# main
......@@ -604,8 +630,6 @@ if __name__=="__main__":
form = cgi.FieldStorage()
handler = ULGCgi()
print "Content-Type: text/html\n"
action = form.getvalue('action',None)
params = dict([(k,form.getvalue(k)) for k in form.keys() if k != 'action'])
......
......@@ -28,6 +28,7 @@ import string
import defaults
import ulgmodel
import ulggraph
# module globals
STRING_EXPECT_SSH_NEWKEY='Are you sure you want to continue connecting'
......@@ -54,6 +55,111 @@ MAC_ADDRESS_REGEXP = '^[0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4}$'
BGP_RED_STATES = ['Idle', 'Active', '(NoNeg)']
BGP_YELLOW_STATES = ['Idle (Admin)',]
REGEX_SH_BGP_UNI_ASLINE = '^\s*([0-9\s]+)(|,.*)\s*$'
regex_sh_bgp_uni_asline = re.compile(REGEX_SH_BGP_UNI_ASLINE)
REGEX_SH_BGP_UNI_AGGR = '\s*\(aggregated by ([0-9]+) [0-9a-fA-F:\.]+\).*'
regex_sh_bgp_uni_aggr = re.compile(REGEX_SH_BGP_UNI_AGGR)
REGEX_SH_BGP_UNI_RECUSE = '\s*\(received & used\).*'
regex_sh_bgp_uni_recuse = re.compile(REGEX_SH_BGP_UNI_RECUSE)
REGEX_SH_BGP_UNI_RECONLY = '\s*\(received-only\).*'
regex_sh_bgp_uni_reconly = re.compile(REGEX_SH_BGP_UNI_RECONLY)
REGEX_SH_BGP_UNI_TABLE_START = '\s*Advertised\s+to\s+update-groups.*'
regex_sh_bgp_uni_table_start = re.compile(REGEX_SH_BGP_UNI_TABLE_START)
REGEX_SH_BGP_UNI_PEERLINE = '\s*([0-9a-fA-F:\.]+)\s.*from\s.*'
regex_sh_bgp_uni_peerline = re.compile(REGEX_SH_BGP_UNI_PEERLINE)
REGEX_SH_BGP_UNI_ORIGLINE_BEST = '\s*Origin\s.*\sbest.*'
regex_sh_bgp_uni_origline_best = re.compile(REGEX_SH_BGP_UNI_ORIGLINE_BEST)
COMMAND_NAME_GRAPH4 = 'Graph - show bgp ipv4 uni <IP subnet>'
def cisco_parse_sh_bgp_uni(lines,prependas):
def split_ases(ases):
return str.split(ases)
def get_info(info):
res = {'recuse':False, 'reconly':False, 'aggr':None}
for g in info.split(','):
if(regex_sh_bgp_uni_recuse.match(g)):
res['recuse'] = True
if(regex_sh_bgp_uni_reconly.match(g)):
res['reconly'] = True
m = regex_sh_bgp_uni_aggr.match(g)
if(m):
res['aggr'] = m.group(1)
return res
paths = []
table_started = False
start_string = False
for l in str.splitlines(lines):
if(table_started):
m = regex_sh_bgp_uni_asline.match(l)
if(m):
ases = ["AS"+str(asn) for asn in [prependas] + split_ases(m.group(1))]
infotext = m.group(2)
if(infotext):
paths.append((ases,get_info(infotext)))
else:
paths.append((ases,{'recuse':False, 'reconly':False, 'aggr':None}))
continue
m = regex_sh_bgp_uni_peerline.match(l)
if(m):
paths[-1][1]['peer'] = m.group(1)
continue
m = regex_sh_bgp_uni_origline_best.match(l)
if(m):
paths[-1][1]['recuse'] = True
else:
if(start_string):
table_started = True
else:
if(regex_sh_bgp_uni_table_start.match(l)):
start_string = True
return paths
def reduce_bgp_paths(paths):
def has_valid(paths,onepath):
for p in paths:
if((onepath[0] == p[0]) and (not p[1]['reconly'])):
return True
return False
def has_used(paths,onepath):
for p in paths:
if((onepath[0] == p[0]) and (p[1]['recuse'])):
return True
return False
def assign_value(path):
if(path[1]['recuse']):
return 1
elif(path[1]['reconly']):
return 100
else:
return 10
newpaths = []
for p in paths:
if((p[1]['reconly']) and has_valid(paths,p)):
pass
elif(not (p[1]['recuse']) and has_used(paths,p)):
pass
else:
newpaths.append(p)
return sorted(newpaths,key=assign_value)
def matchCiscoBGPLines(header,lines):
# Match cisco lines formatted to be aligned to columns. Like:
......@@ -472,16 +578,21 @@ class CiscoCommandGraphShowBgpIPv46Uni(ulgmodel.TextCommand):
def __init__(self,peers,name=None):
ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[
ulgmodel.TextParameter(ulgmodel.TextParameter(IPV4_SUBNET_REGEXP,name=defaults.STRING_IPSUBNET))],
ulgmodel.TextParameter(IPV4_SUBNET_REGEXP,name=defaults.STRING_IPSUBNET)],
name=name)
def decorateResult(self,session,decorator_helper=None):
# TODO return img src=... to result when finished
# decorator_helper.getSpecialContentURL(session!!!)
pass
return (decorator_helper.img(decorator_helper.getSpecialContentURL(session.getSessionId()),"BGP graph"),1)
def getSpecialContent(self,session,**params):
return ''
print "Content-type: image/png\n"
paths = cisco_parse_sh_bgp_uni(session.getResult(),str(session.getRouter().getASN()))
ulggraph.bgp_graph_gen(reduce_bgp_paths(paths),start=session.getRouter().getName(),
end=session.getParameters()[0])
def showRange(self):
return False
class CiscoCommandGraphShowBgpIPv4Uni(CiscoCommandGraphShowBgpIPv46Uni):
COMMAND_TEXT='show bgp ipv4 unicast %s'
......@@ -497,6 +608,7 @@ class CiscoRouter(ulgmodel.RemoteRouter):
_show_bgp_ipv6_uni_neigh = CiscoCommandShowBgpIPv6Neigh(self.getBGPIPv6Peers())
_show_bgp_ipv6_uni_neigh_advertised = CiscoCommandShowBgpIPv6NeighAdv(self.getBGPIPv6Peers())
_show_bgp_ipv6_uni_neigh_received_routes = CiscoCommandShowBgpIPv6NeighRecv(self.getBGPIPv6Peers())
_graph_show_bgp_ipv4_uni = CiscoCommandGraphShowBgpIPv4Uni(self.getBGPIPv4Peers(),COMMAND_NAME_GRAPH4)
return [ulgmodel.TextCommand('show version'),
ulgmodel.TextCommand('show interfaces status'),
......@@ -520,9 +632,10 @@ class CiscoRouter(ulgmodel.RemoteRouter):
ulgmodel.TextCommand('show ipv6 neighbors %s',[ulgmodel.TextParameter('.*',name=defaults.STRING_NONEORINTORIPADDRESS)]),
ulgmodel.TextCommand('show mac-address-table address %s',[ulgmodel.TextParameter(MAC_ADDRESS_REGEXP,name=defaults.STRING_MACADDRESS)]),
ulgmodel.TextCommand('show mac-address-table interface %s',[ulgmodel.TextParameter('.*',name=defaults.STRING_INTERFACE)]),
_graph_show_bgp_ipv4_uni,
]
def __init__(self, host, user, password, port=22, commands=None, enable_bgp=True):
def __init__(self, host, user, password, port=22, commands=None, enable_bgp=True, asn='My ASN'):
self.setHost(host)
self.setPort(port)
self.setUser(user)
......@@ -530,6 +643,7 @@ class CiscoRouter(ulgmodel.RemoteRouter):
self.bgp_ipv4_peers = []
self.bgp_ipv6_peers = []
self.setName(host)
self.setASN(asn)
if enable_bgp:
if(defaults.rescan_on_display):
......
#!/usr/bin/env python
#
# ULG - Universal Looking Glass
# by Tomas Hlavacek (tomas.hlavacek@nic.cz)
# last udate: June 21 2012
#
# 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/>.
# Imports
import defaults
import gv
from pygraph.classes.digraph import digraph
from pygraph.readwrite.dot import write
USED_FILL_COLOR = '#DC738A'
FONT_SIZE = 10
LABEL_FONT_SIZE = 8
def bgp_graph_gen(graphdata,start=None,end=None):
gr = digraph()
def add_edge(graph,v1,v2,color='black',style='solid',penwidth=1.0,fillcolor=None,label=None):
params = [('fontsize',FONT_SIZE)]
if(fillcolor):
params = params + [('style','filled'),('fillcolor',fillcolor)]
if(not graph.has_node(v1)):
graph.add_node(v1,attrs=params)
if(not graph.has_node(v2)):
graph.add_node(v2,attrs=params)
if(v1==v2):
return
eparams = [('color',color),('style',style),('penwidth',penwidth)]
if(label):
eparams = eparams + [('fontsize',LABEL_FONT_SIZE),('label',' '+label+' ')]
if(not graph.has_edge((v1,v2))):
graph.add_edge((v1,v2),attrs=eparams)
for gd in graphdata:
params = {}
if(gd[1]['recuse']):
params['color']='red'
params['penwidth']=1.2
params['fillcolor']=USED_FILL_COLOR
elif(gd[1]['reconly']):
params['color']='gray'
params['style']='dotted'
params['penwidth']=0.5
else:
params['penwidth']=0.5
params['color']='blue'
if(start):
if(not gr.has_node(start)):
gr.add_node(start,attrs=[('shape','box'),('style','filled'),('fillcolor',USED_FILL_COLOR),('fontsize',FONT_SIZE)])
add_edge(gr,start,gd[0][0],**{'color':'red','penwidth':1.2,'style':'dashed','fillcolor':USED_FILL_COLOR})
i = 0
while (i < len(gd[0])):
if(i+1 < len(gd[0])):
if((i==0) and ('peer' in gd[1])):
add_edge(gr,gd[0][i],gd[0][i+1],label=gd[1]['peer'],**params)
else:
add_edge(gr,gd[0][i],gd[0][i+1],**params)
i = i+1
if(not gr.has_node(end)):
gr.add_node(end,attrs=[('shape','box'),('style','filled'),('fillcolor',USED_FILL_COLOR),('fontsize',FONT_SIZE)])
add_edge(gr,gd[0][-1],end,**{'color':'red','penwidth':1.2,'style':'dashed'})
dot = write(gr)
gvv = gv.readstring(dot)
gv.layout(gvv,'dot')
gv.render(gvv,'png')
......@@ -262,12 +262,16 @@ class TextCommand(object):
def getSpecialContent(self,session,**params):
raise Exception("getSpecialContet() is not implemented in ulgmodel.TextCommand.")
def showRange(self):
return True
class AnyCommand(TextCommand):
def __init__(self):
self.command=''
self.parameter = TextParameter('.+', name=defaults.STRING_COMMAND)
self.name=defaults.STRING_ANY
self.asn = 'My ASN'
def getCommandText(self,parameters=None):
c = ''
......@@ -335,6 +339,12 @@ class Router(object):
def getForkNeeded(self):
return False
def setASN(self,asn):
self.asn = asn
def getASN(self):
return self.asn
class RemoteRouter(Router):
def getHost(self):
return self.host
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment