summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorIWASE Yusuke <iwase.yusuke0@gmail.com>2017-02-15 15:38:57 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2017-02-22 11:37:52 +0900
commitef3eefb2ad4088123e467752b20481e5ca8a5129 (patch)
tree28fc617475421c454baaefaa8638258f4ab60d31
parente06ec47232b6870f81c4e056e62c6be0e67c854d (diff)
BGPSpeaker: Support Route Reflector features [RFC4456]
This patch implements the features for acting as a Route Reflector which defined in RFC4456. Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r--ryu/lib/packet/bgp.py2
-rw-r--r--ryu/services/protocols/bgp/application.py2
-rw-r--r--ryu/services/protocols/bgp/bgpspeaker.py28
-rw-r--r--ryu/services/protocols/bgp/peer.py160
-rw-r--r--ryu/services/protocols/bgp/processor.py50
-rw-r--r--ryu/services/protocols/bgp/rtconf/common.py19
-rw-r--r--ryu/services/protocols/bgp/rtconf/neighbors.py22
7 files changed, 217 insertions, 66 deletions
diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py
index 977b7836..cfe8cfff 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 c1a9c43d..a3e386bc 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 078f1a6c..f1784240 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 d380e305..37027395 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 086b777e..c65e9b82 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 806ab453..51154241 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 3b2d5b70..c02d1efa 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]