diff --git a/data/frt.py b/data/fr.py similarity index 82% rename from data/frt.py rename to data/fr.py index 2e8137e..c2f72f1 100644 --- a/data/frt.py +++ b/data/fr.py @@ -17,7 +17,12 @@ # limitations under the License. # +import os + from jnpr.junos.factory import loadyaml from os.path import splitext -_YAML_ = splitext(__file__)[0] + '.yml' -globals().update(loadyaml(_YAML_)) \ No newline at end of file + +_YAML_ = os.getcwd() + '/data/frt.yml' +globals().update(loadyaml(_YAML_)) +_YAML_ = os.getcwd() + '/data/frft.yml' +globals().update(loadyaml(_YAML_)) diff --git a/data/frft.yml b/data/frft.yml new file mode 100644 index 0000000..660ff60 --- /dev/null +++ b/data/frft.yml @@ -0,0 +1,20 @@ +# $Id$ +# Yaml widgets for BGP flow filter data extraction +# - FlowFilterTable extracts 'show firewall filter __flowspec_default_inet__' +# - FlowFilterView identifies most relevant filter fields +# including counter-name, packet-cpunt and byte-count +--- +FlowFilterTable: + rpc: get-firewall-filter-information + args: + filtername: __flowspec_default_inet__ + args_key: filtername + item: filter-information/counter + key: counter-name + view: FlowFilterView + +FlowFilterView: + fields: + name: counter-name + packet_count: packet-count + byte_count: byte-count \ No newline at end of file diff --git a/main.py b/main.py index f2c5d68..48db8d4 100644 --- a/main.py +++ b/main.py @@ -23,10 +23,13 @@ import hashlib import datetime import yaml +import re +import pprint from jnpr.junos.utils.config import Config from jnpr.junos import Device -from data.frt import FlowRoutesTable +from jnpr.junos.exception import ConfigLoadError +from data.fr import FlowRoutesTable, FlowFilterTable class MyDev(object): @@ -34,109 +37,192 @@ class MyDev(object): def __init__(self): self.dev_user = None - self.dev_ip = None self.dev_pw = None self.age_out_interval = None self.flow_active = dict() self.flow_config = dict() + self.filter_active = dict() + self.routers = list() - def addNewFlowRoute(self, flowData=None): + def addNewFlowRoute(self, flowRouteData=None): # env = Environment(autoescape=False, # loader=FileSystemLoader('./template'), trim_blocks=False, lstrip_blocks=False) # template = env.get_template('set-flow-route.conf') - with Device(host=self.dev_ip, user=self.dev_user, password=self.dev_pw) as dev: - cu = Config(dev) - cu.lock() - cu.load(template_path='template/set-flow-route.conf', template_vars=flowData, merge=True) - cu.commit() - cu.unlock() + with Device(host=self.routers[0]['rt1']['ip'], user=self.dev_user, password=self.dev_pw) as dev: - self.flow_config[flowData['flowRouteName']] = {'dstPrefix': flowData['dstPrefix'], - 'srcPrefix': flowData['srcPrefix'], - 'protocol': flowData['protocol'], 'dstPort': flowData['dstPort'], - 'srcPort': flowData['srcPort'], 'action': flowData['action']} + try: - def modFlowRoute(self, flowData=None): + cu = Config(dev) + cu.lock() - with Device(host=self.dev_ip, user=self.dev_user, password=self.dev_pw) as dev: - cu = Config(dev) - cu.lock() - cu.load(template_path='template/mod-flow-route.conf', template_vars=flowData) - cu.commit() - cu.unlock() + cu.load(template_path='template/set-flow-route.conf', template_vars=flowRouteData, merge=True) + cu.commit() + cu.unlock() - self.flow_config[flowData['flowRouteName']] = {'dstPrefix': flowData['dstPrefix'], - 'srcPrefix': flowData['srcPrefix'], - 'protocol': flowData['protocol'], 'dstPort': flowData['dstPort'], - 'srcPort': flowData['srcPort'], 'action': flowData['action']} + except ConfigLoadError as cle: + return False, cle.message - def delFlowRoute(self, flowRoute=None): + self.flow_config[flowRouteData['flowRouteName']] = {'dstPrefix': flowRouteData['dstPrefix'], + 'srcPrefix': flowRouteData['srcPrefix'], + 'protocol': flowRouteData['protocol'], 'dstPort': flowRouteData['dstPort'], + 'srcPort': flowRouteData['srcPort'], 'action': flowRouteData['action']} + return True, 'Successfully added new flow route' - with Device(host=self.dev_ip, user=self.dev_user, password=self.dev_pw) as dev: + def modFlowRoute(self, flowRouteData=None): + + with Device(host=self.routers[0]['rt1']['ip'], user=self.dev_user, password=self.dev_pw) as dev: cu = Config(dev) cu.lock() - cu.load(template_path='template/delete-flow-route.conf', template_vars=flowRoute, merge=True) + cu.load(template_path='template/mod-flow-route.conf', template_vars=flowRouteData) cu.commit() cu.unlock() - self.flow_config.pop(flowRoute['flowRouteName'], None) + self.flow_config[flowRouteData['flowRouteName']] = {'dstPrefix': flowRouteData['dstPrefix'], + 'srcPrefix': flowRouteData['srcPrefix'], + 'protocol': flowRouteData['protocol'], + 'dstPort': flowRouteData['dstPort'], + 'srcPort': flowRouteData['srcPort'], + 'action': flowRouteData['action']} + + def delFlowRoute(self, flowRouteData=None): + + with Device(host=self.routers[0]['rt1']['ip'], user=self.dev_user, password=self.dev_pw) as dev: + + try: + + cu = Config(dev) + cu.lock() + cu.load(template_path='template/delete-flow-route.conf', template_vars=flowRouteData, merge=True) + cu.commit() + cu.unlock() + + except ConfigLoadError as cle: + return False, cle.message + + self.flow_config.pop(flowRouteData['flowRouteName'], None) + + return True, 'Sucessfully deleted flow route' def getActiveFlowRoutes(self): - with Device(host=self.dev_ip, user=self.dev_user, password=self.dev_pw) as dev: + for router in self.routers: + + for name, value in router.iteritems(): + + with Device(host=value['ip'], user=self.dev_user, password=self.dev_pw) as dev: + + # data = dev.rpc.get_config(filter_xml='routing-options/flow/route/name') + frt = FlowRoutesTable(dev) + frt.get() + + for flow in frt: - # data = dev.rpc.get_config(filter_xml='routing-options/flow/route/name') - frt = FlowRoutesTable(dev) - frt.get() + destination = flow.destination.split(',') - for flow in frt: + for index, item in enumerate(destination): + _item = item.split('=') + destination[index] = _item[1] if len(_item) > 1 else _item[0] - destination = flow.destination.split(',') + hash_object = hashlib.sha512(b'{0}{1}'.format(str(destination), str(value['ip']))) + hex_dig = hash_object.hexdigest() - for index, item in enumerate(destination): - _item = item.split('=') - destination[index] = _item[1] if len(_item) > 1 else _item[0] + _age = dict() - hash_object = hashlib.sha512(b'{0}'.format(str(destination))) - hex_dig = hash_object.hexdigest() + if ':' not in flow.age: + _age['current'] = datetime.datetime.strptime(flow.age, '%S').time() + elif len(flow.age.split(':')) == 2: + _age['current'] = datetime.datetime.strptime(flow.age, '%M:%S').time() + elif len(flow.age.split(':')) == 3: + _age['current'] = datetime.datetime.strptime(flow.age, '%H:%M:%S').time() + else: + 'error in time format' + + pattern = r'([^\s]+)' + regex = re.compile(pattern) + _krt_actions = re.findall(regex, flow.tsi) + + if len(_krt_actions) <= 4: + krt_actions = _krt_actions + else: + krt_actions = _krt_actions[4] + + if isinstance(flow.action, str): + if 'traffic-action' in flow.action: + commAction = flow.action.split(":")[1].lstrip().strip() + else: + commAction = None + + elif isinstance(flow.action, list): + commAction = flow.action[1].split(':')[1].lstrip().strip() + else: + commAction = None + + if hex_dig not in self.flow_active: + + self.flow_active[hex_dig] = {'router': name, 'term': flow.term, 'destination': destination, + 'commAction': commAction, 'krtAction': krt_actions, + 'age': _age['current'].strftime("%H:%M:%S"), + 'hash': hex_dig, 'status': 'new'} + else: - _age = dict() + if 'term:N/A' in flow['term']: + self.flow_active.pop(hex_dig, None) - if ':' not in flow.age: - _age['current'] = datetime.datetime.strptime(flow.age, '%S').time() - elif len(flow.age.split(':')) == 2: - _age['current'] = datetime.datetime.strptime(flow.age, '%M:%S').time() - elif len(flow.age.split(':')) == 3: - _age['current'] = datetime.datetime.strptime(flow.age, '%H:%M:%S').time() - else: - 'error in time format' + if _age['current'] > datetime.datetime.strptime(str(self.age_out_interval), + '%H:%M:%S').time(): + self.flow_active[hex_dig]['status'] = 'old' - if hex_dig not in self.flow_active: + try: + if hex_dig in self.flow_active: + self.flow_active[hex_dig].update({'term': flow.term, 'destination': destination, + 'commAction': commAction, 'krtAction': krt_actions, + 'age': _age['current'].strftime("%H:%M:%S")}) + except KeyError as ke: + return False, ke.message - self.flow_active[hex_dig] = {'term': flow.term, 'destination': destination, - 'action': flow.tsi.split(':')[1].strip() if flow.tsi else flow.action, - 'age': _age['current'].strftime("%H:%M:%S"), - 'status': 'new'} - else: + return True, self.flow_active - if 'term:N/A' in flow['term']: - self.flow_active.pop(hex_dig, None) + def get_flow_route_filter(self): - if _age['current'] > datetime.datetime.strptime(str(self.age_out_interval), '%H:%M:%S').time(): - self.flow_active[hex_dig]['status'] = 'old' + if self.routers: - self.flow_active[hex_dig].update({'term': flow.term, 'destination': destination, - 'action': flow.tsi.split(':')[ - 1].strip() if flow.tsi else flow.action, - 'age': _age['current'].strftime("%H:%M:%S")}) + for router in self.routers: - return self.flow_active + for name, value in router.iteritems(): + self.filter_active[name] = list() + + with Device(host=value['ip'], user=self.dev_user, password=self.dev_pw) as dev: + + frft = FlowFilterTable(dev) + frft.get() + + for filter in frft: + + data = filter.name.split(',') + + for didx, item in enumerate(data): + _item = item.split('=') + data[didx] = _item[1] if len(_item) > 1 else _item[0] + + self.filter_active[name].append({'data': data, 'packet_count': filter.packet_count, + 'byte_count': filter.byte_count}) + + return True, self.filter_active def load_flow_config_data(self): - with Device(host=self.dev_ip, user=self.dev_user, password=self.dev_pw) as dev: + dev_ip = list() + + for router in self.routers: + + for name, value in router.iteritems(): + + if 'rr' in value['type']: + dev_ip.append(value['ip']) + + with Device(host=dev_ip[0], user=self.dev_user, password=self.dev_pw) as dev: data = dev.rpc.get_config(options={'format': 'json'}) if 'route' in data['configuration']['routing-options']['flow']: @@ -156,21 +242,20 @@ def load_flow_config_data(self): 'protocol': route['match']['protocol'], 'dstPort': route['match']['destination-port'], 'srcPort': route['match']['source-port'], 'action': _action} - - return self.flow_config + return True, self.flow_config else: - return None + return False, None - def save_settings(self, dev_user=None, dev_ip=None, dev_pw=None, age_out_interval=None): + def save_settings(self, dev_user=None, dev_pw=None, routers=None, age_out_interval=None): self.dev_user = dev_user - self.dev_ip = dev_ip self.dev_pw = dev_pw self.age_out_interval = age_out_interval + self.routers = routers with open('ui/config.yml', 'w') as fp: - config = {'dev_user': self.dev_user, 'dev_ip': self.dev_ip, 'dev_pw': self.dev_pw, + config = {'dev_user': self.dev_user, 'dev_pw': self.dev_pw, 'routers': self.routers, 'age_out_interval': self.age_out_interval} yaml.safe_dump(config, fp, default_flow_style=False) @@ -180,9 +265,9 @@ def load_settings(self): _config = fp.read() config = yaml.safe_load(_config) self.dev_user = config['dev_user'] - self.dev_ip = config['dev_ip'] self.dev_pw = config['dev_pw'] self.age_out_interval = config['age_out_interval'] + self.routers = config['routers'] class BGPFlow(object): @@ -216,42 +301,65 @@ def POST(self, action=None): if action == 'add': input_json = cherrypy.request.json - self.my_dev.addNewFlowRoute(flowData=input_json) - - return True, 'Added new Flow Route' - - elif action == 'save': + resp = self.my_dev.addNewFlowRoute(flowRouteData=input_json) - input_json = cherrypy.request.json - self.my_dev.save_settings(dev_user=input_json['user'], dev_pw=input_json['password'], - dev_ip=input_json['ip'], age_out_interval=input_json['age_out_interval']) - return True, 'Saved settings' + return resp elif action == 'mod': input_json = cherrypy.request.json - self.my_dev.modFlowRoute(flowData=input_json) + self.my_dev.modFlowRoute(flowRouteData=input_json) return True, 'Modified flow route' elif action == 'del': input_json = cherrypy.request.json - self.my_dev.delFlowRoute(flowRoute=input_json) + resp = self.my_dev.delFlowRoute(flowRouteData=input_json) + return resp + + elif action == 'save': - return True, 'Deleted Flow Route' + input_json = cherrypy.request.json + self.my_dev.save_settings(dev_user=input_json['user'], dev_pw=input_json['password'], + dev_ip=input_json['ip'], age_out_interval=input_json['age_out_interval']) + return True, 'Successfully saved configuration settings' else: return False, 'Action not defined' @cherrypy.expose -class DataTable(object): +class Frt(object): def __init__(self, my_dev=None): self.my_dev = my_dev @cherrypy.tools.json_out() def POST(self): - froutes = self.my_dev.load_flow_config_data() - return True, froutes + resp = self.my_dev.getActiveFlowRoutes() + return resp + + +@cherrypy.expose +class Frtc(object): + + def __init__(self, my_dev=None): + self.my_dev = my_dev + + @cherrypy.tools.json_out() + def POST(self): + resp = self.my_dev.load_flow_config_data() + return resp + + +@cherrypy.expose +class Frft(object): + + def __init__(self, my_dev=None): + self.my_dev = my_dev + + @cherrypy.tools.json_out() + def POST(self): + resp = self.my_dev.get_flow_route_filter() + return resp if __name__ == '__main__': @@ -270,7 +378,17 @@ def POST(self): 'tools.response_headers.on': True, 'tools.response_headers.headers': [('Content-Type', 'text/plain')], }, - '/dt': { + '/api/frt': { + 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), + 'tools.response_headers.on': True, + 'tools.response_headers.headers': [('Content-Type', 'text/plain')], + }, + '/api/frct': { + 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), + 'tools.response_headers.on': True, + 'tools.response_headers.headers': [('Content-Type', 'text/plain')], + }, + '/api/frft': { 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), 'tools.response_headers.on': True, 'tools.response_headers.headers': [('Content-Type', 'text/plain')], @@ -281,5 +399,7 @@ def POST(self): my_dev.load_settings() webapp = BGPFlow() webapp.api = BGPFlowWS(my_dev=my_dev) - webapp.dt = DataTable(my_dev=my_dev) + webapp.api.frt = Frt(my_dev=my_dev) + webapp.api.frct = Frtc(my_dev=my_dev) + webapp.api.frft = Frft(my_dev=my_dev) cherrypy.quickstart(webapp, '/', conf) diff --git a/readme.md b/readme.md index 59f147b..456e227 100644 --- a/readme.md +++ b/readme.md @@ -59,6 +59,11 @@ age_out_interval: 00:01:00 dev_ip: 10.11.111.120 dev_pw: juniper123 dev_user: root +asbrs: + - asbr1: + ip: 10.11.111.121 + - asbr2: + ip: 10.11.111.122 ``` - Start tool + Python binary should be in path if not use `which python2.7` to obtian path info diff --git a/template/delete-flow-route.conf b/template/delete-flow-route.conf index b02fdc5..71f6e6b 100644 --- a/template/delete-flow-route.conf +++ b/template/delete-flow-route.conf @@ -1,6 +1,6 @@ routing-options { flow { delete: - route {{flowRouteName}}; + route {{ flowRouteName }}; } } diff --git a/template/mod-flow-route.conf b/template/mod-flow-route.conf index 7172a52..bc1c7d2 100644 --- a/template/mod-flow-route.conf +++ b/template/mod-flow-route.conf @@ -1,6 +1,6 @@ routing-options { flow { - route {{flowRouteName}} { + route {{ flowRouteName }} { replace: match { destination {{dstPrefix}}; diff --git a/template/set-flow-route.conf b/template/set-flow-route.conf index a817570..59858f8 100644 --- a/template/set-flow-route.conf +++ b/template/set-flow-route.conf @@ -1,6 +1,6 @@ routing-options { flow { - route {{flowRouteName}} { + route {{ flowRouteName }} { match { destination {{dstPrefix}}; {%- if dstPort is defined and dstPort !=None %} diff --git a/ui/config.yml b/ui/config.yml index 9e25f41..54835e2 100644 --- a/ui/config.yml +++ b/ui/config.yml @@ -1,4 +1,10 @@ age_out_interval: 00:01:00 -dev_ip: 10.11.111.120 dev_pw: juniper123 dev_user: root +routers: + - rt1: + type: rr + ip: 10.11.111.120 + - rt2: + type: asbr + ip: 10.11.111.121 \ No newline at end of file diff --git a/ui/index.html b/ui/index.html index 6822867..f533b8a 100644 --- a/ui/index.html +++ b/ui/index.html @@ -33,17 +33,19 @@
-