summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorIWASE Yusuke <iwase.yusuke0@gmail.com>2016-08-22 17:21:24 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2016-08-25 13:32:42 +0900
commitc4dac34bad59dd90a2dad63b22cb5631a1fefa5e (patch)
tree31dc332ace2735f51938ca631aa25b986f62defd
parent0255390a76479e4ecb3836dd33828fa1d59f763e (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.py1
-rw-r--r--ryu/lib/packet/bgp.py806
-rw-r--r--ryu/lib/packet/safi.py1
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