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>
This commit is contained in:
IWASE Yusuke 2016-08-22 17:21:29 +09:00 committed by FUJITA Tomonori
parent 55e0097545
commit 985e2557b2
15 changed files with 548 additions and 37 deletions

View File

@ -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
=======

View File

@ -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__)
@ -1284,6 +1320,26 @@ class EvpnNLRI(StringifyMixin, _TypeDisp):
self.length = length
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(
@ -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'

View File

@ -49,6 +49,7 @@ Int2 = IntDescr(2)
Int3 = IntDescr(3)
Int4 = IntDescr(4)
Int8 = IntDescr(8)
Int9 = IntDescr(9)
Int16 = IntDescr(16)

View File

@ -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 = {}

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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`.

View File

@ -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.

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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(
@ -492,6 +497,10 @@ class NeighborConf(ConfWithId, ConfWithStats):
def cap_mbgp_vpnv6(self):
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):

View File

@ -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}

View File

@ -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