summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorIWASE Yusuke <iwase.yusuke0@gmail.com>2016-08-22 17:21:31 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2016-08-25 13:33:20 +0900
commitb8e75e7e7bffe37736704f20e433f94a81071fcd (patch)
tree976c589dbfc7430ddd25d50d873111365d36eb8e
parent59a3049e1326cc58e7063b3aa6642648dc222892 (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.py48
-rw-r--r--ryu/services/protocols/bgp/bgpspeaker.py27
-rw-r--r--ryu/services/protocols/bgp/core_managers/table_manager.py211
-rw-r--r--ryu/services/protocols/bgp/info_base/evpn.py45
-rw-r--r--ryu/services/protocols/bgp/info_base/vpn.py20
-rw-r--r--ryu/services/protocols/bgp/info_base/vrf.py72
-rw-r--r--ryu/services/protocols/bgp/info_base/vrfevpn.py58
-rw-r--r--ryu/services/protocols/bgp/operator/commands/show/vrf.py9
-rw-r--r--ryu/services/protocols/bgp/operator/internal_api.py2
-rw-r--r--ryu/services/protocols/bgp/rtconf/vrfs.py30
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