diff options
author | IWASE Yusuke <iwase.yusuke0@gmail.com> | 2017-02-15 15:38:57 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2017-02-22 11:37:52 +0900 |
commit | ef3eefb2ad4088123e467752b20481e5ca8a5129 (patch) | |
tree | 28fc617475421c454baaefaa8638258f4ab60d31 | |
parent | e06ec47232b6870f81c4e056e62c6be0e67c854d (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.py | 2 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/application.py | 2 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgpspeaker.py | 28 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/peer.py | 160 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/processor.py | 50 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/rtconf/common.py | 19 | ||||
-rw-r--r-- | 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 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] |