diff options
-rw-r--r-- | ryu/lib/packet/bgp.py | 9 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgpspeaker.py | 12 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/peer.py | 4 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/rtconf/common.py | 26 |
4 files changed, 42 insertions, 9 deletions
diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py index c787d82e..977b7836 100644 --- a/ryu/lib/packet/bgp.py +++ b/ryu/lib/packet/bgp.py @@ -2806,13 +2806,12 @@ class _BGPPathAttributeAsPathCommon(_PathAttribute): return count - def has_local_as(self, local_as): + def has_local_as(self, local_as, max_count=0): """Check if *local_as* is already present on path list.""" + _count = 0 for as_path_seg in self.value: - for as_num in as_path_seg: - if as_num == local_as: - return True - return False + _count += list(as_path_seg).count(local_as) + return _count > max_count def has_matching_leftmost(self, remote_as): """Check if leftmost AS matches *remote_as*.""" diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py index d82be624..078f1a6c 100644 --- a/ryu/services/protocols/bgp/bgpspeaker.py +++ b/ryu/services/protocols/bgp/bgpspeaker.py @@ -63,6 +63,7 @@ from ryu.services.protocols.bgp.rtconf.common import DEFAULT_LABEL_RANGE from ryu.services.protocols.bgp.rtconf.common import REFRESH_MAX_EOR_TIME from ryu.services.protocols.bgp.rtconf.common import REFRESH_STALEPATH_TIME from ryu.services.protocols.bgp.rtconf.common import LABEL_RANGE +from ryu.services.protocols.bgp.rtconf.common import ALLOW_LOCAL_AS_IN_COUNT from ryu.services.protocols.bgp.rtconf import neighbors from ryu.services.protocols.bgp.rtconf import vrfs from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV4 @@ -174,7 +175,8 @@ class BGPSpeaker(object): peer_up_handler=None, ssh_console=False, ssh_port=None, ssh_host=None, ssh_host_key=None, - label_range=DEFAULT_LABEL_RANGE): + label_range=DEFAULT_LABEL_RANGE, + allow_local_as_in_count=0): """Create a new BGPSpeaker object with as_number and router_id to listen on bgp_server_port. @@ -222,7 +224,14 @@ class BGPSpeaker(object): ``label_range`` specifies the range of MPLS labels generated automatically. + + ``allow_local_as_in_count`` maximum number of local AS number + occurrences in AS_PATH. This option is useful for e.g. auto RD/RT + 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). """ + super(BGPSpeaker, self).__init__() settings = { @@ -232,6 +241,7 @@ class BGPSpeaker(object): REFRESH_STALEPATH_TIME: refresh_stalepath_time, REFRESH_MAX_EOR_TIME: refresh_max_eor_time, LABEL_RANGE: label_range, + ALLOW_LOCAL_AS_IN_COUNT: allow_local_as_in_count, } self._core_start(settings) self._init_signal_listeners() diff --git a/ryu/services/protocols/bgp/peer.py b/ryu/services/protocols/bgp/peer.py index 8bf96d62..d380e305 100644 --- a/ryu/services/protocols/bgp/peer.py +++ b/ryu/services/protocols/bgp/peer.py @@ -1622,7 +1622,7 @@ class Peer(Source, Sink, NeighborConfListener, Activity): aspath = umsg_pattrs.get(BGP_ATTR_TYPE_AS_PATH) # Check if AS_PATH has loops. - if aspath.has_local_as(self.local_as): + 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 @@ -1750,7 +1750,7 @@ class Peer(Source, Sink, NeighborConfListener, Activity): aspath = umsg_pattrs.get(BGP_ATTR_TYPE_AS_PATH) # Check if AS_PATH has loops. - if aspath.has_local_as(self.local_as): + 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 diff --git a/ryu/services/protocols/bgp/rtconf/common.py b/ryu/services/protocols/bgp/rtconf/common.py index acf4634f..806ab453 100644 --- a/ryu/services/protocols/bgp/rtconf/common.py +++ b/ryu/services/protocols/bgp/rtconf/common.py @@ -41,6 +41,12 @@ LABEL_RANGE = 'label_range' LABEL_RANGE_MAX = 'max' LABEL_RANGE_MIN = 'min' +# Similar to Cisco command 'allowas-in'. Allows the local ASN in the path. +# Facilitates auto rd, auto rt import/export +# ("rd auto/route-target both auto") and simplified spine/leaf architectures, +# sharing an ASN between e.g. leafs. +ALLOW_LOCAL_AS_IN_COUNT = 'allow_local_as_in_count' + # Configuration that can be set at global level as well as per context # (session/vrf) level # Nested configuration override global or higher level configuration as they @@ -80,6 +86,17 @@ DEFAULT_MED = 0 DEFAULT_MAX_PATH_EXT_RTFILTER_ALL = True +@validate(name=ALLOW_LOCAL_AS_IN_COUNT) +def validate_allow_local_as_in_count(count): + if not isinstance(count, numbers.Integral): + raise ConfigTypeError(desc=('Configuration value for %s has to be ' + 'integral type' % ALLOW_LOCAL_AS_IN_COUNT)) + if count < 0: + raise ConfigValueError(desc='Invalid local AS count %s' % count) + + return count + + @validate(name=LOCAL_AS) def validate_local_as(asn): if asn is None: @@ -208,13 +225,16 @@ class CommonConf(BaseConf): LABEL_RANGE, BGP_SERVER_PORT, TCP_CONN_TIMEOUT, BGP_CONN_RETRY_TIME, - MAX_PATH_EXT_RTFILTER_ALL]) + MAX_PATH_EXT_RTFILTER_ALL, + ALLOW_LOCAL_AS_IN_COUNT]) def __init__(self, **kwargs): super(CommonConf, self).__init__(**kwargs) def _init_opt_settings(self, **kwargs): super(CommonConf, self)._init_opt_settings(**kwargs) + self._settings[ALLOW_LOCAL_AS_IN_COUNT] = compute_optional_conf( + ALLOW_LOCAL_AS_IN_COUNT, 0, **kwargs) self._settings[LABEL_RANGE] = compute_optional_conf( LABEL_RANGE, DEFAULT_LABEL_RANGE, **kwargs) self._settings[REFRESH_STALEPATH_TIME] = compute_optional_conf( @@ -248,6 +268,10 @@ class CommonConf(BaseConf): # ========================================================================= @property + def allow_local_as_in_count(self): + return self._settings[ALLOW_LOCAL_AS_IN_COUNT] + + @property def bgp_conn_retry_time(self): return self._settings[BGP_CONN_RETRY_TIME] |