diff options
author | IWASE Yusuke <iwase.yusuke0@gmail.com> | 2016-08-22 17:21:29 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2016-08-25 13:33:10 +0900 |
commit | 985e2557b2fd59a024ae258bb05ddb215ac5409b (patch) | |
tree | c4a7000b7564ec3e65d9762479bc0648f8a12bbc | |
parent | 55e0097545cfbf14f94b03edca2a8099bc673c3b (diff) |
BGPSpeaker: Support Ethernet VPN update messages
This patch enables BGPSpeaker to advertise BGP EVPN routes and
store the advertised BGP EVPN routes from the neighbors.
TODO:
- To support the VRF table for BGP EVPN routes.
This patch supports the global table only.
- To implement Multihoming Functions.
Currently, ONLY Single-Homing is supported.
Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r-- | doc/source/library_bgp_speaker.rst | 4 | ||||
-rw-r--r-- | ryu/lib/packet/bgp.py | 99 | ||||
-rw-r--r-- | ryu/lib/type_desc.py | 1 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/api/base.py | 6 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/api/prefix.py | 86 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/base.py | 15 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgpspeaker.py | 107 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/core_managers/table_manager.py | 65 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/evpn.py | 86 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/operator/commands/show/rib.py | 2 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/operator/internal_api.py | 5 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/rtconf/base.py | 39 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/rtconf/neighbors.py | 18 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/utils/bgp.py | 3 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/utils/validation.py | 49 |
15 files changed, 548 insertions, 37 deletions
diff --git a/doc/source/library_bgp_speaker.rst b/doc/source/library_bgp_speaker.rst index cf809264..ee1694f6 100644 --- a/doc/source/library_bgp_speaker.rst +++ b/doc/source/library_bgp_speaker.rst @@ -6,8 +6,8 @@ Introduction ============ Ryu BGP speaker library helps you to enable your code to speak BGP -protocol. The library supports ipv4, ipv4 vpn, and ipv6 vpn address -families. +protocol. The library supports IPv4, IPv4 MPLS-labeled VPN, IPv6 +MPLS-labeled VPN and L2VPN EVPN address families. Example ======= diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py index 5846dbad..fdcdb0f2 100644 --- a/ryu/lib/packet/bgp.py +++ b/ryu/lib/packet/bgp.py @@ -39,6 +39,7 @@ from ryu.lib.packet import stream_parser from ryu.lib import addrconv from ryu.lib import type_desc from ryu.lib.pack_utils import msg_pack_into +from ryu.utils import binary_str reduce = six.moves.reduce @@ -666,7 +667,7 @@ class _RouteDistinguisher(StringifyMixin, _TypeDisp, _Value): @property def formatted_str(self): - return "%s:%s" % (str(self.admin), str(self.assigned)) + return "%s:%s" % (self.admin, self.assigned) @_RouteDistinguisher.register_type(_RouteDistinguisher.TWO_OCTET_AS) @@ -1046,6 +1047,8 @@ class EvpnEsi(StringifyMixin, _TypeDisp, _Value): AS_BASED = 0x05 MAX = 0xff # Reserved + _TYPE_NAME = None # must be defined in subclass + def __init__(self, type_=None): if type_ is None: type_ = self._rev_lookup_type(self.__class__) @@ -1063,12 +1066,19 @@ class EvpnEsi(StringifyMixin, _TypeDisp, _Value): msg_pack_into(EvpnEsi._PACK_STR, buf, 0, self.type) return six.binary_type(buf + self.serialize_value()) + @property + def formatted_str(self): + return '%s(%s)' % ( + self._TYPE_NAME, + ','.join(str(getattr(self, v)) for v in self._VALUE_FIELDS)) + @EvpnEsi.register_unknown_type() class EvpnUnknownEsi(EvpnEsi): """ ESI value for unknown type """ + _TYPE_NAME = 'unknown' _VALUE_PACK_STR = '!9s' _VALUE_FIELDS = ['value'] @@ -1076,6 +1086,10 @@ class EvpnUnknownEsi(EvpnEsi): super(EvpnUnknownEsi, self).__init__(type_) self.value = value + @property + def formatted_str(self): + return '%s(%s)' % (self._TYPE_NAME, binary_str(self.value)) + @EvpnEsi.register_type(EvpnEsi.ARBITRARY) class EvpnArbitraryEsi(EvpnEsi): @@ -1085,6 +1099,7 @@ class EvpnArbitraryEsi(EvpnEsi): This type indicates an arbitrary 9-octet ESI value, which is managed and configured by the operator. """ + _TYPE_NAME = 'arbitrary' _VALUE_PACK_STR = '!9s' _VALUE_FIELDS = ['value'] @@ -1092,6 +1107,10 @@ class EvpnArbitraryEsi(EvpnEsi): super(EvpnArbitraryEsi, self).__init__(type_) self.value = value + @property + def formatted_str(self): + return '%s(%s)' % (self._TYPE_NAME, binary_str(self.value)) + @EvpnEsi.register_type(EvpnEsi.LACP) class EvpnLACPEsi(EvpnEsi): @@ -1102,6 +1121,7 @@ class EvpnLACPEsi(EvpnEsi): this ESI type indicates an auto-generated ESI value determined from LACP. """ + _TYPE_NAME = 'lacp' _VALUE_PACK_STR = '!6sHx' _VALUE_FIELDS = ['mac_addr', 'port_key'] _TYPE = { @@ -1139,6 +1159,7 @@ class EvpnL2BridgeEsi(EvpnEsi): The ESI Value is auto-generated and determined based on the Layer 2 bridge protocol. """ + _TYPE_NAME = 'l2_bridge' _VALUE_PACK_STR = '!6sHx' _VALUE_FIELDS = ['mac_addr', 'priority'] _TYPE = { @@ -1174,6 +1195,7 @@ class EvpnMacBasedEsi(EvpnEsi): This type indicates a MAC-based ESI Value that can be auto-generated or configured by the operator. """ + _TYPE_NAME = 'mac_based' _VALUE_PACK_STR = '!6s3s' _VALUE_FIELDS = ['mac_addr', 'local_disc'] _TYPE = { @@ -1210,6 +1232,7 @@ class EvpnRouterIDEsi(EvpnEsi): This type indicates a router-ID ESI Value that can be auto-generated or configured by the operator. """ + _TYPE_NAME = 'router_id' _VALUE_PACK_STR = '!4sIx' _VALUE_FIELDS = ['router_id', 'local_disc'] _TYPE = { @@ -1246,6 +1269,7 @@ class EvpnASBasedEsi(EvpnEsi): ESI Value that can be auto-generated or configured by the operator. """ + _TYPE_NAME = 'as_based' _VALUE_PACK_STR = '!IIx' _VALUE_FIELDS = ['as_number', 'local_disc'] @@ -1277,6 +1301,18 @@ class EvpnNLRI(StringifyMixin, _TypeDisp): INCLUSIVE_MULTICAST_ETHERNET_TAG = 0x03 ETHERNET_SEGMENT = 0x04 + ROUTE_TYPE_NAME = None # must be defined in subclass + + # Dictionary of ROUTE_TYPE_NAME to subclass. + # e.g.) + # _NAMES = {'eth_ad': EvpnEthernetAutoDiscoveryNLRI, ...} + _NAMES = {} + + # List of the fields considered to be part of the prefix in the NLRI. + # This list should be defined in subclasses to format NLRI string + # representation. + NLRI_PREFIX_FIELDS = [] + def __init__(self, type_=None, length=None): if type_ is None: type_ = self._rev_lookup_type(self.__class__) @@ -1285,6 +1321,26 @@ class EvpnNLRI(StringifyMixin, _TypeDisp): self.route_dist = None # should be initialized in subclass @classmethod + def register_type(cls, type_): + cls._TYPES = cls._TYPES.copy() + cls._NAMES = cls._NAMES.copy() + + def _register_type(subcls): + cls._TYPES[type_] = subcls + cls._NAMES[subcls.ROUTE_TYPE_NAME] = subcls + cls._REV_TYPES = None + return subcls + + return _register_type + + @classmethod + def _lookup_type_name(cls, type_name): + try: + return cls._NAMES[type_name] + except KeyError: + return EvpnUnknownNLRI + + @classmethod def parser(cls, buf): (route_type, length) = struct.unpack_from( cls._PACK_STR, six.binary_type(buf)) @@ -1390,12 +1446,32 @@ class EvpnNLRI(StringifyMixin, _TypeDisp): label = label << 4 | 1 return six.binary_type(_LabelledAddrPrefix._label_to_bin(label)) + @property + def prefix(self): + def _format(i): + pairs = [] + for k in i.NLRI_PREFIX_FIELDS: + v = getattr(i, k) + if k == 'esi': + pairs.append('%s:%s' % (k, v.formatted_str)) + else: + pairs.append('%s:%s' % (k, v)) + return ','.join(pairs) + + return '%s(%s)' % (self.ROUTE_TYPE_NAME, _format(self)) + + @property + def formatted_nlri_str(self): + return '%s:%s' % (self.route_dist, self.prefix) + @EvpnNLRI.register_unknown_type() class EvpnUnknownNLRI(EvpnNLRI): """ Unknown route type specific EVPN NLRI """ + ROUTE_TYPE_NAME = 'unknown' + NLRI_PREFIX_FIELDS = ['value'] def __init__(self, value, type_, length=None): super(EvpnUnknownNLRI, self).__init__(type_, length) @@ -1410,12 +1486,17 @@ class EvpnUnknownNLRI(EvpnNLRI): def serialize_value(self): return self.value + @property + def formatted_nlri_str(self): + return '%s(%s)' % (self.ROUTE_TYPE_NAME, binary_str(self.value)) + @EvpnNLRI.register_type(EvpnNLRI.ETHERNET_AUTO_DISCOVERY) class EvpnEthernetAutoDiscoveryNLRI(EvpnNLRI): """ Ethernet A-D route type specific EVPN NLRI """ + ROUTE_TYPE_NAME = 'eth_ad' # +---------------------------------------+ # | Route Distinguisher (RD) (8 octets) | @@ -1427,6 +1508,7 @@ class EvpnEthernetAutoDiscoveryNLRI(EvpnNLRI): # | MPLS Label (3 octets) | # +---------------------------------------+ _PACK_STR = "!8s10sI3s" + NLRI_PREFIX_FIELDS = ['esi', 'ethernet_tag_id'] def __init__(self, route_dist, esi, ethernet_tag_id, mpls_label, type_=None, length=None): @@ -1456,12 +1538,17 @@ class EvpnEthernetAutoDiscoveryNLRI(EvpnNLRI): self._PACK_STR, route_dist.serialize(), self.esi.serialize(), self.ethernet_tag_id, self._mpls_label_to_bin(self.mpls_label)) + @property + def label_list(self): + return [self.mpls_label] + @EvpnNLRI.register_type(EvpnNLRI.MAC_IP_ADVERTISEMENT) class EvpnMacIPAdvertisementNLRI(EvpnNLRI): """ MAC/IP Advertisement route type specific EVPN NLRI """ + ROUTE_TYPE_NAME = 'mac_ip_adv' # +---------------------------------------+ # | RD (8 octets) | @@ -1483,6 +1570,8 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI): # | MPLS Label2 (0 or 3 octets) | # +---------------------------------------+ _PACK_STR = "!8s10sIB6sB%ds3s%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': [ 'mac_addr', @@ -1561,12 +1650,17 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI): self.ip_addr_len, ip_addr, mpls_label1, mpls_label2) + @property + def label_list(self): + return self.mpls_labels + @EvpnNLRI.register_type(EvpnNLRI.INCLUSIVE_MULTICAST_ETHERNET_TAG) class EvpnInclusiveMulticastEthernetTagNLRI(EvpnNLRI): """ Inclusive Multicast Ethernet Tag route type specific EVPN NLRI """ + ROUTE_TYPE_NAME = 'multicast_etag' # +---------------------------------------+ # | RD (8 octets) | @@ -1579,6 +1673,7 @@ class EvpnInclusiveMulticastEthernetTagNLRI(EvpnNLRI): # | (4 or 16 octets) | # +---------------------------------------+ _PACK_STR = '!8sIB%ds' + NLRI_PREFIX_FIELDS = ['ethernet_tag_id', 'ip_addr'] _TYPE = { 'ascii': [ 'ip_addr' @@ -1624,6 +1719,7 @@ class EvpnEthernetSegmentNLRI(EvpnNLRI): """ Ethernet Segment route type specific EVPN NLRI """ + ROUTE_TYPE_NAME = 'eth_seg' # +---------------------------------------+ # | RD (8 octets) | @@ -1636,6 +1732,7 @@ class EvpnEthernetSegmentNLRI(EvpnNLRI): # | (4 or 16 octets) | # +---------------------------------------+ _PACK_STR = '!8s10sB%ds' + NLRI_PREFIX_FIELDS = ['esi', 'ip_addr'] _TYPE = { 'ascii': [ 'ip_addr' diff --git a/ryu/lib/type_desc.py b/ryu/lib/type_desc.py index eca80137..cb10b09b 100644 --- a/ryu/lib/type_desc.py +++ b/ryu/lib/type_desc.py @@ -49,6 +49,7 @@ Int2 = IntDescr(2) Int3 = IntDescr(3) Int4 = IntDescr(4) Int8 = IntDescr(8) +Int9 = IntDescr(9) Int16 = IntDescr(16) diff --git a/ryu/services/protocols/bgp/api/base.py b/ryu/services/protocols/bgp/api/base.py index 33a4d8b8..525723d9 100644 --- a/ryu/services/protocols/bgp/api/base.py +++ b/ryu/services/protocols/bgp/api/base.py @@ -43,6 +43,12 @@ VPN_LABEL = 'label' API_SYM = 'name' ORIGIN_RD = 'origin_rd' ROUTE_FAMILY = 'route_family' +EVPN_ROUTE_TYPE = 'route_type' +EVPN_ESI = 'esi' +EVPN_ETHERNET_TAG_ID = 'ethernet_tag_id' +MAC_ADDR = 'mac_addr' +IP_ADDR = 'ip_addr' +MPLS_LABELS = 'mpls_labels' # API call registry _CALL_REGISTRY = {} diff --git a/ryu/services/protocols/bgp/api/prefix.py b/ryu/services/protocols/bgp/api/prefix.py index 3d1047e3..f6cddc31 100644 --- a/ryu/services/protocols/bgp/api/prefix.py +++ b/ryu/services/protocols/bgp/api/prefix.py @@ -18,6 +18,14 @@ """ import logging +from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI +from ryu.lib.packet.bgp import EvpnInclusiveMulticastEthernetTagNLRI +from ryu.services.protocols.bgp.api.base import EVPN_ROUTE_TYPE +from ryu.services.protocols.bgp.api.base import EVPN_ESI +from ryu.services.protocols.bgp.api.base import EVPN_ETHERNET_TAG_ID +from ryu.services.protocols.bgp.api.base import MAC_ADDR +from ryu.services.protocols.bgp.api.base import IP_ADDR +from ryu.services.protocols.bgp.api.base import MPLS_LABELS from ryu.services.protocols.bgp.api.base import NEXT_HOP from ryu.services.protocols.bgp.api.base import PREFIX from ryu.services.protocols.bgp.api.base import RegisterWithArgChecks @@ -28,6 +36,7 @@ from ryu.services.protocols.bgp.base import PREFIX_ERROR_CODE from ryu.services.protocols.bgp.base import validate from ryu.services.protocols.bgp.core import BgpCoreError from ryu.services.protocols.bgp.core_manager import CORE_MANAGER +from ryu.services.protocols.bgp.rtconf.base import ConfigValueError from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV4 @@ -36,6 +45,14 @@ from ryu.services.protocols.bgp.utils import validation LOG = logging.getLogger('bgpspeaker.api.prefix') +# Constants used in API calls for EVPN +EVPN_MAC_IP_ADV_ROUTE = EvpnMacIPAdvertisementNLRI.ROUTE_TYPE_NAME +EVPN_MULTICAST_ETAG_ROUTE = EvpnInclusiveMulticastEthernetTagNLRI.ROUTE_TYPE_NAME +SUPPORTED_EVPN_ROUTE_TYPES = [ + EVPN_MAC_IP_ADV_ROUTE, + EVPN_MULTICAST_ETAG_ROUTE, +] + @add_bgp_error_metadata(code=PREFIX_ERROR_CODE, sub_code=1, @@ -55,6 +72,49 @@ def is_valid_next_hop(next_hop_addr): return validation.is_valid_ipv4(next_hop_addr) +@validate(name=EVPN_ROUTE_TYPE) +def is_valid_evpn_route_type(route_type): + if route_type not in SUPPORTED_EVPN_ROUTE_TYPES: + raise ConfigValueError(conf_name=EVPN_ROUTE_TYPE, + conf_value=route_type) + + +@validate(name=EVPN_ESI) +def is_valid_esi(esi): + if not validation.is_valid_esi(esi): + raise ConfigValueError(conf_name=EVPN_ESI, + conf_value=esi) + + +@validate(name=EVPN_ETHERNET_TAG_ID) +def is_valid_ethernet_tag_id(ethernet_tag_id): + if not validation.is_valid_ethernet_tag_id(ethernet_tag_id): + raise ConfigValueError(conf_name=EVPN_ETHERNET_TAG_ID, + conf_value=ethernet_tag_id) + + +@validate(name=MAC_ADDR) +def is_valid_mac_addr(addr): + if not validation.is_valid_mac(addr): + raise ConfigValueError(conf_name=MAC_ADDR, + conf_value=addr) + + +@validate(name=IP_ADDR) +def is_valid_ip_addr(addr): + if not (validation.is_valid_ipv4(addr) + or validation.is_valid_ipv6(addr)): + raise ConfigValueError(conf_name=IP_ADDR, + conf_value=addr) + + +@validate(name=MPLS_LABELS) +def is_valid_mpls_labels(labels): + if not validation.is_valid_mpls_labels(labels): + raise ConfigValueError(conf_name=MPLS_LABELS, + conf_value=labels) + + @RegisterWithArgChecks(name='prefix.add_local', req_args=[ROUTE_DISTINGUISHER, PREFIX, NEXT_HOP], opt_args=[VRF_RF]) @@ -93,3 +153,29 @@ def delete_local(route_dist, prefix, route_family=VRF_RF_IPV4): VRF_RF: route_family}] except BgpCoreError as e: raise PrefixError(desc=e) + + +# ============================================================================= +# BGP EVPN Routes related APIs +# ============================================================================= + +@RegisterWithArgChecks(name='evpn_prefix.add_local', + req_args=[EVPN_ROUTE_TYPE, ROUTE_DISTINGUISHER, + NEXT_HOP], + opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR, + IP_ADDR]) +def add_evpn_local(route_type, route_dist, next_hop, **kwargs): + tm = CORE_MANAGER.get_core_service().table_manager + tm.add_to_global_evpn_table(route_type, route_dist, next_hop, **kwargs) + return True + + +@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]) +def delete_evpn_local(route_type, route_dist, **kwargs): + tm = CORE_MANAGER.get_core_service().table_manager + tm.add_to_global_evpn_table(route_type, route_dist, is_withdraw=True, + **kwargs) + return True diff --git a/ryu/services/protocols/bgp/base.py b/ryu/services/protocols/bgp/base.py index 9d23adb7..c0c574fc 100644 --- a/ryu/services/protocols/bgp/base.py +++ b/ryu/services/protocols/bgp/base.py @@ -35,6 +35,7 @@ from ryu.lib.packet.bgp import RF_IPv4_UC from ryu.lib.packet.bgp import RF_IPv6_UC from ryu.lib.packet.bgp import RF_IPv4_VPN from ryu.lib.packet.bgp import RF_IPv6_VPN +from ryu.lib.packet.bgp import RF_L2_EVPN from ryu.lib.packet.bgp import RF_RTC_UC from ryu.services.protocols.bgp.utils.circlist import CircularListType from ryu.services.protocols.bgp.utils.evtlet import LoopingCall @@ -48,12 +49,14 @@ OrderedDict = OrderedDict # Currently supported address families. -SUPPORTED_GLOBAL_RF = set([RF_IPv4_UC, - RF_IPv6_UC, - RF_IPv4_VPN, - RF_RTC_UC, - RF_IPv6_VPN - ]) +SUPPORTED_GLOBAL_RF = { + RF_IPv4_UC, + RF_IPv6_UC, + RF_IPv4_VPN, + RF_RTC_UC, + RF_IPv6_VPN, + RF_L2_EVPN, +} # Various error codes diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py index 2ce6372a..ce72e0fa 100644 --- a/ryu/services/protocols/bgp/bgpspeaker.py +++ b/ryu/services/protocols/bgp/bgpspeaker.py @@ -23,9 +23,16 @@ from ryu.services.protocols.bgp.core_manager import CORE_MANAGER from ryu.services.protocols.bgp.signals.emit import BgpSignalBus from ryu.services.protocols.bgp.api.base import call from ryu.services.protocols.bgp.api.base import PREFIX +from ryu.services.protocols.bgp.api.base import EVPN_ROUTE_TYPE +from ryu.services.protocols.bgp.api.base import EVPN_ESI +from ryu.services.protocols.bgp.api.base import EVPN_ETHERNET_TAG_ID +from ryu.services.protocols.bgp.api.base import IP_ADDR +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.prefix import EVPN_MAC_IP_ADV_ROUTE +from ryu.services.protocols.bgp.api.prefix import EVPN_MULTICAST_ETAG_ROUTE 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 @@ -44,6 +51,7 @@ from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV4 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV6 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV4 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV6 +from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_EVPN from ryu.services.protocols.bgp.rtconf.base import CAP_ENHANCED_REFRESH from ryu.services.protocols.bgp.rtconf.base import CAP_FOUR_OCTET_AS_NUMBER from ryu.services.protocols.bgp.rtconf.base import MULTI_EXIT_DISC @@ -51,6 +59,7 @@ from ryu.services.protocols.bgp.rtconf.base import SITE_OF_ORIGINS from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CAP_MBGP_IPV4 from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CAP_MBGP_VPNV4 from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CAP_MBGP_VPNV6 +from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CAP_MBGP_EVPN from ryu.services.protocols.bgp.rtconf.neighbors import ( DEFAULT_CAP_ENHANCED_REFRESH, DEFAULT_CAP_FOUR_OCTET_AS_NUMBER) from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CONNECT_MODE @@ -237,6 +246,7 @@ class BGPSpeaker(object): enable_ipv4=DEFAULT_CAP_MBGP_IPV4, enable_vpnv4=DEFAULT_CAP_MBGP_VPNV4, enable_vpnv6=DEFAULT_CAP_MBGP_VPNV6, + enable_evpn=DEFAULT_CAP_MBGP_EVPN, 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, @@ -264,6 +274,9 @@ class BGPSpeaker(object): ``enable_vpnv6`` enables VPNv6 address family for this neighbor. The default is False. + ``enable_evpn`` enables Ethernet VPN address family for this + neighbor. The default is False. + ``enable_enhanced_refresh`` enables Enhanced Route Refresh for this neighbor. The default is False. @@ -320,11 +333,13 @@ class BGPSpeaker(object): bgp_neighbor[CAP_MBGP_IPV6] = False bgp_neighbor[CAP_MBGP_VPNV4] = enable_vpnv4 bgp_neighbor[CAP_MBGP_VPNV6] = enable_vpnv6 + bgp_neighbor[CAP_MBGP_EVPN] = enable_evpn elif netaddr.valid_ipv6(address): bgp_neighbor[CAP_MBGP_IPV4] = False bgp_neighbor[CAP_MBGP_IPV6] = True bgp_neighbor[CAP_MBGP_VPNV4] = False bgp_neighbor[CAP_MBGP_VPNV6] = False + bgp_neighbor[CAP_MBGP_EVPN] = enable_evpn else: # FIXME: should raise an exception pass @@ -467,6 +482,98 @@ class BGPSpeaker(object): call(func_name, **networks) + def evpn_prefix_add(self, route_type, route_dist, esi=0, + ethernet_tag_id=None, mac_addr=None, ip_addr=None, + next_hop=None): + """ This method adds a new EVPN route to be advertised. + + ``route_type`` specifies one of the EVPN route type name. The + supported route types are EVPN_MAC_IP_ADV_ROUTE and + EVPN_MULTICAST_ETAG_ROUTE. + + ``route_dist`` specifies a route distinguisher value. + + ``esi`` is an integer value to specify the Ethernet Segment + Identifier. 0 is the default and denotes a single-homed site. + + ``ethernet_tag_id`` specifies the Ethernet Tag ID. + + ``mac_addr`` specifies a MAC address to advertise. + + ``ip_addr`` specifies an IPv4 or IPv6 address to advertise. + + ``next_hop`` specifies the next hop address for this prefix. + """ + func_name = 'evpn_prefix.add_local' + + # Check the default values + if not next_hop: + next_hop = '0.0.0.0' + + # Set required arguments + kwargs = {EVPN_ROUTE_TYPE: route_type, + ROUTE_DISTINGUISHER: route_dist, + NEXT_HOP: next_hop} + + # Set route type specific arguments + if route_type == EVPN_MAC_IP_ADV_ROUTE: + kwargs.update({ + EVPN_ESI: esi, + EVPN_ETHERNET_TAG_ID: ethernet_tag_id, + MAC_ADDR: mac_addr, + IP_ADDR: ip_addr, + }) + elif route_type == EVPN_MULTICAST_ETAG_ROUTE: + kwargs.update({ + EVPN_ETHERNET_TAG_ID: ethernet_tag_id, + IP_ADDR: ip_addr, + }) + else: + raise ValueError('Unsupported EVPN route type: %s' % route_type) + + call(func_name, **kwargs) + + def evpn_prefix_del(self, route_type, route_dist, esi=0, + ethernet_tag_id=None, mac_addr=None, ip_addr=None): + """ This method deletes an advertised EVPN route. + + ``route_type`` specifies one of the EVPN route type name. + + ``route_dist`` specifies a route distinguisher value. + + ``esi`` is an integer value to specify the Ethernet Segment + Identifier. 0 is the default and denotes a single-homed site. + + ``ethernet_tag_id`` specifies the Ethernet Tag ID. + + ``mac_addr`` specifies a MAC address to advertise. + + ``ip_addr`` specifies an IPv4 or IPv6 address to advertise. + """ + func_name = 'evpn_prefix.delete_local' + + # Set required arguments + kwargs = {EVPN_ROUTE_TYPE: route_type, + ROUTE_DISTINGUISHER: route_dist} + + # Set route type specific arguments + if route_type == EVPN_MAC_IP_ADV_ROUTE: + kwargs.update({ + EVPN_ESI: esi, + EVPN_ETHERNET_TAG_ID: ethernet_tag_id, + MAC_ADDR: mac_addr, + IP_ADDR: ip_addr, + }) + elif route_type == EVPN_MULTICAST_ETAG_ROUTE: + kwargs.update({ + EVPN_ETHERNET_TAG_ID: ethernet_tag_id, + IP_ADDR: ip_addr, + }) + else: + raise ValueError('Unsupported EVPN route type: %s' % route_type) + + call(func_name, **kwargs) + def vrf_add(self, route_dist, import_rts, export_rts, site_of_origins=None, route_family=RF_VPN_V4, multi_exit_disc=None): """ This method adds a new vrf used for VPN. diff --git a/ryu/services/protocols/bgp/core_managers/table_manager.py b/ryu/services/protocols/bgp/core_managers/table_manager.py index 7858fea8..ec71d3f5 100644 --- a/ryu/services/protocols/bgp/core_managers/table_manager.py +++ b/ryu/services/protocols/bgp/core_managers/table_manager.py @@ -1,7 +1,8 @@ import logging -import netaddr from collections import OrderedDict +import netaddr + from ryu.services.protocols.bgp.base import SUPPORTED_GLOBAL_RF from ryu.services.protocols.bgp.info_base.rtc import RtcTable from ryu.services.protocols.bgp.info_base.ipv4 import Ipv4Path @@ -14,20 +15,27 @@ from ryu.services.protocols.bgp.info_base.vpnv6 import Vpnv6Path from ryu.services.protocols.bgp.info_base.vpnv6 import Vpnv6Table from ryu.services.protocols.bgp.info_base.vrf4 import Vrf4Table from ryu.services.protocols.bgp.info_base.vrf6 import Vrf6Table +from ryu.services.protocols.bgp.info_base.evpn import EvpnPath +from ryu.services.protocols.bgp.info_base.evpn import EvpnTable from ryu.services.protocols.bgp.rtconf import vrfs from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV4 from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV6 +from ryu.services.protocols.bgp.rtconf.vrfs import SUPPORTED_VRF_RF +from ryu.lib import type_desc from ryu.lib.packet.bgp import RF_IPv4_UC from ryu.lib.packet.bgp import RF_IPv6_UC from ryu.lib.packet.bgp import RF_IPv4_VPN from ryu.lib.packet.bgp import RF_IPv6_VPN +from ryu.lib.packet.bgp import RF_L2_EVPN from ryu.lib.packet.bgp import RF_RTC_UC from ryu.lib.packet.bgp import BGPPathAttributeOrigin from ryu.lib.packet.bgp import BGPPathAttributeAsPath from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_IGP +from ryu.lib.packet.bgp import EvpnArbitraryEsi +from ryu.lib.packet.bgp import EvpnNLRI from ryu.lib.packet.bgp import IPAddrPrefix from ryu.lib.packet.bgp import IP6AddrPrefix @@ -76,7 +84,7 @@ class TableCoreManager(object): def remove_vrf_by_vrf_conf(self, vrf_conf): route_family = vrf_conf.route_family - assert route_family in (vrfs.VRF_RF_IPV4, vrfs.VRF_RF_IPV6) + assert route_family in SUPPORTED_VRF_RF table_id = (vrf_conf.route_dist, route_family) vrf_table = self._tables.pop(table_id) @@ -171,10 +179,10 @@ class TableCoreManager(object): global_table = self.get_ipv6_table() elif route_family == RF_IPv4_VPN: global_table = self.get_vpn4_table() - elif route_family == RF_IPv6_VPN: global_table = self.get_vpn6_table() - + elif route_family == RF_L2_EVPN: + global_table = self.get_evpn_table() elif route_family == RF_RTC_UC: global_table = self.get_rtc_table() @@ -245,6 +253,20 @@ class TableCoreManager(object): return vpn_table + def get_evpn_table(self): + """Returns global EVPN table. + + Creates the table if it does not exist. + """ + evpn_table = self._global_tables.get(RF_L2_EVPN) + # Lazy initialization of the table. + if not evpn_table: + evpn_table = EvpnTable(self._core_service, self._signal_bus) + self._global_tables[RF_L2_EVPN] = evpn_table + self._tables[(None, RF_L2_EVPN)] = evpn_table + + return evpn_table + def get_rtc_table(self): """Returns global RTC table. @@ -528,6 +550,41 @@ class TableCoreManager(object): # add to global ipv4 table and propagates to neighbors self.learn_path(new_path) + def add_to_global_evpn_table(self, route_type, route_dist, next_hop=None, + is_withdraw=False, **kwargs): + """Adds BGP EVPN Route to global EVPN Table with given `next_hop`. + + If `is_withdraw` is set to `True`, removes the given route from + global EVPN Table. + """ + + # construct EVPN NLRI instance + subclass = EvpnNLRI._lookup_type_name(route_type) + kwargs['route_dist'] = route_dist + esi = kwargs.get('esi', None) + if esi is not None: + # Note: Currently, we support arbitrary 9-octet ESI value only. + kwargs['esi'] = EvpnArbitraryEsi(type_desc.Int9.from_user(esi)) + nlri = subclass(**kwargs) + + # set mandatory path attributes + origin = BGPPathAttributeOrigin(BGP_ATTR_ORIGIN_IGP) + aspath = BGPPathAttributeAsPath([[]]) + pathattrs = OrderedDict() + pathattrs[BGP_ATTR_TYPE_ORIGIN] = origin + pathattrs[BGP_ATTR_TYPE_AS_PATH] = aspath + + # set the default next_hop address + if next_hop is None: + next_hop = '0.0.0.0' + + new_path = EvpnPath(source=None, nlri=nlri, src_ver_num=1, + pattrs=pathattrs, nexthop=next_hop, + is_withdraw=is_withdraw) + + # add to global EVPN table and propagates to neighbors + self.learn_path(new_path) + def remove_from_vrf(self, route_dist, prefix, route_family): """Removes `prefix` from VRF identified by `route_dist`. diff --git a/ryu/services/protocols/bgp/info_base/evpn.py b/ryu/services/protocols/bgp/info_base/evpn.py new file mode 100644 index 00000000..1a2c6f6b --- /dev/null +++ b/ryu/services/protocols/bgp/info_base/evpn.py @@ -0,0 +1,86 @@ +# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" + Defines data types and models required specifically for EVPN support. +""" + +import logging + +from ryu.lib.packet.bgp import EvpnNLRI +from ryu.lib.packet.bgp import RF_L2_EVPN + +from ryu.services.protocols.bgp.info_base.base import Path +from ryu.services.protocols.bgp.info_base.base import Table +from ryu.services.protocols.bgp.info_base.base import Destination +from ryu.services.protocols.bgp.info_base.base import NonVrfPathProcessingMixin + +LOG = logging.getLogger('bgpspeaker.info_base.evpn') + + +class EvpnDest(Destination, NonVrfPathProcessingMixin): + """EVPN Destination + + Store EVPN paths. + """ + ROUTE_FAMILY = RF_L2_EVPN + + def _best_path_lost(self): + old_best_path = self._best_path + NonVrfPathProcessingMixin._best_path_lost(self) + self._core_service._signal_bus.best_path_changed(old_best_path, True) + + def _new_best_path(self, best_path): + NonVrfPathProcessingMixin._new_best_path(self, best_path) + self._core_service._signal_bus.best_path_changed(best_path, False) + + +class EvpnTable(Table): + """Global table to store EVPN routing information. + + Uses `EvpnDest` to store destination information for each known EVPN + paths. + """ + ROUTE_FAMILY = RF_L2_EVPN + VPN_DEST_CLASS = EvpnDest + + def __init__(self, core_service, signal_bus): + super(EvpnTable, self).__init__(None, core_service, signal_bus) + + def _table_key(self, nlri): + """Return a key that will uniquely identify this NLRI inside + this table. + """ + return nlri.formatted_nlri_str + + def _create_dest(self, nlri): + return self.VPN_DEST_CLASS(self, nlri) + + def __str__(self): + return '%s(scope_id: %s, rf: %s)' % ( + self.__class__.__name__, self.scope_id, self.route_family + ) + + +class EvpnPath(Path): + """Represents a way of reaching an EVPN destination.""" + ROUTE_FAMILY = RF_L2_EVPN + VRF_PATH_CLASS = None # defined in init - anti cyclic import hack + NLRI_CLASS = EvpnNLRI + + def __init__(self, *args, **kwargs): + super(EvpnPath, self).__init__(*args, **kwargs) + # TODO: + # To support the VRF table for BGP EVPN routes. diff --git a/ryu/services/protocols/bgp/operator/commands/show/rib.py b/ryu/services/protocols/bgp/operator/commands/show/rib.py index 27d5b73c..05380455 100644 --- a/ryu/services/protocols/bgp/operator/commands/show/rib.py +++ b/ryu/services/protocols/bgp/operator/commands/show/rib.py @@ -13,7 +13,7 @@ from ryu.services.protocols.bgp.operator.commands.responses import \ class RibBase(Command, RouteFormatterMixin): - supported_families = ['ipv4', 'ipv6', 'vpnv4', 'rtfilter', 'vpnv6'] + supported_families = ['ipv4', 'ipv6', 'vpnv4', 'rtfilter', 'vpnv6', 'evpn'] class Rib(RibBase): diff --git a/ryu/services/protocols/bgp/operator/internal_api.py b/ryu/services/protocols/bgp/operator/internal_api.py index 70543d21..c37b1cf0 100644 --- a/ryu/services/protocols/bgp/operator/internal_api.py +++ b/ryu/services/protocols/bgp/operator/internal_api.py @@ -6,6 +6,7 @@ from ryu.lib.packet.bgp import RF_IPv4_UC from ryu.lib.packet.bgp import RF_IPv6_UC from ryu.lib.packet.bgp import RF_IPv4_VPN from ryu.lib.packet.bgp import RF_IPv6_VPN +from ryu.lib.packet.bgp import RF_L2_EVPN from ryu.lib.packet.bgp import RF_RTC_UC from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH @@ -82,10 +83,12 @@ class InternalApi(object): 'ipv6': RF_IPv6_UC, 'vpnv4': RF_IPv4_VPN, 'vpnv6': RF_IPv6_VPN, + 'evpn': RF_L2_EVPN, 'rtfilter': RF_RTC_UC } if addr_family not in rfs: - raise WrongParamError('Unknown or unsupported family') + raise WrongParamError('Unknown or unsupported family: %s' % + addr_family) rf = rfs.get(addr_family) table_manager = self.get_core_service().table_manager diff --git a/ryu/services/protocols/bgp/rtconf/base.py b/ryu/services/protocols/bgp/rtconf/base.py index 8746b2dd..6c2975b4 100644 --- a/ryu/services/protocols/bgp/rtconf/base.py +++ b/ryu/services/protocols/bgp/rtconf/base.py @@ -44,6 +44,7 @@ CAP_MBGP_IPV4 = 'cap_mbgp_ipv4' CAP_MBGP_IPV6 = 'cap_mbgp_ipv6' CAP_MBGP_VPNV4 = 'cap_mbgp_vpnv4' CAP_MBGP_VPNV6 = 'cap_mbgp_vpnv6' +CAP_MBGP_EVPN = 'cap_mbgp_evpn' CAP_RTC = 'cap_rtc' RTC_AS = 'rtc_as' HOLD_TIME = 'hold_time' @@ -172,15 +173,15 @@ class BaseConf(object): return self._settings.copy() @classmethod - def get_valid_evts(self): + def get_valid_evts(cls): return set() @classmethod - def get_req_settings(self): + def get_req_settings(cls): return set() @classmethod - def get_opt_settings(self): + def get_opt_settings(cls): return set() @abstractmethod @@ -582,8 +583,8 @@ def validate_stats_time(stats_time): @validate(name=CAP_REFRESH) def validate_cap_refresh(crefresh): if crefresh not in (True, False): - raise ConfigTypeError(desc='Invalid Refresh capability settings: %s ' - ' boolean value expected' % crefresh) + raise ConfigTypeError(desc='Invalid Refresh capability settings: %s. ' + 'Boolean value expected' % crefresh) return crefresh @@ -591,7 +592,7 @@ def validate_cap_refresh(crefresh): def validate_cap_enhanced_refresh(cer): if cer not in (True, False): raise ConfigTypeError(desc='Invalid Enhanced Refresh capability ' - 'settings: %s boolean value expected' % cer) + 'settings: %s. Boolean value expected' % cer) return cer @@ -606,8 +607,8 @@ def validate_cap_four_octet_as_number(cfoan): @validate(name=CAP_MBGP_IPV4) def validate_cap_mbgp_ipv4(cmv4): if cmv4 not in (True, False): - raise ConfigTypeError(desc='Invalid Enhanced Refresh capability ' - 'settings: %s boolean value expected' % cmv4) + raise ConfigTypeError(desc='Invalid MP-BGP IPv4 capability ' + 'settings: %s. Boolean value expected' % cmv4) return cmv4 @@ -615,8 +616,8 @@ def validate_cap_mbgp_ipv4(cmv4): @validate(name=CAP_MBGP_IPV6) def validate_cap_mbgp_ipv6(cmv6): if cmv6 not in (True, False): - raise ConfigTypeError(desc='Invalid Enhanced Refresh capability ' - 'settings: %s boolean value expected' % cmv6) + raise ConfigTypeError(desc='Invalid MP-BGP IPv6 capability ' + 'settings: %s. Boolean value expected' % cmv6) return cmv6 @@ -624,8 +625,8 @@ def validate_cap_mbgp_ipv6(cmv6): @validate(name=CAP_MBGP_VPNV4) def validate_cap_mbgp_vpnv4(cmv4): if cmv4 not in (True, False): - raise ConfigTypeError(desc='Invalid Enhanced Refresh capability ' - 'settings: %s boolean value expected' % cmv4) + raise ConfigTypeError(desc='Invalid MP-BGP VPNv4 capability ' + 'settings: %s. Boolean value expected' % cmv4) return cmv4 @@ -633,12 +634,20 @@ def validate_cap_mbgp_vpnv4(cmv4): @validate(name=CAP_MBGP_VPNV6) def validate_cap_mbgp_vpnv6(cmv6): if cmv6 not in (True, False): - raise ConfigTypeError(desc='Invalid Enhanced Refresh capability ' - 'settings: %s boolean value expected' % cmv6) + raise ConfigTypeError(desc='Invalid MP-BGP VPNv6 capability ' + 'settings: %s. Boolean value expected' % cmv6) return cmv6 +@validate(name=CAP_MBGP_EVPN) +def validate_cap_mbgp_evpn(cmevpn): + if cmevpn not in (True, False): + raise ConfigTypeError(desc='Invalid Ethernet VPN capability ' + 'settings: %s. Boolean value expected' % cmevpn) + return cmevpn + + @validate(name=CAP_RTC) def validate_cap_rtc(cap_rtc): if cap_rtc not in (True, False): @@ -688,7 +697,7 @@ def validate_soo_list(soo_list): unique_rts = set(soo_list) if len(unique_rts) != len(soo_list): raise ConfigValueError(desc='Duplicate value provided in %s' % - (soo_list)) + soo_list) return soo_list diff --git a/ryu/services/protocols/bgp/rtconf/neighbors.py b/ryu/services/protocols/bgp/rtconf/neighbors.py index bc27542d..c252d5f2 100644 --- a/ryu/services/protocols/bgp/rtconf/neighbors.py +++ b/ryu/services/protocols/bgp/rtconf/neighbors.py @@ -25,6 +25,7 @@ from ryu.lib.packet.bgp import RF_IPv4_UC from ryu.lib.packet.bgp import RF_IPv6_UC from ryu.lib.packet.bgp import RF_IPv4_VPN from ryu.lib.packet.bgp import RF_IPv6_VPN +from ryu.lib.packet.bgp import RF_L2_EVPN from ryu.lib.packet.bgp import RF_RTC_UC from ryu.lib.packet.bgp import BGPOptParamCapabilityFourOctetAsNumber from ryu.lib.packet.bgp import BGPOptParamCapabilityEnhancedRouteRefresh @@ -45,6 +46,7 @@ from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV4 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV6 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV4 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV6 +from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_EVPN from ryu.services.protocols.bgp.rtconf.base import CAP_REFRESH from ryu.services.protocols.bgp.rtconf.base import CAP_RTC from ryu.services.protocols.bgp.rtconf.base import compute_optional_conf @@ -100,6 +102,7 @@ DEFAULT_CAP_MBGP_IPV4 = True DEFAULT_CAP_MBGP_IPV6 = False DEFAULT_CAP_MBGP_VPNV4 = False DEFAULT_CAP_MBGP_VPNV6 = False +DEFAULT_CAP_MBGP_EVPN = False DEFAULT_HOLD_TIME = 40 DEFAULT_ENABLED = True DEFAULT_CAP_RTC = False @@ -302,7 +305,7 @@ class NeighborConf(ConfWithId, ConfWithStats): CAP_FOUR_OCTET_AS_NUMBER, CAP_MBGP_IPV4, CAP_MBGP_IPV6, CAP_MBGP_VPNV4, CAP_MBGP_VPNV6, - CAP_RTC, RTC_AS, HOLD_TIME, + CAP_RTC, CAP_MBGP_EVPN, RTC_AS, HOLD_TIME, ENABLED, MULTI_EXIT_DISC, MAX_PREFIXES, ADVERTISE_PEER_AS, SITE_OF_ORIGINS, LOCAL_ADDRESS, LOCAL_PORT, LOCAL_AS, @@ -328,6 +331,8 @@ class NeighborConf(ConfWithId, ConfWithStats): CAP_MBGP_IPV6, DEFAULT_CAP_MBGP_IPV6, **kwargs) self._settings[CAP_MBGP_VPNV4] = compute_optional_conf( CAP_MBGP_VPNV4, DEFAULT_CAP_MBGP_VPNV4, **kwargs) + self._settings[CAP_MBGP_EVPN] = compute_optional_conf( + CAP_MBGP_EVPN, DEFAULT_CAP_MBGP_EVPN, **kwargs) self._settings[CAP_MBGP_VPNV6] = compute_optional_conf( CAP_MBGP_VPNV6, DEFAULT_CAP_MBGP_VPNV6, **kwargs) self._settings[HOLD_TIME] = compute_optional_conf( @@ -493,6 +498,10 @@ class NeighborConf(ConfWithId, ConfWithStats): return self._settings[CAP_MBGP_VPNV6] @property + def cap_mbgp_evpn(self): + return self._settings[CAP_MBGP_EVPN] + + @property def cap_rtc(self): return self._settings[CAP_RTC] @@ -607,6 +616,11 @@ class NeighborConf(ConfWithId, ConfWithStats): BGPOptParamCapabilityMultiprotocol( RF_RTC_UC.afi, RF_RTC_UC.safi)) + if self.cap_mbgp_evpn: + mbgp_caps.append( + BGPOptParamCapabilityMultiprotocol( + RF_L2_EVPN.afi, RF_L2_EVPN.safi)) + if mbgp_caps: capabilities[BGP_CAP_MULTIPROTOCOL] = mbgp_caps @@ -631,7 +645,7 @@ class NeighborConf(ConfWithId, ConfWithStats): self.enabled) def __str__(self): - return 'Neighbor: %s' % (self.ip_address) + return 'Neighbor: %s' % self.ip_address class NeighborsConf(BaseConf): diff --git a/ryu/services/protocols/bgp/utils/bgp.py b/ryu/services/protocols/bgp/utils/bgp.py index 43793570..faad4b4d 100644 --- a/ryu/services/protocols/bgp/utils/bgp.py +++ b/ryu/services/protocols/bgp/utils/bgp.py @@ -25,6 +25,7 @@ from ryu.lib.packet.bgp import ( RF_IPv6_UC, RF_IPv4_VPN, RF_IPv6_VPN, + RF_L2_EVPN, RF_RTC_UC, RouteTargetMembershipNLRI, BGP_ATTR_TYPE_MULTI_EXIT_DISC, @@ -41,6 +42,7 @@ from ryu.services.protocols.bgp.info_base.ipv4 import Ipv4Path from ryu.services.protocols.bgp.info_base.ipv6 import Ipv6Path from ryu.services.protocols.bgp.info_base.vpnv4 import Vpnv4Path from ryu.services.protocols.bgp.info_base.vpnv6 import Vpnv6Path +from ryu.services.protocols.bgp.info_base.evpn import EvpnPath LOG = logging.getLogger('utils.bgp') @@ -50,6 +52,7 @@ _ROUTE_FAMILY_TO_PATH_MAP = {RF_IPv4_UC: Ipv4Path, RF_IPv6_UC: Ipv6Path, RF_IPv4_VPN: Vpnv4Path, RF_IPv6_VPN: Vpnv6Path, + RF_L2_EVPN: EvpnPath, RF_RTC_UC: RtcPath} diff --git a/ryu/services/protocols/bgp/utils/validation.py b/ryu/services/protocols/bgp/utils/validation.py index f0fb6e57..41425d47 100644 --- a/ryu/services/protocols/bgp/utils/validation.py +++ b/ryu/services/protocols/bgp/utils/validation.py @@ -17,11 +17,25 @@ Module provides utilities for validation. """ import numbers +import re import socket import six +def is_valid_mac(mac): + """Returns True if the given MAC address is valid. + + The given MAC address should be a colon hexadecimal notation string. + + Samples: + - valid address: aa:bb:cc:dd:ee:ff, 11:22:33:44:55:66 + - invalid address: aa:bb:cc:dd, 11-22-33-44-55-66, etc. + """ + return bool(re.match(r'^' + r'[\:\-]'.join([r'([0-9a-f]{2})'] * 6) + + r'$', mac.lower())) + + def is_valid_ipv4(ipv4): """Returns True if given is a valid ipv4 address. @@ -190,14 +204,25 @@ def is_valid_mpls_label(label): A value of 3 represents the "Implicit NULL Label". Values 4-15 are reserved. """ - valid = True - if (not isinstance(label, numbers.Integral) or - (label >= 4 and label <= 15) or + (4 <= label <= 15) or (label < 0 or label > 2 ** 20)): - valid = False + return False - return valid + return True + + +def is_valid_mpls_labels(labels): + """Returns True if the given value is a list of valid MPLS labels. + """ + if not isinstance(labels, (list, tuple)): + return False + + for label in labels: + if not is_valid_mpls_label(label): + return False + + return True def is_valid_route_dist(route_dist): @@ -237,3 +262,17 @@ def is_valid_ext_comm_attr(attr): is_valid = False return is_valid + + +def is_valid_esi(esi): + """Returns True if the given EVPN Ethernet SegmentEthernet ID is valid.""" + # Note: Currently, only integer type value is supported + return isinstance(esi, numbers.Integral) + + +def is_valid_ethernet_tag_id(etag_id): + """Returns True if the given EVPN Ethernet Tag ID is valid. + + Ethernet Tag ID should be a 32-bit field number. + """ + return isinstance(etag_id, numbers.Integral) and 0 <= etag_id <= 0xffffffff |