diff options
-rw-r--r-- | ryu/lib/packet/bgp.py | 153 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/api/base.py | 1 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/api/prefix.py | 12 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgpspeaker.py | 16 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/core_managers/table_manager.py | 7 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/vpn.py | 7 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/vrf.py | 14 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/utils/validation.py | 9 |
8 files changed, 174 insertions, 45 deletions
diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py index 6d7cf3f6..c980bc45 100644 --- a/ryu/lib/packet/bgp.py +++ b/ryu/lib/packet/bgp.py @@ -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', ] } diff --git a/ryu/services/protocols/bgp/api/base.py b/ryu/services/protocols/bgp/api/base.py index 49e78063..9007da0f 100644 --- a/ryu/services/protocols/bgp/api/base.py +++ b/ryu/services/protocols/bgp/api/base.py @@ -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 = {} diff --git a/ryu/services/protocols/bgp/api/prefix.py b/ryu/services/protocols/bgp/api/prefix.py index 2bcb4e08..8963f513 100644 --- a/ryu/services/protocols/bgp/api/prefix.py +++ b/ryu/services/protocols/bgp/api/prefix.py @@ -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*. """ diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py index 879eaf24..62ba1462 100644 --- a/ryu/services/protocols/bgp/bgpspeaker.py +++ b/ryu/services/protocols/bgp/bgpspeaker.py @@ -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, diff --git a/ryu/services/protocols/bgp/core_managers/table_manager.py b/ryu/services/protocols/bgp/core_managers/table_manager.py index 5320dbfa..4b958537 100644 --- a/ryu/services/protocols/bgp/core_managers/table_manager.py +++ b/ryu/services/protocols/bgp/core_managers/table_manager.py @@ -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( diff --git a/ryu/services/protocols/bgp/info_base/vpn.py b/ryu/services/protocols/bgp/info_base/vpn.py index 46cf47fb..87c44376 100644 --- a/ryu/services/protocols/bgp/info_base/vpn.py +++ b/ryu/services/protocols/bgp/info_base/vpn.py @@ -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) diff --git a/ryu/services/protocols/bgp/info_base/vrf.py b/ryu/services/protocols/bgp/info_base/vrf.py index 6a60fc82..5e22e299 100644 --- a/ryu/services/protocols/bgp/info_base/vrf.py +++ b/ryu/services/protocols/bgp/info_base/vrf.py @@ -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), diff --git a/ryu/services/protocols/bgp/utils/validation.py b/ryu/services/protocols/bgp/utils/validation.py index 6cf292da..ff6ed506 100644 --- a/ryu/services/protocols/bgp/utils/validation.py +++ b/ryu/services/protocols/bgp/utils/validation.py @@ -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 |