summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorIWASE Yusuke <iwase.yusuke0@gmail.com>2016-08-22 17:21:29 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2016-08-25 13:33:10 +0900
commit985e2557b2fd59a024ae258bb05ddb215ac5409b (patch)
treec4a7000b7564ec3e65d9762479bc0648f8a12bbc
parent55e0097545cfbf14f94b03edca2a8099bc673c3b (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.rst4
-rw-r--r--ryu/lib/packet/bgp.py99
-rw-r--r--ryu/lib/type_desc.py1
-rw-r--r--ryu/services/protocols/bgp/api/base.py6
-rw-r--r--ryu/services/protocols/bgp/api/prefix.py86
-rw-r--r--ryu/services/protocols/bgp/base.py15
-rw-r--r--ryu/services/protocols/bgp/bgpspeaker.py107
-rw-r--r--ryu/services/protocols/bgp/core_managers/table_manager.py65
-rw-r--r--ryu/services/protocols/bgp/info_base/evpn.py86
-rw-r--r--ryu/services/protocols/bgp/operator/commands/show/rib.py2
-rw-r--r--ryu/services/protocols/bgp/operator/internal_api.py5
-rw-r--r--ryu/services/protocols/bgp/rtconf/base.py39
-rw-r--r--ryu/services/protocols/bgp/rtconf/neighbors.py18
-rw-r--r--ryu/services/protocols/bgp/utils/bgp.py3
-rw-r--r--ryu/services/protocols/bgp/utils/validation.py49
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