This patch enables to advertise VNI as MPLS lables field in the
MAC/IP Advertisement Route of EVPN.

Signed-off-by: IWASE Yusuke <[email protected]>
---
 ryu/lib/packet/bgp.py                              | 153 +++++++++++++++++----
 ryu/services/protocols/bgp/api/base.py             |   1 +
 ryu/services/protocols/bgp/api/prefix.py           |  12 +-
 ryu/services/protocols/bgp/bgpspeaker.py           |  16 ++-
 .../protocols/bgp/core_managers/table_manager.py   |   7 +
 ryu/services/protocols/bgp/info_base/vpn.py        |   7 +-
 ryu/services/protocols/bgp/info_base/vrf.py        |  14 +-
 ryu/services/protocols/bgp/utils/validation.py     |   9 ++
 8 files changed, 174 insertions(+), 45 deletions(-)

diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py
index 6d7cf3f..c980bc4 100644
--- a/ryu/lib/packet/bgp.py
+++ b/ryu/lib/packet/bgp.py
@@ -1446,6 +1446,14 @@ class EvpnNLRI(StringifyMixin, _TypeDisp):
             label = label << 4 | 1
         return six.binary_type(_LabelledAddrPrefix._label_to_bin(label))
 
+    @staticmethod
+    def _vni_from_bin(buf):
+        return type_desc.Int3.to_user(six.binary_type(buf[:3])), buf[3:]
+
+    @staticmethod
+    def _vni_to_bin(vni):
+        return type_desc.Int3.from_user(vni)
+
     @property
     def prefix(self):
         def _format(i):
@@ -1509,34 +1517,78 @@ class EvpnEthernetAutoDiscoveryNLRI(EvpnNLRI):
     # +---------------------------------------+
     _PACK_STR = "!8s10sI3s"
     NLRI_PREFIX_FIELDS = ['esi', 'ethernet_tag_id']
+    _TYPE = {
+        'ascii': [
+            'route_dist',
+        ]
+    }
 
-    def __init__(self, route_dist, esi, ethernet_tag_id, mpls_label,
+    def __init__(self, route_dist, esi, ethernet_tag_id,
+                 mpls_label=None, vni=None, label=None,
                  type_=None, length=None):
         super(EvpnEthernetAutoDiscoveryNLRI, self).__init__(type_, length)
         self.route_dist = route_dist
         self.esi = esi
         self.ethernet_tag_id = ethernet_tag_id
-        self.mpls_label = mpls_label
+        if label:
+            # If binary type label field value is specified, stores it
+            # and decodes as MPLS label and VNI.
+            self._label = label
+            self._mpls_label, _, _ = self._mpls_label_from_bin(label)
+            self._vni, _ = self._vni_from_bin(label)
+        else:
+            # If either MPLS label or VNI is specified, stores it
+            # and encodes into binary type label field value.
+            self._label = self._serialize_label(mpls_label, vni)
+            self._mpls_label = mpls_label
+            self._vni = vni
+
+    def _serialize_label(self, mpls_label, vni):
+        if mpls_label:
+            return self._mpls_label_to_bin(mpls_label, is_stack=True)
+        elif vni:
+            return self._vni_to_bin(vni)
+        else:
+            return b'\x00' * 3
 
     @classmethod
     def parse_value(cls, buf):
         route_dist, rest = cls._rd_from_bin(buf)
         esi, rest = cls._esi_from_bin(rest)
         ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest)
-        mpls_label, rest, _ = cls._mpls_label_from_bin(rest)
 
         return {
             'route_dist': route_dist.formatted_str,
             'esi': esi,
             'ethernet_tag_id': ethernet_tag_id,
-            'mpls_label': mpls_label,
+            'label': rest,
         }
 
     def serialize_value(self):
         route_dist = _RouteDistinguisher.from_str(self.route_dist)
         return struct.pack(
             self._PACK_STR, route_dist.serialize(), self.esi.serialize(),
-            self.ethernet_tag_id, self._mpls_label_to_bin(self.mpls_label))
+            self.ethernet_tag_id, self._label)
+
+    @property
+    def mpls_label(self):
+        return self._mpls_label
+
+    @mpls_label.setter
+    def mpls_label(self, mpls_label):
+        self._label = self._mpls_label_to_bin(mpls_label, is_stack=True)
+        self._mpls_label = mpls_label
+        self._vni = None  # disables VNI
+
+    @property
+    def vni(self):
+        return self._vni
+
+    @vni.setter
+    def vni(self, vni):
+        self._label = self._vni_to_bin(vni)
+        self._mpls_label = None  # disables MPLS label
+        self._vni = vni
 
     @property
     def label_list(self):
@@ -1569,18 +1621,20 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
     # +---------------------------------------+
     # |  MPLS Label2 (0 or 3 octets)          |
     # +---------------------------------------+
-    _PACK_STR = "!8s10sIB6sB%ds3s%ds"
+    _PACK_STR = "!8s10sIB6sB%ds%ds"
     # Note: mac_addr_len and ip_addr_len are omitted for readability.
     NLRI_PREFIX_FIELDS = ['ethernet_tag_id', 'mac_addr', 'ip_addr']
     _TYPE = {
         'ascii': [
+            'route_dist',
             'mac_addr',
             'ip_addr',
         ]
     }
 
     def __init__(self, route_dist, esi, ethernet_tag_id, mac_addr, ip_addr,
-                 mpls_labels, mac_addr_len=None, ip_addr_len=None,
+                 mpls_labels=None, vni=None, labels=None,
+                 mac_addr_len=None, ip_addr_len=None,
                  type_=None, length=None):
         super(EvpnMacIPAdvertisementNLRI, self).__init__(type_, length)
         self.route_dist = route_dist
@@ -1590,7 +1644,43 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
         self.mac_addr = mac_addr
         self.ip_addr_len = ip_addr_len
         self.ip_addr = ip_addr
-        self.mpls_labels = mpls_labels
+        if labels:
+            # If binary type labels field value is specified, stores it
+            # and decodes as MPLS labels and VNI.
+            self._mpls_labels, self._vni = self._parse_labels(labels)
+            self._labels = labels
+        else:
+            # If either MPLS labels or VNI is specified, stores it
+            # and encodes into binary type labels field value.
+            self._labels = self._serialize_labels(mpls_labels, vni)
+            self._mpls_labels = mpls_labels
+            self._vni = vni
+
+    def _parse_labels(self, labels):
+        mpls_label1, rest, is_stack = self._mpls_label_from_bin(labels)
+        mpls_labels = [mpls_label1]
+        if rest and is_stack:
+            mpls_label2, rest, _ = self._mpls_label_from_bin(rest)
+            mpls_labels.append(mpls_label2)
+        vni, _ = self._vni_from_bin(labels)
+        return mpls_labels, vni
+
+    def _serialize_labels(self, mpls_labels, vni):
+        if mpls_labels:
+            return self._serialize_mpls_labels(mpls_labels)
+        elif vni:
+            return self._vni_to_bin(vni)
+        else:
+            return b'\x00' * 3
+
+    def _serialize_mpls_labels(self, mpls_labels):
+        if len(mpls_labels) == 1:
+            return self._mpls_label_to_bin(mpls_labels[0], is_stack=False)
+        elif len(mpls_labels) == 2:
+            return (self._mpls_label_to_bin(mpls_labels[0], is_stack=True) +
+                    self._mpls_label_to_bin(mpls_labels[1], is_stack=False))
+        else:
+            return b'\x00' * 3
 
     @classmethod
     def parse_value(cls, buf):
@@ -1604,11 +1694,6 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
             ip_addr, rest = cls._ip_addr_from_bin(rest, ip_addr_len)
         else:
             ip_addr = None
-        mpls_label1, rest, is_stack = cls._mpls_label_from_bin(rest)
-        mpls_labels = [mpls_label1]
-        if rest and is_stack:
-            mpls_label2, rest, _ = cls._mpls_label_from_bin(rest)
-            mpls_labels.append(mpls_label2)
 
         return {
             'route_dist': route_dist.formatted_str,
@@ -1618,7 +1703,7 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
             'mac_addr': mac_addr,
             'ip_addr_len': ip_addr_len,
             'ip_addr': ip_addr,
-            'mpls_labels': mpls_labels,
+            'labels': rest,
         }
 
     def serialize_value(self):
@@ -1631,24 +1716,34 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
             ip_addr = b''
         ip_addr_len = len(ip_addr)
         self.ip_addr_len = ip_addr_len * 8  # fixup
-        mpls_label1 = b''
-        mpls_label2 = b''
-        if len(self.mpls_labels) == 1:
-            mpls_label1 = self._mpls_label_to_bin(self.mpls_labels[0],
-                                                  is_stack=False)
-        elif len(self.mpls_labels) == 2:
-            mpls_label1 = self._mpls_label_to_bin(self.mpls_labels[0],
-                                                  is_stack=True)
-            mpls_label2 = self._mpls_label_to_bin(self.mpls_labels[1],
-                                                  is_stack=False)
 
         return struct.pack(
-            self._PACK_STR % (ip_addr_len, len(mpls_label2)),
+            self._PACK_STR % (ip_addr_len, len(self._labels)),
             route_dist.serialize(), self.esi.serialize(),
             self.ethernet_tag_id,
             self.mac_addr_len, mac_addr,
             self.ip_addr_len, ip_addr,
-            mpls_label1, mpls_label2)
+            self._labels)
+
+    @property
+    def mpls_labels(self):
+        return self._mpls_labels
+
+    @mpls_labels.setter
+    def mpls_labels(self, mpls_labels):
+        self._labels = self._serialize_mpls_labels(mpls_labels)
+        self._mpls_labels = mpls_labels
+        self._vni = None  # disables VNI
+
+    @property
+    def vni(self):
+        return self._vni
+
+    @vni.setter
+    def vni(self, vni):
+        self._labels = self._vni_to_bin(vni)
+        self._mpls_labels = None  # disables MPLS labels
+        self._vni = vni
 
     @property
     def label_list(self):
@@ -1676,7 +1771,8 @@ class EvpnInclusiveMulticastEthernetTagNLRI(EvpnNLRI):
     NLRI_PREFIX_FIELDS = ['ethernet_tag_id', 'ip_addr']
     _TYPE = {
         'ascii': [
-            'ip_addr'
+            'route_dist',
+            'ip_addr',
         ]
     }
 
@@ -1735,7 +1831,8 @@ class EvpnEthernetSegmentNLRI(EvpnNLRI):
     NLRI_PREFIX_FIELDS = ['esi', 'ip_addr']
     _TYPE = {
         'ascii': [
-            'ip_addr'
+            'route_dist',
+            'ip_addr',
         ]
     }
 
diff --git a/ryu/services/protocols/bgp/api/base.py 
b/ryu/services/protocols/bgp/api/base.py
index 49e7806..9007da0 100644
--- a/ryu/services/protocols/bgp/api/base.py
+++ b/ryu/services/protocols/bgp/api/base.py
@@ -50,6 +50,7 @@ MAC_ADDR = 'mac_addr'
 IP_ADDR = 'ip_addr'
 MPLS_LABELS = 'mpls_labels'
 TUNNEL_TYPE = 'tunnel_type'
+EVPN_VNI = 'vni'
 
 # API call registry
 _CALL_REGISTRY = {}
diff --git a/ryu/services/protocols/bgp/api/prefix.py 
b/ryu/services/protocols/bgp/api/prefix.py
index 2bcb4e0..8963f51 100644
--- a/ryu/services/protocols/bgp/api/prefix.py
+++ b/ryu/services/protocols/bgp/api/prefix.py
@@ -31,6 +31,7 @@ from ryu.services.protocols.bgp.api.base import PREFIX
 from ryu.services.protocols.bgp.api.base import RegisterWithArgChecks
 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.base import add_bgp_error_metadata
 from ryu.services.protocols.bgp.base import PREFIX_ERROR_CODE
@@ -137,6 +138,13 @@ def is_valid_mpls_labels(labels):
                                conf_value=labels)
 
 
+@validate(name=EVPN_VNI)
+def is_valid_vni(vni):
+    if not validation.is_valid_vni(vni):
+        raise ConfigValueError(conf_name=EVPN_VNI,
+                               conf_value=vni)
+
+
 @validate(name=TUNNEL_TYPE)
 def is_valid_tunnel_type(tunnel_type):
     if tunnel_type not in SUPPORTED_TUNNEL_TYPES:
@@ -193,7 +201,7 @@ 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, TUNNEL_TYPE])
+                                 IP_ADDR, EVPN_VNI, TUNNEL_TYPE])
 def add_evpn_local(route_type, route_dist, next_hop, **kwargs):
     """Adds EVPN route from VRF identified by *route_dist*.
     """
@@ -220,7 +228,7 @@ def add_evpn_local(route_type, route_dist, next_hop, 
**kwargs):
 @RegisterWithArgChecks(name='evpn_prefix.delete_local',
                        req_args=[EVPN_ROUTE_TYPE, ROUTE_DISTINGUISHER],
                        opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR,
-                                 IP_ADDR])
+                                 IP_ADDR, EVPN_VNI])
 def delete_evpn_local(route_type, route_dist, **kwargs):
     """Deletes/withdraws 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 879eaf2..62ba146 100644
--- a/ryu/services/protocols/bgp/bgpspeaker.py
+++ b/ryu/services/protocols/bgp/bgpspeaker.py
@@ -31,10 +31,12 @@ from ryu.services.protocols.bgp.api.base import MAC_ADDR
 from ryu.services.protocols.bgp.api.base import NEXT_HOP
 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.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_MPLS
+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.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
@@ -492,7 +494,7 @@ class BGPSpeaker(object):
 
     def evpn_prefix_add(self, route_type, route_dist, esi=0,
                         ethernet_tag_id=None, mac_addr=None, ip_addr=None,
-                        next_hop=None, tunnel_type=TUNNEL_TYPE_MPLS):
+                        vni=None, next_hop=None, tunnel_type=None):
         """ This method adds a new EVPN route to be advertised.
 
         ``route_type`` specifies one of the EVPN route type name. The
@@ -510,10 +512,15 @@ class BGPSpeaker(object):
 
         ``ip_addr`` specifies an IPv4 or IPv6 address to advertise.
 
+        ``vni`` specifies an Virtual Network Identifier for VXLAN
+        or Virtual Subnet Identifier for NVGRE.
+        If tunnel_type is not 'vxlan' or 'nvgre', this field is ignored.
+
         ``next_hop`` specifies the next hop address for this prefix.
 
         ``tunnel_type`` specifies the data plane encapsulation type
-        to advertise. The default is the MPLS encapsulation type.
+        to advertise. By the default, this encapsulation attribute is
+        not advertised.
         """
         func_name = 'evpn_prefix.add_local'
 
@@ -538,6 +545,9 @@ class BGPSpeaker(object):
                 MAC_ADDR: mac_addr,
                 IP_ADDR: ip_addr,
             })
+            # Set tunnel type specific arguments
+            if tunnel_type in [TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE]:
+                kwargs[EVPN_VNI] = vni
         elif route_type == EVPN_MULTICAST_ETAG_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 5320dbf..4b95853 100644
--- a/ryu/services/protocols/bgp/core_managers/table_manager.py
+++ b/ryu/services/protocols/bgp/core_managers/table_manager.py
@@ -538,6 +538,13 @@ class TableCoreManager(object):
             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.
+                from ryu.services.protocols.bgp.api.prefix import (
+                    TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE)
+                assert tunnel_type in [TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE]
+                gen_lbl = False
             prefix = subclass(**kwargs)
         else:
             raise BgpCoreError(
diff --git a/ryu/services/protocols/bgp/info_base/vpn.py 
b/ryu/services/protocols/bgp/info_base/vpn.py
index 46cf47f..87c4437 100644
--- a/ryu/services/protocols/bgp/info_base/vpn.py
+++ b/ryu/services/protocols/bgp/info_base/vpn.py
@@ -65,10 +65,9 @@ class VpnPath(Path):
 
     def clone_to_vrf(self, is_withdraw=False):
         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)
+            # Because NLRI class is the same if the route family is EVPN,
+            # we re-use the NLRI instance.
+            vrf_nlri = self._nlri
         else:  # self.ROUTE_FAMILY in [RF_IPv4_VPN, RF_IPv46_VPN]
             vrf_nlri = self.NLRI_CLASS(self._nlri.prefix)
 
diff --git a/ryu/services/protocols/bgp/info_base/vrf.py 
b/ryu/services/protocols/bgp/info_base/vrf.py
index 6a60fc8..5e22e29 100644
--- a/ryu/services/protocols/bgp/info_base/vrf.py
+++ b/ryu/services/protocols/bgp/info_base/vrf.py
@@ -157,10 +157,9 @@ class VrfTable(Table):
             source = VRF_TABLE
 
         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)
+            # Because NLRI class is the same if the route family is EVPN,
+            # we re-use the NLRI instance.
+            vrf_nlri = vpn_path.nlri
         else:  # self.VPN_ROUTE_FAMILY in [RF_IPv4_VPN, RF_IPv6_VPN]
             # Copy NLRI instance
             ip, masklen = vpn_path.nlri.prefix.split('/')
@@ -528,10 +527,9 @@ class VrfPath(Path):
 
     def clone_to_vpn(self, route_dist, for_withdrawal=False):
         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)
+            # Because NLRI class is the same if the route family is EVPN,
+            # we re-use the NLRI instance.
+            vpn_nlri = self._nlri
         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),
diff --git a/ryu/services/protocols/bgp/utils/validation.py 
b/ryu/services/protocols/bgp/utils/validation.py
index 6cf292d..ff6ed50 100644
--- a/ryu/services/protocols/bgp/utils/validation.py
+++ b/ryu/services/protocols/bgp/utils/validation.py
@@ -250,3 +250,12 @@ def is_valid_ethernet_tag_id(etag_id):
     Ethernet Tag ID should be a 32-bit field number.
     """
     return isinstance(etag_id, numbers.Integral) and 0 <= etag_id <= 0xffffffff
+
+
+def is_valid_vni(vni):
+    """Returns True if the given Virtual Network Identifier for VXLAN
+    is valid.
+
+    Virtual Network Identifier should be a 24-bit field number.
+    """
+    return isinstance(vni, numbers.Integral) and 0 <= vni <= 0xffffff
-- 
2.7.4


------------------------------------------------------------------------------
_______________________________________________
Ryu-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to