1# Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12# implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16""" 17 Running or runtime configuration related to Virtual Routing and Forwarding 18 tables (VRFs). 19""" 20import abc 21import logging 22 23from ryu.lib.packet.bgp import RF_IPv4_UC 24from ryu.lib.packet.bgp import RF_IPv6_UC 25from ryu.lib.packet.bgp import RF_L2_EVPN 26from ryu.lib.packet.bgp import RF_IPv4_FLOWSPEC 27from ryu.lib.packet.bgp import RF_IPv6_FLOWSPEC 28from ryu.lib.packet.bgp import RF_L2VPN_FLOWSPEC 29 30from ryu.services.protocols.bgp.utils import validation 31from ryu.services.protocols.bgp.base import get_validator 32from ryu.services.protocols.bgp.rtconf.base import BaseConf 33from ryu.services.protocols.bgp.rtconf.base import BaseConfListener 34from ryu.services.protocols.bgp.rtconf.base import ConfigTypeError 35from ryu.services.protocols.bgp.rtconf.base import ConfigValueError 36from ryu.services.protocols.bgp.rtconf.base import ConfWithId 37from ryu.services.protocols.bgp.rtconf.base import ConfWithIdListener 38from ryu.services.protocols.bgp.rtconf.base import ConfWithStats 39from ryu.services.protocols.bgp.rtconf.base import ConfWithStatsListener 40from ryu.services.protocols.bgp.rtconf.base import MAX_NUM_EXPORT_RT 41from ryu.services.protocols.bgp.rtconf.base import MAX_NUM_IMPORT_RT 42from ryu.services.protocols.bgp.rtconf.base import MULTI_EXIT_DISC 43from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError 44from ryu.services.protocols.bgp.rtconf.base import SITE_OF_ORIGINS 45from ryu.services.protocols.bgp.rtconf.base import validate 46from ryu.services.protocols.bgp.rtconf.base import validate_med 47from ryu.services.protocols.bgp.rtconf.base import validate_soo_list 48 49 50LOG = logging.getLogger('bgpspeaker.rtconf.vrfs') 51 52# Configuration setting names. 53ROUTE_DISTINGUISHER = 'route_dist' 54IMPORT_RTS = 'import_rts' 55EXPORT_RTS = 'export_rts' 56VRF_NAME = 'vrf_name' 57VRF_DESC = 'vrf_desc' 58VRF_RF = 'route_family' 59IMPORT_MAPS = 'import_maps' 60 61# Supported VRF route-families 62VRF_RF_IPV4 = 'ipv4' 63VRF_RF_IPV6 = 'ipv6' 64VRF_RF_L2_EVPN = 'evpn' 65VRF_RF_IPV4_FLOWSPEC = 'ipv4fs' 66VRF_RF_IPV6_FLOWSPEC = 'ipv6fs' 67VRF_RF_L2VPN_FLOWSPEC = 'l2vpnfs' 68SUPPORTED_VRF_RF = ( 69 VRF_RF_IPV4, 70 VRF_RF_IPV6, 71 VRF_RF_L2_EVPN, 72 VRF_RF_IPV4_FLOWSPEC, 73 VRF_RF_IPV6_FLOWSPEC, 74 VRF_RF_L2VPN_FLOWSPEC, 75) 76 77 78# Default configuration values. 79DEFAULT_VRF_NAME = 'no-vrf-name' 80DEFAULT_VRF_DESC = 'no-vrf-desc' 81 82 83@validate(name=IMPORT_RTS) 84def validate_import_rts(import_rts): 85 if not isinstance(import_rts, list): 86 raise ConfigTypeError(conf_name=IMPORT_RTS, conf_value=import_rts) 87 if not (len(import_rts) <= MAX_NUM_IMPORT_RT): 88 raise ConfigValueError(desc='Max. import RT is limited to %s' % 89 MAX_NUM_IMPORT_RT) 90 if not all(validation.is_valid_ext_comm_attr(rt) for rt in import_rts): 91 raise ConfigValueError(conf_name=IMPORT_RTS, conf_value=import_rts) 92 # Check if we have duplicates 93 unique_rts = set(import_rts) 94 if len(unique_rts) != len(import_rts): 95 raise ConfigValueError(desc='Duplicate value provided %s' % import_rts) 96 97 return import_rts 98 99 100@validate(name=EXPORT_RTS) 101def validate_export_rts(export_rts): 102 if not isinstance(export_rts, list): 103 raise ConfigTypeError(conf_name=EXPORT_RTS, conf_value=export_rts) 104 if not (len(export_rts) <= MAX_NUM_EXPORT_RT): 105 raise ConfigValueError(desc='Max. import RT is limited to %s' % 106 MAX_NUM_EXPORT_RT) 107 108 if not all(validation.is_valid_ext_comm_attr(rt) for rt in export_rts): 109 raise ConfigValueError(conf_name=EXPORT_RTS, conf_value=export_rts) 110 # Check if we have duplicates 111 unique_rts = set(export_rts) 112 if len(unique_rts) != len(export_rts): 113 raise ConfigValueError(desc='Duplicate value provided in %s' % 114 export_rts) 115 return export_rts 116 117 118@validate(name=ROUTE_DISTINGUISHER) 119def validate_rd(route_dist): 120 if not validation.is_valid_route_dist(route_dist): 121 raise ConfigValueError(conf_name=ROUTE_DISTINGUISHER, 122 conf_value=route_dist) 123 return route_dist 124 125 126@validate(name=VRF_RF) 127def validate_vrf_rf(vrf_rf): 128 if vrf_rf not in SUPPORTED_VRF_RF: 129 raise ConfigValueError(desc='Give VRF route family %s is not ' 130 'supported.' % vrf_rf) 131 return vrf_rf 132 133 134class VrfConf(ConfWithId, ConfWithStats): 135 """Class that encapsulates configurations for one VRF.""" 136 137 VRF_CHG_EVT = 'vrf_chg_evt' 138 139 VALID_EVT = frozenset([VRF_CHG_EVT]) 140 141 REQUIRED_SETTINGS = frozenset([ROUTE_DISTINGUISHER, 142 IMPORT_RTS, 143 EXPORT_RTS]) 144 145 OPTIONAL_SETTINGS = frozenset( 146 [VRF_NAME, MULTI_EXIT_DISC, SITE_OF_ORIGINS, VRF_RF, IMPORT_MAPS] 147 ) 148 149 def __init__(self, **kwargs): 150 """Create an instance of VRF runtime configuration.""" 151 super(VrfConf, self).__init__(**kwargs) 152 153 def _init_opt_settings(self, **kwargs): 154 super(VrfConf, self)._init_opt_settings(**kwargs) 155 # We do not have valid default MED value. 156 # If no MED attribute is provided then we do not have to use MED. 157 # If MED attribute is provided we have to validate it and use it. 158 med = kwargs.pop(MULTI_EXIT_DISC, None) 159 if med and validate_med(med): 160 self._settings[MULTI_EXIT_DISC] = med 161 162 # We do not have valid default SOO value. 163 # If no SOO attribute is provided then we do not have to use SOO. 164 # If SOO attribute is provided we have to validate it and use it. 165 soos = kwargs.pop(SITE_OF_ORIGINS, None) 166 if soos and validate_soo_list(soos): 167 self._settings[SITE_OF_ORIGINS] = soos 168 169 # Current we we only support VRF for IPv4 and IPv6 with default IPv4 170 vrf_rf = kwargs.pop(VRF_RF, VRF_RF_IPV4) 171 if vrf_rf and validate_vrf_rf(vrf_rf): 172 self._settings[VRF_RF] = vrf_rf 173 174 import_maps = kwargs.pop(IMPORT_MAPS, []) 175 self._settings[IMPORT_MAPS] = import_maps 176 177 # ========================================================================= 178 # Required attributes 179 # ========================================================================= 180 181 @property 182 def route_dist(self): 183 return self._settings[ROUTE_DISTINGUISHER] 184 185 # ========================================================================= 186 # Optional attributes with valid defaults. 187 # ========================================================================= 188 189 @property 190 def import_rts(self): 191 return list(self._settings[IMPORT_RTS]) 192 193 @property 194 def export_rts(self): 195 return list(self._settings[EXPORT_RTS]) 196 197 @property 198 def soo_list(self): 199 soos = self._settings.get(SITE_OF_ORIGINS) 200 if soos: 201 soos = list(soos) 202 else: 203 soos = [] 204 return soos 205 206 @property 207 def multi_exit_disc(self): 208 """Returns configured value of MED, else None. 209 210 This configuration does not have default value. 211 """ 212 return self._settings.get(MULTI_EXIT_DISC) 213 214 @property 215 def route_family(self): 216 """Returns configured route family for this VRF 217 218 This configuration does not change. 219 """ 220 return self._settings.get(VRF_RF) 221 222 @property 223 def rd_rf_id(self): 224 return VrfConf.create_rd_rf_id(self.route_dist, self.route_family) 225 226 @property 227 def import_maps(self): 228 return self._settings.get(IMPORT_MAPS) 229 230 @staticmethod 231 def create_rd_rf_id(route_dist, route_family): 232 return route_dist, route_family 233 234 @staticmethod 235 def vrf_rf_2_rf(vrf_rf): 236 if vrf_rf == VRF_RF_IPV4: 237 return RF_IPv4_UC 238 elif vrf_rf == VRF_RF_IPV6: 239 return RF_IPv6_UC 240 elif vrf_rf == VRF_RF_L2_EVPN: 241 return RF_L2_EVPN 242 elif vrf_rf == VRF_RF_IPV4_FLOWSPEC: 243 return RF_IPv4_FLOWSPEC 244 elif vrf_rf == VRF_RF_IPV6_FLOWSPEC: 245 return RF_IPv6_FLOWSPEC 246 elif vrf_rf == VRF_RF_L2VPN_FLOWSPEC: 247 return RF_L2VPN_FLOWSPEC 248 else: 249 raise ValueError('Unsupported VRF route family given %s' % vrf_rf) 250 251 @staticmethod 252 def rf_2_vrf_rf(route_family): 253 if route_family == RF_IPv4_UC: 254 return VRF_RF_IPV4 255 elif route_family == RF_IPv6_UC: 256 return VRF_RF_IPV6 257 elif route_family == RF_L2_EVPN: 258 return VRF_RF_L2_EVPN 259 elif route_family == RF_IPv4_FLOWSPEC: 260 return VRF_RF_IPV4_FLOWSPEC 261 elif route_family == RF_IPv6_FLOWSPEC: 262 return VRF_RF_IPV6_FLOWSPEC 263 elif route_family == RF_L2VPN_FLOWSPEC: 264 return VRF_RF_L2VPN_FLOWSPEC 265 else: 266 raise ValueError('No supported mapping for route family ' 267 'to vrf_route_family exists for %s' % 268 route_family) 269 270 @property 271 def settings(self): 272 """Returns a copy of current settings. 273 274 As some of the attributes are themselves containers, we clone the 275 settings to provide clones for those containers as well. 276 """ 277 # Shallow copy first 278 cloned_setting = self._settings.copy() 279 # Don't want clone to link to same RT containers 280 cloned_setting[IMPORT_RTS] = self.import_rts 281 cloned_setting[EXPORT_RTS] = self.export_rts 282 cloned_setting[SITE_OF_ORIGINS] = self.soo_list 283 return cloned_setting 284 285 @classmethod 286 def get_opt_settings(cls): 287 self_confs = super(VrfConf, cls).get_opt_settings() 288 self_confs.update(VrfConf.OPTIONAL_SETTINGS) 289 return self_confs 290 291 @classmethod 292 def get_req_settings(cls): 293 self_confs = super(VrfConf, cls).get_req_settings() 294 self_confs.update(VrfConf.REQUIRED_SETTINGS) 295 return self_confs 296 297 @classmethod 298 def get_valid_evts(cls): 299 self_valid_evts = super(VrfConf, cls).get_valid_evts() 300 self_valid_evts.update(VrfConf.VALID_EVT) 301 return self_valid_evts 302 303 def update(self, **kwargs): 304 """Updates this `VrfConf` settings. 305 306 Notifies listeners if any settings changed. Returns `True` if update 307 was successful. This vrfs' route family, id and route dist settings 308 cannot be updated/changed. 309 """ 310 # Update inherited configurations 311 super(VrfConf, self).update(**kwargs) 312 vrf_id = kwargs.get(ConfWithId.ID) 313 vrf_rd = kwargs.get(ROUTE_DISTINGUISHER) 314 vrf_rf = kwargs.get(VRF_RF) 315 if (vrf_id != self.id or 316 vrf_rd != self.route_dist or 317 vrf_rf != self.route_family): 318 raise ConfigValueError(desc='id/route-distinguisher/route-family' 319 ' do not match configured value.') 320 321 # Validate and update individual settings 322 new_imp_rts, old_imp_rts = \ 323 self._update_import_rts(**kwargs) 324 export_rts_changed = self._update_export_rts(**kwargs) 325 soos_list_changed = self._update_soo_list(**kwargs) 326 med_changed = self._update_med(**kwargs) 327 re_export_needed = (export_rts_changed or 328 soos_list_changed or 329 med_changed) 330 import_maps = kwargs.get(IMPORT_MAPS, []) 331 re_import_needed = self._update_importmaps(import_maps) 332 333 # If we did have any change in value of any settings, we notify 334 # listeners 335 if (new_imp_rts is not None or 336 old_imp_rts is not None or 337 re_export_needed or re_import_needed): 338 evt_value = ( 339 new_imp_rts, 340 old_imp_rts, 341 import_maps, 342 re_export_needed, 343 re_import_needed 344 ) 345 self._notify_listeners(VrfConf.VRF_CHG_EVT, evt_value) 346 return True 347 348 def _update_import_rts(self, **kwargs): 349 import_rts = kwargs.get(IMPORT_RTS) 350 get_validator(IMPORT_RTS)(import_rts) 351 curr_import_rts = set(self._settings[IMPORT_RTS]) 352 353 import_rts = set(import_rts) 354 if not import_rts.symmetric_difference(curr_import_rts): 355 return None, None 356 357 # Get the difference between current and new RTs 358 new_import_rts = import_rts - curr_import_rts 359 old_import_rts = curr_import_rts - import_rts 360 361 # Update current RTs and notify listeners. 362 self._settings[IMPORT_RTS] = import_rts 363 return new_import_rts, old_import_rts 364 365 def _update_export_rts(self, **kwargs): 366 export_rts = kwargs.get(EXPORT_RTS) 367 get_validator(EXPORT_RTS)(export_rts) 368 curr_export_rts = set(self._settings[EXPORT_RTS]) 369 370 if curr_export_rts.symmetric_difference(export_rts): 371 # Update current RTs and notify listeners. 372 self._settings[EXPORT_RTS] = list(export_rts) 373 return True 374 375 return False 376 377 def _update_soo_list(self, **kwargs): 378 soo_list = kwargs.get(SITE_OF_ORIGINS, []) 379 get_validator(SITE_OF_ORIGINS)(soo_list) 380 curr_soos = set(self.soo_list) 381 382 # If given list is different from existing settings, we update it 383 if curr_soos.symmetric_difference(soo_list): 384 self._settings[SITE_OF_ORIGINS] = soo_list[:] 385 return True 386 387 return False 388 389 def _update_med(self, **kwargs): 390 multi_exit_disc = kwargs.get(MULTI_EXIT_DISC, None) 391 if multi_exit_disc: 392 get_validator(MULTI_EXIT_DISC)(multi_exit_disc) 393 394 if multi_exit_disc != self.multi_exit_disc: 395 self._settings[MULTI_EXIT_DISC] = multi_exit_disc 396 return True 397 398 return False 399 400 def _update_importmaps(self, import_maps): 401 if set(self._settings[IMPORT_MAPS]).symmetric_difference(import_maps): 402 self._settings[IMPORT_MAPS] = import_maps 403 return True 404 405 return False 406 407 def __repr__(self): 408 return ('<%s(route_dist: %r, import_rts: %r, export_rts: %r, ' 409 'soo_list: %r)>' % (self.__class__.__name__, 410 self.route_dist, self.import_rts, 411 self.export_rts, self.soo_list)) 412 413 def __str__(self): 414 return 'VrfConf-%s' % self.route_dist 415 416 417class VrfsConf(BaseConf): 418 """Container for all VRF configurations.""" 419 420 ADD_VRF_CONF_EVT, REMOVE_VRF_CONF_EVT = range(2) 421 422 VALID_EVT = frozenset([ADD_VRF_CONF_EVT, REMOVE_VRF_CONF_EVT]) 423 424 def __init__(self): 425 super(VrfsConf, self).__init__() 426 self._vrfs_by_rd_rf = {} 427 self._vrfs_by_id = {} 428 429 def _init_opt_settings(self, **kwargs): 430 pass 431 432 @property 433 def vrf_confs(self): 434 """Returns a list of configured `VrfConf`s 435 """ 436 return list(self._vrfs_by_rd_rf.values()) 437 438 @property 439 def vrf_interested_rts(self): 440 interested_rts = set() 441 for vrf_conf in self._vrfs_by_id.values(): 442 interested_rts.update(vrf_conf.import_rts) 443 return interested_rts 444 445 def update(self, **kwargs): 446 raise NotImplementedError('Use either add/remove_vrf_conf' 447 ' methods instead.') 448 449 def add_vrf_conf(self, vrf_conf): 450 if vrf_conf.rd_rf_id in self._vrfs_by_rd_rf.keys(): 451 raise RuntimeConfigError( 452 desc='VrfConf with rd_rf %s already exists' 453 % str(vrf_conf.rd_rf_id) 454 ) 455 if vrf_conf.id in self._vrfs_by_id: 456 raise RuntimeConfigError( 457 desc='VrfConf with id %s already exists' % str(vrf_conf.id) 458 ) 459 460 self._vrfs_by_rd_rf[vrf_conf.rd_rf_id] = vrf_conf 461 self._vrfs_by_id[vrf_conf.id] = vrf_conf 462 self._notify_listeners(VrfsConf.ADD_VRF_CONF_EVT, vrf_conf) 463 464 def remove_vrf_conf(self, route_dist=None, vrf_id=None, 465 vrf_rf=None): 466 """Removes any matching `VrfConf` for given `route_dist` or `vrf_id` 467 468 Parameters: 469 - `route_dist`: (str) route distinguisher of a configured VRF 470 - `vrf_id`: (str) vrf ID 471 - `vrf_rf`: (str) route family of the VRF configuration 472 If only `route_dist` is given, removes `VrfConf`s for all supported 473 address families for this `route_dist`. If `vrf_rf` is given, than only 474 removes `VrfConf` for that specific route family. If only `vrf_id` is 475 given, matching `VrfConf` will be removed. 476 """ 477 if route_dist is None and vrf_id is None: 478 raise RuntimeConfigError(desc='To delete supply route_dist or id.') 479 480 # By default we remove all VRFs for given Id or RD 481 vrf_rfs = SUPPORTED_VRF_RF 482 # If asked to delete specific route family vrf conf. 483 if vrf_rf: 484 vrf_rfs = vrf_rf 485 486 # For all vrf route family asked to be deleted, we collect all deleted 487 # VrfConfs 488 removed_vrf_confs = [] 489 for route_family in vrf_rfs: 490 if route_dist is not None: 491 rd_rf_id = VrfConf.create_rd_rf_id(route_dist, route_family) 492 vrf_conf = self._vrfs_by_rd_rf.pop(rd_rf_id, None) 493 if vrf_conf: 494 self._vrfs_by_id.pop(vrf_conf.id, None) 495 removed_vrf_confs.append(vrf_conf) 496 else: 497 vrf_conf = self._vrfs_by_id.pop(vrf_id, None) 498 if vrf_conf: 499 self._vrfs_by_rd_rf.pop(vrf_conf.rd_rd_id, None) 500 removed_vrf_confs.append(vrf_conf) 501 502 # We do not raise any exception if we cannot find asked VRF. 503 for vrf_conf in removed_vrf_confs: 504 self._notify_listeners(VrfsConf.REMOVE_VRF_CONF_EVT, vrf_conf) 505 return removed_vrf_confs 506 507 def get_vrf_conf(self, route_dist, vrf_rf, vrf_id=None): 508 if route_dist is None and vrf_id is None: 509 raise RuntimeConfigError(desc='To get VRF supply route_dist ' 510 'or vrf_id.') 511 if route_dist is not None and vrf_id is not None: 512 vrf1 = self._vrfs_by_id.get(vrf_id) 513 rd_rf_id = VrfConf.create_rd_rf_id(route_dist, vrf_rf) 514 vrf2 = self._vrfs_by_rd_rf.get(rd_rf_id) 515 if vrf1 is not vrf2: 516 raise RuntimeConfigError(desc='Given VRF ID (%s) and RD (%s)' 517 ' are not of same VRF.' % 518 (vrf_id, route_dist)) 519 vrf = vrf1 520 elif route_dist is not None: 521 rd_rf_id = VrfConf.create_rd_rf_id(route_dist, vrf_rf) 522 vrf = self._vrfs_by_rd_rf.get(rd_rf_id) 523 else: 524 vrf = self._vrfs_by_id.get(vrf_id) 525 return vrf 526 527 @property 528 def vrfs_by_rd_rf_id(self): 529 return dict(self._vrfs_by_rd_rf) 530 531 @classmethod 532 def get_valid_evts(cls): 533 self_valid_evts = super(VrfsConf, cls).get_valid_evts() 534 self_valid_evts.update(VrfsConf.VALID_EVT) 535 return self_valid_evts 536 537 def __repr__(self): 538 return '<%s(%r)>' % (self.__class__.__name__, self._vrfs_by_id) 539 540 @property 541 def settings(self): 542 return [vrf.settings for vrf in self._vrfs_by_id.values()] 543 544 545class VrfConfListener(ConfWithIdListener, ConfWithStatsListener): 546 """Base listener for various VRF configuration change event.""" 547 548 def __init__(self, vrf_conf): 549 super(VrfConfListener, self).__init__(vrf_conf) 550 vrf_conf.add_listener(VrfConf.VRF_CHG_EVT, self.on_chg_vrf_conf) 551 552 def on_chg_vrf_conf(self, evt): 553 raise NotImplementedError('This method should be overridden') 554 555 556class VrfsConfListener(BaseConfListener): 557 """Base listener for VRF container change events.""" 558 559 def __init__(self, vrfs_conf): 560 super(VrfsConfListener, self).__init__(vrfs_conf) 561 vrfs_conf.add_listener(VrfsConf.ADD_VRF_CONF_EVT, self.on_add_vrf_conf) 562 vrfs_conf.add_listener(VrfsConf.REMOVE_VRF_CONF_EVT, 563 self.on_remove_vrf_conf) 564 565 @abc.abstractmethod 566 def on_add_vrf_conf(self, evt): 567 raise NotImplementedError('This method should be overridden') 568 569 @abc.abstractmethod 570 def on_remove_vrf_conf(self, evt): 571 raise NotImplementedError('This method should be overridden') 572