├── .gitignore ├── LICENSE ├── data ├── __init__.py ├── fr.py ├── frft.yml └── frt.yml ├── main.py ├── readme.md ├── requirements.txt ├── resources ├── Screen_Shot_2018-04-11_at_11.25.39.png ├── Screen_Shot_2018-04-11_at_11.26.18.png ├── Screen_Shot_2018-04-11_at_11.26.31.png ├── Screen_Shot_2018-04-11_at_11.26.42.png └── Screen_Shot_2018-04-11_at_11.26.53.png ├── template ├── delete-flow-route.conf ├── mod-flow-route.conf └── set-flow-route.conf ├── ui ├── config.yml ├── index.html ├── jnpr.png ├── ui.css └── ui.js └── utils ├── __init__.py └── testdata.py /.gitignore: -------------------------------------------------------------------------------- 1 | *retry 2 | attic 3 | .idea 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Juniper Networks, Inc. All rights reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JNPRAutomate/BGP_Flowspec_automation_with_PyEZ/223bca46ac4005247a3166d4dfee16e723dfd5a8/data/__init__.py -------------------------------------------------------------------------------- /data/fr.py: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER 3 | # 4 | # Copyright (c) 2018 Juniper Networks, Inc. 5 | # All rights reserved. 6 | # 7 | # Use is subject to license terms. 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the ?License?); you may not 10 | # use this file except in compliance with the License. You may obtain a copy of 11 | # the License at http://www.apache.org/licenses/LICENSE-2.0. 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | import os 21 | 22 | from jnpr.junos.factory import loadyaml 23 | from os.path import splitext 24 | 25 | _YAML_ = os.getcwd() + '/data/frt.yml' 26 | globals().update(loadyaml(_YAML_)) 27 | _YAML_ = os.getcwd() + '/data/frft.yml' 28 | globals().update(loadyaml(_YAML_)) 29 | -------------------------------------------------------------------------------- /data/frft.yml: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | # Yaml widgets for BGP flow filter data extraction 3 | # - FlowFilterTable extracts 'show firewall filter __flowspec_default_inet__' 4 | # - FlowFilterView identifies most relevant filter fields 5 | # including counter-name, packet-cpunt and byte-count 6 | --- 7 | FlowFilterTable: 8 | rpc: get-firewall-filter-information 9 | args: 10 | filtername: __flowspec_default_inet__ 11 | args_key: filtername 12 | item: filter-information/counter 13 | key: counter-name 14 | view: FlowFilterView 15 | 16 | FlowFilterView: 17 | fields: 18 | name: counter-name 19 | packet_count: packet-count 20 | byte_count: byte-count -------------------------------------------------------------------------------- /data/frt.yml: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | # Yaml widgets for BGP flow route data extraction 3 | # - FlowRoutesTable extracts 'show route table inetflow.0 extensive' 4 | # and rt-prefix-length variables are used as class keys() 5 | # in format 'term:1' etc. 6 | # - FlowRoutesView identifies most relevant fields 7 | # including destination as NLRI composition, age and action 8 | --- 9 | FlowRoutesTable: 10 | rpc: get-route-information 11 | args: 12 | extensive: True 13 | table: 'inetflow.0' 14 | args_key: table 15 | item: route-table/rt 16 | key: rt-destination 17 | view: FlowRoutesView 18 | 19 | FlowRoutesView: 20 | fields: 21 | destination: rt-destination 22 | term: rt-prefix-length 23 | active: rt-entry/active-tag 24 | age: rt-entry/age 25 | action: rt-entry/communities/community 26 | action_141: rt-entry/communities/extended-community 27 | tsi: tsi -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER 3 | # 4 | # Copyright (c) 2018 Juniper Networks, Inc. 5 | # All rights reserved. 6 | # 7 | # Use is subject to license terms. 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the ?License?); you may not 10 | # use this file except in compliance with the License. You may obtain a copy of 11 | # the License at http://www.apache.org/licenses/LICENSE-2.0. 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | 21 | import os 22 | import cherrypy 23 | import hashlib 24 | import datetime 25 | import yaml 26 | import re 27 | 28 | from jinja2 import Environment, FileSystemLoader 29 | from jnpr.junos.utils.config import Config 30 | from jnpr.junos import Device 31 | from jnpr.junos.exception import ConfigLoadError, CommitError 32 | from data.fr import FlowRoutesTable, FlowFilterTable 33 | 34 | 35 | class MyDev(object): 36 | 37 | def __init__(self): 38 | 39 | self.dev_user = None 40 | self.dev_pw = None 41 | self.age_out_interval = None 42 | self.flow_active = dict() 43 | self.flow_config = dict() 44 | self.filter_active = dict() 45 | self.routers = list() 46 | 47 | def addNewFlowRoute(self, flowRouteData=None): 48 | 49 | env = Environment(autoescape=False, 50 | loader=FileSystemLoader('./template'), trim_blocks=False, lstrip_blocks=False) 51 | template = env.get_template('set-flow-route.conf') 52 | 53 | # print template.render(flowRouteData) 54 | 55 | my_router = None 56 | for router in self.routers: 57 | 58 | for name, value in router.iteritems(): 59 | if 'rr' in value['type']: 60 | my_router = [value['ip']] 61 | 62 | with Device(host=my_router[0], user=self.dev_user, password=self.dev_pw) as dev: 63 | 64 | try: 65 | 66 | cu = Config(dev) 67 | cu.lock() 68 | 69 | cu.load(template_path='template/set-flow-route.conf', template_vars=flowRouteData, merge=True) 70 | cu.commit() 71 | cu.unlock() 72 | 73 | except ConfigLoadError as cle: 74 | return False, cle.message 75 | 76 | self.flow_config[flowRouteData['flowRouteName']] = { 77 | 'dstPrefix': flowRouteData['dstPrefix'] if 'dstPrefix' in flowRouteData else None, 78 | 'srcPrefix': flowRouteData['srcPrefix'] if 'srcPrefix' in flowRouteData else None, 79 | 'protocol': flowRouteData['protocol'] if 'protocol' in flowRouteData else None, 80 | 'dstPort': flowRouteData['dstPort'] if 'dstPort' in flowRouteData else None, 81 | 'srcPort': flowRouteData['srcPort'] if 'srcPort' in flowRouteData else None, 82 | 'action': flowRouteData['action']} 83 | return True, 'Successfully added new flow route' 84 | 85 | def modFlowRoute(self, flowRouteData=None): 86 | 87 | my_router = None 88 | for router in self.routers: 89 | 90 | for name, value in router.iteritems(): 91 | if 'rr' in value['type']: 92 | my_router = [value['ip']] 93 | 94 | with Device(host=my_router[0], user=self.dev_user, password=self.dev_pw) as dev: 95 | 96 | try: 97 | cu = Config(dev) 98 | cu.lock() 99 | cu.load(template_path='template/mod-flow-route.conf', template_vars=flowRouteData) 100 | cu.commit() 101 | cu.unlock() 102 | 103 | except CommitError as ce: 104 | return False, ce.message 105 | 106 | self.flow_config[flowRouteData['flowRouteName']] = { 107 | 'dstPrefix': flowRouteData['dstPrefix'] if 'dstPrefix' in flowRouteData else None, 108 | 'srcPrefix': flowRouteData['srcPrefix'] if 'srcPrefix' in flowRouteData else None, 109 | 'protocol': flowRouteData['protocol'] if 'protocol' in flowRouteData else None, 110 | 'dstPort': flowRouteData['dstPort'] if 'dstPort' in flowRouteData else None, 111 | 'srcPort': flowRouteData['srcPort'] if 'srcPort' in flowRouteData else None, 112 | 'action': flowRouteData['action']} 113 | 114 | return True, 'Successfully modified flow route' 115 | 116 | def delFlowRoute(self, flowRouteData=None): 117 | 118 | my_router = None 119 | for router in self.routers: 120 | 121 | for name, value in router.iteritems(): 122 | if 'rr' in value['type']: 123 | my_router = [value['ip']] 124 | 125 | with Device(host=my_router[0], user=self.dev_user, password=self.dev_pw) as dev: 126 | 127 | try: 128 | 129 | cu = Config(dev) 130 | cu.lock() 131 | cu.load(template_path='template/delete-flow-route.conf', template_vars=flowRouteData, merge=True) 132 | cu.commit() 133 | cu.unlock() 134 | 135 | except ConfigLoadError as cle: 136 | return False, cle.message 137 | 138 | self.flow_config.pop(flowRouteData['flowRouteName'], None) 139 | 140 | return True, 'Sucessfully deleted flow route' 141 | 142 | def getActiveFlowRoutes(self): 143 | 144 | t = datetime.datetime.strptime(self.age_out_interval, "%H:%M:%S") 145 | self.flow_active = dict() 146 | 147 | for router in self.routers: 148 | 149 | for name, value in router.iteritems(): 150 | 151 | with Device(host=value['ip'], user=self.dev_user, password=self.dev_pw) as dev: 152 | 153 | # data = dev.rpc.get_config(filter_xml='routing-options/flow/route/name') 154 | frt = FlowRoutesTable(dev) 155 | frt.get() 156 | 157 | for flow in frt: 158 | 159 | destination = flow.destination.split(',') 160 | 161 | for index, item in enumerate(destination): 162 | _item = item.split('=') 163 | destination[index] = _item[1] if len(_item) > 1 else _item[0] 164 | 165 | hash_object = hashlib.sha512(b'{0}{1}'.format(str(destination), str(value['ip']))) 166 | hex_dig = hash_object.hexdigest() 167 | _age = dict() 168 | 169 | if len(flow.age) <= 2: 170 | _age['current'] = datetime.timedelta(seconds=int(flow.age)) 171 | elif len(flow.age) == 4 or len(flow.age) == 5: 172 | ms = flow.age.split(':') 173 | _age['current'] = datetime.timedelta(minutes=int(ms[0]), seconds=int(ms[1])) 174 | elif len(flow.age) == 7 or len(flow.age) == 8: 175 | ms = flow.age.split(':') 176 | _age['current'] = datetime.timedelta(hours=int(ms[0]), minutes=int(ms[1]), 177 | seconds=int(ms[2])) 178 | else: 179 | pattern = r'(.*)\s(.*?):(.*?):(.*)' 180 | regex = re.compile(pattern) 181 | age = re.findall(regex, flow.age) 182 | _age['current'] = datetime.timedelta(days=int(age[0][0][:-1]), hours=int(age[0][1]), 183 | minutes=int(age[0][2]), seconds=int(age[0][3])) 184 | 185 | pattern = r'([^\s]+)' 186 | regex = re.compile(pattern) 187 | _krt_actions = re.findall(regex, flow.tsi) 188 | 189 | if len(_krt_actions) <= 4: 190 | krt_actions = _krt_actions 191 | else: 192 | krt_actions = _krt_actions[4] 193 | 194 | # Junos 14.1RX different XPATH for BGP communities 195 | version = dev.facts['version'].split('R')[0].split('.') 196 | 197 | if int(version[0]) <= 14 and int(version[1]) <= 1: 198 | 199 | if isinstance(flow.action_141, str): 200 | if 'traffic-action' in flow.action_141: 201 | commAction = flow.action_141.split(":")[1].lstrip().strip() 202 | else: 203 | commAction = flow.action_141 204 | 205 | elif isinstance(flow.action_141, list): 206 | commAction = flow.action_141[1].split(':')[1].lstrip().strip() 207 | else: 208 | commAction = flow.action_141 209 | 210 | else: 211 | 212 | if isinstance(flow.action, str): 213 | if 'traffic-action' in flow.action: 214 | commAction = flow.action.split(":")[1].lstrip().strip() 215 | else: 216 | commAction = flow.action 217 | 218 | elif isinstance(flow.action, list): 219 | commAction = flow.action[1].split(':')[1].lstrip().strip() 220 | else: 221 | commAction = flow.action 222 | 223 | if hex_dig not in self.flow_active: 224 | 225 | self.flow_active[hex_dig] = {'router': name, 'term': flow.term, 'destination': destination, 226 | 'commAction': commAction, 'krtAction': krt_actions, 227 | 'age': str(_age['current']), 228 | 'hash': hex_dig, 'status': 'new'} 229 | else: 230 | 231 | if 'term:N/A' in flow['term']: 232 | self.flow_active.pop(hex_dig, None) 233 | 234 | if _age['current']: 235 | 236 | if _age['current'] > datetime.timedelta(hours=t.hour, minutes=t.minute, 237 | seconds=t.second): 238 | self.flow_active[hex_dig]['status'] = 'old' 239 | 240 | try: 241 | if hex_dig in self.flow_active: 242 | self.flow_active[hex_dig].update({'term': flow.term, 'destination': destination, 243 | 'commAction': commAction, 244 | 'krtAction': krt_actions, 245 | 'age': str(_age['current'])}) 246 | 247 | except KeyError as ke: 248 | return False, ke.message 249 | 250 | return True, self.flow_active 251 | 252 | def getActiveFlowRouteFilter(self): 253 | 254 | if self.routers: 255 | 256 | for router in self.routers: 257 | 258 | for name, value in router.iteritems(): 259 | self.filter_active[name] = list() 260 | 261 | with Device(host=value['ip'], user=self.dev_user, password=self.dev_pw) as dev: 262 | 263 | frft = FlowFilterTable(dev) 264 | frft.get() 265 | 266 | for filter in frft: 267 | 268 | data = filter.name.split(',') 269 | 270 | for didx, item in enumerate(data): 271 | _item = item.split('=') 272 | data[didx] = _item[1] if len(_item) > 1 else _item[0] 273 | 274 | self.filter_active[name].append({'data': data, 'packet_count': filter.packet_count, 275 | 'byte_count': filter.byte_count}) 276 | 277 | return True, self.filter_active 278 | 279 | def loadFlowRouteConfig(self): 280 | 281 | dev_ip = list() 282 | 283 | for router in self.routers: 284 | 285 | for name, value in router.iteritems(): 286 | 287 | if 'rr' in value['type']: 288 | dev_ip.append(value['ip']) 289 | 290 | with Device(host=dev_ip[0], user=self.dev_user, password=self.dev_pw, normalize=True) as dev: 291 | version = dev.facts['version'].split('R')[0].split('.') 292 | 293 | # Junos 14.1RX does not support json so let's go with XML here 294 | 295 | if int(version[0]) <= 14 and int(version[1]) <= 1: 296 | 297 | data = dev.rpc.get_config(options={'format': 'xml'}, filter_xml='routing-options/flow') 298 | 299 | for route in data.iter('route'): 300 | my_list = list() 301 | 302 | for item in route: 303 | 304 | if 'name' in item.tag: 305 | my_list.append(item.text) 306 | self.flow_config[item.text] = {} 307 | 308 | elif 'match' in item.tag: 309 | tag = None 310 | 311 | for child in item.iterchildren(): 312 | 313 | if 'destination-port' in child.tag: 314 | tag = 'dstPort' 315 | elif 'source-port' in child.tag: 316 | tag = 'srcPort' 317 | elif 'destination' in child.tag: 318 | tag = 'dstPrefix' 319 | elif 'source' in child.tag: 320 | tag = 'srcPrefix' 321 | elif 'protocol' in child.tag: 322 | tag = 'protocol' 323 | 324 | self.flow_config[my_list[0]][tag] = child.text 325 | 326 | elif 'then' in item.tag: 327 | 328 | _action = dict() 329 | 330 | for child in item.iterchildren(): 331 | 332 | for value in child.iter(): 333 | _action[child.tag] = {'value': value.text} 334 | 335 | self.flow_config[my_list[0]]['action'] = _action 336 | 337 | return True, self.flow_config 338 | 339 | else: 340 | 341 | data = dev.rpc.get_config(options={'format': 'json'}) 342 | 343 | if 'route' in data['configuration']['routing-options']['flow']: 344 | 345 | for route in data['configuration']['routing-options']['flow']['route']: 346 | _action = dict() 347 | 348 | for key, value in route['then'].iteritems(): 349 | 350 | if value[0]: 351 | _action[key] = {'value': value} 352 | else: 353 | _action[key] = {'value': None} 354 | 355 | self.flow_config[route['name']] = { 356 | 'dstPrefix': route['match']['destination'] if 'destination' in route['match'] else None, 357 | 'srcPrefix': route['match']['source'] if 'source' in route['match'] else None, 358 | 'protocol': route['match']['protocol'] if 'protocol' in route['match'] else None, 359 | 'dstPort': route['match']['destination-port'] if 'destination-port' in route[ 360 | 'match'] else None, 361 | 'srcPort': route['match']['source-port'] if 'source-port' in route['match'] else None, 362 | 'action': _action} 363 | return True, self.flow_config 364 | 365 | else: 366 | return False, self.flow_config 367 | 368 | def save_settings(self, dev_user=None, dev_pw=None, routers=None, age_out_interval=None): 369 | 370 | self.dev_user = dev_user 371 | self.dev_pw = dev_pw 372 | self.age_out_interval = age_out_interval 373 | # self.routers = routers 374 | 375 | # with open('ui/config.yml', 'w') as fp: 376 | # config = {'dev_user': self.dev_user, 'dev_pw': self.dev_pw, 'routers': self.routers, 377 | # 'age_out_interval': self.age_out_interval} 378 | # yaml.safe_dump(config, fp, default_flow_style=False) 379 | 380 | def load_settings(self): 381 | 382 | with open('ui/config.yml', 'r') as fp: 383 | _config = fp.read() 384 | config = yaml.safe_load(_config) 385 | self.dev_user = config['dev_user'] 386 | self.dev_pw = config['dev_pw'] 387 | self.age_out_interval = config['age_out_interval'] 388 | self.routers = config['routers'] 389 | 390 | 391 | class BGPFlow(object): 392 | 393 | @cherrypy.expose 394 | def index(self): 395 | return open('ui/index.html', 'r') 396 | 397 | 398 | @cherrypy.expose 399 | class BGPFlowWS(object): 400 | 401 | def __init__(self, my_dev=None): 402 | self.my_dev = my_dev 403 | 404 | @cherrypy.tools.json_out() 405 | @cherrypy.tools.json_in() 406 | def GET(self, action=None): 407 | 408 | if action == 'active': 409 | data = self.my_dev.getActiveFlowRoutes() 410 | 411 | return data 412 | 413 | @cherrypy.tools.json_out() 414 | @cherrypy.tools.json_in() 415 | def POST(self, action=None): 416 | 417 | if action == 'add': 418 | 419 | input_json = cherrypy.request.json 420 | resp = self.my_dev.addNewFlowRoute(flowRouteData=input_json) 421 | return resp 422 | 423 | elif action == 'mod': 424 | input_json = cherrypy.request.json 425 | resp = self.my_dev.modFlowRoute(flowRouteData=input_json) 426 | return resp 427 | 428 | elif action == 'del': 429 | input_json = cherrypy.request.json 430 | resp = self.my_dev.delFlowRoute(flowRouteData=input_json) 431 | return resp 432 | 433 | elif action == 'save': 434 | 435 | input_json = cherrypy.request.json 436 | self.my_dev.save_settings(dev_user=input_json['user'], dev_pw=input_json['password'], 437 | age_out_interval=input_json['age_out_interval']) 438 | return True, 'Successfully saved configuration settings' 439 | 440 | else: 441 | return False, 'Action not defined' 442 | 443 | 444 | @cherrypy.expose 445 | class Frt(object): 446 | 447 | def __init__(self, my_dev=None): 448 | self.my_dev = my_dev 449 | 450 | @cherrypy.tools.json_out() 451 | def POST(self): 452 | resp = self.my_dev.getActiveFlowRoutes() 453 | return resp 454 | 455 | 456 | @cherrypy.expose 457 | class Frtc(object): 458 | 459 | def __init__(self, my_dev=None): 460 | self.my_dev = my_dev 461 | 462 | @cherrypy.tools.json_out() 463 | def POST(self): 464 | resp = self.my_dev.loadFlowRouteConfig() 465 | return resp 466 | 467 | 468 | @cherrypy.expose 469 | class Frft(object): 470 | 471 | def __init__(self, my_dev=None): 472 | self.my_dev = my_dev 473 | 474 | @cherrypy.tools.json_out() 475 | def POST(self): 476 | resp = self.my_dev.getActiveFlowRouteFilter() 477 | return resp 478 | 479 | 480 | if __name__ == '__main__': 481 | cherrypy.config.update({'log.screen': False, 482 | 'log.access_file': '', 483 | 'log.error_file': ''}) 484 | conf = { 485 | '/': { 486 | 'tools.sessions.on': True, 487 | 'tools.staticdir.root': os.path.abspath(os.getcwd()), 488 | 'tools.staticdir.on': True, 489 | 'tools.staticdir.dir': 'ui' 490 | }, 491 | '/api': { 492 | 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), 493 | 'tools.response_headers.on': True, 494 | 'tools.response_headers.headers': [('Content-Type', 'text/plain')], 495 | }, 496 | '/api/frt': { 497 | 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), 498 | 'tools.response_headers.on': True, 499 | 'tools.response_headers.headers': [('Content-Type', 'text/plain')], 500 | }, 501 | '/api/frct': { 502 | 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), 503 | 'tools.response_headers.on': True, 504 | 'tools.response_headers.headers': [('Content-Type', 'text/plain')], 505 | }, 506 | '/api/frft': { 507 | 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), 508 | 'tools.response_headers.on': True, 509 | 'tools.response_headers.headers': [('Content-Type', 'text/plain')], 510 | }, 511 | } 512 | 513 | my_dev = MyDev() 514 | my_dev.load_settings() 515 | webapp = BGPFlow() 516 | webapp.api = BGPFlowWS(my_dev=my_dev) 517 | webapp.api.frt = Frt(my_dev=my_dev) 518 | webapp.api.frct = Frtc(my_dev=my_dev) 519 | webapp.api.frft = Frft(my_dev=my_dev) 520 | cherrypy.config.update({'log.screen': False, 521 | 'server.socket_host': '0.0.0.0', 522 | 'server.socket_port': 8080, 523 | }) 524 | cherrypy.quickstart(webapp, '/', conf) 525 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Demo BGP Flow Spec with PyEZ # 2 | 3 | 4 | ## Overview ## 5 | In case we have to demonstrate our capabilities automating BGP FlowSpec data handling on a route reflector `BFS` can be used for. 6 | 7 | ### Use Cases ### 8 | - Automate handling of flow route data on the route reflector or external flow route source 9 | - Automate generating flow route test data on route reflector 10 | 11 | ### Features ### 12 | 13 | - Flow route actions on route reflector 14 | + Add new flow route 15 | + Modify flow route 16 | + Delete flow route 17 | 18 | - Flow route viewer 19 | + retrieve flow route information from RR / PE / ASBR / etc. 20 | 21 | - Interact with external flow route source like Arbor APS 22 | + send change request to flow route source 23 | 24 | - Test data generator 25 | + Generate static flow route entries on route reflector to demonstrate scale / functionality 26 | 27 | ## Requirements ## 28 | 29 | - Linux Box (Centos 7) 30 | + 1 CPU 31 | + 1G RAM 32 | - Python 2.7 33 | - Working internet connection 34 | + WebUI loads needed javascript libraries and CSS files from CDN 35 | - vMX 36 | + Tested with version 14.1R1 / 17.1R1 37 | 38 | ## Installation ## 39 | All steps should be done as `root` user. 40 | 41 | - Get `Centos 7` box ready 42 | + http://isoredirect.centos.org/centos/7/isos/x86_64/CentOS-7-x86_64-Minimal-1708.iso 43 | + Turn off SELinux `setstatus // setenforce 0` 44 | + Turn off firewall `systemctl stop firewalld` 45 | - Log into to Centos 7 box via SSH 46 | - Install required packages if needed (This step is only needed if we have to install a newer python version) 47 | + Centos 7.x should come with python 2.7.5 48 | + With the next steps we would install python 2.7.14 which is not needed if 2.7.5 is installed 49 | 50 | ```bash 51 | yum install python-devel libxml2-devel libxslt-devel gcc openssl libffi-devel wget curl 52 | yum groupinstall "Development tools" 53 | yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel 54 | ``` 55 | - Install python 2.7 56 | 57 | ```bash 58 | cd /usr/src 59 | wget https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tgz 60 | tar xzf Python-2.7.14.tgz 61 | cd Python-2.7.14 62 | ./configure 63 | make altinstall 64 | ``` 65 | - Install BFS Demo Tool 66 | + pip2.7 should be directly available if not use `which pip2.7` to obtain the path to binary 67 | 68 | ```bash 69 | git clone https://github.com/JNPRAutomate/BGP_Flowspec_automation_with_PyEZ.git 70 | cd BGP_Flowspec_automation_with_PyEZ 71 | pip2.7 or pip install --upgrade -r requirements.txt 72 | ``` 73 | 74 | ## Configuration ## 75 | We need to add some information about RR and / or the edge router by editing `ui/config.yml` file and change the 76 | settings to fit your environment. 77 | 78 | __At least one router of type RR has to be configured__ 79 | 80 | ```yaml 81 | age_out_interval: 00:01:00 82 | dev_pw: juniper123 83 | dev_user: root 84 | routers: 85 | - rt1: 86 | type: rr 87 | ip: 10.11.111.120 88 | - rt2: 89 | type: asbr 90 | ip: 10.11.111.121 91 | ``` 92 | 93 | - Start bfs tool 94 | + Python binary should be in path if not use `which python2.7` to obtain path info 95 | + change directory to bfs root 96 | + Start UI with `python2.7 main.py or python main.py` 97 | - Access WebUI URL `:8080` 98 | 99 | ## WebUI ## 100 | 101 | ![Screen_Shot_2018-04-11_at_11.25.39](resources/Screen_Shot_2018-04-11_at_11.25.39.png) 102 | 103 | ![Screen_Shot_2018-04-11_at_11.26.18](resources/Screen_Shot_2018-04-11_at_11.26.18.png) 104 | 105 | ![Screen_Shot_2018-04-11_at_11.26.31](resources/Screen_Shot_2018-04-11_at_11.26.31.png) 106 | 107 | ![Screen_Shot_2018-04-11_at_11.26.42](resources/Screen_Shot_2018-04-11_at_11.26.42.png) 108 | 109 | ![Screen_Shot_2018-04-11_at_11.26.53](resources/Screen_Shot_2018-04-11_at_11.26.53.png) 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | junos-eznc==2.1.7 2 | jxmlease==1.0.1 3 | CherryPy==11.0.0 4 | requests==2.18.4 5 | PyYAML==3.12 6 | Jinja2==2.10 7 | -------------------------------------------------------------------------------- /resources/Screen_Shot_2018-04-11_at_11.25.39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JNPRAutomate/BGP_Flowspec_automation_with_PyEZ/223bca46ac4005247a3166d4dfee16e723dfd5a8/resources/Screen_Shot_2018-04-11_at_11.25.39.png -------------------------------------------------------------------------------- /resources/Screen_Shot_2018-04-11_at_11.26.18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JNPRAutomate/BGP_Flowspec_automation_with_PyEZ/223bca46ac4005247a3166d4dfee16e723dfd5a8/resources/Screen_Shot_2018-04-11_at_11.26.18.png -------------------------------------------------------------------------------- /resources/Screen_Shot_2018-04-11_at_11.26.31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JNPRAutomate/BGP_Flowspec_automation_with_PyEZ/223bca46ac4005247a3166d4dfee16e723dfd5a8/resources/Screen_Shot_2018-04-11_at_11.26.31.png -------------------------------------------------------------------------------- /resources/Screen_Shot_2018-04-11_at_11.26.42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JNPRAutomate/BGP_Flowspec_automation_with_PyEZ/223bca46ac4005247a3166d4dfee16e723dfd5a8/resources/Screen_Shot_2018-04-11_at_11.26.42.png -------------------------------------------------------------------------------- /resources/Screen_Shot_2018-04-11_at_11.26.53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JNPRAutomate/BGP_Flowspec_automation_with_PyEZ/223bca46ac4005247a3166d4dfee16e723dfd5a8/resources/Screen_Shot_2018-04-11_at_11.26.53.png -------------------------------------------------------------------------------- /template/delete-flow-route.conf: -------------------------------------------------------------------------------- 1 | routing-options { 2 | flow { 3 | delete: 4 | route {{ flowRouteName }}; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /template/mod-flow-route.conf: -------------------------------------------------------------------------------- 1 | routing-options { 2 | flow { 3 | route {{ flowRouteName }} { 4 | replace: 5 | match { 6 | {%- if dstPrefix is defined and dstPrefix !=None %} 7 | destination {{dstPrefix}}; 8 | {%- endif %} 9 | {%- if dstPort is defined and dstPort !=None %} 10 | destination-port {{ dstPort }}; 11 | {%- endif %} 12 | {%- if dscp is defined and dscp !=None %} 13 | dscp {{dscp}}; 14 | {%- endif %} 15 | {%- if fragment is defined and fragment !=None %} 16 | fragment {{fragment}}; 17 | {%- endif %} 18 | {%- if icmp_code is defined and icmp_code !=None %} 19 | icmp-code {{icmp_code}}; 20 | {%- endif %} 21 | {%- if icmp_type is defined and icmp_type !=None %} 22 | icmp-type {{icmp_type}}; 23 | {%- endif %} 24 | {%- if packet_length is defined and packet_length !=None %} 25 | packet-length {{packet_length}}; 26 | {%- endif %} 27 | {%- if port is defined and port !=None %} 28 | port {{port}}; 29 | {%- endif %} 30 | {%- if protocol is defined and protocol !=None %} 31 | protocol {{protocol}}; 32 | {%- endif %} 33 | {%- if srcPrefix is defined and srcPrefix !=None %} 34 | source {{srcPrefix}}; 35 | {%- endif %} 36 | {%- if srcPort is defined and srcPortt !=None %} 37 | source-port {{srcPort}}; 38 | {%- endif %} 39 | {%- if tcp_flags is defined and tcp_flags !=None %} 40 | tcp-flags {{tcp_flags}}; 41 | {%- endif %} 42 | } 43 | replace: 44 | then { 45 | {{action}}; 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /template/set-flow-route.conf: -------------------------------------------------------------------------------- 1 | routing-options { 2 | flow { 3 | route {{ flowRouteName }} { 4 | match { 5 | {%- if dstPrefix is defined and dstPrefix !=None %} 6 | destination {{dstPrefix}}; 7 | {%- endif %} 8 | {%- if dstPort is defined and dstPort !=None %} 9 | destination-port {{ dstPort }}; 10 | {%- endif %} 11 | {%- if dscp is defined and dscp !=None %} 12 | dscp {{dscp}}; 13 | {%- endif %} 14 | {%- if fragment is defined and fragment !=None %} 15 | fragment {{fragment}}; 16 | {%- endif %} 17 | {%- if icmp_code is defined and icmp_code !=None %} 18 | icmp-code {{icmp_code}}; 19 | {%- endif %} 20 | {%- if icmp_type is defined and icmp_type !=None %} 21 | icmp-type {{icmp_type}}; 22 | {%- endif %} 23 | {%- if packet_length is defined and packet_length !=None %} 24 | packet-length {{packet_length}}; 25 | {%- endif %} 26 | {%- if port is defined and port !=None %} 27 | port {{port}}; 28 | {%- endif %} 29 | {%- if protocol is defined and protocol !=None %} 30 | protocol {{protocol}}; 31 | {%- endif %} 32 | {%- if srcPrefix is defined and srcPrefix !=None %} 33 | source {{srcPrefix}}; 34 | {%- endif %} 35 | {%- if srcPort is defined and srcPortt !=None %} 36 | source-port {{srcPort}}; 37 | {%- endif %} 38 | {%- if tcp_flags is defined and tcp_flags !=None %} 39 | tcp-flags {{tcp_flags}}; 40 | {%- endif %} 41 | } 42 | then { 43 | {{action}}; 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /ui/config.yml: -------------------------------------------------------------------------------- 1 | age_out_interval: 00:03:00 2 | dev_pw: juniper123 3 | dev_user: root 4 | communities: 5 | - bgp_flow_arbor:1000:1666 6 | routers: 7 | - RR: 8 | type: rr 9 | ip: 10.11.111.120 10 | - PE: 11 | type: asbr 12 | ip: 10.11.111.121 -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 24 | 25 | 26 | BGP Flow Spec Demo 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |

DevOps against DDoS

37 |

Monitoring BGP FlowSpec with Junos PyEZ

38 |
39 | 52 |
53 |
54 |
55 |
56 | Juniper Networks 57 |
58 |
59 |
60 |
61 |
62 |

Route Reflector Static Flow Configuration

63 |
64 |

65 | 69 | 72 | 75 |

76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
NameDestination PrefixSource PrefixProtocolDestination PortSource PortAction
92 |
93 |
94 |
95 |
96 |
97 |
98 |

Active Flow Routes

99 |
100 |
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
RouterNameDestination PrefixSource PrefixProtocolDestination PortSource PortKRT ActionCommunity ActionAge
119 |
120 |
121 |
122 |
123 |
124 |
125 |

Active Flow Filter

126 |
127 |
128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
NameDestination PrefixSource PrefixProtocolDestination PortSource PortPacket CountByte Count
144 |
145 |
146 |
147 |
148 |
149 |
150 |

Settings

151 |
152 |
153 | 154 |
155 | 156 |
157 |
158 |
159 | 160 |
161 | 162 |
163 |
164 |
165 | 166 |
167 | 168 |
169 |
170 |
171 | 172 |
173 | 174 |
175 |
176 |

177 | 180 |

181 |
182 |
183 |
184 |
185 |
186 | 187 | 188 | 261 | 262 | 263 | 334 | 335 | 340 | 341 | 342 | 343 | 344 | 345 | -------------------------------------------------------------------------------- /ui/jnpr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JNPRAutomate/BGP_Flowspec_automation_with_PyEZ/223bca46ac4005247a3166d4dfee16e723dfd5a8/ui/jnpr.png -------------------------------------------------------------------------------- /ui/ui.css: -------------------------------------------------------------------------------- 1 | .mainheader { 2 | color: #575d68; 3 | } 4 | 5 | .btn-custom { 6 | background-color: hsl(0, 0%, 75%) !important; 7 | background-repeat: repeat-x; 8 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#c9c9c9", endColorstr="#bfbfbf"); 9 | background-image: -khtml-gradient(linear, left top, left bottom, from(#c9c9c9), to(#bfbfbf)); 10 | background-image: -moz-linear-gradient(top, #c9c9c9, #bfbfbf); 11 | background-image: -ms-linear-gradient(top, #c9c9c9, #bfbfbf); 12 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #c9c9c9), color-stop(100%, #bfbfbf)); 13 | background-image: -webkit-linear-gradient(top, #c9c9c9, #bfbfbf); 14 | background-image: -o-linear-gradient(top, #c9c9c9, #bfbfbf); 15 | background-image: linear-gradient(#c9c9c9, #bfbfbf); 16 | border-color: #bfbfbf #bfbfbf hsl(0, 0%, 74%); 17 | color: #333 !important; 18 | text-shadow: 0 1px 1px rgba(255, 255, 255, 0.06); 19 | -webkit-font-smoothing: antialiased; 20 | } 21 | 22 | tr.newRow { 23 | background-color: red; 24 | } 25 | 26 | body { 27 | padding : 10px ; 28 | } 29 | 30 | .nav-pills>li.active>a, 31 | .nav-pills>li.active>a:focus, 32 | .nav-pills>li.active>a:hover { 33 | background-color: #bcbfc4; 34 | } 35 | 36 | #exTab1 .tab-content { 37 | color : black; 38 | background-color: #bcbfc4; 39 | padding : 5px 15px; 40 | } 41 | 42 | #exTab1 .nav-pills > li > a { 43 | border-radius: 0; 44 | } -------------------------------------------------------------------------------- /ui/ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | # 3 | # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER 4 | # 5 | # Copyright (c) 2018 Juniper Networks, Inc. 6 | # All rights reserved. 7 | # 8 | # Use is subject to license terms. 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the ?License?); you may not 11 | # use this file except in compliance with the License. You may obtain a copy of 12 | # the License at http://www.apache.org/licenses/LICENSE-2.0. 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | */ 21 | 22 | var pollInterval = 5000; // 1000 = 1 second, 3000 = 3 seconds 23 | var ageOutInterval = "0:03:00" //H:M:S 24 | 25 | $(document).ready(function () { 26 | 27 | $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { 28 | var target = $(e.target).attr("href") 29 | }); 30 | 31 | flowRouteAddNewConfigEventHandler(); 32 | flowRouteModModalBtnEventHandler(); 33 | flowRouteModBtnEventHandler(); 34 | flowRouteDelBtnEventHandler(); 35 | saveSettingsBtnEventHandler(); 36 | 37 | var t_flow_config = $('#t_flow_config').DataTable({ 38 | 'ajax' : { 39 | "type" : "POST", 40 | "url" : "/api/frct", 41 | "contentType": "application/json", 42 | "processData": true, 43 | "dataType": "json", 44 | "dataSrc": function (response) { 45 | 46 | var return_data = new Array(); 47 | 48 | $.each( response[1], function( name, flow ) { 49 | 50 | var action_val = new Array(); 51 | 52 | $.each( flow.action, function( action, value ) { 53 | 54 | if (value['value'] === null){ 55 | action_val.push([action]); 56 | 57 | } else { 58 | action_val.push([action, value['value']]); 59 | } 60 | }); 61 | 62 | return_data.push({ 63 | 'name': name, 64 | 'dstPrefix': flow.dstPrefix, 65 | 'dstPort': flow.dstPort, 66 | 'srcPrefix': flow.srcPrefix, 67 | 'srcPort': flow.srcPort, 68 | 'protocol': flow.protocol, 69 | 'action': action_val 70 | }) 71 | }); 72 | return return_data; 73 | } 74 | }, 75 | "columns": [ 76 | { 77 | "data": "name", 78 | "defaultContent": "" 79 | }, 80 | { 81 | "data": "dstPrefix", 82 | "defaultContent": "" 83 | }, 84 | { 85 | "data": "srcPrefix", 86 | "defaultContent": "" 87 | }, 88 | { 89 | "data": "protocol", 90 | "defaultContent": "" 91 | }, 92 | { 93 | "data": "dstPort", 94 | "defaultContent": "" 95 | }, 96 | { 97 | "data": "srcPort", 98 | "defaultContent": "" 99 | }, 100 | { 101 | "data": "action", 102 | "defaultContent": "" 103 | }] 104 | }); 105 | 106 | var t_active_flow = $('#t_active_flow').DataTable({ 107 | 108 | 'ajax' : { 109 | "type" : "POST", 110 | "url" : "/api/frt", 111 | "contentType": "application/json", 112 | "processData": true, 113 | "dataType": "json", 114 | "dataSrc": function (response) { 115 | 116 | var return_data = new Array(); 117 | 118 | $.each( response[1], function( key, flow ) { 119 | 120 | return_data.push({ 121 | 'router': flow.router, 122 | 'term': flow.term, 123 | 'dstPrefix': flow.destination[0], 124 | 'dstPort': flow.destination[3], 125 | 'srcPrefix': flow.destination[1], 126 | 'srcPort': flow.destination[4], 127 | 'protocol': flow.destination[2], 128 | 'krtAction': flow.krtAction, 129 | 'commAction': flow.commAction, 130 | 'age': flow.age 131 | }) 132 | }); 133 | return return_data; 134 | }, 135 | "complete": function (response) { 136 | getActiveFlowRoutes(pollInterval); 137 | } 138 | }, 139 | "createdRow": function( row, data, dataIndex ) { 140 | 141 | if (data.age <= ageOutInterval) { 142 | $(row).css( 'color', 'red' ).animate( { color: 'black' }); 143 | } 144 | }, 145 | "columns": [ 146 | { 147 | "data": "router", 148 | "defaultContent": "" 149 | }, 150 | { 151 | "data": "term", 152 | "defaultContent": "" 153 | }, 154 | { 155 | "data": "dstPrefix", 156 | "defaultContent": "" 157 | }, 158 | { 159 | "data": "srcPrefix", 160 | "defaultContent": "" 161 | }, 162 | { 163 | "data": "protocol", 164 | "defaultContent": "" 165 | }, 166 | { 167 | "data": "dstPort", 168 | "defaultContent": "" 169 | }, 170 | { 171 | "data": "srcPort", 172 | "defaultContent": "" 173 | }, 174 | { 175 | "data": "krtAction", 176 | "defaultContent": "" 177 | }, 178 | { 179 | "data": "commAction", 180 | "defaultContent": "" 181 | }, 182 | { 183 | "data": "age", 184 | "defaultContent": "" 185 | }] 186 | }); 187 | 188 | var t_active_filter = $('#t_active_filter').DataTable({ 189 | 190 | 'ajax' : { 191 | "type" : "POST", 192 | "url" : "/api/frft", 193 | "contentType": "application/json", 194 | "processData": true, 195 | "dataType": "json", 196 | "dataSrc": function (response) { 197 | 198 | var return_data = new Array(); 199 | 200 | $.each( response[1], function( rname, router ) { 201 | $.each(router, function( fidx, filter ) { 202 | 203 | return_data.push({ 204 | 'name': rname, 205 | 'dstPrefix': filter.data[0], 206 | 'dstPort': filter.data[3], 207 | 'srcPrefix': filter.data[1], 208 | 'srcPort': filter.data[4], 209 | 'protocol': filter.data[2], 210 | 'packetCount': filter.packet_count, 211 | 'byteCount': filter.byte_count 212 | }) 213 | }); 214 | }); 215 | return return_data; 216 | }, 217 | "complete": function (response) { 218 | getActiveFlowFilter(pollInterval); 219 | } 220 | }, 221 | "columns": [ 222 | { 223 | "data": "name", 224 | "defaultContent": "" 225 | }, 226 | { 227 | "data": "dstPrefix", 228 | "defaultContent": "" 229 | }, 230 | { 231 | "data": "srcPrefix", 232 | "defaultContent": "" 233 | }, 234 | { 235 | "data": "protocol", 236 | "defaultContent": "" 237 | }, 238 | { 239 | "data": "dstPort", 240 | "defaultContent": "" 241 | }, 242 | { 243 | "data": "srcPort", 244 | "defaultContent": "" 245 | }, 246 | { 247 | "data": "packetCount", 248 | "defaultContent": "" 249 | }, 250 | { 251 | "data": "byteCount", 252 | "defaultContent": "" 253 | }] 254 | }); 255 | 256 | $("#t_flow_config tbody").on('click', 'tr', function () { 257 | if ( $(this).hasClass('selected') ) { 258 | $(this).removeClass('selected'); 259 | } 260 | else { 261 | t_flow_config.$('tr.selected').removeClass('selected'); 262 | $(this).addClass('selected'); 263 | } 264 | }); 265 | 266 | $('#selectProtocol').on('change', function(){ 267 | var selected = $(this).find("option:selected").val(); 268 | 269 | if(selected === 'ICMP'){ 270 | 271 | if ($('#g_icmp').length){ 272 | $('#g_icmp').remove(); 273 | } 274 | 275 | var html = "
" + 276 | "" + 277 | "
" + 278 | "" + 279 | "
" + 280 | "" + 281 | "
" + 282 | "" + 283 | "
" + 284 | "
"; 285 | 286 | $('#fg_protocol').append(html); 287 | 288 | } else { 289 | 290 | if ($('#g_icmp').length){ 291 | $('#g_icmp').remove(); 292 | } 293 | } 294 | }); 295 | 296 | $('#selectAddNewFlowAction').on('change', function(){ 297 | var selected = $(this).find("option:selected").val(); 298 | 299 | if(selected === 'community'){ 300 | 301 | if ($('#g_community').length){ 302 | $('#g_community').remove(); 303 | } 304 | 305 | var html = "
" + 306 | "
" + 307 | "" + 308 | "
" + 309 | "
"; 310 | 311 | $('#fg_action').append(html); 312 | 313 | } else { 314 | 315 | if ($('#g_community').length){ 316 | $('#g_community').remove(); 317 | } 318 | } 319 | }); 320 | 321 | $('#selectModFlowAction').on('change', function(){ 322 | var selected = $(this).find("option:selected").val(); 323 | 324 | if(selected === 'community'){ 325 | 326 | if ($('#g_mod_community').length){ 327 | $('#g_mod_community').remove(); 328 | } 329 | 330 | var html = "
" + 331 | "
" + 332 | "" + 333 | "
" + 334 | "
"; 335 | 336 | $('#fg_mod_action').append(html); 337 | 338 | } else { 339 | 340 | if ($('#g_mod_community').length){ 341 | $('#g_mod_community').remove(); 342 | } 343 | } 344 | }); 345 | }); 346 | 347 | function flowRouteAddNewConfigEventHandler(){ 348 | 349 | $("#btnAddFlowRoute").on( "click", function() { 350 | var data = new Object(); 351 | 352 | data.flowRouteName = $('#inputFlowRouteName').val(); 353 | 354 | if ($('#inputSrcPrefix').val()){ 355 | data.srcPrefix = $('#inputSrcPrefix').val(); 356 | } 357 | if ($('#inputSrcPort').val()){ 358 | data.srcPort = $('#inputSrcPort').val(); 359 | } 360 | if ($('#inputDstPrefix').val()){ 361 | data.dstPrefix = $('#inputDstPrefix').val(); 362 | } 363 | if ($('#inputDstPort').val()) { 364 | data.dstPort = $('#inputDstPort').val(); 365 | } 366 | if ($('#selectProtocol').val()) { 367 | data.protocol = $('#selectProtocol').val(); 368 | } 369 | 370 | data.action = $('#selectAddNewFlowAction').val(); 371 | 372 | if (data.action == 'community') { 373 | data.action = 'community ' + $('#inputAddNewActionCommunity').val(); 374 | } 375 | addNewFlowRouteConfig(data); 376 | }); 377 | } 378 | 379 | function flowRouteModBtnEventHandler(){ 380 | 381 | $('#btnModFlowRoute').click( function () { 382 | 383 | var data = new Object(); 384 | 385 | data.flowRouteName = $('#inputModFlowRouteName').val(); 386 | if ($('#inputModSrcPrefix').val()){ 387 | data.srcPrefix = $('#inputModSrcPrefix').val(); 388 | } 389 | if ($('#inputModSrcPort').val()){ 390 | data.srcPort = $('#inputModSrcPort').val(); 391 | } 392 | if ($('#inputModDstPrefix').val()){ 393 | data.dstPrefix = $('#inputModDstPrefix').val(); 394 | } 395 | if ($('#inputModDstPort').val()) { 396 | data.dstPort = $('#inputModDstPort').val(); 397 | } 398 | if ($('#selectModProtocol').val()) { 399 | data.protocol = $('#selectModProtocol').val(); 400 | } 401 | if ($('#selectModFlowAction').val()) { 402 | data.action = $('#selectModFlowAction').val(); 403 | } 404 | if (data.action == 'community') { 405 | data.action = 'community ' + $('#inputModActionCommunity').val(); 406 | } 407 | 408 | modFlowRouteConfig(data); 409 | }); 410 | } 411 | 412 | function flowRouteModModalBtnEventHandler(){ 413 | 414 | $('#flowModBtn').click( function () { 415 | 416 | var table = $('#t_flow_config').DataTable(); 417 | var rowData = table.row( '.selected' ).data(); 418 | 419 | $("#inputModFlowRouteName").val(rowData.name); 420 | $("#inputModDstPrefix").val(rowData.dstPrefix); 421 | $("#inputModSrcPrefix").val(rowData.srcPrefix); 422 | $("#selectModProtocol").val(rowData.protocol); 423 | $("#inputModDstPort").val(rowData.dstPort); 424 | $("#inputModSrcPort").val(rowData.srcPort); 425 | 426 | if (rowData.action[0][0] === 'community'){ 427 | 428 | if ($('#g_mod_community').length){ 429 | $('#g_mod_community').remove(); 430 | } 431 | 432 | $("#selectModFlowAction").val(rowData.action[0][0]); 433 | var html = "
" + 434 | "
" + 435 | "" + 436 | "
" + 437 | "
"; 438 | 439 | $('#fg_mod_action').append(html); 440 | $("#inputModActionCommunity").val(rowData.action[0][1]); 441 | 442 | } else { 443 | 444 | if ($('#g_mod_community').length){ 445 | $('#g_mod_community').remove(); 446 | $("#selectModFlowAction").val(rowData.action[0][0]); 447 | } else { 448 | $("#selectModFlowAction").val(rowData.action[0][0]); 449 | } 450 | } 451 | $("#modalFlowMod").modal("toggle"); 452 | }); 453 | } 454 | 455 | function flowRouteDelBtnEventHandler(){ 456 | 457 | $('#flowDelBtn').click( function () { 458 | var table = $('#t_flow_config').DataTable(); 459 | var rowData = table.row( '.selected' ).data(); 460 | delFlowRouteConfig(rowData.name); 461 | }); 462 | } 463 | 464 | function addNewFlowRouteConfig(flowRouteData) { 465 | 466 | $.ajax({ 467 | url: '/api?action=add', 468 | type: 'POST', 469 | data: JSON.stringify(flowRouteData), 470 | cache: false, 471 | processData: true, 472 | dataType: 'json', 473 | contentType: 'application/json', 474 | success: function (response) { 475 | 476 | if (response[0]){ 477 | 478 | $("#t_flow_config").DataTable().ajax.reload(); 479 | BootstrapDialog.show({ 480 | type: BootstrapDialog.TYPE_SUCCESS, 481 | title: 'Successfully added new flow route', 482 | message: response[1] 483 | }) 484 | } else { 485 | 486 | BootstrapDialog.show({ 487 | type: BootstrapDialog.TYPE_WARNING, 488 | title: 'Error adding new flow route', 489 | message: response[1] 490 | }) 491 | } 492 | }, 493 | error : function (data, errorText) { 494 | $("#errormsg").html(errorText).show(); 495 | } 496 | }); 497 | } 498 | 499 | function modFlowRouteConfig(flowRouteData) { 500 | 501 | $.ajax({ 502 | url: '/api?action=mod', 503 | type: 'POST', 504 | data: JSON.stringify(flowRouteData), 505 | cache: false, 506 | processData: true, 507 | dataType: 'json', 508 | contentType: 'application/json', 509 | success: function (response) { 510 | 511 | if (response[0]){ 512 | 513 | $("#t_flow_config").DataTable().ajax.reload(); 514 | BootstrapDialog.show({ 515 | type: BootstrapDialog.TYPE_SUCCESS, 516 | title: 'Successfully modified flow route', 517 | message: response[1] 518 | }) 519 | } 520 | }, 521 | error : function (data, errorText) { 522 | $("#errormsg").html(errorText).show(); 523 | } 524 | }); 525 | } 526 | 527 | function delFlowRouteConfig(flowRouteName){ 528 | 529 | $.ajax({ 530 | url: '/api?action=del', 531 | type: 'POST', 532 | data: JSON.stringify({"flowRouteName": flowRouteName}), 533 | cache: false, 534 | processData: true, 535 | dataType: 'json', 536 | contentType: 'application/json', 537 | success: function (response) { 538 | 539 | if (response[0]){ 540 | 541 | var table = $('#t_flow_config').DataTable(); 542 | table.row('.selected').remove().draw( false ); 543 | BootstrapDialog.show({ 544 | type: BootstrapDialog.TYPE_SUCCESS, 545 | title: 'Successfully deleted flow route', 546 | message: response[1] 547 | }) 548 | } else { 549 | BootstrapDialog.show({ 550 | type: BootstrapDialog.TYPE_WARNING, 551 | title: 'Failed to delete flow route', 552 | message: response[1] 553 | }) 554 | } 555 | }, 556 | error : function (data, errorText) { 557 | $("#errormsg").html(errorText).show(); 558 | } 559 | }); 560 | } 561 | 562 | function getActiveFlowRoutes(pollInterval){ 563 | 564 | function poll() { 565 | var t = $('#t_active_flow').dataTable().api(); 566 | t.ajax.reload() 567 | } 568 | setTimeout(poll, pollInterval); 569 | } 570 | 571 | function getActiveFlowFilter(pollInterval){ 572 | 573 | function poll() { 574 | var t = $('#t_active_filter').dataTable().api(); 575 | t.ajax.reload() 576 | } 577 | setTimeout(poll, pollInterval); 578 | } 579 | 580 | function saveSettingsBtnEventHandler(){ 581 | 582 | $("#saveDevSettingsBtn").on( "click", function() { 583 | 584 | var data = new Object(); 585 | 586 | data.user = $('#inputDevUser').val(); 587 | data.password = $('#inputDevPassword').val(); 588 | data.ip = $('#inputDevIP').val(); 589 | data.age_out_interval = $('#inputAgeOutInterval').val(); 590 | ageOutInterval = $('#inputAgeOutInterval').val(); 591 | pollInterval = $('#inputPollInterval').val(); 592 | 593 | saveSettings(data); 594 | }); 595 | } 596 | 597 | function saveSettings(settings){ 598 | 599 | $.ajax({ 600 | url: '/api?action=save', 601 | type: 'POST', 602 | data: JSON.stringify(settings), 603 | cache: false, 604 | processData: true, 605 | dataType: 'json', 606 | contentType: 'application/json', 607 | success: function (response) { 608 | }, 609 | error : function (data, errorText) { 610 | $("#errormsg").html(errorText).show(); 611 | } 612 | }); 613 | } -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JNPRAutomate/BGP_Flowspec_automation_with_PyEZ/223bca46ac4005247a3166d4dfee16e723dfd5a8/utils/__init__.py -------------------------------------------------------------------------------- /utils/testdata.py: -------------------------------------------------------------------------------- 1 | import random 2 | import StringIO 3 | import yaml 4 | import pprint 5 | 6 | from jinja2 import Environment, FileSystemLoader 7 | from jnpr.junos.utils.config import Config 8 | from jnpr.junos import Device 9 | from jnpr.junos.exception import ConfigLoadError 10 | 11 | if __name__ == '__main__': 12 | 13 | print 'Generate static BGP Flow Spec test data on RR device' 14 | 15 | with open('../ui/config.yml', 'r') as fp: 16 | _config = fp.read() 17 | config = yaml.safe_load(_config) 18 | dev_user = config['dev_user'] 19 | dev_pw = config['dev_pw'] 20 | routers = config['routers'] 21 | communities = config['communities'] 22 | 23 | my_router = None 24 | for router in routers: 25 | 26 | for name, value in router.iteritems(): 27 | if 'rr' in value['type']: 28 | my_router = [value['ip']] 29 | 30 | with Device(host=my_router[0], user=dev_user, password=dev_pw) as dev: 31 | 32 | testdata = dict() 33 | start = 1 34 | stop = 1001 35 | step = 1 36 | protocol = ['tcp', 'udp'] 37 | action = ['accept', 'discard', 'sample', 'community'] 38 | 39 | for idx in range(start, stop, step): 40 | testdata['flowRoute' + str(idx)] = { 41 | 'dstPrefix': '10.{0}.{1}.{2}/32'.format(random.randint(1, 200), random.randint(1, 200), 42 | random.randint(1, 200)), 43 | 'srcPrefix': '10.{0}.{1}.{2}/32'.format(random.randint(1, 200), random.randint(1, 200), 44 | random.randint(1, 200)), 45 | 'protocol': protocol[random.randint(0, 1)], 'dstPort': '{0}'.format(random.randint(1, 9999)), 46 | 'srcPort': '{0}'.format(random.randint(1, 9999)), 47 | 'action': '{0} {1}'.format(action[3],communities[random.randint(0, len(communities)-1)]) if 'community' in action[ 48 | random.randint(0, 3)] else action[random.randint(0, 2)]} 49 | 50 | env = Environment(autoescape=False, 51 | loader=FileSystemLoader('../template'), trim_blocks=False, lstrip_blocks=False) 52 | template = env.get_template('set-flow-route.conf') 53 | 54 | _template = StringIO.StringIO() 55 | 56 | for key, flow in testdata.iteritems(): 57 | _template.write(template.render(flowRouteName=key, **flow)) 58 | 59 | try: 60 | 61 | cu = Config(dev) 62 | cu.lock() 63 | cu.load(_template.getvalue(), format='text', merge=True) 64 | cu.commit() 65 | cu.unlock() 66 | 67 | except ConfigLoadError as cle: 68 | print cle.message 69 | 70 | _template.close() 71 | --------------------------------------------------------------------------------