This patch adds support to advertise the BGP PMSI Tunnel Attribute for the Path attributes.
Signed-off-by: Shinpei Muraoka <[email protected]> --- ryu/services/protocols/bgp/api/base.py | 1 + ryu/services/protocols/bgp/api/prefix.py | 23 ++++- ryu/services/protocols/bgp/bgpspeaker.py | 21 ++++- .../protocols/bgp/core_managers/table_manager.py | 10 ++- ryu/services/protocols/bgp/info_base/vrf.py | 18 ++++ ryu/services/protocols/bgp/peer.py | 8 ++ .../unit/services/protocols/bgp/test_bgpspeaker.py | 97 ++++++++++++++++++++++ 7 files changed, 174 insertions(+), 4 deletions(-) diff --git a/ryu/services/protocols/bgp/api/base.py b/ryu/services/protocols/bgp/api/base.py index b96a4f3..11fd747 100644 --- a/ryu/services/protocols/bgp/api/base.py +++ b/ryu/services/protocols/bgp/api/base.py @@ -50,6 +50,7 @@ IP_ADDR = 'ip_addr' MPLS_LABELS = 'mpls_labels' TUNNEL_TYPE = 'tunnel_type' EVPN_VNI = 'vni' +PMSI_TUNNEL_TYPE = 'pmsi_tunnel_type' # API call registry _CALL_REGISTRY = {} diff --git a/ryu/services/protocols/bgp/api/prefix.py b/ryu/services/protocols/bgp/api/prefix.py index 8963f51..43ddc67 100644 --- a/ryu/services/protocols/bgp/api/prefix.py +++ b/ryu/services/protocols/bgp/api/prefix.py @@ -20,6 +20,7 @@ import logging from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI from ryu.lib.packet.bgp import EvpnInclusiveMulticastEthernetTagNLRI +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 @@ -33,6 +34,7 @@ from ryu.services.protocols.bgp.api.base import ROUTE_DISTINGUISHER from ryu.services.protocols.bgp.api.base import VPN_LABEL 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.base import add_bgp_error_metadata from ryu.services.protocols.bgp.base import PREFIX_ERROR_CODE from ryu.services.protocols.bgp.base import validate @@ -69,6 +71,17 @@ SUPPORTED_TUNNEL_TYPES = [ TUNNEL_TYPE_MPLS_IN_GRE, TUNNEL_TYPE_VXLAN_GRE, ] +# Constants for PMSI Tunnel Attribute +PMSI_TYPE_NO_TUNNEL_INFO = ( + BGPPathAttributePmsiTunnel.TYPE_NO_TUNNEL_INFORMATION_PRESENT +) +PMSI_TYPE_INGRESS_REP = ( + BGPPathAttributePmsiTunnel.TYPE_INGRESS_REPLICATION +) +SUPPORTED_PMSI_TUNNEL_TYPES = [ + PMSI_TYPE_NO_TUNNEL_INFO, + PMSI_TYPE_INGRESS_REP, +] @add_bgp_error_metadata(code=PREFIX_ERROR_CODE, @@ -152,6 +165,13 @@ def is_valid_tunnel_type(tunnel_type): conf_value=tunnel_type) +@validate(name=PMSI_TUNNEL_TYPE) +def is_valid_pmsi_tunnel_type(pmsi_tunnel_type): + if pmsi_tunnel_type not in SUPPORTED_PMSI_TUNNEL_TYPES: + raise ConfigValueError(conf_name=PMSI_TUNNEL_TYPE, + conf_value=pmsi_tunnel_type) + + @RegisterWithArgChecks(name='prefix.add_local', req_args=[ROUTE_DISTINGUISHER, PREFIX, NEXT_HOP], opt_args=[VRF_RF]) @@ -201,7 +221,8 @@ def delete_local(route_dist, prefix, route_family=VRF_RF_IPV4): req_args=[EVPN_ROUTE_TYPE, ROUTE_DISTINGUISHER, NEXT_HOP], opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR, - IP_ADDR, EVPN_VNI, TUNNEL_TYPE]) + 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*. """ diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py index 2d19899..045e38b 100644 --- a/ryu/services/protocols/bgp/bgpspeaker.py +++ b/ryu/services/protocols/bgp/bgpspeaker.py @@ -33,10 +33,14 @@ from ryu.services.protocols.bgp.api.base import ROUTE_DISTINGUISHER 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_MAC_IP_ADV_ROUTE from ryu.services.protocols.bgp.api.prefix import EVPN_MULTICAST_ETAG_ROUTE 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 ( + PMSI_TYPE_NO_TUNNEL_INFO, + PMSI_TYPE_INGRESS_REP) from ryu.services.protocols.bgp.rtconf.common import LOCAL_AS from ryu.services.protocols.bgp.rtconf.common import ROUTER_ID from ryu.services.protocols.bgp.rtconf.common import BGP_SERVER_PORT @@ -514,7 +518,8 @@ class BGPSpeaker(object): def evpn_prefix_add(self, route_type, route_dist, esi=0, ethernet_tag_id=None, mac_addr=None, ip_addr=None, - vni=None, next_hop=None, tunnel_type=None): + vni=None, next_hop=None, tunnel_type=None, + pmsi_tunnel_type=None): """ This method adds a new EVPN route to be advertised. ``route_type`` specifies one of the EVPN route type name. The @@ -541,6 +546,11 @@ class BGPSpeaker(object): ``tunnel_type`` specifies the data plane encapsulation type to advertise. By the default, this encapsulation attribute is not advertised. + + ```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 + EVPN_MULTICAST_ETAG_ROUTE. """ func_name = 'evpn_prefix.add_local' @@ -573,6 +583,15 @@ class BGPSpeaker(object): EVPN_ETHERNET_TAG_ID: ethernet_tag_id, IP_ADDR: ip_addr, }) + + # Set PMSI Tunnel Attribute arguments + if pmsi_tunnel_type in [ + PMSI_TYPE_NO_TUNNEL_INFO, + PMSI_TYPE_INGRESS_REP]: + kwargs[PMSI_TUNNEL_TYPE] = pmsi_tunnel_type + elif pmsi_tunnel_type is not None: + raise ValueError('Unsupported PMSI tunnel type: %s' % + pmsi_tunnel_type) else: raise ValueError('Unsupported EVPN route type: %s' % route_type) diff --git a/ryu/services/protocols/bgp/core_managers/table_manager.py b/ryu/services/protocols/bgp/core_managers/table_manager.py index aaa67cb..be3e8bc 100644 --- a/ryu/services/protocols/bgp/core_managers/table_manager.py +++ b/ryu/services/protocols/bgp/core_managers/table_manager.py @@ -483,7 +483,7 @@ 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, **kwargs): + is_withdraw=False, pmsi_tunnel_type=None, **kwargs): """Update a BGP route in the VRF table identified by `route_dist` with the given `next_hop`. @@ -496,6 +496,11 @@ 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. +` `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 + EVPN_MULTICAST_ETAG_ROUTE. + Returns assigned VPN label. """ from ryu.services.protocols.bgp.core import BgpCoreError @@ -554,7 +559,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, tunnel_type=tunnel_type, + pmsi_tunnel_type=pmsi_tunnel_type) def update_global_table(self, prefix, next_hop=None, is_withdraw=False): """Update a BGP route in the Global table for the given `prefix` diff --git a/ryu/services/protocols/bgp/info_base/vrf.py b/ryu/services/protocols/bgp/info_base/vrf.py index d3bbe75..9681297 100644 --- a/ryu/services/protocols/bgp/info_base/vrf.py +++ b/ryu/services/protocols/bgp/info_base/vrf.py @@ -24,6 +24,7 @@ import six 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_TYPE_EXTENDED_COMMUNITIES +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 @@ -31,6 +32,8 @@ 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 BGPPathAttributePmsiTunnel +from ryu.lib.packet.bgp import PmsiTunnelIdIngressReplication from ryu.lib.packet.bgp import RF_L2_EVPN from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI @@ -271,6 +274,21 @@ class VrfTable(Table): # 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: + from ryu.services.protocols.bgp.api.prefix import ( + PMSI_TYPE_INGRESS_REP) + if pmsi_tunnel_type == PMSI_TYPE_INGRESS_REP: + tunnel_id = PmsiTunnelIdIngressReplication( + tunnel_endpoint_ip=self._core_service.router_id) + else: # pmsi_tunnel_type == PMSI_TYPE_NO_TUNNEL_INFO + tunnel_id = None + pattrs[BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE] = \ + BGPPathAttributePmsiTunnel(pmsi_flags=0, + 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] diff --git a/ryu/services/protocols/bgp/peer.py b/ryu/services/protocols/bgp/peer.py index 9accd08..8bf96d6 100644 --- a/ryu/services/protocols/bgp/peer.py +++ b/ryu/services/protocols/bgp/peer.py @@ -91,6 +91,7 @@ from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MP_UNREACH_NLRI from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC from ryu.lib.packet.bgp import BGP_ATTR_TYPE_COMMUNITIES from ryu.lib.packet.bgp import BGP_ATTR_TYPE_EXTENDED_COMMUNITIES +from ryu.lib.packet.bgp import BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE from ryu.lib.packet.bgp import BGPTwoOctetAsSpecificExtendedCommunity from ryu.lib.packet.bgp import BGPIPv4AddressSpecificExtendedCommunity @@ -988,6 +989,7 @@ class Peer(Source, Sink, NeighborConfListener, Activity): extcomm_attr = None community_attr = None localpref_attr = None + pmsi_tunnel_attr = None unknown_opttrans_attrs = None nlri_list = [path.nlri] @@ -1164,6 +1166,10 @@ class Peer(Source, Sink, NeighborConfListener, Activity): communities=communities ) + pmsi_tunnel_attr = pathattr_map.get( + BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE + ) + # UNKNOWN Attributes. # Get optional transitive path attributes unknown_opttrans_attrs = bgp_utils.get_unknown_opttrans_attr(path) @@ -1192,6 +1198,8 @@ class Peer(Source, Sink, NeighborConfListener, Activity): new_pathattr.append(community_attr) if extcomm_attr: new_pathattr.append(extcomm_attr) + if pmsi_tunnel_attr: + new_pathattr.append(pmsi_tunnel_attr) if unknown_opttrans_attrs: new_pathattr.extend(unknown_opttrans_attrs.values()) diff --git a/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py b/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py index 67ef3e9..535f8a0 100644 --- a/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py +++ b/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py @@ -318,3 +318,100 @@ class Test_BGPSpeaker(unittest.TestCase): # Check mock_call.assert_called_with( 'evpn_prefix.delete_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_add_pmsi_no_tunnel_info(self, mock_call): + # Prepare test data + route_type = bgpspeaker.EVPN_MULTICAST_ETAG_ROUTE + route_dist = '65000:100' + ethernet_tag_id = 200 + next_hop = '0.0.0.0' + ip_addr = '192.168.0.1' + pmsi_tunnel_type = bgpspeaker.PMSI_TYPE_NO_TUNNEL_INFO + expected_kwargs = { + 'route_type': route_type, + 'route_dist': route_dist, + 'ethernet_tag_id': ethernet_tag_id, + 'next_hop': next_hop, + 'ip_addr': ip_addr, + 'pmsi_tunnel_type': pmsi_tunnel_type, + } + + # Test + speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1') + speaker.evpn_prefix_add( + route_type=route_type, + route_dist=route_dist, + ethernet_tag_id=ethernet_tag_id, + ip_addr=ip_addr, + pmsi_tunnel_type=pmsi_tunnel_type, + ) + + # 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_pmsi_ingress_rep(self, mock_call): + # Prepare test data + route_type = bgpspeaker.EVPN_MULTICAST_ETAG_ROUTE + route_dist = '65000:100' + ethernet_tag_id = 200 + next_hop = '0.0.0.0' + ip_addr = '192.168.0.1' + pmsi_tunnel_type = bgpspeaker.PMSI_TYPE_INGRESS_REP + expected_kwargs = { + 'route_type': route_type, + 'route_dist': route_dist, + 'ethernet_tag_id': ethernet_tag_id, + 'next_hop': next_hop, + 'ip_addr': ip_addr, + 'pmsi_tunnel_type': pmsi_tunnel_type, + } + + # Test + speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1') + speaker.evpn_prefix_add( + route_type=route_type, + route_dist=route_dist, + ethernet_tag_id=ethernet_tag_id, + ip_addr=ip_addr, + pmsi_tunnel_type=pmsi_tunnel_type, + ) + + # Check + mock_call.assert_called_with( + 'evpn_prefix.add_local', **expected_kwargs) + + @raises(ValueError) + @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_invalid_pmsi_tunnel_type(self, mock_call): + # Prepare test data + route_type = bgpspeaker.EVPN_MULTICAST_ETAG_ROUTE + route_dist = '65000:100' + ethernet_tag_id = 200 + next_hop = '0.0.0.0' + ip_addr = '192.168.0.1' + pmsi_tunnel_type = 1 + + # Test + speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1') + speaker.evpn_prefix_add( + route_type=route_type, + route_dist=route_dist, + ethernet_tag_id=ethernet_tag_id, + ip_addr=ip_addr, + pmsi_tunnel_type=pmsi_tunnel_type, + ) + + # Check + mock_call.assert_called_with( + 'evpn_prefix.add_local', 'Invalid arguments detected') -- 2.7.4 ------------------------------------------------------------------------------ The Command Line: Reinvented for Modern Developers Did the resurgence of CLI tooling catch you by surprise? Reconnect with the command line and become more productive. Learn the new .NET and ASP.NET CLI. Get your free copy! http://sdm.link/telerik _______________________________________________ Ryu-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/ryu-devel
