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.yusu...@gmail.com>
---
 doc/source/library_bgp_speaker.rst                 |   4 +-
 ryu/lib/packet/bgp.py                              |  99 ++++++++++++++++++-
 ryu/lib/type_desc.py                               |   1 +
 ryu/services/protocols/bgp/api/base.py             |   6 ++
 ryu/services/protocols/bgp/api/prefix.py           |  86 +++++++++++++++++
 ryu/services/protocols/bgp/base.py                 |  15 +--
 ryu/services/protocols/bgp/bgpspeaker.py           | 107 +++++++++++++++++++++
 .../protocols/bgp/core_managers/table_manager.py   |  65 ++++++++++++-
 ryu/services/protocols/bgp/info_base/evpn.py       |  86 +++++++++++++++++
 .../protocols/bgp/operator/commands/show/rib.py    |   2 +-
 .../protocols/bgp/operator/internal_api.py         |   5 +-
 ryu/services/protocols/bgp/rtconf/base.py          |  39 +++++---
 ryu/services/protocols/bgp/rtconf/neighbors.py     |  18 +++-
 ryu/services/protocols/bgp/utils/bgp.py            |   3 +
 ryu/services/protocols/bgp/utils/validation.py     |  49 +++++++++-
 15 files changed, 548 insertions(+), 37 deletions(-)
 create mode 100644 ryu/services/protocols/bgp/info_base/evpn.py

diff --git a/doc/source/library_bgp_speaker.rst 
b/doc/source/library_bgp_speaker.rst
index cf80926..ee1694f 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 5846dba..fdcdb0f 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 eca8013..cb10b09 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 33a4d8b..525723d 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 3d1047e..f6cddc3 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 9d23adb..c0c574f 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 2ce6372..ce72e0f 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 7858fea..ec71d3f 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 0000000..1a2c6f6
--- /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 27d5b73..0538045 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 70543d2..c37b1cf 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 8746b2d..6c2975b 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 bc27542..c252d5f 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 4379357..faad4b4 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 f0fb6e5..41425d4 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
-- 
2.7.4


------------------------------------------------------------------------------
_______________________________________________
Ryu-devel mailing list
Ryu-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to