diff options
author | IWASE Yusuke <iwase.yusuke0@gmail.com> | 2016-08-22 17:21:31 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2016-08-25 13:33:20 +0900 |
commit | b8e75e7e7bffe37736704f20e433f94a81071fcd (patch) | |
tree | 976c589dbfc7430ddd25d50d873111365d36eb8e | |
parent | 59a3049e1326cc58e7063b3aa6642648dc222892 (diff) |
BGPSpeaker: Support VRF Table for Ethernet VPN
This patch enables BGPSpeaker to store EVPN routes into the VRF
tables and to provide the API for advertising routes.
Usage example:
speaker = BGPSpeaker(as_number=65001,
router_id='172.17.0.1')
speaker.neighbor_add(address='172.17.0.2', remote_as=65002,
enable_evpn=True)
speaker.vrf_add(route_dist='65001:100',
import_rts=['65001:100'],
export_rts=['65001:100'],
route_family=RF_L2_EVPN)
speaker.evpn_prefix_add(route_type=EVPN_MAC_IP_ADV_ROUTE,
route_dist='65001:100',
esi=0,
ethernet_tag_id=200,
mac_addr='aa:bb:cc:dd:ee:ff',
ip_addr='10.0.0.1',
next_hop='172.19.0.1')
Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r-- | ryu/services/protocols/bgp/api/prefix.py | 48 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgpspeaker.py | 27 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/core_managers/table_manager.py | 211 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/evpn.py | 45 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/vpn.py | 20 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/vrf.py | 72 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/vrfevpn.py | 58 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/operator/commands/show/vrf.py | 9 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/operator/internal_api.py | 2 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/rtconf/vrfs.py | 30 |
10 files changed, 289 insertions, 233 deletions
diff --git a/ryu/services/protocols/bgp/api/prefix.py b/ryu/services/protocols/bgp/api/prefix.py index f6cddc31..3340112f 100644 --- a/ryu/services/protocols/bgp/api/prefix.py +++ b/ryu/services/protocols/bgp/api/prefix.py @@ -40,6 +40,7 @@ from ryu.services.protocols.bgp.rtconf.base import ConfigValueError from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV4 +from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_L2_EVPN from ryu.services.protocols.bgp.utils import validation @@ -125,7 +126,7 @@ def add_local(route_dist, prefix, next_hop, route_family=VRF_RF_IPV4): try: # Create new path and insert into appropriate VRF table. tm = CORE_MANAGER.get_core_service().table_manager - label = tm.add_to_vrf(route_dist, prefix, next_hop, route_family) + label = tm.update_vrf_table(route_dist, prefix, next_hop, route_family) # Currently we only allocate one label per local_prefix, # so we share first label from the list. if label: @@ -147,8 +148,9 @@ def delete_local(route_dist, prefix, route_family=VRF_RF_IPV4): """ try: tm = CORE_MANAGER.get_core_service().table_manager - tm.remove_from_vrf(route_dist, prefix, route_family) - # Send success response to ApgwAgent. + tm.update_vrf_table(route_dist, prefix, + route_family=route_family, is_withdraw=True) + # Send success response. return [{ROUTE_DISTINGUISHER: route_dist, PREFIX: prefix, VRF_RF: route_family}] except BgpCoreError as e: @@ -165,9 +167,26 @@ def delete_local(route_dist, prefix, route_family=VRF_RF_IPV4): opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR, IP_ADDR]) def add_evpn_local(route_type, route_dist, next_hop, **kwargs): - tm = CORE_MANAGER.get_core_service().table_manager - tm.add_to_global_evpn_table(route_type, route_dist, next_hop, **kwargs) - return True + """Adds EVPN route from VRF identified by *route_dist*. + """ + try: + # Create new path and insert into appropriate VRF table. + tm = CORE_MANAGER.get_core_service().table_manager + label = tm.update_vrf_table(route_dist, next_hop=next_hop, + route_family=VRF_RF_L2_EVPN, + route_type=route_type, **kwargs) + # Currently we only allocate one label per local route, + # so we share first label from the list. + if label: + label = label[0] + + # Send success response with new label. + return [{EVPN_ROUTE_TYPE: route_type, + ROUTE_DISTINGUISHER: route_dist, + VRF_RF: VRF_RF_L2_EVPN, + VPN_LABEL: label}.update(kwargs)] + except BgpCoreError as e: + raise PrefixError(desc=e) @RegisterWithArgChecks(name='evpn_prefix.delete_local', @@ -175,7 +194,16 @@ def add_evpn_local(route_type, route_dist, next_hop, **kwargs): opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR, IP_ADDR]) def delete_evpn_local(route_type, route_dist, **kwargs): - tm = CORE_MANAGER.get_core_service().table_manager - tm.add_to_global_evpn_table(route_type, route_dist, is_withdraw=True, - **kwargs) - return True + """Deletes/withdraws EVPN route from VRF identified by *route_dist*. + """ + try: + tm = CORE_MANAGER.get_core_service().table_manager + tm.update_vrf_table(route_dist, + route_family=VRF_RF_L2_EVPN, + route_type=route_type, is_withdraw=True, **kwargs) + # Send success response. + return [{EVPN_ROUTE_TYPE: route_type, + ROUTE_DISTINGUISHER: route_dist, + VRF_RF: VRF_RF_L2_EVPN}.update(kwargs)] + except BgpCoreError as e: + raise PrefixError(desc=e) diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py index ce72e0fa..168220bd 100644 --- a/ryu/services/protocols/bgp/bgpspeaker.py +++ b/ryu/services/protocols/bgp/bgpspeaker.py @@ -70,6 +70,7 @@ from ryu.services.protocols.bgp.rtconf.neighbors import IS_NEXT_HOP_SELF from ryu.services.protocols.bgp.rtconf.neighbors import CONNECT_MODE from ryu.services.protocols.bgp.rtconf.neighbors import LOCAL_ADDRESS from ryu.services.protocols.bgp.rtconf.neighbors import LOCAL_PORT +from ryu.services.protocols.bgp.rtconf.vrfs import SUPPORTED_VRF_RF from ryu.services.protocols.bgp.info_base.base import Filter from ryu.services.protocols.bgp.info_base.ipv4 import Ipv4Path from ryu.services.protocols.bgp.info_base.ipv6 import Ipv6Path @@ -80,6 +81,7 @@ from ryu.services.protocols.bgp.info_base.vpnv6 import Vpnv6Path NEIGHBOR_CONF_MED = 'multi_exit_disc' RF_VPN_V4 = vrfs.VRF_RF_IPV4 RF_VPN_V6 = vrfs.VRF_RF_IPV6 +RF_L2_EVPN = vrfs.VRF_RF_L2_EVPN class EventPrefix(object): @@ -588,29 +590,30 @@ class BGPSpeaker(object): This parameter must be a list of string. ``route_family`` specifies route family of the VRF. - This parameter must be RF_VPN_V4 or RF_VPN_V6. + This parameter must be RF_VPN_V4, RF_VPN_V6 or RF_L2_EVPN. """ - assert route_family in (RF_VPN_V4, RF_VPN_V6),\ - 'route_family must be RF_VPN_V4 or RF_VPN_V6' + assert route_family in SUPPORTED_VRF_RF,\ + 'route_family must be RF_VPN_V4, RF_VPN_V6 or RF_L2_EVPN' + + vrf = { + vrfs.ROUTE_DISTINGUISHER: route_dist, + vrfs.IMPORT_RTS: import_rts, + vrfs.EXPORT_RTS: export_rts, + vrfs.SITE_OF_ORIGINS: site_of_origins, + vrfs.VRF_RF: route_family, + } - vrf = {} - vrf[vrfs.ROUTE_DISTINGUISHER] = route_dist - vrf[vrfs.IMPORT_RTS] = import_rts - vrf[vrfs.EXPORT_RTS] = export_rts - vrf[vrfs.SITE_OF_ORIGINS] = site_of_origins - vrf[vrfs.VRF_RF] = route_family call('vrf.create', **vrf) def vrf_del(self, route_dist): """ This method deletes the existing vrf. ``route_dist`` specifies a route distinguisher value. - """ - vrf = {} - vrf[vrfs.ROUTE_DISTINGUISHER] = route_dist + vrf = {vrfs.ROUTE_DISTINGUISHER: route_dist} + call('vrf.delete', **vrf) def vrfs_get(self, format='json'): diff --git a/ryu/services/protocols/bgp/core_managers/table_manager.py b/ryu/services/protocols/bgp/core_managers/table_manager.py index ec71d3f5..49d8f2a3 100644 --- a/ryu/services/protocols/bgp/core_managers/table_manager.py +++ b/ryu/services/protocols/bgp/core_managers/table_manager.py @@ -15,11 +15,12 @@ from ryu.services.protocols.bgp.info_base.vpnv6 import Vpnv6Path from ryu.services.protocols.bgp.info_base.vpnv6 import Vpnv6Table from ryu.services.protocols.bgp.info_base.vrf4 import Vrf4Table from ryu.services.protocols.bgp.info_base.vrf6 import Vrf6Table -from ryu.services.protocols.bgp.info_base.evpn import EvpnPath +from ryu.services.protocols.bgp.info_base.vrfevpn import VrfEvpnTable from ryu.services.protocols.bgp.info_base.evpn import EvpnTable from ryu.services.protocols.bgp.rtconf import vrfs from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV4 from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV6 +from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_L2_EVPN from ryu.services.protocols.bgp.rtconf.vrfs import SUPPORTED_VRF_RF from ryu.lib import type_desc @@ -102,24 +103,25 @@ class TableCoreManager(object): LOG.debug('VRF with RD %s marked for removal', vrf_conf.route_dist) def import_all_vpn_paths_to_vrf(self, vrf_table, import_rts=None): - """Imports Vpnv4/6 paths from Global/VPN table into given Vrfv4/6 - table. + """Imports VPNv4/6 or EVPN paths from Global/VPN table into given + VRFv4/6 or VRFEVPN table. :param vrf_table: Vrf table to which we import :type vrf_table: VrfTable :param import_rts: import RTs to override default import_rts of vrf table for this import :type import_rts: set of strings - Checks if we have any path RT common with VRF table's import RT. """ - rfs = (Vrf4Table.ROUTE_FAMILY, Vrf6Table.ROUTE_FAMILY) - assert vrf_table.route_family in rfs, 'Invalid VRF table.' - if vrf_table.route_family == Vrf4Table.ROUTE_FAMILY: vpn_table = self.get_vpn4_table() - else: + elif vrf_table.route_family == Vrf6Table.ROUTE_FAMILY: vpn_table = self.get_vpn6_table() + elif vrf_table.route_family == VrfEvpnTable.ROUTE_FAMILY: + vpn_table = self.get_evpn_table() + else: + raise ValueError('Invalid VRF table route family: %s' % + vrf_table.route_family) vrf_table.import_vpn_paths_from_table(vpn_table, import_rts) @@ -320,8 +322,8 @@ class TableCoreManager(object): for path in dest.known_path_list: if path.source is None: vrf_table.insert_vrf_path( - path.nlri, - path.nexthop, + nlri=path.nlri, + next_hop=path.nexthop, gen_lbl=True ) LOG.debug('Re-installed NC paths with current policy for table %s.', @@ -364,26 +366,24 @@ class TableCoreManager(object): importing/installing of paths from global tables. Returns created table. """ - route_family = vrf_conf.route_family - assert route_family in (VRF_RF_IPV4, VRF_RF_IPV6) - vrf_table = None - if route_family == VRF_RF_IPV4: - vrf_table = Vrf4Table( - vrf_conf, self._core_service, self._signal_bus - ) - table_id = (vrf_conf.route_dist, route_family) - self._tables[table_id] = vrf_table + if route_family == VRF_RF_IPV4: + vrf_table = Vrf4Table elif route_family == VRF_RF_IPV6: - vrf_table = Vrf6Table( - vrf_conf, self._core_service, self._signal_bus - ) - table_id = (vrf_conf.route_dist, route_family) - self._tables[table_id] = vrf_table + vrf_table = Vrf6Table + elif route_family == VRF_RF_L2_EVPN: + vrf_table = VrfEvpnTable + else: + raise ValueError('Unsupported route family for VRF: %s' % + route_family) + + vrf_table = vrf_table(vrf_conf, self._core_service, self._signal_bus) + table_id = (vrf_conf.route_dist, route_family) + self._tables[table_id] = vrf_table assert vrf_table is not None - LOG.debug('Added new VrfTable with rd: %s and add_fmly: %s', + LOG.debug('Added new VrfTable with route_dist:%s and route_family:%s', vrf_conf.route_dist, route_family) import_rts = vrf_conf.import_rts @@ -435,13 +435,11 @@ class TableCoreManager(object): uninteresting_dest_count) def import_single_vpn_path_to_all_vrfs(self, vpn_path, path_rts=None): - """Imports *vpnv4_path* to qualifying VRF tables. + """Imports *vpn_path* to qualifying VRF tables. Import RTs of VRF table is matched with RTs from *vpn4_path* and if we have any common RTs we import the path into VRF. """ - assert (vpn_path.route_family in - (Vpnv4Path.ROUTE_FAMILY, Vpnv6Path.ROUTE_FAMILY)) LOG.debug('Importing path %s to qualifying VRFs', vpn_path) # If this path has no RTs we are done. @@ -453,9 +451,16 @@ class TableCoreManager(object): interested_tables = set() # Get route family of VRF to when this VPN Path can be imported to - route_family = RF_IPv4_UC - if vpn_path.route_family != RF_IPv4_VPN: + if vpn_path.route_family == RF_IPv4_VPN: + route_family = RF_IPv4_UC + elif vpn_path.route_family == RF_IPv6_VPN: route_family = RF_IPv6_UC + elif vpn_path.route_family == RF_L2_EVPN: + route_family = RF_L2_EVPN + else: + raise ValueError('Unsupported route family for VRF: %s' % + vpn_path.route_family) + for rt in path_rts: rt_rf_id = rt + ':' + str(route_family) vrf_rt_tables = self._tables_for_rt.get(rt_rf_id) @@ -478,44 +483,70 @@ class TableCoreManager(object): # If we do not have any VRF with import RT that match with path RT LOG.debug('No VRF table found that imports RTs: %s', path_rts) - def add_to_vrf(self, route_dist, prefix, next_hop, route_family): - """Adds `prefix` to VRF identified by `route_dist` with given - `next_hop`. + def update_vrf_table(self, route_dist, prefix=None, next_hop=None, + route_family=None, route_type=None, + is_withdraw=False, **kwargs): + """Update a BGP route in the VRF table identified by `route_dist` + with the given `next_hop`. + + If `is_withdraw` is False, which is the default, add a BGP route + to the VRF table identified by `route_dist` with the given + `next_hop`. + If `is_withdraw` is True, remove a BGP route from the VRF table + and the given `next_hop` is ignored. + + If `route_family` is VRF_RF_L2_EVPN, `route_type` and `kwargs` + are required to construct EVPN NLRI and `prefix` is ignored. Returns assigned VPN label. """ from ryu.services.protocols.bgp.core import BgpCoreError - assert route_dist and prefix and next_hop - if route_family not in (VRF_RF_IPV4, VRF_RF_IPV6): - raise ValueError('Given route_family %s is not supported.' % - route_family) + assert route_dist + + if is_withdraw: + gen_lbl = False + next_hop = None + else: + gen_lbl = True + if not (is_valid_ipv4(next_hop) or is_valid_ipv6(next_hop)): + raise BgpCoreError( + desc='Invalid IPv4/IPv6 nexthop: %s' % next_hop) + + vrf_table = self._tables.get((route_dist, route_family)) + if vrf_table is None: + raise BgpCoreError( + desc='VRF table does not exist: route_dist=%s, ' + 'route_family=%s' % (route_dist, route_family)) - vrf_table = None - table_id = (route_dist, route_family) if route_family == VRF_RF_IPV4: - vrf_table = self._tables.get(table_id) - if vrf_table is None: - raise BgpCoreError(desc='VRF table for RD: %s does not ' - 'exist.' % route_dist) - if not is_valid_ipv4_prefix(prefix) or not is_valid_ipv4(next_hop): - raise BgpCoreError(desc='Invalid Ipv4 prefix or nexthop.') + if not is_valid_ipv4_prefix(prefix): + raise BgpCoreError(desc='Invalid IPv4 prefix: %s' % prefix) ip, masklen = prefix.split('/') prefix = IPAddrPrefix(int(masklen), ip) elif route_family == VRF_RF_IPV6: - vrf_table = self._tables.get(table_id) - if vrf_table is None: - raise BgpCoreError(desc='VRF table for RD: %s does not ' - 'exist.' % route_dist) - if not is_valid_ipv6_prefix(prefix) or not is_valid_ipv6(next_hop): - raise BgpCoreError(desc='Invalid Ipv6 prefix or nexthop.') + if not is_valid_ipv6_prefix(prefix): + raise BgpCoreError(desc='Invalid IPv6 prefix: %s' % prefix) ip6, masklen = prefix.split('/') prefix = IP6AddrPrefix(int(masklen), ip6) + elif route_family == VRF_RF_L2_EVPN: + assert route_type + subclass = EvpnNLRI._lookup_type_name(route_type) + kwargs['route_dist'] = route_dist + esi = kwargs.get('esi', None) + if esi is not None: + # Note: Currently, we support arbitrary 9-octet ESI value only. + kwargs['esi'] = EvpnArbitraryEsi(type_desc.Int9.from_user(esi)) + prefix = subclass(**kwargs) + else: + raise BgpCoreError( + desc='Unsupported route family %s' % route_family) + # We do not check if we have a path to given prefix, we issue + # withdrawal. Hence multiple withdrawals have not side effect. return vrf_table.insert_vrf_path( - prefix, next_hop=next_hop, - gen_lbl=True - ) + nlri=prefix, next_hop=next_hop, gen_lbl=gen_lbl, + is_withdraw=is_withdraw) def add_to_global_table(self, prefix, nexthop=None, is_withdraw=False): @@ -550,78 +581,6 @@ class TableCoreManager(object): # add to global ipv4 table and propagates to neighbors self.learn_path(new_path) - def add_to_global_evpn_table(self, route_type, route_dist, next_hop=None, - is_withdraw=False, **kwargs): - """Adds BGP EVPN Route to global EVPN Table with given `next_hop`. - - If `is_withdraw` is set to `True`, removes the given route from - global EVPN Table. - """ - - # construct EVPN NLRI instance - subclass = EvpnNLRI._lookup_type_name(route_type) - kwargs['route_dist'] = route_dist - esi = kwargs.get('esi', None) - if esi is not None: - # Note: Currently, we support arbitrary 9-octet ESI value only. - kwargs['esi'] = EvpnArbitraryEsi(type_desc.Int9.from_user(esi)) - nlri = subclass(**kwargs) - - # set mandatory path attributes - origin = BGPPathAttributeOrigin(BGP_ATTR_ORIGIN_IGP) - aspath = BGPPathAttributeAsPath([[]]) - pathattrs = OrderedDict() - pathattrs[BGP_ATTR_TYPE_ORIGIN] = origin - pathattrs[BGP_ATTR_TYPE_AS_PATH] = aspath - - # set the default next_hop address - if next_hop is None: - next_hop = '0.0.0.0' - - new_path = EvpnPath(source=None, nlri=nlri, src_ver_num=1, - pattrs=pathattrs, nexthop=next_hop, - is_withdraw=is_withdraw) - - # add to global EVPN table and propagates to neighbors - self.learn_path(new_path) - - def remove_from_vrf(self, route_dist, prefix, route_family): - """Removes `prefix` from VRF identified by `route_dist`. - - Returns assigned VPN label. - """ - from ryu.services.protocols.bgp.core import BgpCoreError - # Validate given - if route_family not in (VRF_RF_IPV4, VRF_RF_IPV6): - raise BgpCoreError(desc='Unsupported route family %s' % - route_family) - val_ipv4 = route_family == VRF_RF_IPV4\ - and is_valid_ipv4_prefix(prefix) - val_ipv6 = route_family == VRF_RF_IPV6\ - and is_valid_ipv6_prefix(prefix) - - if not val_ipv4 and not val_ipv6: - raise BgpCoreError(desc='Invalid prefix or nexthop.') - - table_id = (route_dist, route_family) - if route_family == VRF_RF_IPV4: - vrf_table = self._tables.get(table_id) - if not vrf_table: - raise BgpCoreError(desc='Vrf for route distinguisher %s does ' - 'not exist.' % route_dist) - ip, masklen = prefix.split('/') - prefix = IPAddrPrefix(int(masklen), ip) - else: - vrf_table = self._tables.get(table_id) - if not vrf_table: - raise BgpCoreError(desc='Vrf for route distinguisher %s does ' - 'not exist.' % route_dist) - ip6, masklen = prefix.split('/') - prefix = IP6AddrPrefix(int(masklen), ip6) - # We do not check if we have a path to given prefix, we issue - # withdrawal. Hence multiple withdrawals have not side effect. - return vrf_table.insert_vrf_path(prefix, is_withdraw=True) - def clean_stale_routes(self, peer, route_family=None): """Removes old routes from `peer` from `route_family` table. diff --git a/ryu/services/protocols/bgp/info_base/evpn.py b/ryu/services/protocols/bgp/info_base/evpn.py index 1a2c6f6b..c5f49a4f 100644 --- a/ryu/services/protocols/bgp/info_base/evpn.py +++ b/ryu/services/protocols/bgp/info_base/evpn.py @@ -22,32 +22,22 @@ import logging from ryu.lib.packet.bgp import EvpnNLRI from ryu.lib.packet.bgp import RF_L2_EVPN -from ryu.services.protocols.bgp.info_base.base import Path -from ryu.services.protocols.bgp.info_base.base import Table -from ryu.services.protocols.bgp.info_base.base import Destination -from ryu.services.protocols.bgp.info_base.base import NonVrfPathProcessingMixin +from ryu.services.protocols.bgp.info_base.vpn import VpnDest +from ryu.services.protocols.bgp.info_base.vpn import VpnPath +from ryu.services.protocols.bgp.info_base.vpn import VpnTable LOG = logging.getLogger('bgpspeaker.info_base.evpn') -class EvpnDest(Destination, NonVrfPathProcessingMixin): +class EvpnDest(VpnDest): """EVPN Destination - Store EVPN paths. + Store EVPN Paths. """ ROUTE_FAMILY = RF_L2_EVPN - def _best_path_lost(self): - old_best_path = self._best_path - NonVrfPathProcessingMixin._best_path_lost(self) - self._core_service._signal_bus.best_path_changed(old_best_path, True) - def _new_best_path(self, best_path): - NonVrfPathProcessingMixin._new_best_path(self, best_path) - self._core_service._signal_bus.best_path_changed(best_path, False) - - -class EvpnTable(Table): +class EvpnTable(VpnTable): """Global table to store EVPN routing information. Uses `EvpnDest` to store destination information for each known EVPN @@ -56,25 +46,8 @@ class EvpnTable(Table): ROUTE_FAMILY = RF_L2_EVPN VPN_DEST_CLASS = EvpnDest - def __init__(self, core_service, signal_bus): - super(EvpnTable, self).__init__(None, core_service, signal_bus) - - def _table_key(self, nlri): - """Return a key that will uniquely identify this NLRI inside - this table. - """ - return nlri.formatted_nlri_str - - def _create_dest(self, nlri): - return self.VPN_DEST_CLASS(self, nlri) - - def __str__(self): - return '%s(scope_id: %s, rf: %s)' % ( - self.__class__.__name__, self.scope_id, self.route_family - ) - -class EvpnPath(Path): +class EvpnPath(VpnPath): """Represents a way of reaching an EVPN destination.""" ROUTE_FAMILY = RF_L2_EVPN VRF_PATH_CLASS = None # defined in init - anti cyclic import hack @@ -82,5 +55,5 @@ class EvpnPath(Path): def __init__(self, *args, **kwargs): super(EvpnPath, self).__init__(*args, **kwargs) - # TODO: - # To support the VRF table for BGP EVPN routes. + from ryu.services.protocols.bgp.info_base.vrfevpn import VrfEvpnPath + self.VRF_PATH_CLASS = VrfEvpnPath diff --git a/ryu/services/protocols/bgp/info_base/vpn.py b/ryu/services/protocols/bgp/info_base/vpn.py index 0f591070..46cf47fb 100644 --- a/ryu/services/protocols/bgp/info_base/vpn.py +++ b/ryu/services/protocols/bgp/info_base/vpn.py @@ -21,6 +21,7 @@ import abc import logging import six +from ryu.lib.packet.bgp import RF_L2_EVPN from ryu.services.protocols.bgp.info_base.base import Destination from ryu.services.protocols.bgp.info_base.base import NonVrfPathProcessingMixin from ryu.services.protocols.bgp.info_base.base import Path @@ -63,23 +64,30 @@ class VpnPath(Path): NLRI_CLASS = None def clone_to_vrf(self, is_withdraw=False): - vrf_nlri = self.NLRI_CLASS(self._nlri.prefix) + if self.ROUTE_FAMILY == RF_L2_EVPN: + nlri_cls = self.NLRI_CLASS._lookup_type(self._nlri.type) + kwargs = dict(self._nlri.__dict__) + kwargs.pop('type', None) + vrf_nlri = nlri_cls(**kwargs) + else: # self.ROUTE_FAMILY in [RF_IPv4_VPN, RF_IPv46_VPN] + vrf_nlri = self.NLRI_CLASS(self._nlri.prefix) pathattrs = None if not is_withdraw: pathattrs = self.pathattr_map vrf_path = self.VRF_PATH_CLASS( - self.VRF_PATH_CLASS.create_puid( + puid=self.VRF_PATH_CLASS.create_puid( self._nlri.route_dist, - self._nlri.prefix - ), - self.source, vrf_nlri, - self.source_version_num, + self._nlri.prefix), + source=self.source, + nlri=vrf_nlri, + src_ver_num=self.source_version_num, pattrs=pathattrs, nexthop=self.nexthop, is_withdraw=is_withdraw, label_list=self._nlri.label_list) + return vrf_path diff --git a/ryu/services/protocols/bgp/info_base/vrf.py b/ryu/services/protocols/bgp/info_base/vrf.py index c3f6603c..ca6fdac2 100644 --- a/ryu/services/protocols/bgp/info_base/vrf.py +++ b/ryu/services/protocols/bgp/info_base/vrf.py @@ -30,6 +30,7 @@ from ryu.lib.packet.bgp import BGPPathAttributeAsPath from ryu.lib.packet.bgp import BGPPathAttributeExtendedCommunities from ryu.lib.packet.bgp import BGPTwoOctetAsSpecificExtendedCommunity from ryu.lib.packet.bgp import BGPPathAttributeMultiExitDisc +from ryu.lib.packet.bgp import RF_L2_EVPN from ryu.services.protocols.bgp.base import OrderedDict from ryu.services.protocols.bgp.constants import VPN_TABLE @@ -87,7 +88,10 @@ class VrfTable(Table): """Return a key that will uniquely identify this NLRI inside this table. """ - return str(nlri) + # Note: We use `prefix` representation of the NLRI, because + # BGP route can be identified without the route distinguisher + # value in the VRF space. + return nlri.prefix def _create_dest(self, nlri): return self.VRF_DEST_CLASS(self, nlri) @@ -134,7 +138,8 @@ class VrfTable(Table): self.import_vpn_path(vpn_path) def import_vpn_path(self, vpn_path): - """Imports `vpnv(4|6)_path` into `vrf(4|6)_table`. + """Imports `vpnv(4|6)_path` into `vrf(4|6)_table` or `evpn_path` + into vrfevpn_table`. :Parameters: - `vpn_path`: (Path) VPN path that will be cloned and imported @@ -148,17 +153,24 @@ class VrfTable(Table): source = vpn_path.source if not source: source = VRF_TABLE - ip, masklen = vpn_path.nlri.prefix.split('/') - vrf_nlri = self.NLRI_CLASS(length=int(masklen), addr=ip) - vpn_nlri = vpn_path.nlri - puid = self.VRF_PATH_CLASS.create_puid(vpn_nlri.route_dist, - vpn_nlri.prefix) + if self.VPN_ROUTE_FAMILY == RF_L2_EVPN: + nlri_cls = self.NLRI_CLASS._lookup_type(vpn_path.nlri.type) + kwargs = dict(vpn_path.nlri.__dict__) + kwargs.pop('type', None) + vrf_nlri = nlri_cls(**kwargs) + else: # self.VPN_ROUTE_FAMILY in [RF_IPv4_VPN, RF_IPv6_VPN] + # Copy NLRI instance + ip, masklen = vpn_path.nlri.prefix.split('/') + vrf_nlri = self.NLRI_CLASS(length=int(masklen), addr=ip) + vrf_path = self.VRF_PATH_CLASS( - puid, - source, - vrf_nlri, - vpn_path.source_version_num, + puid=self.VRF_PATH_CLASS.create_puid( + vpn_path.nlri.route_dist, + vpn_path.nlri.prefix), + source=source, + nlri=vrf_nlri, + src_ver_num=vpn_path.source_version_num, pattrs=vpn_path.pathattr_map, nexthop=vpn_path.nexthop, is_withdraw=vpn_path.is_withdraw, @@ -197,9 +209,9 @@ class VrfTable(Table): changed_dests.append(dest) return changed_dests - def insert_vrf_path(self, ip_nlri, next_hop=None, + def insert_vrf_path(self, nlri, next_hop=None, gen_lbl=False, is_withdraw=False): - assert ip_nlri + assert nlri pattrs = None label_list = [] vrf_conf = self.vrf_conf @@ -253,10 +265,10 @@ class VrfTable(Table): label_list.append(table_manager.get_next_vpnv4_label()) puid = self.VRF_PATH_CLASS.create_puid( - vrf_conf.route_dist, ip_nlri.prefix - ) + vrf_conf.route_dist, nlri.prefix) + path = self.VRF_PATH_CLASS( - puid, None, ip_nlri, 0, pattrs=pattrs, + puid, None, nlri, 0, pattrs=pattrs, nexthop=next_hop, label_list=label_list, is_withdraw=is_withdraw ) @@ -410,7 +422,7 @@ class VrfDest(Destination): # version num. as new_paths are implicit withdrawal of old # paths and when doing RouteRefresh (not EnhancedRouteRefresh) # we get same paths again. - if (new_path.puid == path.puid): + if new_path.puid == path.puid: old_paths.append(path) break @@ -489,22 +501,30 @@ class VrfPath(Path): return clone def clone_to_vpn(self, route_dist, for_withdrawal=False): - ip, masklen = self._nlri.prefix.split('/') - vpn_nlri = self.VPN_NLRI_CLASS(length=int(masklen), - addr=ip, - labels=self.label_list, - route_dist=route_dist) + if self.ROUTE_FAMILY == RF_L2_EVPN: + nlri_cls = self.VPN_NLRI_CLASS._lookup_type(self._nlri.type) + kwargs = dict(self._nlri.__dict__) + kwargs.pop('type', None) + vpn_nlri = nlri_cls(**kwargs) + else: # self.ROUTE_FAMILY in [RF_IPv4_UC, RF_IPv6_UC] + ip, masklen = self._nlri.prefix.split('/') + vpn_nlri = self.VPN_NLRI_CLASS(length=int(masklen), + addr=ip, + labels=self.label_list, + route_dist=route_dist) pathattrs = None if not for_withdrawal: pathattrs = self.pathattr_map + vpnv_path = self.VPN_PATH_CLASS( - self.source, vpn_nlri, - self.source_version_num, + source=self.source, + nlri=vpn_nlri, + src_ver_num=self.source_version_num, pattrs=pathattrs, nexthop=self.nexthop, - is_withdraw=for_withdrawal - ) + is_withdraw=for_withdrawal) + return vpnv_path def __eq__(self, b_path): diff --git a/ryu/services/protocols/bgp/info_base/vrfevpn.py b/ryu/services/protocols/bgp/info_base/vrfevpn.py new file mode 100644 index 00000000..5c3a571c --- /dev/null +++ b/ryu/services/protocols/bgp/info_base/vrfevpn.py @@ -0,0 +1,58 @@ +# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" + Defines data types and models required specifically for VRF (for EVPN) + support. Represents data structures for VRF not VPN/global. +""" + +import logging + +from ryu.lib.packet.bgp import RF_L2_EVPN +from ryu.lib.packet.bgp import EvpnNLRI + +from ryu.services.protocols.bgp.info_base.evpn import EvpnPath +from ryu.services.protocols.bgp.info_base.vrf import VrfDest +from ryu.services.protocols.bgp.info_base.vrf import VrfNlriImportMap +from ryu.services.protocols.bgp.info_base.vrf import VrfPath +from ryu.services.protocols.bgp.info_base.vrf import VrfTable + +LOG = logging.getLogger('bgpspeaker.info_base.vrfevpn') + + +class VrfEvpnPath(VrfPath): + """Represents a way of reaching an EVPN destination with a VPN.""" + ROUTE_FAMILY = RF_L2_EVPN + VPN_PATH_CLASS = EvpnPath + VPN_NLRI_CLASS = EvpnNLRI + + +class VrfEvpnDest(VrfDest): + """Destination for EVPN VRFs.""" + ROUTE_FAMILY = RF_L2_EVPN + + +class VrfEvpnTable(VrfTable): + """Virtual Routing and Forwarding information base for EVPN.""" + ROUTE_FAMILY = RF_L2_EVPN + VPN_ROUTE_FAMILY = RF_L2_EVPN + NLRI_CLASS = EvpnNLRI + VRF_PATH_CLASS = VrfEvpnPath + VRF_DEST_CLASS = VrfEvpnDest + + +class VrfEvpnNlriImportMap(VrfNlriImportMap): + VRF_PATH_CLASS = VrfEvpnPath + NLRI_CLASS = EvpnNLRI diff --git a/ryu/services/protocols/bgp/operator/commands/show/vrf.py b/ryu/services/protocols/bgp/operator/commands/show/vrf.py index 8730665c..c89421f7 100644 --- a/ryu/services/protocols/bgp/operator/commands/show/vrf.py +++ b/ryu/services/protocols/bgp/operator/commands/show/vrf.py @@ -15,10 +15,12 @@ from .route_formatter_mixin import RouteFormatterMixin LOG = logging.getLogger('bgpspeaker.operator.commands.show.vrf') +SUPPORTED_VRF_RF = ('ipv4', 'ipv6', 'evpn') + class Routes(Command, RouteFormatterMixin): help_msg = 'show routes present for vrf' - param_help_msg = '<vpn-name> <route-family>(ipv4, ipv6)' + param_help_msg = '<vpn-name> <route-family>%s' % str(SUPPORTED_VRF_RF) command = 'routes' def __init__(self, *args, **kwargs): @@ -32,8 +34,9 @@ class Routes(Command, RouteFormatterMixin): return WrongParamResp() vrf_name = params[0] vrf_rf = params[1] - if vrf_rf not in ('ipv4', 'ipv6'): - return WrongParamResp('route-family not one of (ipv4, ipv6)') + if vrf_rf not in SUPPORTED_VRF_RF: + return WrongParamResp('route-family not one of %s' % + str(SUPPORTED_VRF_RF)) from ryu.services.protocols.bgp.operator.internal_api import \ WrongParamError diff --git a/ryu/services/protocols/bgp/operator/internal_api.py b/ryu/services/protocols/bgp/operator/internal_api.py index c37b1cf0..7f9449ed 100644 --- a/ryu/services/protocols/bgp/operator/internal_api.py +++ b/ryu/services/protocols/bgp/operator/internal_api.py @@ -180,7 +180,7 @@ class InternalApi(object): route_families.extend(SUPPORTED_GLOBAL_RF) else: route_family = RouteFamily(afi, safi) - if (route_family not in SUPPORTED_GLOBAL_RF): + if route_family not in SUPPORTED_GLOBAL_RF: raise WrongParamError('Not supported address-family' ' %s, %s' % (afi, safi)) route_families.append(route_family) diff --git a/ryu/services/protocols/bgp/rtconf/vrfs.py b/ryu/services/protocols/bgp/rtconf/vrfs.py index ecf6463c..1d6581aa 100644 --- a/ryu/services/protocols/bgp/rtconf/vrfs.py +++ b/ryu/services/protocols/bgp/rtconf/vrfs.py @@ -22,6 +22,7 @@ import logging from ryu.lib.packet.bgp import RF_IPv4_UC from ryu.lib.packet.bgp import RF_IPv6_UC +from ryu.lib.packet.bgp import RF_L2_EVPN from ryu.services.protocols.bgp.utils import validation from ryu.services.protocols.bgp.base import get_validator @@ -54,10 +55,11 @@ VRF_DESC = 'vrf_desc' VRF_RF = 'route_family' IMPORT_MAPS = 'import_maps' -# Two supported VRF route-families -VRF_RF_IPV6 = 'ipv6' +# Supported VRF route-families VRF_RF_IPV4 = 'ipv4' -SUPPORTED_VRF_RF = (VRF_RF_IPV4, VRF_RF_IPV6) +VRF_RF_IPV6 = 'ipv6' +VRF_RF_L2_EVPN = 'evpn' +SUPPORTED_VRF_RF = (VRF_RF_IPV4, VRF_RF_IPV6, VRF_RF_L2_EVPN) # Default configuration values. @@ -77,8 +79,7 @@ def validate_import_rts(import_rts): # Check if we have duplicates unique_rts = set(import_rts) if len(unique_rts) != len(import_rts): - raise ConfigValueError(desc='Duplicate value provided %s' % - (import_rts)) + raise ConfigValueError(desc='Duplicate value provided %s' % import_rts) return import_rts @@ -97,7 +98,7 @@ def validate_export_rts(export_rts): unique_rts = set(export_rts) if len(unique_rts) != len(export_rts): raise ConfigValueError(desc='Duplicate value provided in %s' % - (export_rts)) + export_rts) return export_rts @@ -223,6 +224,8 @@ class VrfConf(ConfWithId, ConfWithStats): return RF_IPv4_UC elif vrf_rf == VRF_RF_IPV6: return RF_IPv6_UC + elif vrf_rf == VRF_RF_L2_EVPN: + return RF_L2_EVPN else: raise ValueError('Unsupported VRF route family given %s' % vrf_rf) @@ -232,6 +235,8 @@ class VrfConf(ConfWithId, ConfWithStats): return VRF_RF_IPV4 elif route_family == RF_IPv6_UC: return VRF_RF_IPV6 + elif route_family == RF_L2_EVPN: + return VRF_RF_L2_EVPN else: raise ValueError('No supported mapping for route family ' 'to vrf_route_family exists for %s' % @@ -322,7 +327,7 @@ class VrfConf(ConfWithId, ConfWithStats): import_rts = set(import_rts) if not import_rts.symmetric_difference(curr_import_rts): - return (None, None) + return None, None # Get the difference between current and new RTs new_import_rts = import_rts - curr_import_rts @@ -330,7 +335,7 @@ class VrfConf(ConfWithId, ConfWithStats): # Update current RTs and notify listeners. self._settings[IMPORT_RTS] = import_rts - return (new_import_rts, old_import_rts) + return new_import_rts, old_import_rts def _update_export_rts(self, **kwargs): export_rts = kwargs.get(EXPORT_RTS) @@ -381,7 +386,7 @@ class VrfConf(ConfWithId, ConfWithStats): self.export_rts, self.soo_list)) def __str__(self): - return ('VrfConf-%s' % (self.route_dist)) + return 'VrfConf-%s' % self.route_dist class VrfsConf(BaseConf): @@ -451,7 +456,7 @@ class VrfsConf(BaseConf): vrf_rfs = SUPPORTED_VRF_RF # If asked to delete specific route family vrf conf. if vrf_rf: - vrf_rfs = (vrf_rf) + vrf_rfs = vrf_rf # For all vrf route family asked to be deleted, we collect all deleted # VrfConfs @@ -478,7 +483,6 @@ class VrfsConf(BaseConf): if route_dist is None and vrf_id is None: raise RuntimeConfigError(desc='To get VRF supply route_dist ' 'or vrf_id.') - vrf = None if route_dist is not None and vrf_id is not None: vrf1 = self._vrfs_by_id.get(vrf_id) rd_rf_id = VrfConf.create_rd_rf_id(route_dist, vrf_rf) @@ -500,8 +504,8 @@ class VrfsConf(BaseConf): return dict(self._vrfs_by_rd_rf) @classmethod - def get_valid_evts(self): - self_valid_evts = super(VrfsConf, self).get_valid_evts() + def get_valid_evts(cls): + self_valid_evts = super(VrfsConf, cls).get_valid_evts() self_valid_evts.update(VrfsConf.VALID_EVT) return self_valid_evts |