mirror of
https://github.com/faucetsdn/ryu.git
synced 2026-05-10 14:56:11 +02:00
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:
parent
40fe7ffdc5
commit
396473593e
@ -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',
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@ -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 = {}
|
||||
|
||||
@ -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*.
|
||||
"""
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user