This patch implements the features for acting as a Route Reflector
which defined in RFC4456.

Signed-off-by: IWASE Yusuke <iwase.yusu...@gmail.com>
---
 ryu/lib/packet/bgp.py                          |   2 +-
 ryu/services/protocols/bgp/application.py      |   2 +
 ryu/services/protocols/bgp/bgpspeaker.py       |  28 ++++-
 ryu/services/protocols/bgp/peer.py             | 160 +++++++++++++++++--------
 ryu/services/protocols/bgp/processor.py        |  50 ++++++--
 ryu/services/protocols/bgp/rtconf/common.py    |  19 ++-
 ryu/services/protocols/bgp/rtconf/neighbors.py |  22 +++-
 7 files changed, 217 insertions(+), 66 deletions(-)

diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py
index 977b783..cfe8cff 100644
--- a/ryu/lib/packet/bgp.py
+++ b/ryu/lib/packet/bgp.py
@@ -3112,7 +3112,7 @@ class BGPPathAttributeOriginatorId(_PathAttribute):
     _VALUE_PACK_STR = '!4s'
     _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
     _TYPE = {
-        'ascii': [
+        'asciilist': [
             'value'
         ]
     }
diff --git a/ryu/services/protocols/bgp/application.py 
b/ryu/services/protocols/bgp/application.py
index c1a9c43..a3e386b 100644
--- a/ryu/services/protocols/bgp/application.py
+++ b/ryu/services/protocols/bgp/application.py
@@ -260,6 +260,8 @@ class RyuBGPSpeaker(RyuApp):
             REFRESH_MAX_EOR_TIME, DEFAULT_REFRESH_MAX_EOR_TIME)
         bgp_settings[LABEL_RANGE] = settings.get(
             LABEL_RANGE, DEFAULT_LABEL_RANGE)
+        bgp_settings['allow_local_as_in_count'] = settings.get(
+            'allow_local_as_in_count', 0)
 
         # Create BGPSpeaker instance.
         LOG.debug('Starting BGPSpeaker...')
diff --git a/ryu/services/protocols/bgp/bgpspeaker.py 
b/ryu/services/protocols/bgp/bgpspeaker.py
index 078f1a6..f178424 100644
--- a/ryu/services/protocols/bgp/bgpspeaker.py
+++ b/ryu/services/protocols/bgp/bgpspeaker.py
@@ -55,6 +55,7 @@ from ryu.services.protocols.bgp.api.prefix import (
     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 CLUSTER_ID
 from ryu.services.protocols.bgp.rtconf.common import BGP_SERVER_PORT
 from ryu.services.protocols.bgp.rtconf.common import DEFAULT_BGP_SERVER_PORT
 from ryu.services.protocols.bgp.rtconf.common import (
@@ -85,8 +86,12 @@ from ryu.services.protocols.bgp.rtconf.neighbors import (
 from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CONNECT_MODE
 from ryu.services.protocols.bgp.rtconf.neighbors import PEER_NEXT_HOP
 from ryu.services.protocols.bgp.rtconf.neighbors import PASSWORD
-from ryu.services.protocols.bgp.rtconf.neighbors import IS_ROUTE_SERVER_CLIENT
-from ryu.services.protocols.bgp.rtconf.neighbors import IS_NEXT_HOP_SELF
+from ryu.services.protocols.bgp.rtconf.neighbors import (
+    DEFAULT_IS_ROUTE_SERVER_CLIENT, IS_ROUTE_SERVER_CLIENT)
+from ryu.services.protocols.bgp.rtconf.neighbors import (
+    DEFAULT_IS_ROUTE_REFLECTOR_CLIENT, IS_ROUTE_REFLECTOR_CLIENT)
+from ryu.services.protocols.bgp.rtconf.neighbors import (
+    DEFAULT_IS_NEXT_HOP_SELF, 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
@@ -176,7 +181,8 @@ class BGPSpeaker(object):
                  ssh_console=False,
                  ssh_port=None, ssh_host=None, ssh_host_key=None,
                  label_range=DEFAULT_LABEL_RANGE,
-                 allow_local_as_in_count=0):
+                 allow_local_as_in_count=0,
+                 cluster_id=None):
         """Create a new BGPSpeaker object with as_number and router_id to
         listen on bgp_server_port.
 
@@ -230,6 +236,10 @@ class BGPSpeaker(object):
         configurations in leaf/spine architecture with shared AS numbers.
         The default is 0 and means "local AS number is not allowed in
         AS_PATH".  To allow local AS, 3 is recommended (Cisco's default).
+
+        ``cluster_id`` specifies the cluster identifier for Route Reflector.
+        It must be the string representation of an IPv4 address.
+        If omitted, "router_id" is used for this field.
         """
 
         super(BGPSpeaker, self).__init__()
@@ -242,6 +252,7 @@ class BGPSpeaker(object):
             REFRESH_MAX_EOR_TIME: refresh_max_eor_time,
             LABEL_RANGE: label_range,
             ALLOW_LOCAL_AS_IN_COUNT: allow_local_as_in_count,
+            CLUSTER_ID: cluster_id,
         }
         self._core_start(settings)
         self._init_signal_listeners()
@@ -322,8 +333,11 @@ class BGPSpeaker(object):
                      enable_enhanced_refresh=DEFAULT_CAP_ENHANCED_REFRESH,
                      
enable_four_octet_as_number=DEFAULT_CAP_FOUR_OCTET_AS_NUMBER,
                      next_hop=None, password=None, multi_exit_disc=None,
-                     site_of_origins=None, is_route_server_client=False,
-                     is_next_hop_self=False, local_address=None,
+                     site_of_origins=None,
+                     is_route_server_client=DEFAULT_IS_ROUTE_SERVER_CLIENT,
+                     
is_route_reflector_client=DEFAULT_IS_ROUTE_REFLECTOR_CLIENT,
+                     is_next_hop_self=DEFAULT_IS_NEXT_HOP_SELF,
+                     local_address=None,
                      local_port=None, local_as=None,
                      connect_mode=DEFAULT_CONNECT_MODE):
         """ This method registers a new neighbor. The BGP speaker tries to
@@ -374,6 +388,9 @@ class BGPSpeaker(object):
         ``is_route_server_client`` specifies whether this neighbor is a
         router server's client or not.
 
+        ``is_route_reflector_client`` specifies whether this neighbor is a
+        router reflector's client or not.
+
         ``is_next_hop_self`` specifies whether the BGP speaker announces
         its own ip address to iBGP neighbor or not as path's next_hop address.
 
@@ -397,6 +414,7 @@ class BGPSpeaker(object):
             PEER_NEXT_HOP: next_hop,
             PASSWORD: password,
             IS_ROUTE_SERVER_CLIENT: is_route_server_client,
+            IS_ROUTE_REFLECTOR_CLIENT: is_route_reflector_client,
             IS_NEXT_HOP_SELF: is_next_hop_self,
             CONNECT_MODE: connect_mode,
             CAP_ENHANCED_REFRESH: enable_enhanced_refresh,
diff --git a/ryu/services/protocols/bgp/peer.py 
b/ryu/services/protocols/bgp/peer.py
index d380e30..3702739 100644
--- a/ryu/services/protocols/bgp/peer.py
+++ b/ryu/services/protocols/bgp/peer.py
@@ -75,6 +75,8 @@ from ryu.lib.packet.bgp import BGPPathAttributeAsPath
 from ryu.lib.packet.bgp import BGPPathAttributeAs4Path
 from ryu.lib.packet.bgp import BGPPathAttributeLocalPref
 from ryu.lib.packet.bgp import BGPPathAttributeExtendedCommunities
+from ryu.lib.packet.bgp import BGPPathAttributeOriginatorId
+from ryu.lib.packet.bgp import BGPPathAttributeClusterList
 from ryu.lib.packet.bgp import BGPPathAttributeMpReachNLRI
 from ryu.lib.packet.bgp import BGPPathAttributeMpUnreachNLRI
 from ryu.lib.packet.bgp import BGPPathAttributeCommunities
@@ -90,6 +92,8 @@ from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MP_REACH_NLRI
 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_ORIGINATOR_ID
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_CLUSTER_LIST
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_EXTENDED_COMMUNITIES
 from ryu.lib.packet.bgp import BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE
 
@@ -439,6 +443,10 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
         return self._neigh_conf.is_route_server_client
 
     @property
+    def is_route_reflector_client(self):
+        return self._neigh_conf.is_route_reflector_client
+
+    @property
     def check_first_as(self):
         return self._neigh_conf.check_first_as
 
@@ -976,8 +984,37 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
                 new_pathattr.append(mpunreach_attr)
         elif self.is_route_server_client:
             nlri_list = [path.nlri]
-            for pathattr in path.pathattr_map.values():
-                new_pathattr.append(pathattr)
+            new_pathattr.extend(pathattr_map.values())
+        elif self.is_route_reflector_client:
+            nlri_list = [path.nlri]
+
+            # Append ORIGINATOR_ID attribute if not already exists.
+            if BGP_ATTR_TYPE_ORIGINATOR_ID not in pathattr_map:
+                originator_id = path.source
+                if originator_id is None:
+                    originator_id = self._common_conf.router_id
+                elif isinstance(path.source, Peer):
+                    originator_id = path.source.ip_address
+                new_pathattr.append(
+                    BGPPathAttributeOriginatorId(value=originator_id))
+
+            # Append CLUSTER_LIST attribute if not already exists.
+            if BGP_ATTR_TYPE_CLUSTER_LIST not in pathattr_map:
+                new_pathattr.append(
+                    BGPPathAttributeClusterList(
+                        [self._common_conf.cluster_id]))
+
+            for t, path_attr in pathattr_map.items():
+                if t == BGP_ATTR_TYPE_CLUSTER_LIST:
+                    # Append own CLUSTER_ID into CLUSTER_LIST attribute
+                    # if already exists.
+                    cluster_list = list(path_attr.value)
+                    if self._common_conf.cluster_id not in cluster_list:
+                        cluster_list.append(self._common_conf.cluster_id)
+                    new_pathattr.append(
+                        BGPPathAttributeClusterList(cluster_list))
+                else:
+                    new_pathattr.append(path_attr)
         else:
             # Supported and un-supported/unknown attributes.
             origin_attr = None
@@ -1503,36 +1540,42 @@ class Peer(Source, Sink, NeighborConfListener, 
Activity):
         Assumes Multiprotocol Extensions capability is supported and enabled.
         """
         assert self.state.bgp_state == const.BGP_FSM_ESTABLISHED
+
+        # Increment count of update received.
         self.state.incr(PeerCounterNames.RECV_UPDATES)
+
         if not self._validate_update_msg(update_msg):
             # If update message was not valid for some reason, we ignore its
             # routes.
             LOG.error('UPDATE message was invalid, hence ignoring its routes.')
             return
 
-        # Increment count of update received.
-        mp_reach_attr = update_msg.get_path_attr(BGP_ATTR_TYPE_MP_REACH_NLRI)
-        mp_unreach_attr = update_msg.get_path_attr(
-            BGP_ATTR_TYPE_MP_UNREACH_NLRI)
-
         # Extract advertised path attributes and reconstruct AS_PATH attribute
         self._extract_and_reconstruct_as_path(update_msg)
 
-        nlri_list = update_msg.nlri
-        withdraw_list = update_msg.withdrawn_routes
+        # Check if path attributes have loops.
+        if self._is_looped_path_attrs(update_msg):
+            return
 
+        umsg_pattrs = update_msg.pathattr_map
+        mp_reach_attr = umsg_pattrs.get(BGP_ATTR_TYPE_MP_REACH_NLRI, None)
         if mp_reach_attr:
-            # Extract advertised paths from given message.
+            # Extract advertised MP-BGP paths from given message.
             self._extract_and_handle_mpbgp_new_paths(update_msg)
 
+        mp_unreach_attr = umsg_pattrs.get(BGP_ATTR_TYPE_MP_UNREACH_NLRI, None)
         if mp_unreach_attr:
-            # Extract withdraws from given message.
+            # Extract MP-BGP withdraws from given message.
             self._extract_and_handle_mpbgp_withdraws(mp_unreach_attr)
 
+        nlri_list = update_msg.nlri
         if nlri_list:
+            # Extract advertised BGP paths from given message.
             self._extract_and_handle_bgp4_new_paths(update_msg)
 
+        withdraw_list = update_msg.withdrawn_routes
         if withdraw_list:
+            # Extract BGP withdraws from given message.
             self._extract_and_handle_bgp4_withdraws(withdraw_list)
 
     def _extract_and_reconstruct_as_path(self, update_msg):
@@ -1597,6 +1640,48 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
                 as_path = self._construct_as_path_attr(as_path, as4_path)
                 update_msg.path_attributes.append(as_path)
 
+    def _is_looped_path_attrs(self, update_msg):
+        """
+        Extracts path attributes from the given UPDATE message and checks
+        if the given attributes have loops or not.
+
+        :param update_msg: UPDATE message instance.
+        :return: True if attributes have loops. Otherwise False.
+        """
+        umsg_pattrs = update_msg.pathattr_map
+        recv_open_msg = self.protocol.recv_open_msg
+
+        # Check if AS_PATH has loops.
+        aspath = umsg_pattrs.get(BGP_ATTR_TYPE_AS_PATH)
+        if (aspath is not None
+                and aspath.has_local_as(
+                    self.local_as,
+                    max_count=self._common_conf.allow_local_as_in_count)):
+            LOG.error(
+                'AS_PATH on UPDATE message has loops. '
+                'Ignoring this message: %s',
+                update_msg)
+            return
+
+        # Check if ORIGINATOR_ID has loops. [RFC4456]
+        originator_id = umsg_pattrs.get(BGP_ATTR_TYPE_ORIGINATOR_ID, None)
+        if (originator_id
+                and recv_open_msg.bgp_identifier == originator_id):
+            LOG.error(
+                'ORIGINATOR_ID on UPDATE message has loops. '
+                'Ignoring this message: %s',
+                update_msg)
+            return
+
+        # Check if CLUSTER_LIST has loops. [RFC4456]
+        cluster_list = umsg_pattrs.get(BGP_ATTR_TYPE_CLUSTER_LIST, None)
+        if (cluster_list
+                and self._common_conf.cluster_id in cluster_list.value):
+            LOG.error(
+                'CLUSTER_LIST on UPDATE message has loops. '
+                'Ignoring this message: %s', update_msg)
+            return
+
     def _extract_and_handle_bgp4_new_paths(self, update_msg):
         """Extracts new paths advertised in the given update message's
          *MpReachNlri* attribute.
@@ -1611,23 +1696,8 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
         processing.
         """
         umsg_pattrs = update_msg.pathattr_map
-
-        msg_rf = RF_IPv4_UC
-        # Check if this route family is among supported route families.
-        if msg_rf not in SUPPORTED_GLOBAL_RF:
-            LOG.info(('Received route for route family %s which is'
-                      ' not supported. Ignoring paths from this UPDATE: %s') %
-                     (msg_rf, update_msg))
-            return
-
-        aspath = umsg_pattrs.get(BGP_ATTR_TYPE_AS_PATH)
-        # Check if AS_PATH has loops.
-        if aspath.has_local_as(self.local_as, 
max_count=self._common_conf.allow_local_as_in_count):
-            LOG.error('Update message AS_PATH has loops. Ignoring this'
-                      ' UPDATE. %s', update_msg)
-            return
-
         next_hop = update_msg.get_path_attr(BGP_ATTR_TYPE_NEXT_HOP).value
+
         # Nothing to do if we do not have any new NLRIs in this message.
         msg_nlri_list = update_msg.nlri
         if not msg_nlri_list:
@@ -1684,16 +1754,6 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
         processing.
         """
         msg_rf = RF_IPv4_UC
-        # Check if this route family is among supported route families.
-        if msg_rf not in SUPPORTED_GLOBAL_RF:
-            LOG.info(
-                (
-                    'Received route for route family %s which is'
-                    ' not supported. Ignoring withdraws form this UPDATE.'
-                ) % msg_rf
-            )
-            return
-
         w_nlris = withdraw_list
         if not w_nlris:
             # If this is EOR of some kind, handle it
@@ -1748,13 +1808,6 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
                      (msg_rf, update_msg))
             return
 
-        aspath = umsg_pattrs.get(BGP_ATTR_TYPE_AS_PATH)
-        # Check if AS_PATH has loops.
-        if aspath.has_local_as(self.local_as, 
max_count=self._common_conf.allow_local_as_in_count):
-            LOG.error('Update message AS_PATH has loops. Ignoring this'
-                      ' UPDATE. %s', update_msg)
-            return
-
         if msg_rf in (RF_IPv4_VPN, RF_IPv6_VPN):
             # Check if we have Extended Communities Attribute.
             # TODO(PH): Check if RT_NLRI afi/safi will ever have this attribute
@@ -1784,6 +1837,7 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
                 return
 
         next_hop = mpreach_nlri_attr.next_hop
+
         # Nothing to do if we do not have any new NLRIs in this message.
         msg_nlri_list = mpreach_nlri_attr.nlri
         if not msg_nlri_list:
@@ -1846,11 +1900,9 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
         # Check if this route family is among supported route families.
         if msg_rf not in SUPPORTED_GLOBAL_RF:
             LOG.info(
-                (
-                    'Received route for route family %s which is'
-                    ' not supported. Ignoring withdraws form this UPDATE.'
-                ) % msg_rf
-            )
+                'Received route family %s is not supported. '
+                'Ignoring withdraw routes on this UPDATE message.',
+                msg_rf)
             return
 
         w_nlris = mp_unreach_attr.withdrawn_routes
@@ -2183,8 +2235,14 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
             # routing information contained in that UPDATE message to other
             # internal peers (unless the speaker acts as a BGP Route
             # Reflector) [RFC4271].
-            if (self.remote_as == self._core_service.asn and
-                    self.remote_as == path.source.remote_as):
+            if (self.remote_as == self._core_service.asn
+                    and self.remote_as == path.source.remote_as
+                    and isinstance(path.source, Peer)
+                    and not path.source.is_route_reflector_client
+                    and not self.is_route_reflector_client):
+                LOG.debug(
+                    'Skipping sending iBGP route to iBGP peer %s AS %s',
+                    self.ip_address, self.remote_as)
                 return
 
             # If new best path has community attribute, it should be taken into
diff --git a/ryu/services/protocols/bgp/processor.py 
b/ryu/services/protocols/bgp/processor.py
index 086b777..c65e9b8 100644
--- a/ryu/services/protocols/bgp/processor.py
+++ b/ryu/services/protocols/bgp/processor.py
@@ -30,6 +30,8 @@ from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_LOCAL_PREF
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGINATOR_ID
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_CLUSTER_LIST
 from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_IGP
 from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_EGP
 from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_INCOMPLETE
@@ -107,7 +109,7 @@ class BgpProcessor(Activity):
         dest_processed = 0
         LOG.debug('Processing destination...')
         while (dest_processed < self.work_units_per_cycle and
-                not self._dest_queue.is_empty()):
+               not self._dest_queue.is_empty()):
             # We process the first destination in the queue.
             next_dest = self._dest_queue.pop_first()
             if next_dest:
@@ -169,6 +171,7 @@ BPR_MED = 'MED'
 BPR_ASN = 'ASN'
 BPR_IGP_COST = 'IGP Cost'
 BPR_ROUTER_ID = 'Router ID'
+BPR_CLUSTER_LIST = 'Cluster List'
 
 
 def _compare_by_version(path1, path2):
@@ -212,6 +215,8 @@ def compute_best_path(local_asn, path1, path2):
     9.  Select the route with the lowest IGP cost to the next hop.
     10. Select the route received from the peer with the lowest BGP
         router ID.
+    11. Select the route received from the peer with the shorter
+        CLUSTER_LIST length.
 
     Returns None if best-path among given paths cannot be computed else best
     path.
@@ -252,9 +257,12 @@ def compute_best_path(local_asn, path1, path2):
         best_path = _cmp_by_router_id(local_asn, path1, path2)
         best_path_reason = BPR_ROUTER_ID
     if best_path is None:
+        best_path = _cmp_by_cluster_list(path1, path2)
+        best_path_reason = BPR_CLUSTER_LIST
+    if best_path is None:
         best_path_reason = BPR_UNKNOWN
 
-    return (best_path, best_path_reason)
+    return best_path, best_path_reason
 
 
 def _cmp_by_reachable_nh(path1, path2):
@@ -462,10 +470,14 @@ def _cmp_by_router_id(local_asn, path1, path2):
         else:
             return path_source.remote_as
 
-    def get_router_id(path_source, local_bgp_id):
+    def get_router_id(path, local_bgp_id):
+        path_source = path.source
         if path_source is None:
             return local_bgp_id
         else:
+            originator_id = path.get_pattr(BGP_ATTR_TYPE_ORIGINATOR_ID)
+            if originator_id:
+                return originator_id.value
             return path_source.protocol.recv_open_msg.bgp_identifier
 
     path_source1 = path1.source
@@ -482,7 +494,7 @@ def _cmp_by_router_id(local_asn, path1, path2):
     is_ebgp2 = asn2 != local_asn
     # If both paths are from eBGP peers, then according to RFC we need
     # not tie break using router id.
-    if (is_ebgp1 and is_ebgp2):
+    if is_ebgp1 and is_ebgp2:
         return None
 
     if ((is_ebgp1 is True and is_ebgp2 is False) or
@@ -497,8 +509,8 @@ def _cmp_by_router_id(local_asn, path1, path2):
         local_bgp_id = path_source2.protocol.sent_open_msg.bgp_identifier
 
     # Get router ids.
-    router_id1 = get_router_id(path_source1, local_bgp_id)
-    router_id2 = get_router_id(path_source2, local_bgp_id)
+    router_id1 = get_router_id(path1, local_bgp_id)
+    router_id2 = get_router_id(path2, local_bgp_id)
 
     # If both router ids are same/equal we cannot decide.
     # This case is possible since router ids are arbitrary.
@@ -507,7 +519,31 @@ def _cmp_by_router_id(local_asn, path1, path2):
 
     # Select the path with lowest router Id.
     from ryu.services.protocols.bgp.utils.bgp import from_inet_ptoi
-    if (from_inet_ptoi(router_id1) < from_inet_ptoi(router_id2)):
+    if from_inet_ptoi(router_id1) < from_inet_ptoi(router_id2):
         return path1
     else:
         return path2
+
+
+def _cmp_by_cluster_list(path1, path2):
+    """Selects the route received from the peer with the shorter
+    CLUSTER_LIST length. [RFC4456]
+
+    The CLUSTER_LIST length is evaluated as zero if a route does not
+    carry the CLUSTER_LIST attribute.
+    """
+    def _get_cluster_list_len(path):
+        c_list = path.get_pattr(BGP_ATTR_TYPE_CLUSTER_LIST)
+        if c_list is None:
+            return 0
+        else:
+            return len(c_list.value)
+
+    c_list_len1 = _get_cluster_list_len(path1)
+    c_list_len2 = _get_cluster_list_len(path2)
+    if c_list_len1 < c_list_len2:
+        return path1
+    elif c_list_len1 > c_list_len2:
+        return path2
+    else:
+        return None
diff --git a/ryu/services/protocols/bgp/rtconf/common.py 
b/ryu/services/protocols/bgp/rtconf/common.py
index 806ab45..5115424 100644
--- a/ryu/services/protocols/bgp/rtconf/common.py
+++ b/ryu/services/protocols/bgp/rtconf/common.py
@@ -37,6 +37,7 @@ LOG = logging.getLogger('bgpspeaker.rtconf.common')
 # Global configuration settings.
 LOCAL_AS = 'local_as'
 ROUTER_ID = 'router_id'
+CLUSTER_ID = 'cluster_id'
 LABEL_RANGE = 'label_range'
 LABEL_RANGE_MAX = 'max'
 LABEL_RANGE_MIN = 'min'
@@ -121,6 +122,16 @@ def validate_router_id(router_id):
     return router_id
 
 
+@validate(name=CLUSTER_ID)
+def validate_router_id(cluster_id):
+    if not isinstance(cluster_id, str):
+        raise ConfigTypeError(conf_name=CLUSTER_ID)
+    if not is_valid_ipv4(cluster_id):
+        raise ConfigValueError(desc='Invalid cluster id %s' % cluster_id)
+
+    return cluster_id
+
+
 @validate(name=REFRESH_STALEPATH_TIME)
 def validate_refresh_stalepath_time(rst):
     if not isinstance(rst, numbers.Integral):
@@ -226,7 +237,8 @@ class CommonConf(BaseConf):
                                    TCP_CONN_TIMEOUT,
                                    BGP_CONN_RETRY_TIME,
                                    MAX_PATH_EXT_RTFILTER_ALL,
-                                   ALLOW_LOCAL_AS_IN_COUNT])
+                                   ALLOW_LOCAL_AS_IN_COUNT,
+                                   CLUSTER_ID])
 
     def __init__(self, **kwargs):
         super(CommonConf, self).__init__(**kwargs)
@@ -250,6 +262,8 @@ class CommonConf(BaseConf):
         self._settings[MAX_PATH_EXT_RTFILTER_ALL] = compute_optional_conf(
             MAX_PATH_EXT_RTFILTER_ALL, DEFAULT_MAX_PATH_EXT_RTFILTER_ALL,
             **kwargs)
+        self._settings[CLUSTER_ID] = compute_optional_conf(
+            CLUSTER_ID, kwargs[ROUTER_ID], **kwargs)
 
     # =========================================================================
     # Required attributes
@@ -266,6 +280,9 @@ class CommonConf(BaseConf):
     # =========================================================================
     # Optional attributes with valid defaults.
     # =========================================================================
+    @property
+    def cluster_id(self):
+        return self._settings[CLUSTER_ID]
 
     @property
     def allow_local_as_in_count(self):
diff --git a/ryu/services/protocols/bgp/rtconf/neighbors.py 
b/ryu/services/protocols/bgp/rtconf/neighbors.py
index 3b2d5b7..c02d1ef 100644
--- a/ryu/services/protocols/bgp/rtconf/neighbors.py
+++ b/ryu/services/protocols/bgp/rtconf/neighbors.py
@@ -86,6 +86,7 @@ PASSWORD = 'password'
 IN_FILTER = 'in_filter'
 OUT_FILTER = 'out_filter'
 IS_ROUTE_SERVER_CLIENT = 'is_route_server_client'
+IS_ROUTE_REFLECTOR_CLIENT = 'is_route_reflector_client'
 CHECK_FIRST_AS = 'check_first_as'
 ATTRIBUTE_MAP = 'attribute_map'
 IS_NEXT_HOP_SELF = 'is_next_hop_self'
@@ -110,6 +111,7 @@ DEFAULT_CAP_RTC = False
 DEFAULT_IN_FILTER = []
 DEFAULT_OUT_FILTER = []
 DEFAULT_IS_ROUTE_SERVER_CLIENT = False
+DEFAULT_IS_ROUTE_REFLECTOR_CLIENT = False
 DEFAULT_CHECK_FIRST_AS = False
 DEFAULT_IS_NEXT_HOP_SELF = False
 DEFAULT_CONNECT_MODE = CONNECT_MODE_BOTH
@@ -264,6 +266,15 @@ def 
validate_is_route_server_client(is_route_server_client):
     return is_route_server_client
 
 
+@validate(name=IS_ROUTE_REFLECTOR_CLIENT)
+def validate_is_route_reflector_client(is_route_reflector_client):
+    if is_route_reflector_client not in (True, False):
+        raise ConfigValueError(desc='Invalid is_route_reflector_client(%s)' %
+                                    is_route_reflector_client)
+
+    return is_route_reflector_client
+
+
 @validate(name=CHECK_FIRST_AS)
 def validate_check_first_as(check_first_as):
     if check_first_as not in (True, False):
@@ -312,7 +323,9 @@ class NeighborConf(ConfWithId, ConfWithStats):
                                    LOCAL_ADDRESS, LOCAL_PORT, LOCAL_AS,
                                    PEER_NEXT_HOP, PASSWORD,
                                    IN_FILTER, OUT_FILTER,
-                                   IS_ROUTE_SERVER_CLIENT, CHECK_FIRST_AS,
+                                   IS_ROUTE_SERVER_CLIENT,
+                                   IS_ROUTE_REFLECTOR_CLIENT,
+                                   CHECK_FIRST_AS,
                                    IS_NEXT_HOP_SELF, CONNECT_MODE])
 
     def __init__(self, **kwargs):
@@ -351,6 +364,9 @@ class NeighborConf(ConfWithId, ConfWithStats):
         self._settings[IS_ROUTE_SERVER_CLIENT] = compute_optional_conf(
             IS_ROUTE_SERVER_CLIENT,
             DEFAULT_IS_ROUTE_SERVER_CLIENT, **kwargs)
+        self._settings[IS_ROUTE_REFLECTOR_CLIENT] = compute_optional_conf(
+            IS_ROUTE_REFLECTOR_CLIENT,
+            DEFAULT_IS_ROUTE_REFLECTOR_CLIENT, **kwargs)
         self._settings[CHECK_FIRST_AS] = compute_optional_conf(
             CHECK_FIRST_AS, DEFAULT_CHECK_FIRST_AS, **kwargs)
         self._settings[IS_NEXT_HOP_SELF] = compute_optional_conf(
@@ -560,6 +576,10 @@ class NeighborConf(ConfWithId, ConfWithStats):
         return self._settings[IS_ROUTE_SERVER_CLIENT]
 
     @property
+    def is_route_reflector_client(self):
+        return self._settings[IS_ROUTE_REFLECTOR_CLIENT]
+
+    @property
     def check_first_as(self):
         return self._settings[CHECK_FIRST_AS]
 
-- 
2.7.4


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, SlashDot.org! http://sdm.link/slashdot
_______________________________________________
Ryu-devel mailing list
Ryu-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to