diff options
author | IWASE Yusuke <iwase.yusuke0@gmail.com> | 2016-08-22 17:21:24 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2016-08-25 13:32:42 +0900 |
commit | c4dac34bad59dd90a2dad63b22cb5631a1fefa5e (patch) | |
tree | 31dc332ace2735f51938ca631aa25b986f62defd | |
parent | 0255390a76479e4ecb3836dd33828fa1d59f763e (diff) |
packet/bgp: Support MPLS-Based Ethernet VPN (RFC7432)
Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r-- | ryu/lib/packet/afi.py | 1 | ||||
-rw-r--r-- | ryu/lib/packet/bgp.py | 806 | ||||
-rw-r--r-- | ryu/lib/packet/safi.py | 1 |
3 files changed, 777 insertions, 31 deletions
diff --git a/ryu/lib/packet/afi.py b/ryu/lib/packet/afi.py index c84bf47f..70760423 100644 --- a/ryu/lib/packet/afi.py +++ b/ryu/lib/packet/afi.py @@ -22,3 +22,4 @@ address-family-numbers.xhtml IP = 1 IP6 = 2 +L2VPN = 25 diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py index 99da8677..697eff3a 100644 --- a/ryu/lib/packet/bgp.py +++ b/ryu/lib/packet/bgp.py @@ -37,6 +37,7 @@ from ryu.lib.packet import safi as subaddr_family from ryu.lib.packet import packet_base 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 reduce = six.moves.reduce @@ -180,9 +181,7 @@ class _Value(object): args = [] for f in self._VALUE_FIELDS: args.append(getattr(self, f)) - buf = bytearray() - msg_pack_into(self._VALUE_PACK_STR, buf, 0, *args) - return buf + return struct.pack(self._VALUE_PACK_STR, *args) class _TypeDisp(object): @@ -597,6 +596,7 @@ RF_IPv4_VPN = RouteFamily(addr_family.IP, subaddr_family.MPLS_VPN) RF_IPv6_VPN = RouteFamily(addr_family.IP6, subaddr_family.MPLS_VPN) RF_IPv4_MPLS = RouteFamily(addr_family.IP, subaddr_family.MPLS_LABEL) RF_IPv6_MPLS = RouteFamily(addr_family.IP6, subaddr_family.MPLS_LABEL) +RF_L2_EVPN = RouteFamily(addr_family.L2VPN, subaddr_family.EVPN) RF_RTC_UC = RouteFamily(addr_family.IP, subaddr_family.ROUTE_TARGET_CONSTRAINTS) @@ -607,6 +607,7 @@ _rf_map = { (addr_family.IP6, subaddr_family.MPLS_VPN): RF_IPv6_VPN, (addr_family.IP, subaddr_family.MPLS_LABEL): RF_IPv4_MPLS, (addr_family.IP6, subaddr_family.MPLS_LABEL): RF_IPv6_MPLS, + (addr_family.L2VPN, subaddr_family.EVPN): RF_L2_EVPN, (addr_family.IP, subaddr_family.ROUTE_TARGET_CONSTRAINTS): RF_RTC_UC } @@ -661,7 +662,7 @@ class _RouteDistinguisher(StringifyMixin, _TypeDisp, _Value): value = self.serialize_value() buf = bytearray() msg_pack_into(self._PACK_STR, buf, 0, self.type) - return buf + value + return six.binary_type(buf + value) @property def formatted_str(self): @@ -820,7 +821,7 @@ class _LabelledAddrPrefix(_AddrPrefix): (label & 0xff0000) >> 16, (label & 0x00ff00) >> 8, (label & 0x0000ff) >> 0) - return buf + return six.binary_type(buf) @classmethod def _label_from_bin(cls, label): @@ -1030,6 +1031,651 @@ class LabelledVPNIP6AddrPrefix(_LabelledAddrPrefix, _VPNAddrPrefix, return "%s:%s" % (self.route_dist, self.prefix) +class EvpnEsi(StringifyMixin, _TypeDisp, _Value): + """ + Ethernet Segment Identifier + """ + _PACK_STR = "!B" # ESI Type + _ESI_LEN = 10 + + ARBITRARY = 0x00 + LACP = 0x01 + L2_BRIDGE = 0x02 + MAC_BASED = 0x03 + ROUTER_ID = 0x04 + AS_BASED = 0x05 + MAX = 0xff # Reserved + + def __init__(self, type_=None): + if type_ is None: + type_ = self._rev_lookup_type(self.__class__) + self.type = type_ + + @classmethod + def parser(cls, buf): + (esi_type,) = struct.unpack_from( + cls._PACK_STR, six.binary_type(buf)) + subcls = cls._lookup_type(esi_type) + return subcls(**subcls.parse_value(buf[1:cls._ESI_LEN])) + + def serialize(self): + buf = bytearray() + msg_pack_into(EvpnEsi._PACK_STR, buf, 0, self.type) + return six.binary_type(buf + self.serialize_value()) + + +@EvpnEsi.register_unknown_type() +class EvpnUnknownEsi(EvpnEsi): + """ + ESI value for unknown type + """ + _VALUE_PACK_STR = '!9s' + _VALUE_FIELDS = ['value'] + + def __init__(self, value, type_=None): + super(EvpnUnknownEsi, self).__init__(type_) + self.value = value + + +@EvpnEsi.register_type(EvpnEsi.ARBITRARY) +class EvpnArbitraryEsi(EvpnEsi): + """ + Arbitrary 9-octet ESI value + + This type indicates an arbitrary 9-octet ESI value, + which is managed and configured by the operator. + """ + _VALUE_PACK_STR = '!9s' + _VALUE_FIELDS = ['value'] + + def __init__(self, value, type_=None): + super(EvpnArbitraryEsi, self).__init__(type_) + self.value = value + + +@EvpnEsi.register_type(EvpnEsi.LACP) +class EvpnLACPEsi(EvpnEsi): + """ + ESI value for LACP + + When IEEE 802.1AX LACP is used between the PEs and CEs, + this ESI type indicates an auto-generated ESI value + determined from LACP. + """ + _VALUE_PACK_STR = '!6sHx' + _VALUE_FIELDS = ['mac_addr', 'port_key'] + _TYPE = { + 'ascii': [ + 'mac_addr' + ] + } + + def __init__(self, mac_addr, port_key, type_=None): + super(EvpnLACPEsi, self).__init__(type_) + self.mac_addr = mac_addr + self.port_key = port_key + + @classmethod + def parse_value(cls, buf): + (mac_addr, port_key) = struct.unpack_from(cls._VALUE_PACK_STR, buf) + return { + 'mac_addr': addrconv.mac.bin_to_text(mac_addr), + 'port_key': port_key, + } + + def serialize_value(self): + return struct.pack( + self._VALUE_PACK_STR, + addrconv.mac.text_to_bin(self.mac_addr), self.port_key) + + +@EvpnEsi.register_type(EvpnEsi.L2_BRIDGE) +class EvpnL2BridgeEsi(EvpnEsi): + """ + ESI value for Layer 2 Bridge + + This type is used in the case of indirectly connected hosts + via a bridged LAN between the CEs and the PEs. + The ESI Value is auto-generated and determined based + on the Layer 2 bridge protocol. + """ + _VALUE_PACK_STR = '!6sHx' + _VALUE_FIELDS = ['mac_addr', 'priority'] + _TYPE = { + 'ascii': [ + 'mac_addr' + ] + } + + def __init__(self, mac_addr, priority, type_=None): + super(EvpnL2BridgeEsi, self).__init__(type_) + self.mac_addr = mac_addr + self.priority = priority + + @classmethod + def parse_value(cls, buf): + (mac_addr, priority) = struct.unpack_from(cls._VALUE_PACK_STR, buf) + return { + 'mac_addr': addrconv.mac.bin_to_text(mac_addr), + 'priority': priority, + } + + def serialize_value(self): + return struct.pack( + self._VALUE_PACK_STR, + addrconv.mac.text_to_bin(self.mac_addr), self.priority) + + +@EvpnEsi.register_type(EvpnEsi.MAC_BASED) +class EvpnMacBasedEsi(EvpnEsi): + """ + MAC-based ESI Value + + This type indicates a MAC-based ESI Value that + can be auto-generated or configured by the operator. + """ + _VALUE_PACK_STR = '!6s3s' + _VALUE_FIELDS = ['mac_addr', 'local_disc'] + _TYPE = { + 'ascii': [ + 'mac_addr' + ] + } + + def __init__(self, mac_addr, local_disc, type_=None): + super(EvpnMacBasedEsi, self).__init__(type_) + self.mac_addr = mac_addr + self.local_disc = local_disc + + @classmethod + def parse_value(cls, buf): + (mac_addr, local_disc) = struct.unpack_from(cls._VALUE_PACK_STR, buf) + return { + 'mac_addr': addrconv.mac.bin_to_text(mac_addr), + 'local_disc': type_desc.Int3.to_user(local_disc), + } + + def serialize_value(self): + return struct.pack( + self._VALUE_PACK_STR, + addrconv.mac.text_to_bin(self.mac_addr), + type_desc.Int3.from_user(self.local_disc)) + + +@EvpnEsi.register_type(EvpnEsi.ROUTER_ID) +class EvpnRouterIDEsi(EvpnEsi): + """ + Router-ID ESI Value + + This type indicates a router-ID ESI Value that + can be auto-generated or configured by the operator. + """ + _VALUE_PACK_STR = '!4sIx' + _VALUE_FIELDS = ['router_id', 'local_disc'] + _TYPE = { + 'ascii': [ + 'router_id' + ] + } + + def __init__(self, router_id, local_disc, type_=None): + super(EvpnRouterIDEsi, self).__init__(type_) + self.router_id = router_id + self.local_disc = local_disc + + @classmethod + def parse_value(cls, buf): + (router_id, local_disc) = struct.unpack_from(cls._VALUE_PACK_STR, buf) + return { + 'router_id': addrconv.ipv4.bin_to_text(router_id), + 'local_disc': local_disc, + } + + def serialize_value(self): + return struct.pack( + self._VALUE_PACK_STR, + addrconv.ipv4.text_to_bin(self.router_id), self.local_disc) + + +@EvpnEsi.register_type(EvpnEsi.AS_BASED) +class EvpnASBasedEsi(EvpnEsi): + """ + AS based ESI value + + This type indicates an Autonomous System(AS)-based + ESI Value that can be auto-generated or configured by + the operator. + """ + _VALUE_PACK_STR = '!IIx' + _VALUE_FIELDS = ['as_number', 'local_disc'] + + def __init__(self, as_number, local_disc, type_=None): + super(EvpnASBasedEsi, self).__init__(type_) + self.as_number = as_number + self.local_disc = local_disc + + +class EvpnNLRI(StringifyMixin, _TypeDisp): + """ + BGP Network Layer Reachability Information (NLRI) for EVPN + """ + ROUTE_FAMILY = RF_L2_EVPN + + # EVPN NLRI: + # +-----------------------------------+ + # | Route Type (1 octet) | + # +-----------------------------------+ + # | Length (1 octet) | + # +-----------------------------------+ + # | Route Type specific (variable) | + # +-----------------------------------+ + _PACK_STR = "!BB" + _PACK_STR_SIZE = struct.calcsize(_PACK_STR) + + ETHERNET_AUTO_DISCOVERY = 0x01 + MAC_IP_ADVERTISEMENT = 0x02 + INCLUSIVE_MULTICAST_ETHERNET_TAG = 0x03 + ETHERNET_SEGMENT = 0x04 + + def __init__(self, type_=None, length=None): + if type_ is None: + type_ = self._rev_lookup_type(self.__class__) + self.type = type_ + self.length = length + self.route_dist = None # should be initialized in subclass + + @classmethod + def parser(cls, buf): + (route_type, length) = struct.unpack_from( + cls._PACK_STR, six.binary_type(buf)) + offset = cls._PACK_STR_SIZE + length + subcls = cls._lookup_type(route_type) + values = subcls.parse_value(buf[cls._PACK_STR_SIZE:offset]) + return subcls(type_=route_type, length=length, + **values), buf[offset:] + + def serialize_value(self): + # Overrided in subclass + return b'' + + def serialize(self): + value_bin = self.serialize_value() + # fixup + self.length = len(value_bin) + return struct.pack(EvpnNLRI._PACK_STR, + self.type, self.length) + value_bin + + @staticmethod + def _rd_from_bin(buf): + return _RouteDistinguisher.parser(buf[:8]), buf[8:] + + @staticmethod + def _rd_to_bin(rd): + return six.binary_type(rd.serialize()) + + @staticmethod + def _esi_from_bin(buf): + return EvpnEsi.parser(buf[:10]), buf[10:] + + @staticmethod + def _esi_to_bin(esi): + return esi.serialize() + + @staticmethod + def _ethernet_tag_id_from_bin(buf): + return type_desc.Int4.to_user(six.binary_type(buf[:4])), buf[4:] + + @staticmethod + def _ethernet_tag_id_to_bin(tag_id): + return type_desc.Int4.from_user(tag_id) + + @staticmethod + def _mac_addr_len_from_bin(buf): + return type_desc.Int1.to_user(six.binary_type(buf[:1])), buf[1:] + + @staticmethod + def _mac_addr_len_to_bin(mac_len): + return type_desc.Int1.from_user(mac_len) + + @staticmethod + def _mac_addr_from_bin(buf, mac_len): + mac_len //= 8 + return addrconv.mac.bin_to_text(buf[:mac_len]), buf[mac_len:] + + @staticmethod + def _mac_addr_to_bin(mac_addr): + return addrconv.mac.text_to_bin(mac_addr) + + @staticmethod + def _ip_addr_len_from_bin(buf): + return type_desc.Int1.to_user(six.binary_type(buf[:1])), buf[1:] + + @staticmethod + def _ip_addr_len_to_bin(ip_len): + return type_desc.Int1.from_user(ip_len) + + @staticmethod + def _ip_addr_from_bin(buf, ip_len): + _len = ip_len // 8 + if _len == 4: + return addrconv.ipv4.bin_to_text(buf[:_len]), buf[_len:] + elif _len == 16: + return addrconv.ipv6.bin_to_text(buf[:_len]), buf[_len:] + else: + raise struct.error('Invalid ip address length: %s' % ip_len) + + @staticmethod + def _ip_addr_to_bin(ip_addr): + if '.' in ip_addr: + # IPv4 address + return addrconv.ipv4.text_to_bin(ip_addr) + else: + # IPv6 address + return addrconv.ipv6.text_to_bin(ip_addr) + + @staticmethod + def _mpls_label_from_bin(buf): + mpls_label, rest = _LabelledAddrPrefix._label_from_bin(buf) + if mpls_label & 1: + is_stack = False + else: + is_stack = True + return mpls_label >> 4, rest, is_stack + + @staticmethod + def _mpls_label_to_bin(label, is_stack=False): + if is_stack: + label = label << 4 | 0 + else: + label = label << 4 | 1 + return six.binary_type(_LabelledAddrPrefix._label_to_bin(label)) + + +@EvpnNLRI.register_unknown_type() +class EvpnUnknownNLRI(EvpnNLRI): + """ + Unknown route type specific EVPN NLRI + """ + + def __init__(self, value, type_, length=None): + super(EvpnUnknownNLRI, self).__init__(type_, length) + self.value = value + + @classmethod + def parse_value(cls, buf): + return { + 'value': buf + } + + def serialize_value(self): + return self.value + + +@EvpnNLRI.register_type(EvpnNLRI.ETHERNET_AUTO_DISCOVERY) +class EvpnEthernetAutoDiscoveryNLRI(EvpnNLRI): + """ + Ethernet A-D route type specific EVPN NLRI + """ + + # +---------------------------------------+ + # | Route Distinguisher (RD) (8 octets) | + # +---------------------------------------+ + # |Ethernet Segment Identifier (10 octets)| + # +---------------------------------------+ + # | Ethernet Tag ID (4 octets) | + # +---------------------------------------+ + # | MPLS Label (3 octets) | + # +---------------------------------------+ + _PACK_STR = "!8s10sI3s" + + def __init__(self, route_dist, esi, ethernet_tag_id, mpls_label, + 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 + + @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, + } + + 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)) + + +@EvpnNLRI.register_type(EvpnNLRI.MAC_IP_ADVERTISEMENT) +class EvpnMacIPAdvertisementNLRI(EvpnNLRI): + """ + MAC/IP Advertisement route type specific EVPN NLRI + """ + + # +---------------------------------------+ + # | RD (8 octets) | + # +---------------------------------------+ + # |Ethernet Segment Identifier (10 octets)| + # +---------------------------------------+ + # | Ethernet Tag ID (4 octets) | + # +---------------------------------------+ + # | MAC Address Length (1 octet) | + # +---------------------------------------+ + # | MAC Address (6 octets) | + # +---------------------------------------+ + # | IP Address Length (1 octet) | + # +---------------------------------------+ + # | IP Address (0, 4, or 16 octets) | + # +---------------------------------------+ + # | MPLS Label1 (3 octets) | + # +---------------------------------------+ + # | MPLS Label2 (0 or 3 octets) | + # +---------------------------------------+ + _PACK_STR = "!8s10sIB6sB%ds3s%ds" + _TYPE = { + 'ascii': [ + '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, + type_=None, length=None): + super(EvpnMacIPAdvertisementNLRI, self).__init__(type_, length) + self.route_dist = route_dist + self.esi = esi + self.ethernet_tag_id = ethernet_tag_id + self.mac_addr_len = mac_addr_len + self.mac_addr = mac_addr + self.ip_addr_len = ip_addr_len + self.ip_addr = ip_addr + self.mpls_labels = mpls_labels + + @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) + mac_addr_len, rest = cls._mac_addr_len_from_bin(rest) + mac_addr, rest = cls._mac_addr_from_bin(rest, mac_addr_len) + ip_addr_len, rest = cls._ip_addr_len_from_bin(rest) + if ip_addr_len != 0: + 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, + 'esi': esi, + 'ethernet_tag_id': ethernet_tag_id, + 'mac_addr_len': mac_addr_len, + 'mac_addr': mac_addr, + 'ip_addr_len': ip_addr_len, + 'ip_addr': ip_addr, + 'mpls_labels': mpls_labels, + } + + def serialize_value(self): + route_dist = _RouteDistinguisher.from_str(self.route_dist) + mac_addr = self._mac_addr_to_bin(self.mac_addr) + self.mac_addr_len = len(mac_addr) * 8 # fixup + if self.ip_addr: + ip_addr = self._ip_addr_to_bin(self.ip_addr) + else: + 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)), + 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) + + +@EvpnNLRI.register_type(EvpnNLRI.INCLUSIVE_MULTICAST_ETHERNET_TAG) +class EvpnInclusiveMulticastEthernetTagNLRI(EvpnNLRI): + """ + Inclusive Multicast Ethernet Tag route type specific EVPN NLRI + """ + + # +---------------------------------------+ + # | RD (8 octets) | + # +---------------------------------------+ + # | Ethernet Tag ID (4 octets) | + # +---------------------------------------+ + # | IP Address Length (1 octet) | + # +---------------------------------------+ + # | Originating Router's IP Address | + # | (4 or 16 octets) | + # +---------------------------------------+ + _PACK_STR = '!8sIB%ds' + _TYPE = { + 'ascii': [ + 'ip_addr' + ] + } + + def __init__(self, route_dist, ethernet_tag_id, ip_addr, + ip_addr_len=None, type_=None, length=None): + super(EvpnInclusiveMulticastEthernetTagNLRI, + self).__init__(type_, length) + self.route_dist = route_dist + self.ethernet_tag_id = ethernet_tag_id + self.ip_addr_len = ip_addr_len + self.ip_addr = ip_addr + + @classmethod + def parse_value(cls, buf): + route_dist, rest = cls._rd_from_bin(buf) + ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest) + ip_addr_len, rest = cls._ip_addr_len_from_bin(rest) + ip_addr, rest = cls._ip_addr_from_bin(rest, ip_addr_len) + + return { + 'route_dist': route_dist.formatted_str, + 'ethernet_tag_id': ethernet_tag_id, + 'ip_addr_len': ip_addr_len, + 'ip_addr': ip_addr, + } + + def serialize_value(self): + route_dist = _RouteDistinguisher.from_str(self.route_dist) + ip_addr = self._ip_addr_to_bin(self.ip_addr) + self.ip_addr_len = len(ip_addr) * 8 # fixup + + return struct.pack( + self._PACK_STR % len(ip_addr), + route_dist.serialize(), self.ethernet_tag_id, + self.ip_addr_len, ip_addr) + + +@EvpnNLRI.register_type(EvpnNLRI.ETHERNET_SEGMENT) +class EvpnEthernetSegmentNLRI(EvpnNLRI): + """ + Ethernet Segment route type specific EVPN NLRI + """ + + # +---------------------------------------+ + # | RD (8 octets) | + # +---------------------------------------+ + # |Ethernet Segment Identifier (10 octets)| + # +---------------------------------------+ + # | IP Address Length (1 octet) | + # +---------------------------------------+ + # | Originating Router's IP Address | + # | (4 or 16 octets) | + # +---------------------------------------+ + _PACK_STR = '!8s10sB%ds' + _TYPE = { + 'ascii': [ + 'ip_addr' + ] + } + + def __init__(self, route_dist, esi, ip_addr, ip_addr_len=None, + type_=None, length=None): + super(EvpnEthernetSegmentNLRI, self).__init__(type_, length) + self.route_dist = route_dist + self.esi = esi + self.ip_addr_len = ip_addr_len + self.ip_addr = ip_addr + + @classmethod + def parse_value(cls, buf): + route_dist, rest = cls._rd_from_bin(buf) + esi, rest = cls._esi_from_bin(rest) + ip_addr_len, rest = cls._ip_addr_len_from_bin(rest) + ip_addr, rest = cls._ip_addr_from_bin(rest, ip_addr_len) + + return { + 'route_dist': route_dist.formatted_str, + 'esi': esi, + 'ip_addr_len': ip_addr_len, + 'ip_addr': ip_addr, + } + + def serialize_value(self): + route_dist = _RouteDistinguisher.from_str(self.route_dist) + ip_addr = self._ip_addr_to_bin(self.ip_addr) + # fixup + self.ip_addr_len = len(ip_addr) * 8 + + return struct.pack( + self._PACK_STR % len(ip_addr), + route_dist.serialize(), self.esi.serialize(), + self.ip_addr_len, ip_addr) + + @functools.total_ordering class RouteTargetMembershipNLRI(StringifyMixin): """Route Target Membership NLRI. @@ -1139,6 +1785,7 @@ _ADDR_CLASSES = { _addr_class_key(RF_IPv6_MPLS): LabelledIP6AddrPrefix, _addr_class_key(RF_IPv4_VPN): LabelledVPNIPAddrPrefix, _addr_class_key(RF_IPv6_VPN): LabelledVPNIP6AddrPrefix, + _addr_class_key(RF_L2_EVPN): EvpnNLRI, _addr_class_key(RF_RTC_UC): RouteTargetMembershipNLRI, } @@ -1870,6 +2517,7 @@ class BGPPathAttributeClusterList(_PathAttribute): # 00 03 Route Origin Community (two-octet AS specific) # 01 03 Route Origin Community (IPv4 address specific) # 02 03 Route Origin Community (four-octet AS specific, RFC 5668) +# 06 sub-type Ethernet VPN Extended Community (RFC 7432) @_PathAttribute.register_type(BGP_ATTR_TYPE_EXTENDED_COMMUNITIES) class BGPPathAttributeExtendedCommunities(_PathAttribute): @@ -1923,7 +2571,9 @@ class BGPPathAttributeExtendedCommunities(_PathAttribute): class _ExtendedCommunity(StringifyMixin, _TypeDisp, _Value): - _PACK_STR = '!B7s' # type high (+ type low) + value + _PACK_STR = '!B7s' # type high (+ type low), value + _PACK_STR_SIZE = struct.calcsize(_PACK_STR) + _SUBTYPE_PACK_STR = '!B' # subtype IANA_AUTHORITY = 0x80 TRANSITIVE = 0x40 _TYPE_HIGH_MASK = ~TRANSITIVE @@ -1932,27 +2582,39 @@ class _ExtendedCommunity(StringifyMixin, _TypeDisp, _Value): IPV4_ADDRESS_SPECIFIC = 0x01 FOUR_OCTET_AS_SPECIFIC = 0x02 OPAQUE = 0x03 + EVPN = 0x06 + EVPN_MAC_MOBILITY = (EVPN, 0x00) + EVPN_ESI_LABEL = (EVPN, 0x01) + EVPN_ES_IMPORT_RT = (EVPN, 0x02) def __init__(self, type_=None): if type_ is None: type_ = self._rev_lookup_type(self.__class__) + if isinstance(type_, (tuple, list)): + type_ = type_[0] self.type = type_ @classmethod + def parse_subtype(cls, buf): + (subtype,) = struct.unpack_from(cls._SUBTYPE_PACK_STR, buf) + return subtype + + @classmethod def parse(cls, buf): - (type_high, payload) = struct.unpack_from(cls._PACK_STR, - six.binary_type(buf)) - rest = buf[struct.calcsize(cls._PACK_STR):] - type_ = type_high & cls._TYPE_HIGH_MASK - subcls = cls._lookup_type(type_) - return subcls(type_=type_high, - **subcls.parse_value(payload)), rest + (type_, value) = struct.unpack_from(cls._PACK_STR, buf) + rest = buf[cls._PACK_STR_SIZE:] + type_low = type_ & cls._TYPE_HIGH_MASK + if type_low == cls.EVPN: + subtype = cls.parse_subtype(value) + subcls = cls._lookup_type((type_low, subtype)) + else: + subcls = cls._lookup_type(type_low) + + return subcls(type_=type_, **subcls.parse_value(value)), rest def serialize(self): - buf = bytearray() - msg_pack_into(self._PACK_STR, buf, 0, self.type, - bytes(self.serialize_value())) - return buf + return struct.pack(self._PACK_STR, self.type, + self.serialize_value()) @_ExtendedCommunity.register_type(_ExtendedCommunity.TWO_OCTET_AS_SPECIFIC) @@ -1987,15 +2649,9 @@ class BGPIPv4AddressSpecificExtendedCommunity(_ExtendedCommunity): return d_ def serialize_value(self): - args = [] - for f in self._VALUE_FIELDS: - v = getattr(self, f) - if f == 'ipv4_address': - v = bytes(addrconv.ipv4.text_to_bin(v)) - args.append(v) - buf = bytearray() - msg_pack_into(self._VALUE_PACK_STR, buf, 0, *args) - return buf + return struct.pack(self._VALUE_PACK_STR, self.subtype, + addrconv.ipv4.text_to_bin(self.ipv4_address), + self.local_administrator) @_ExtendedCommunity.register_type(_ExtendedCommunity.FOUR_OCTET_AS_SPECIFIC) @@ -2010,14 +2666,102 @@ class BGPFourOctetAsSpecificExtendedCommunity(_ExtendedCommunity): @_ExtendedCommunity.register_type(_ExtendedCommunity.OPAQUE) class BGPOpaqueExtendedCommunity(_ExtendedCommunity): - _VALUE_PACK_STR = '!7s' # opaque value - _VALUE_FIELDS = ['opaque'] + _VALUE_PACK_STR = '!B6s' + _VALUE_FIELDS = ['subtype', 'opaque'] def __init__(self, **kwargs): super(BGPOpaqueExtendedCommunity, self).__init__() self.do_init(BGPOpaqueExtendedCommunity, self, kwargs) +@_ExtendedCommunity.register_type(_ExtendedCommunity.EVPN_MAC_MOBILITY) +class BGPEvpnMacMobilityExtendedCommunity(_ExtendedCommunity): + """ + MAC Mobility Extended Community + """ + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type=0x06 | Sub-Type=0x00 |Flags(1 octet)| Reserved=0 | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Sequence Number | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + _VALUE_PACK_STR = '!BBxI' + _VALUE_FIELDS = ['subtype', 'flags', 'sequence_number'] + + def __init__(self, **kwargs): + super(BGPEvpnMacMobilityExtendedCommunity, self).__init__() + self.do_init(BGPEvpnMacMobilityExtendedCommunity, self, kwargs) + + +@_ExtendedCommunity.register_type(_ExtendedCommunity.EVPN_ESI_LABEL) +class BGPEvpnEsiLabelExtendedCommunity(_ExtendedCommunity): + """ + ESI Label Extended Community + """ + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type=0x06 | Sub-Type=0x01 | Flags(1 octet)| Reserved=0 | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Reserved=0 | ESI Label | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + _VALUE_PACK_STR = '!BB2x3s' + _VALUE_FIELDS = ['subtype', 'flags', 'esi_label'] + + def __init__(self, **kwargs): + super(BGPEvpnEsiLabelExtendedCommunity, self).__init__() + self.do_init(BGPEvpnEsiLabelExtendedCommunity, self, kwargs) + + @classmethod + def parse_value(cls, buf): + (subtype, flags, + esi_label) = struct.unpack_from(cls._VALUE_PACK_STR, buf) + return { + 'subtype': subtype, + 'flags': flags, + 'esi_label': type_desc.Int3.to_user(esi_label), + } + + def serialize_value(self): + return struct.pack(self._VALUE_PACK_STR, self.subtype, self.flags, + type_desc.Int3.from_user(self.esi_label)) + + +@_ExtendedCommunity.register_type(_ExtendedCommunity.EVPN_ES_IMPORT_RT) +class BGPEvpnEsImportRTExtendedCommunity(_ExtendedCommunity): + """ + ES-Import Route Target Extended Community + """ + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type=0x06 | Sub-Type=0x02 | ES-Import | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | ES-Import Cont'd | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + _VALUE_PACK_STR = '!B6s' + _VALUE_FIELDS = ['subtype', 'es_import'] + _TYPE = { + 'ascii': [ + 'es_import' + ] + } + + def __init__(self, **kwargs): + super(BGPEvpnEsImportRTExtendedCommunity, self).__init__() + self.do_init(BGPEvpnEsImportRTExtendedCommunity, self, kwargs) + + @classmethod + def parse_value(cls, buf): + (subtype, es_import) = struct.unpack_from(cls._VALUE_PACK_STR, buf) + return { + 'subtype': subtype, + 'es_import': addrconv.mac.bin_to_text(es_import), + } + + def serialize_value(self): + return struct.pack(self._VALUE_PACK_STR, self.subtype, + addrconv.mac.text_to_bin(self.es_import)) + + @_ExtendedCommunity.register_unknown_type() class BGPUnknownExtendedCommunity(_ExtendedCommunity): _VALUE_PACK_STR = '!7s' # opaque value @@ -2049,7 +2793,7 @@ class BGPPathAttributeMpReachNLRI(_PathAttribute): self.safi = safi self.next_hop_len = next_hop_len self.next_hop = next_hop - if afi == addr_family.IP: + if afi == addr_family.IP or afi == addr_family.L2VPN: self._next_hop_bin = addrconv.ipv4.text_to_bin(next_hop) elif afi == addr_family.IP6: self._next_hop_bin = addrconv.ipv6.text_to_bin(next_hop) @@ -2084,9 +2828,9 @@ class BGPPathAttributeMpReachNLRI(_PathAttribute): elif rf == RF_IPv4_VPN: next_hop = addrconv.ipv4.bin_to_text(next_hop_bin[cls._rd_length:]) next_hop_len -= cls._rd_length - elif afi == addr_family.IP: + elif afi == addr_family.IP or (rf == RF_L2_EVPN and next_hop_len == 4): next_hop = addrconv.ipv4.bin_to_text(next_hop_bin) - elif afi == addr_family.IP6: + elif afi == addr_family.IP6 or (rf == RF_L2_EVPN and next_hop_len > 4): # next_hop_bin can include global address and link-local address # according to RFC2545. Since a link-local address isn't needed in # Ryu BGPSpeaker, we ignore it if both addresses were sent. diff --git a/ryu/lib/packet/safi.py b/ryu/lib/packet/safi.py index 17ca138c..973d3c7b 100644 --- a/ryu/lib/packet/safi.py +++ b/ryu/lib/packet/safi.py @@ -22,5 +22,6 @@ http://www.iana.org/assignments/safi-namespace/safi-namespace.xhtml UNICAST = 1 MULTICAST = 2 MPLS_LABEL = 4 # RFC 3107 +EVPN = 70 # RFC 7432 MPLS_VPN = 128 # RFC 4364 ROUTE_TARGET_CONSTRAINTS = 132 # RFC 4684 |