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

Reply via email to