BGPSpeaker: Extend to advertise VNI for EVPN routes

This patch enables to advertise VNI as MPLS lables field in the
MAC/IP Advertisement Route of EVPN.

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-09-05 09:41:36 +09:00 committed by FUJITA Tomonori
parent 40fe7ffdc5
commit 396473593e
8 changed files with 174 additions and 45 deletions

View File

@ -1446,6 +1446,14 @@ class EvpnNLRI(StringifyMixin, _TypeDisp):
label = label << 4 | 1
return six.binary_type(_LabelledAddrPrefix._label_to_bin(label))
@staticmethod
def _vni_from_bin(buf):
return type_desc.Int3.to_user(six.binary_type(buf[:3])), buf[3:]
@staticmethod
def _vni_to_bin(vni):
return type_desc.Int3.from_user(vni)
@property
def prefix(self):
def _format(i):
@ -1509,34 +1517,78 @@ class EvpnEthernetAutoDiscoveryNLRI(EvpnNLRI):
# +---------------------------------------+
_PACK_STR = "!8s10sI3s"
NLRI_PREFIX_FIELDS = ['esi', 'ethernet_tag_id']
_TYPE = {
'ascii': [
'route_dist',
]
}
def __init__(self, route_dist, esi, ethernet_tag_id, mpls_label,
def __init__(self, route_dist, esi, ethernet_tag_id,
mpls_label=None, vni=None, label=None,
type_=None, length=None):
super(EvpnEthernetAutoDiscoveryNLRI, self).__init__(type_, length)
self.route_dist = route_dist
self.esi = esi
self.ethernet_tag_id = ethernet_tag_id
self.mpls_label = mpls_label
if label:
# If binary type label field value is specified, stores it
# and decodes as MPLS label and VNI.
self._label = label
self._mpls_label, _, _ = self._mpls_label_from_bin(label)
self._vni, _ = self._vni_from_bin(label)
else:
# If either MPLS label or VNI is specified, stores it
# and encodes into binary type label field value.
self._label = self._serialize_label(mpls_label, vni)
self._mpls_label = mpls_label
self._vni = vni
def _serialize_label(self, mpls_label, vni):
if mpls_label:
return self._mpls_label_to_bin(mpls_label, is_stack=True)
elif vni:
return self._vni_to_bin(vni)
else:
return b'\x00' * 3
@classmethod
def parse_value(cls, buf):
route_dist, rest = cls._rd_from_bin(buf)
esi, rest = cls._esi_from_bin(rest)
ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest)
mpls_label, rest, _ = cls._mpls_label_from_bin(rest)
return {
'route_dist': route_dist.formatted_str,
'esi': esi,
'ethernet_tag_id': ethernet_tag_id,
'mpls_label': mpls_label,
'label': rest,
}
def serialize_value(self):
route_dist = _RouteDistinguisher.from_str(self.route_dist)
return struct.pack(
self._PACK_STR, route_dist.serialize(), self.esi.serialize(),
self.ethernet_tag_id, self._mpls_label_to_bin(self.mpls_label))
self.ethernet_tag_id, self._label)
@property
def mpls_label(self):
return self._mpls_label
@mpls_label.setter
def mpls_label(self, mpls_label):
self._label = self._mpls_label_to_bin(mpls_label, is_stack=True)
self._mpls_label = mpls_label
self._vni = None # disables VNI
@property
def vni(self):
return self._vni
@vni.setter
def vni(self, vni):
self._label = self._vni_to_bin(vni)
self._mpls_label = None # disables MPLS label
self._vni = vni
@property
def label_list(self):
@ -1569,18 +1621,20 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
# +---------------------------------------+
# | MPLS Label2 (0 or 3 octets) |
# +---------------------------------------+
_PACK_STR = "!8s10sIB6sB%ds3s%ds"
_PACK_STR = "!8s10sIB6sB%ds%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': [
'route_dist',
'mac_addr',
'ip_addr',
]
}
def __init__(self, route_dist, esi, ethernet_tag_id, mac_addr, ip_addr,
mpls_labels, mac_addr_len=None, ip_addr_len=None,
mpls_labels=None, vni=None, labels=None,
mac_addr_len=None, ip_addr_len=None,
type_=None, length=None):
super(EvpnMacIPAdvertisementNLRI, self).__init__(type_, length)
self.route_dist = route_dist
@ -1590,7 +1644,43 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
self.mac_addr = mac_addr
self.ip_addr_len = ip_addr_len
self.ip_addr = ip_addr
self.mpls_labels = mpls_labels
if labels:
# If binary type labels field value is specified, stores it
# and decodes as MPLS labels and VNI.
self._mpls_labels, self._vni = self._parse_labels(labels)
self._labels = labels
else:
# If either MPLS labels or VNI is specified, stores it
# and encodes into binary type labels field value.
self._labels = self._serialize_labels(mpls_labels, vni)
self._mpls_labels = mpls_labels
self._vni = vni
def _parse_labels(self, labels):
mpls_label1, rest, is_stack = self._mpls_label_from_bin(labels)
mpls_labels = [mpls_label1]
if rest and is_stack:
mpls_label2, rest, _ = self._mpls_label_from_bin(rest)
mpls_labels.append(mpls_label2)
vni, _ = self._vni_from_bin(labels)
return mpls_labels, vni
def _serialize_labels(self, mpls_labels, vni):
if mpls_labels:
return self._serialize_mpls_labels(mpls_labels)
elif vni:
return self._vni_to_bin(vni)
else:
return b'\x00' * 3
def _serialize_mpls_labels(self, mpls_labels):
if len(mpls_labels) == 1:
return self._mpls_label_to_bin(mpls_labels[0], is_stack=False)
elif len(mpls_labels) == 2:
return (self._mpls_label_to_bin(mpls_labels[0], is_stack=True) +
self._mpls_label_to_bin(mpls_labels[1], is_stack=False))
else:
return b'\x00' * 3
@classmethod
def parse_value(cls, buf):
@ -1604,11 +1694,6 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
ip_addr, rest = cls._ip_addr_from_bin(rest, ip_addr_len)
else:
ip_addr = None
mpls_label1, rest, is_stack = cls._mpls_label_from_bin(rest)
mpls_labels = [mpls_label1]
if rest and is_stack:
mpls_label2, rest, _ = cls._mpls_label_from_bin(rest)
mpls_labels.append(mpls_label2)
return {
'route_dist': route_dist.formatted_str,
@ -1618,7 +1703,7 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
'mac_addr': mac_addr,
'ip_addr_len': ip_addr_len,
'ip_addr': ip_addr,
'mpls_labels': mpls_labels,
'labels': rest,
}
def serialize_value(self):
@ -1631,24 +1716,34 @@ class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
ip_addr = b''
ip_addr_len = len(ip_addr)
self.ip_addr_len = ip_addr_len * 8 # fixup
mpls_label1 = b''
mpls_label2 = b''
if len(self.mpls_labels) == 1:
mpls_label1 = self._mpls_label_to_bin(self.mpls_labels[0],
is_stack=False)
elif len(self.mpls_labels) == 2:
mpls_label1 = self._mpls_label_to_bin(self.mpls_labels[0],
is_stack=True)
mpls_label2 = self._mpls_label_to_bin(self.mpls_labels[1],
is_stack=False)
return struct.pack(
self._PACK_STR % (ip_addr_len, len(mpls_label2)),
self._PACK_STR % (ip_addr_len, len(self._labels)),
route_dist.serialize(), self.esi.serialize(),
self.ethernet_tag_id,
self.mac_addr_len, mac_addr,
self.ip_addr_len, ip_addr,
mpls_label1, mpls_label2)
self._labels)
@property
def mpls_labels(self):
return self._mpls_labels
@mpls_labels.setter
def mpls_labels(self, mpls_labels):
self._labels = self._serialize_mpls_labels(mpls_labels)
self._mpls_labels = mpls_labels
self._vni = None # disables VNI
@property
def vni(self):
return self._vni
@vni.setter
def vni(self, vni):
self._labels = self._vni_to_bin(vni)
self._mpls_labels = None # disables MPLS labels
self._vni = vni
@property
def label_list(self):
@ -1676,7 +1771,8 @@ class EvpnInclusiveMulticastEthernetTagNLRI(EvpnNLRI):
NLRI_PREFIX_FIELDS = ['ethernet_tag_id', 'ip_addr']
_TYPE = {
'ascii': [
'ip_addr'
'route_dist',
'ip_addr',
]
}
@ -1735,7 +1831,8 @@ class EvpnEthernetSegmentNLRI(EvpnNLRI):
NLRI_PREFIX_FIELDS = ['esi', 'ip_addr']
_TYPE = {
'ascii': [
'ip_addr'
'route_dist',
'ip_addr',
]
}

View File

@ -50,6 +50,7 @@ MAC_ADDR = 'mac_addr'
IP_ADDR = 'ip_addr'
MPLS_LABELS = 'mpls_labels'
TUNNEL_TYPE = 'tunnel_type'
EVPN_VNI = 'vni'
# API call registry
_CALL_REGISTRY = {}

View File

@ -31,6 +31,7 @@ from ryu.services.protocols.bgp.api.base import PREFIX
from ryu.services.protocols.bgp.api.base import RegisterWithArgChecks
from ryu.services.protocols.bgp.api.base import ROUTE_DISTINGUISHER
from ryu.services.protocols.bgp.api.base import VPN_LABEL
from ryu.services.protocols.bgp.api.base import EVPN_VNI
from ryu.services.protocols.bgp.api.base import TUNNEL_TYPE
from ryu.services.protocols.bgp.base import add_bgp_error_metadata
from ryu.services.protocols.bgp.base import PREFIX_ERROR_CODE
@ -137,6 +138,13 @@ def is_valid_mpls_labels(labels):
conf_value=labels)
@validate(name=EVPN_VNI)
def is_valid_vni(vni):
if not validation.is_valid_vni(vni):
raise ConfigValueError(conf_name=EVPN_VNI,
conf_value=vni)
@validate(name=TUNNEL_TYPE)
def is_valid_tunnel_type(tunnel_type):
if tunnel_type not in SUPPORTED_TUNNEL_TYPES:
@ -193,7 +201,7 @@ def delete_local(route_dist, prefix, route_family=VRF_RF_IPV4):
req_args=[EVPN_ROUTE_TYPE, ROUTE_DISTINGUISHER,
NEXT_HOP],
opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR,
IP_ADDR, TUNNEL_TYPE])
IP_ADDR, EVPN_VNI, TUNNEL_TYPE])
def add_evpn_local(route_type, route_dist, next_hop, **kwargs):
"""Adds EVPN route from VRF identified by *route_dist*.
"""
@ -220,7 +228,7 @@ def add_evpn_local(route_type, route_dist, next_hop, **kwargs):
@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])
IP_ADDR, EVPN_VNI])
def delete_evpn_local(route_type, route_dist, **kwargs):
"""Deletes/withdraws EVPN route from VRF identified by *route_dist*.
"""

View File

@ -31,10 +31,12 @@ 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.base import EVPN_VNI
from ryu.services.protocols.bgp.api.base import TUNNEL_TYPE
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.api.prefix import TUNNEL_TYPE_MPLS
from ryu.services.protocols.bgp.api.prefix import TUNNEL_TYPE_VXLAN
from ryu.services.protocols.bgp.api.prefix import TUNNEL_TYPE_NVGRE
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
@ -492,7 +494,7 @@ class BGPSpeaker(object):
def evpn_prefix_add(self, route_type, route_dist, esi=0,
ethernet_tag_id=None, mac_addr=None, ip_addr=None,
next_hop=None, tunnel_type=TUNNEL_TYPE_MPLS):
vni=None, next_hop=None, tunnel_type=None):
""" This method adds a new EVPN route to be advertised.
``route_type`` specifies one of the EVPN route type name. The
@ -510,10 +512,15 @@ class BGPSpeaker(object):
``ip_addr`` specifies an IPv4 or IPv6 address to advertise.
``vni`` specifies an Virtual Network Identifier for VXLAN
or Virtual Subnet Identifier for NVGRE.
If tunnel_type is not 'vxlan' or 'nvgre', this field is ignored.
``next_hop`` specifies the next hop address for this prefix.
``tunnel_type`` specifies the data plane encapsulation type
to advertise. The default is the MPLS encapsulation type.
to advertise. By the default, this encapsulation attribute is
not advertised.
"""
func_name = 'evpn_prefix.add_local'
@ -538,6 +545,9 @@ class BGPSpeaker(object):
MAC_ADDR: mac_addr,
IP_ADDR: ip_addr,
})
# Set tunnel type specific arguments
if tunnel_type in [TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE]:
kwargs[EVPN_VNI] = vni
elif route_type == EVPN_MULTICAST_ETAG_ROUTE:
kwargs.update({
EVPN_ETHERNET_TAG_ID: ethernet_tag_id,

View File

@ -538,6 +538,13 @@ class TableCoreManager(object):
if esi is not None:
# Note: Currently, we support arbitrary 9-octet ESI value only.
kwargs['esi'] = EvpnArbitraryEsi(type_desc.Int9.from_user(esi))
if 'vni' in kwargs:
# Disable to generate MPLS labels, because encapsulation type
# is not MPLS.
from ryu.services.protocols.bgp.api.prefix import (
TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE)
assert tunnel_type in [TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE]
gen_lbl = False
prefix = subclass(**kwargs)
else:
raise BgpCoreError(

View File

@ -65,10 +65,9 @@ class VpnPath(Path):
def clone_to_vrf(self, is_withdraw=False):
if self.ROUTE_FAMILY == RF_L2_EVPN:
nlri_cls = self.NLRI_CLASS._lookup_type(self._nlri.type)
kwargs = dict(self._nlri.__dict__)
kwargs.pop('type', None)
vrf_nlri = nlri_cls(**kwargs)
# Because NLRI class is the same if the route family is EVPN,
# we re-use the NLRI instance.
vrf_nlri = self._nlri
else: # self.ROUTE_FAMILY in [RF_IPv4_VPN, RF_IPv46_VPN]
vrf_nlri = self.NLRI_CLASS(self._nlri.prefix)

View File

@ -157,10 +157,9 @@ class VrfTable(Table):
source = VRF_TABLE
if self.VPN_ROUTE_FAMILY == RF_L2_EVPN:
nlri_cls = self.NLRI_CLASS._lookup_type(vpn_path.nlri.type)
kwargs = dict(vpn_path.nlri.__dict__)
kwargs.pop('type', None)
vrf_nlri = nlri_cls(**kwargs)
# Because NLRI class is the same if the route family is EVPN,
# we re-use the NLRI instance.
vrf_nlri = vpn_path.nlri
else: # self.VPN_ROUTE_FAMILY in [RF_IPv4_VPN, RF_IPv6_VPN]
# Copy NLRI instance
ip, masklen = vpn_path.nlri.prefix.split('/')
@ -528,10 +527,9 @@ class VrfPath(Path):
def clone_to_vpn(self, route_dist, for_withdrawal=False):
if self.ROUTE_FAMILY == RF_L2_EVPN:
nlri_cls = self.VPN_NLRI_CLASS._lookup_type(self._nlri.type)
kwargs = dict(self._nlri.__dict__)
kwargs.pop('type', None)
vpn_nlri = nlri_cls(**kwargs)
# Because NLRI class is the same if the route family is EVPN,
# we re-use the NLRI instance.
vpn_nlri = self._nlri
else: # self.ROUTE_FAMILY in [RF_IPv4_UC, RF_IPv6_UC]
ip, masklen = self._nlri.prefix.split('/')
vpn_nlri = self.VPN_NLRI_CLASS(length=int(masklen),

View File

@ -250,3 +250,12 @@ def is_valid_ethernet_tag_id(etag_id):
Ethernet Tag ID should be a 32-bit field number.
"""
return isinstance(etag_id, numbers.Integral) and 0 <= etag_id <= 0xffffffff
def is_valid_vni(vni):
"""Returns True if the given Virtual Network Identifier for VXLAN
is valid.
Virtual Network Identifier should be a 24-bit field number.
"""
return isinstance(vni, numbers.Integral) and 0 <= vni <= 0xffffff