diff options
author | Shinpei Muraoka <shinpei.muraoka@gmail.com> | 2016-11-22 17:26:58 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2016-12-10 14:57:01 +0900 |
commit | 17acdbb6100d8a2335c06b56687645bfd78ec66f (patch) | |
tree | 31805e51a9e670f7cc64125cdaf8371a306cda34 | |
parent | c4a84cb2464f4249d83ef000c43dea56bd1b8c0c (diff) |
BGPSpeaker: Support Ethernet A-D Route and Ethernet Segment Route
This patch supports Ethernet Auto-discovery Route and
Ethernet Segment Route in BGPSpeaker.
Signed-off-by: Shinpei Muraoka <shinpei.muraoka@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r-- | ryu/lib/packet/bgp.py | 6 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/api/base.py | 1 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/api/prefix.py | 60 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgp_sample_conf.py | 28 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgpspeaker.py | 70 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/core_managers/table_manager.py | 29 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/vrf.py | 79 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/utils/validation.py | 5 | ||||
-rw-r--r-- | ryu/tests/unit/services/protocols/bgp/core_managers/test_table_manager.py | 31 | ||||
-rw-r--r-- | ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py | 190 |
10 files changed, 451 insertions, 48 deletions
diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py index 4a52fd86..38787965 100644 --- a/ryu/lib/packet/bgp.py +++ b/ryu/lib/packet/bgp.py @@ -1310,6 +1310,9 @@ class EvpnNLRI(StringifyMixin, _TypeDisp): ROUTE_TYPE_NAME = None # must be defined in subclass + # Reserved value for Ethernet Tag ID. + MAX_ET = 0xFFFFFFFF + # Dictionary of ROUTE_TYPE_NAME to subclass. # e.g.) # _NAMES = {'eth_ad': EvpnEthernetAutoDiscoveryNLRI, ...} @@ -3075,6 +3078,9 @@ class BGPEvpnEsiLabelExtendedCommunity(_ExtendedCommunity): _VALUE_PACK_STR = '!BB2x3s' _VALUE_FIELDS = ['subtype', 'flags'] + # Classification for Flags. + SINGLE_ACTIVE_BIT = 1 << 0 + def __init__(self, label=None, mpls_label=None, vni=None, **kwargs): super(BGPEvpnEsiLabelExtendedCommunity, self).__init__() self.do_init(BGPEvpnEsiLabelExtendedCommunity, self, kwargs) diff --git a/ryu/services/protocols/bgp/api/base.py b/ryu/services/protocols/bgp/api/base.py index 3dd252f9..125fee93 100644 --- a/ryu/services/protocols/bgp/api/base.py +++ b/ryu/services/protocols/bgp/api/base.py @@ -45,6 +45,7 @@ ROUTE_FAMILY = 'route_family' EVPN_ROUTE_TYPE = 'route_type' EVPN_ESI = 'esi' EVPN_ETHERNET_TAG_ID = 'ethernet_tag_id' +REDUNDANCY_MODE = 'redundancy_mode' MAC_ADDR = 'mac_addr' IP_ADDR = 'ip_addr' IP_PREFIX = 'ip_prefix' diff --git a/ryu/services/protocols/bgp/api/prefix.py b/ryu/services/protocols/bgp/api/prefix.py index 3cc61aff..e175f176 100644 --- a/ryu/services/protocols/bgp/api/prefix.py +++ b/ryu/services/protocols/bgp/api/prefix.py @@ -18,13 +18,18 @@ """ import logging +from ryu.lib.packet.bgp import EvpnEsi +from ryu.lib.packet.bgp import EvpnNLRI +from ryu.lib.packet.bgp import EvpnEthernetAutoDiscoveryNLRI from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI from ryu.lib.packet.bgp import EvpnInclusiveMulticastEthernetTagNLRI +from ryu.lib.packet.bgp import EvpnEthernetSegmentNLRI from ryu.lib.packet.bgp import EvpnIpPrefixNLRI from ryu.lib.packet.bgp import BGPPathAttributePmsiTunnel from ryu.services.protocols.bgp.api.base import EVPN_ROUTE_TYPE from ryu.services.protocols.bgp.api.base import EVPN_ESI from ryu.services.protocols.bgp.api.base import EVPN_ETHERNET_TAG_ID +from ryu.services.protocols.bgp.api.base import REDUNDANCY_MODE from ryu.services.protocols.bgp.api.base import MAC_ADDR from ryu.services.protocols.bgp.api.base import IP_ADDR from ryu.services.protocols.bgp.api.base import IP_PREFIX @@ -53,16 +58,49 @@ from ryu.services.protocols.bgp.utils import validation LOG = logging.getLogger('bgpspeaker.api.prefix') +# Maximum value of the Ethernet Tag ID +EVPN_MAX_ET = EvpnNLRI.MAX_ET + +# ESI Types +ESI_TYPE_ARBITRARY = EvpnEsi.ARBITRARY +ESI_TYPE_LACP = EvpnEsi.LACP +ESI_TYPE_L2_BRIDGE = EvpnEsi.L2_BRIDGE +ESI_TYPE_MAC_BASED = EvpnEsi.MAC_BASED +ESI_TYPE_ROUTER_ID = EvpnEsi.ROUTER_ID +ESI_TYPE_AS_BASED = EvpnEsi.AS_BASED +SUPPORTED_ESI_TYPES = [ + ESI_TYPE_ARBITRARY, + ESI_TYPE_LACP, + ESI_TYPE_L2_BRIDGE, + ESI_TYPE_MAC_BASED, + ESI_TYPE_ROUTER_ID, + ESI_TYPE_AS_BASED, +] + # Constants used in API calls for EVPN +EVPN_ETH_AUTO_DISCOVERY = EvpnEthernetAutoDiscoveryNLRI.ROUTE_TYPE_NAME EVPN_MAC_IP_ADV_ROUTE = EvpnMacIPAdvertisementNLRI.ROUTE_TYPE_NAME -EVPN_MULTICAST_ETAG_ROUTE = EvpnInclusiveMulticastEthernetTagNLRI.ROUTE_TYPE_NAME +EVPN_MULTICAST_ETAG_ROUTE = ( + EvpnInclusiveMulticastEthernetTagNLRI.ROUTE_TYPE_NAME) +EVPN_ETH_SEGMENT = EvpnEthernetSegmentNLRI.ROUTE_TYPE_NAME EVPN_IP_PREFIX_ROUTE = EvpnIpPrefixNLRI.ROUTE_TYPE_NAME SUPPORTED_EVPN_ROUTE_TYPES = [ + EVPN_ETH_AUTO_DISCOVERY, EVPN_MAC_IP_ADV_ROUTE, EVPN_MULTICAST_ETAG_ROUTE, + EVPN_ETH_SEGMENT, EVPN_IP_PREFIX_ROUTE, ] +# Constants for ESI Label extended community +REDUNDANCY_MODE_ALL_ACTIVE = 'all_active' +REDUNDANCY_MODE_SINGLE_ACTIVE = 'single_active' +REDUNDANCY_MODE_TYPES = [ + None, + REDUNDANCY_MODE_ALL_ACTIVE, + REDUNDANCY_MODE_SINGLE_ACTIVE, +] + # Constants for BGP Tunnel Encapsulation Attribute TUNNEL_TYPE_VXLAN = 'vxlan' TUNNEL_TYPE_NVGRE = 'nvgre' @@ -134,6 +172,13 @@ def is_valid_ethernet_tag_id(ethernet_tag_id): conf_value=ethernet_tag_id) +@validate(name=REDUNDANCY_MODE) +def is_valid_redundancy_mode(redundancy_mode): + if redundancy_mode not in REDUNDANCY_MODE_TYPES: + raise ConfigValueError(conf_name=REDUNDANCY_MODE, + conf_value=redundancy_mode) + + @validate(name=MAC_ADDR) def is_valid_mac_addr(addr): if not validation.is_valid_mac(addr): @@ -241,12 +286,19 @@ def delete_local(route_dist, prefix, route_family=VRF_RF_IPV4): @RegisterWithArgChecks(name='evpn_prefix.add_local', req_args=[EVPN_ROUTE_TYPE, ROUTE_DISTINGUISHER, NEXT_HOP], - opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR, - IP_ADDR, IP_PREFIX, GW_IP_ADDR, EVPN_VNI, - TUNNEL_TYPE, PMSI_TUNNEL_TYPE]) + opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, + REDUNDANCY_MODE, MAC_ADDR, IP_ADDR, IP_PREFIX, + GW_IP_ADDR, EVPN_VNI, TUNNEL_TYPE, + PMSI_TUNNEL_TYPE]) def add_evpn_local(route_type, route_dist, next_hop, **kwargs): """Adds EVPN route from VRF identified by *route_dist*. """ + + if(route_type in [EVPN_ETH_AUTO_DISCOVERY, EVPN_ETH_SEGMENT] + and kwargs['esi'] == 0): + raise ConfigValueError(conf_name=EVPN_ESI, + conf_value=kwargs['esi']) + try: # Create new path and insert into appropriate VRF table. tm = CORE_MANAGER.get_core_service().table_manager diff --git a/ryu/services/protocols/bgp/bgp_sample_conf.py b/ryu/services/protocols/bgp/bgp_sample_conf.py index de8caf02..9b9564c3 100644 --- a/ryu/services/protocols/bgp/bgp_sample_conf.py +++ b/ryu/services/protocols/bgp/bgp_sample_conf.py @@ -3,11 +3,16 @@ import os from ryu.services.protocols.bgp.bgpspeaker import RF_VPN_V4 from ryu.services.protocols.bgp.bgpspeaker import RF_VPN_V6 from ryu.services.protocols.bgp.bgpspeaker import RF_L2_EVPN +from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAX_ET +from ryu.services.protocols.bgp.bgpspeaker import ESI_TYPE_LACP +from ryu.services.protocols.bgp.bgpspeaker import ESI_TYPE_MAC_BASED +from ryu.services.protocols.bgp.bgpspeaker import EVPN_ETH_AUTO_DISCOVERY from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAC_IP_ADV_ROUTE from ryu.services.protocols.bgp.bgpspeaker import TUNNEL_TYPE_VXLAN from ryu.services.protocols.bgp.bgpspeaker import EVPN_MULTICAST_ETAG_ROUTE +from ryu.services.protocols.bgp.bgpspeaker import EVPN_ETH_SEGMENT from ryu.services.protocols.bgp.bgpspeaker import EVPN_IP_PREFIX_ROUTE - +from ryu.services.protocols.bgp.bgpspeaker import REDUNDANCY_MODE_SINGLE_ACTIVE # ============================================================================= # BGP configuration. @@ -92,6 +97,17 @@ BGP = { }, # Example of EVPN prefix { + 'route_type': EVPN_ETH_AUTO_DISCOVERY, + 'route_dist': '65001:200', + 'esi': { + 'type': ESI_TYPE_LACP, + 'mac_addr': 'aa:bb:cc:dd:ee:ff', + 'port_key': 100, + }, + 'ethernet_tag_id': EVPN_MAX_ET, + 'redundancy_mode': REDUNDANCY_MODE_SINGLE_ACTIVE, + }, + { 'route_type': EVPN_MAC_IP_ADV_ROUTE, 'route_dist': '65001:200', 'esi': 0, @@ -110,6 +126,16 @@ BGP = { 'ip_addr': '10.40.1.1', }, { + 'route_type': EVPN_ETH_SEGMENT, + 'route_dist': '65001:200', + 'esi': { + 'type': ESI_TYPE_MAC_BASED, + 'mac_addr': 'aa:bb:cc:dd:ee:ff', + 'local_disc': 100, + }, + 'ip_addr': '172.17.0.1', + }, + { 'route_type': EVPN_IP_PREFIX_ROUTE, 'route_dist': '65001:200', 'esi': 0, diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py index c56f8dd7..6e90ced8 100644 --- a/ryu/services/protocols/bgp/bgpspeaker.py +++ b/ryu/services/protocols/bgp/bgpspeaker.py @@ -26,6 +26,7 @@ from ryu.services.protocols.bgp.api.base import PREFIX from ryu.services.protocols.bgp.api.base import EVPN_ROUTE_TYPE from ryu.services.protocols.bgp.api.base import EVPN_ESI from ryu.services.protocols.bgp.api.base import EVPN_ETHERNET_TAG_ID +from ryu.services.protocols.bgp.api.base import REDUNDANCY_MODE from ryu.services.protocols.bgp.api.base import IP_ADDR from ryu.services.protocols.bgp.api.base import MAC_ADDR from ryu.services.protocols.bgp.api.base import NEXT_HOP @@ -36,9 +37,17 @@ from ryu.services.protocols.bgp.api.base import ROUTE_FAMILY from ryu.services.protocols.bgp.api.base import EVPN_VNI from ryu.services.protocols.bgp.api.base import TUNNEL_TYPE from ryu.services.protocols.bgp.api.base import PMSI_TUNNEL_TYPE +from ryu.services.protocols.bgp.api.prefix import EVPN_MAX_ET +from ryu.services.protocols.bgp.api.prefix import ESI_TYPE_LACP +from ryu.services.protocols.bgp.api.prefix import ESI_TYPE_L2_BRIDGE +from ryu.services.protocols.bgp.api.prefix import ESI_TYPE_MAC_BASED +from ryu.services.protocols.bgp.api.prefix import EVPN_ETH_AUTO_DISCOVERY from ryu.services.protocols.bgp.api.prefix import EVPN_MAC_IP_ADV_ROUTE from ryu.services.protocols.bgp.api.prefix import EVPN_MULTICAST_ETAG_ROUTE +from ryu.services.protocols.bgp.api.prefix import EVPN_ETH_SEGMENT from ryu.services.protocols.bgp.api.prefix import EVPN_IP_PREFIX_ROUTE +from ryu.services.protocols.bgp.api.prefix import REDUNDANCY_MODE_ALL_ACTIVE +from ryu.services.protocols.bgp.api.prefix import REDUNDANCY_MODE_SINGLE_ACTIVE from ryu.services.protocols.bgp.api.prefix import TUNNEL_TYPE_VXLAN from ryu.services.protocols.bgp.api.prefix import TUNNEL_TYPE_NVGRE from ryu.services.protocols.bgp.api.prefix import ( @@ -538,18 +547,25 @@ class BGPSpeaker(object): def evpn_prefix_add(self, route_type, route_dist, esi=0, ethernet_tag_id=None, mac_addr=None, ip_addr=None, ip_prefix=None, gw_ip_addr=None, vni=None, - next_hop=None, tunnel_type=None, - pmsi_tunnel_type=None): + next_hop=None, tunnel_type=None, pmsi_tunnel_type=None, + redundancy_mode=None): """ This method adds a new EVPN route to be advertised. ``route_type`` specifies one of the EVPN route type name. The - supported route types are EVPN_MAC_IP_ADV_ROUTE, - EVPN_MULTICAST_ETAG_ROUTE and EVPN_IP_PREFIX_ROUTE. + supported route types are EVPN_ETH_AUTO_DISCOVERY, + EVPN_MAC_IP_ADV_ROUTE, EVPN_MULTICAST_ETAG_ROUTE, EVPN_ETH_SEGMENT + and EVPN_IP_PREFIX_ROUTE. ``route_dist`` specifies a route distinguisher value. - ``esi`` is an integer value to specify the Ethernet Segment - Identifier. 0 is the default and denotes a single-homed site. + ``esi`` is an value to specify the Ethernet Segment Identifier. + 0 is the default and denotes a single-homed site. + If you want to advertise esi other than 0, + it must be set as a dictionary type. + The following keys and values must be set. + - type: specifies one of the ESI type. + The remaining keys and values are the same as the argument of + the class corresponding to esi_type. ``ethernet_tag_id`` specifies the Ethernet Tag ID. @@ -576,6 +592,10 @@ class BGPSpeaker(object): used to encode the multicast tunnel identifier. This field is advertised only if route_type is EVPN_MULTICAST_ETAG_ROUTE. + + ``redundancy_mode`` specifies a redundancy mode type. + The supported redundancy mode types are REDUNDANCY_MODE_ALL_ACTIVE + and REDUNDANCY_MODE_SINGLE_ACTIVE. """ func_name = 'evpn_prefix.add_local' @@ -593,7 +613,16 @@ class BGPSpeaker(object): kwargs[TUNNEL_TYPE] = tunnel_type # Set route type specific arguments - if route_type == EVPN_MAC_IP_ADV_ROUTE: + if route_type == EVPN_ETH_AUTO_DISCOVERY: + # REDUNDANCY_MODE is parameter for extended community + kwargs.update({ + EVPN_ESI: esi, + EVPN_ETHERNET_TAG_ID: ethernet_tag_id, + REDUNDANCY_MODE: redundancy_mode, + }) + if vni is not None: + kwargs[EVPN_VNI] = vni + elif route_type == EVPN_MAC_IP_ADV_ROUTE: kwargs.update({ EVPN_ESI: esi, EVPN_ETHERNET_TAG_ID: ethernet_tag_id, @@ -617,6 +646,11 @@ class BGPSpeaker(object): elif pmsi_tunnel_type is not None: raise ValueError('Unsupported PMSI tunnel type: %s' % pmsi_tunnel_type) + elif route_type == EVPN_ETH_SEGMENT: + kwargs.update({ + EVPN_ESI: esi, + IP_ADDR: ip_addr, + }) elif route_type == EVPN_IP_PREFIX_ROUTE: kwargs.update({ EVPN_ESI: esi, @@ -641,8 +675,14 @@ class BGPSpeaker(object): ``route_dist`` specifies a route distinguisher value. - ``esi`` is an integer value to specify the Ethernet Segment - Identifier. 0 is the default and denotes a single-homed site. + ``esi`` is an value to specify the Ethernet Segment Identifier. + 0 is the default and denotes a single-homed site. + If you want to advertise esi other than 0, + it must be set as a dictionary type. + The following keys and values must be set. + - type: specifies one of the ESI type. + The remaining keys and values are the same as the argument of + the class corresponding to esi_type. ``ethernet_tag_id`` specifies the Ethernet Tag ID. @@ -659,7 +699,12 @@ class BGPSpeaker(object): ROUTE_DISTINGUISHER: route_dist} # Set route type specific arguments - if route_type == EVPN_MAC_IP_ADV_ROUTE: + if route_type == EVPN_ETH_AUTO_DISCOVERY: + kwargs.update({ + EVPN_ESI: esi, + EVPN_ETHERNET_TAG_ID: ethernet_tag_id, + }) + elif route_type == EVPN_MAC_IP_ADV_ROUTE: kwargs.update({ EVPN_ESI: esi, EVPN_ETHERNET_TAG_ID: ethernet_tag_id, @@ -671,6 +716,11 @@ class BGPSpeaker(object): EVPN_ETHERNET_TAG_ID: ethernet_tag_id, IP_ADDR: ip_addr, }) + elif route_type == EVPN_ETH_SEGMENT: + kwargs.update({ + EVPN_ESI: esi, + IP_ADDR: ip_addr, + }) elif route_type == EVPN_IP_PREFIX_ROUTE: kwargs.update({ EVPN_ETHERNET_TAG_ID: ethernet_tag_id, diff --git a/ryu/services/protocols/bgp/core_managers/table_manager.py b/ryu/services/protocols/bgp/core_managers/table_manager.py index be3e8bc6..0e084b82 100644 --- a/ryu/services/protocols/bgp/core_managers/table_manager.py +++ b/ryu/services/protocols/bgp/core_managers/table_manager.py @@ -32,6 +32,7 @@ from ryu.lib.packet.bgp import BGPPathAttributeAsPath from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_IGP +from ryu.lib.packet.bgp import EvpnEsi from ryu.lib.packet.bgp import EvpnArbitraryEsi from ryu.lib.packet.bgp import EvpnNLRI from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI @@ -483,7 +484,8 @@ class TableCoreManager(object): def update_vrf_table(self, route_dist, prefix=None, next_hop=None, route_family=None, route_type=None, tunnel_type=None, - is_withdraw=False, pmsi_tunnel_type=None, **kwargs): + is_withdraw=False, redundancy_mode=None, + pmsi_tunnel_type=None, **kwargs): """Update a BGP route in the VRF table identified by `route_dist` with the given `next_hop`. @@ -496,6 +498,8 @@ class TableCoreManager(object): If `route_family` is VRF_RF_L2_EVPN, `route_type` and `kwargs` are required to construct EVPN NLRI and `prefix` is ignored. + ``redundancy_mode`` specifies a redundancy mode type. + ` `pmsi_tunnel_type` specifies the type of the PMSI tunnel attribute used to encode the multicast tunnel identifier. This field is advertised only if route_type is @@ -522,6 +526,8 @@ class TableCoreManager(object): desc='VRF table does not exist: route_dist=%s, ' 'route_family=%s' % (route_dist, route_family)) + vni = kwargs.get('vni', None) + if route_family == VRF_RF_IPV4: if not is_valid_ipv4_prefix(prefix): raise BgpCoreError(desc='Invalid IPv4 prefix: %s' % prefix) @@ -541,14 +547,20 @@ class TableCoreManager(object): 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)) - if 'vni' in kwargs: - # Disable to generate MPLS labels, because encapsulation type - # is not MPLS. + if isinstance(esi, dict): + esi_type = esi.get('type', 0) + esi_class = EvpnEsi._lookup_type(esi_type) + kwargs['esi'] = esi_class.from_jsondict(esi) + else: # isinstance(esi, numbers.Integral) + kwargs['esi'] = EvpnArbitraryEsi( + type_desc.Int9.from_user(esi)) + if vni is not None: + # Disable to generate MPLS labels, + # because encapsulation type is not MPLS. from ryu.services.protocols.bgp.api.prefix import ( TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE) - assert tunnel_type in [TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE] + assert tunnel_type in [ + None, TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE] gen_lbl = False prefix = subclass(**kwargs) else: @@ -559,7 +571,8 @@ class TableCoreManager(object): # withdrawal. Hence multiple withdrawals have not side effect. return vrf_table.insert_vrf_path( nlri=prefix, next_hop=next_hop, gen_lbl=gen_lbl, - is_withdraw=is_withdraw, tunnel_type=tunnel_type, + is_withdraw=is_withdraw, redundancy_mode=redundancy_mode, + vni=vni, tunnel_type=tunnel_type, pmsi_tunnel_type=pmsi_tunnel_type) def update_global_table(self, prefix, next_hop=None, is_withdraw=False): diff --git a/ryu/services/protocols/bgp/info_base/vrf.py b/ryu/services/protocols/bgp/info_base/vrf.py index f6b50bcc..c4e41efa 100644 --- a/ryu/services/protocols/bgp/info_base/vrf.py +++ b/ryu/services/protocols/bgp/info_base/vrf.py @@ -28,10 +28,13 @@ from ryu.lib.packet.bgp import BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC from ryu.lib.packet.bgp import BGPPathAttributeOrigin from ryu.lib.packet.bgp import BGPPathAttributeAsPath +from ryu.lib.packet.bgp import EvpnEthernetSegmentNLRI 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 BGPEncapsulationExtendedCommunity +from ryu.lib.packet.bgp import BGPEvpnEsiLabelExtendedCommunity +from ryu.lib.packet.bgp import BGPEvpnEsImportRTExtendedCommunity from ryu.lib.packet.bgp import BGPPathAttributePmsiTunnel from ryu.lib.packet.bgp import PmsiTunnelIdIngressReplication from ryu.lib.packet.bgp import RF_L2_EVPN @@ -221,6 +224,28 @@ class VrfTable(Table): label_list = [] vrf_conf = self.vrf_conf if not is_withdraw: + table_manager = self._core_service.table_manager + if gen_lbl and next_hop: + # Label per next_hop demands we use a different label + # per next_hop. Here connected interfaces are advertised per + # VRF. + label_key = (vrf_conf.route_dist, next_hop) + nh_label = table_manager.get_nexthop_label(label_key) + if not nh_label: + nh_label = table_manager.get_next_vpnv4_label() + table_manager.set_nexthop_label(label_key, nh_label) + label_list.append(nh_label) + + elif gen_lbl: + # If we do not have next_hop, get a new label. + label_list.append(table_manager.get_next_vpnv4_label()) + + # Set MPLS labels with the generated labels + if gen_lbl and isinstance(nlri, EvpnMacIPAdvertisementNLRI): + nlri.mpls_labels = label_list[:2] + elif gen_lbl and isinstance(nlri, EvpnIpPrefixNLRI): + nlri.mpls_label = label_list[0] + # Create a dictionary for path-attrs. pattrs = OrderedDict() @@ -232,6 +257,15 @@ class VrfTable(Table): EXPECTED_ORIGIN) pattrs[BGP_ATTR_TYPE_AS_PATH] = BGPPathAttributeAsPath([]) communities = [] + + # Set ES-Import Route Target + if isinstance(nlri, EvpnEthernetSegmentNLRI): + subtype = 2 + es_import = nlri.esi.mac_addr + communities.append(BGPEvpnEsImportRTExtendedCommunity( + subtype=subtype, + es_import=es_import)) + for rt in vrf_conf.export_rts: as_num, local_admin = rt.split(':') subtype = 2 @@ -253,28 +287,35 @@ class VrfTable(Table): communities.append( BGPEncapsulationExtendedCommunity.from_str(tunnel_type)) + # Set ESI Label Extended Community + redundancy_mode = kwargs.get('redundancy_mode', None) + if redundancy_mode is not None: + subtype = 1 + flags = 0 + + from ryu.services.protocols.bgp.api.prefix import ( + REDUNDANCY_MODE_SINGLE_ACTIVE) + if redundancy_mode == REDUNDANCY_MODE_SINGLE_ACTIVE: + flags |= BGPEvpnEsiLabelExtendedCommunity.SINGLE_ACTIVE_BIT + + vni = kwargs.get('vni', None) + if vni is not None: + communities.append(BGPEvpnEsiLabelExtendedCommunity( + subtype=subtype, + flags=flags, + vni=vni)) + else: + communities.append(BGPEvpnEsiLabelExtendedCommunity( + subtype=subtype, + flags=flags, + mpls_label=label_list[0])) + pattrs[BGP_ATTR_TYPE_EXTENDED_COMMUNITIES] = \ BGPPathAttributeExtendedCommunities(communities=communities) if vrf_conf.multi_exit_disc: pattrs[BGP_ATTR_TYPE_MULTI_EXIT_DISC] = \ BGPPathAttributeMultiExitDisc(vrf_conf.multi_exit_disc) - table_manager = self._core_service.table_manager - if gen_lbl and next_hop: - # Label per next_hop demands we use a different label - # per next_hop. Here connected interfaces are advertised per - # VRF. - label_key = (vrf_conf.route_dist, next_hop) - nh_label = table_manager.get_nexthop_label(label_key) - if not nh_label: - nh_label = table_manager.get_next_vpnv4_label() - table_manager.set_nexthop_label(label_key, nh_label) - label_list.append(nh_label) - - elif gen_lbl: - # If we do not have next_hop, get a new label. - label_list.append(table_manager.get_next_vpnv4_label()) - # Set PMSI Tunnel Attribute pmsi_tunnel_type = kwargs.get('pmsi_tunnel_type', None) if pmsi_tunnel_type is not None: @@ -290,12 +331,6 @@ class VrfTable(Table): tunnel_type=pmsi_tunnel_type, tunnel_id=tunnel_id) - # Set MPLS labels with the generated labels - if gen_lbl and isinstance(nlri, EvpnMacIPAdvertisementNLRI): - nlri.mpls_labels = label_list[:2] - elif gen_lbl and isinstance(nlri, EvpnIpPrefixNLRI): - nlri.mpls_label = label_list[0] - puid = self.VRF_PATH_CLASS.create_puid( vrf_conf.route_dist, nlri.prefix) diff --git a/ryu/services/protocols/bgp/utils/validation.py b/ryu/services/protocols/bgp/utils/validation.py index ff6ed506..35dc4c72 100644 --- a/ryu/services/protocols/bgp/utils/validation.py +++ b/ryu/services/protocols/bgp/utils/validation.py @@ -240,8 +240,9 @@ def is_valid_ext_comm_attr(attr): def is_valid_esi(esi): """Returns True if the given EVPN Ethernet SegmentEthernet ID is valid.""" - # Note: Currently, only integer type value is supported - return isinstance(esi, numbers.Integral) + if isinstance(esi, numbers.Integral): + return 0 <= esi <= 0xffffffffffffffffff + return isinstance(esi, dict) def is_valid_ethernet_tag_id(etag_id): diff --git a/ryu/tests/unit/services/protocols/bgp/core_managers/test_table_manager.py b/ryu/tests/unit/services/protocols/bgp/core_managers/test_table_manager.py index a53e4c43..48c99e9f 100644 --- a/ryu/tests/unit/services/protocols/bgp/core_managers/test_table_manager.py +++ b/ryu/tests/unit/services/protocols/bgp/core_managers/test_table_manager.py @@ -31,8 +31,12 @@ from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH from ryu.lib.packet.bgp import IPAddrPrefix from ryu.lib.packet.bgp import IP6AddrPrefix from ryu.lib.packet.bgp import EvpnArbitraryEsi +from ryu.lib.packet.bgp import EvpnLACPEsi +from ryu.lib.packet.bgp import EvpnEthernetAutoDiscoveryNLRI from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI from ryu.lib.packet.bgp import EvpnInclusiveMulticastEthernetTagNLRI +from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAX_ET +from ryu.services.protocols.bgp.bgpspeaker import ESI_TYPE_LACP from ryu.services.protocols.bgp.core import BgpCoreError from ryu.services.protocols.bgp.core_managers import table_manager from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV4 @@ -115,7 +119,7 @@ class Test_TableCoreManager(unittest.TestCase): next_hop, route_family, route_type, **kwargs) - def test_update_vrf_table_l2_evpn_with_esi(self): + def test_update_vrf_table_l2_evpn_with_esi_int(self): # Prepare test data route_dist = '65000:100' prefix_str = None # should be ignored @@ -139,6 +143,31 @@ class Test_TableCoreManager(unittest.TestCase): next_hop, route_family, route_type, **kwargs) + def test_update_vrf_table_l2_evpn_with_esi_dict(self): + # Prepare test data + route_dist = '65000:100' + prefix_str = None # should be ignored + kwargs = { + 'ethernet_tag_id': EVPN_MAX_ET, + } + esi = EvpnLACPEsi(mac_addr='aa:bb:cc:dd:ee:ff', port_key=100) + prefix_inst = EvpnEthernetAutoDiscoveryNLRI( + route_dist=route_dist, + esi=esi, + **kwargs) + next_hop = '0.0.0.0' + route_family = VRF_RF_L2_EVPN + route_type = EvpnEthernetAutoDiscoveryNLRI.ROUTE_TYPE_NAME + kwargs['esi'] = { + 'type': ESI_TYPE_LACP, + 'mac_addr': 'aa:bb:cc:dd:ee:ff', + 'port_key': 100, + } + + self._test_update_vrf_table(prefix_inst, route_dist, prefix_str, + next_hop, route_family, route_type, + **kwargs) + def test_update_vrf_table_l2_evpn_without_esi(self): # Prepare test data route_dist = '65000:100' diff --git a/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py b/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py index d5e4908e..da0f4827 100644 --- a/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py +++ b/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py @@ -23,6 +23,12 @@ except ImportError: from nose.tools import raises from ryu.services.protocols.bgp import bgpspeaker +from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAX_ET +from ryu.services.protocols.bgp.bgpspeaker import ESI_TYPE_LACP +from ryu.services.protocols.bgp.api.prefix import ESI_TYPE_L2_BRIDGE +from ryu.services.protocols.bgp.bgpspeaker import ESI_TYPE_MAC_BASED +from ryu.services.protocols.bgp.api.prefix import REDUNDANCY_MODE_ALL_ACTIVE +from ryu.services.protocols.bgp.api.prefix import REDUNDANCY_MODE_SINGLE_ACTIVE LOG = logging.getLogger(__name__) @@ -36,6 +42,86 @@ class Test_BGPSpeaker(unittest.TestCase): @mock.patch('ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__', mock.MagicMock(return_value=None)) @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call') + def test_evpn_prefix_add_eth_auto_discovery(self, mock_call): + # Prepare test data + route_type = bgpspeaker.EVPN_ETH_AUTO_DISCOVERY + route_dist = '65000:100' + esi = { + 'type': ESI_TYPE_LACP, + 'mac_addr': 'aa:bb:cc:dd:ee:ff', + 'port_key': 100, + } + ethernet_tag_id = EVPN_MAX_ET + redundancy_mode = REDUNDANCY_MODE_ALL_ACTIVE + next_hop = '0.0.0.0' + expected_kwargs = { + 'route_type': route_type, + 'route_dist': route_dist, + 'esi': esi, + 'ethernet_tag_id': ethernet_tag_id, + 'redundancy_mode': redundancy_mode, + 'next_hop': next_hop, + } + + # Test + speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1') + speaker.evpn_prefix_add( + route_type=route_type, + route_dist=route_dist, + esi=esi, + ethernet_tag_id=ethernet_tag_id, + redundancy_mode=redundancy_mode, + ) + + # Check + mock_call.assert_called_with( + 'evpn_prefix.add_local', **expected_kwargs) + + @mock.patch( + 'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__', + mock.MagicMock(return_value=None)) + @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call') + def test_evpn_prefix_add_eth_auto_discovery_vni(self, mock_call): + # Prepare test data + route_type = bgpspeaker.EVPN_ETH_AUTO_DISCOVERY + route_dist = '65000:100' + esi = { + 'type': ESI_TYPE_L2_BRIDGE, + 'mac_addr': 'aa:bb:cc:dd:ee:ff', + 'priority': 100, + } + ethernet_tag_id = EVPN_MAX_ET + redundancy_mode = REDUNDANCY_MODE_SINGLE_ACTIVE + vni = 500 + next_hop = '0.0.0.0' + expected_kwargs = { + 'route_type': route_type, + 'route_dist': route_dist, + 'esi': esi, + 'ethernet_tag_id': ethernet_tag_id, + 'redundancy_mode': redundancy_mode, + 'vni': vni, + 'next_hop': next_hop, + } + + # Test + speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1') + speaker.evpn_prefix_add( + route_type=route_type, + route_dist=route_dist, + esi=esi, + ethernet_tag_id=ethernet_tag_id, + redundancy_mode=redundancy_mode, + vni=vni + ) + + # Check + mock_call.assert_called_with( + 'evpn_prefix.add_local', **expected_kwargs) + + @mock.patch('ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__', + mock.MagicMock(return_value=None)) + @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call') def test_evpn_prefix_add_mac_ip_adv(self, mock_call): # Prepare test data route_type = bgpspeaker.EVPN_MAC_IP_ADV_ROUTE @@ -195,6 +281,42 @@ class Test_BGPSpeaker(unittest.TestCase): 'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__', mock.MagicMock(return_value=None)) @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call') + def test_evpn_prefix_add_eth_segment(self, mock_call): + # Prepare test data + route_type = bgpspeaker.EVPN_ETH_SEGMENT + route_dist = '65000:100' + esi = { + 'type': ESI_TYPE_MAC_BASED, + 'mac_addr': 'aa:bb:cc:dd:ee:ff', + 'local_disc': 100, + } + ip_addr = '192.168.0.1' + next_hop = '0.0.0.0' + expected_kwargs = { + 'route_type': route_type, + 'route_dist': route_dist, + 'esi': esi, + 'ip_addr': ip_addr, + 'next_hop': next_hop, + } + + # Test + speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1') + speaker.evpn_prefix_add( + route_type=route_type, + route_dist=route_dist, + esi=esi, + ip_addr=ip_addr, + ) + + # Check + mock_call.assert_called_with( + 'evpn_prefix.add_local', **expected_kwargs) + + @mock.patch( + 'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__', + mock.MagicMock(return_value=None)) + @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call') def test_evpn_prefix_add_ip_prefix_route(self, mock_call): # Prepare test data route_type = bgpspeaker.EVPN_IP_PREFIX_ROUTE @@ -303,6 +425,40 @@ class Test_BGPSpeaker(unittest.TestCase): mock_call.assert_called_with( 'evpn_prefix.add_local', 'Invalid arguments detected') + @mock.patch( + 'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__', + mock.MagicMock(return_value=None)) + @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call') + def test_evpn_prefix_del_auto_discovery(self, mock_call): + # Prepare test data + route_type = bgpspeaker.EVPN_ETH_AUTO_DISCOVERY + route_dist = '65000:100' + esi = { + 'type': ESI_TYPE_LACP, + 'mac_addr': 'aa:bb:cc:dd:ee:ff', + 'port_key': 100, + } + ethernet_tag_id = EVPN_MAX_ET + expected_kwargs = { + 'route_type': route_type, + 'route_dist': route_dist, + 'esi': esi, + 'ethernet_tag_id': ethernet_tag_id, + } + + # Test + speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1') + speaker.evpn_prefix_del( + route_type=route_type, + route_dist=route_dist, + esi=esi, + ethernet_tag_id=ethernet_tag_id, + ) + + # Check + mock_call.assert_called_with( + 'evpn_prefix.delete_local', **expected_kwargs) + @mock.patch('ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__', mock.MagicMock(return_value=None)) @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call') @@ -405,6 +561,40 @@ class Test_BGPSpeaker(unittest.TestCase): 'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__', mock.MagicMock(return_value=None)) @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call') + def test_evpn_prefix_del_eth_segment(self, mock_call): + # Prepare test data + route_type = bgpspeaker.EVPN_ETH_SEGMENT + route_dist = '65000:100' + esi = { + 'esi_type': ESI_TYPE_MAC_BASED, + 'mac_addr': 'aa:bb:cc:dd:ee:ff', + 'local_disc': 100, + } + ip_addr = '192.168.0.1' + expected_kwargs = { + 'route_type': route_type, + 'route_dist': route_dist, + 'esi': esi, + 'ip_addr': ip_addr, + } + + # Test + speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1') + speaker.evpn_prefix_del( + route_type=route_type, + route_dist=route_dist, + esi=esi, + ip_addr=ip_addr, + ) + + # Check + mock_call.assert_called_with( + 'evpn_prefix.delete_local', **expected_kwargs) + + @mock.patch( + 'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__', + mock.MagicMock(return_value=None)) + @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call') def test_evpn_prefix_del_ip_prefix_route(self, mock_call): # Prepare test data route_type = bgpspeaker.EVPN_IP_PREFIX_ROUTE |