summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorIWASE Yusuke <iwase.yusuke0@gmail.com>2016-11-25 17:02:55 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2016-12-10 22:00:34 +0900
commitb3804a872748bf4ad874f387aecc953e3c857b5c (patch)
tree04f7657432d8debc32010ff1b4ae8480f616d30e
parenta6dc0bc8a7b8f16775d56500f1ffc677ebd0c04d (diff)
mrtlib: Add parser for MRT format [RFC6396]
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/mrtlib.py1224
1 files changed, 1224 insertions, 0 deletions
diff --git a/ryu/lib/mrtlib.py b/ryu/lib/mrtlib.py
new file mode 100644
index 00000000..92a9b979
--- /dev/null
+++ b/ryu/lib/mrtlib.py
@@ -0,0 +1,1224 @@
+# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Library for reading/writing MRT (Multi-Threaded Routing Toolkit) Routing
+Information Export Format [RFC6396].
+"""
+
+import abc
+import logging
+import struct
+import time
+
+import netaddr
+import six
+
+from ryu.lib import addrconv
+from ryu.lib import ip
+from ryu.lib import stringify
+from ryu.lib import type_desc
+from ryu.lib.packet import bgp
+from ryu.lib.packet import ospf
+
+
+LOG = logging.getLogger(__name__)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class MrtRecord(stringify.StringifyMixin, type_desc.TypeDisp):
+ """
+ MRT record.
+ """
+ _HEADER_FMT = '!IHHI' # the same as MRT Common Header
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+ MESSAGE_CLS = None # parser class for message field
+
+ # MRT Types
+ TYPE_OSPFv2 = 11
+ TYPE_TABLE_DUMP = 12
+ TYPE_TABLE_DUMP_V2 = 13
+ TYPE_BGP4MP = 16
+ TYPE_BGP4MP_ET = 17
+ TYPE_ISIS = 32
+ TYPE_ISIS_ET = 33
+ TYPE_OSPFv3 = 48
+ TYPE_OSPFv3_ET = 49
+
+ # List of MRT type using Extended Timestamp MRT Header
+ _EXT_TS_TYPES = [TYPE_BGP4MP_ET, TYPE_ISIS_ET, TYPE_OSPFv3_ET]
+
+ def __init__(self, message, timestamp=None, type_=None, subtype=None,
+ length=None):
+ assert issubclass(message.__class__, MrtMessage)
+ self.message = message
+ self.timestamp = timestamp
+ if type_ is None:
+ type_ = self._rev_lookup_type(self.__class__)
+ self.type = type_
+ if subtype is None:
+ subtype = self.MESSAGE_CLS._rev_lookup_type(message.__class__)
+ self.subtype = subtype
+ self.length = length
+
+ @classmethod
+ def parse_common_header(cls, buf):
+ header_fields = struct.unpack_from(
+ cls._HEADER_FMT, buf)
+
+ return list(header_fields), buf[cls.HEADER_SIZE:]
+
+ @classmethod
+ def parse_extended_header(cls, buf):
+ # If extended header field exist, override this in subclass.
+ return [], buf
+
+ @classmethod
+ def parse_pre(cls, buf):
+ buf = six.binary_type(buf) # for convenience
+
+ header_fields, _ = cls.parse_common_header(buf)
+ # timestamp = header_fields[0]
+ type_ = header_fields[1]
+ # subtype = header_fields[2]
+ length = header_fields[3]
+ if type_ in cls._EXT_TS_TYPES:
+ header_cls = ExtendedTimestampMrtRecord
+ else:
+ header_cls = MrtCommonRecord
+
+ required_len = header_cls.HEADER_SIZE + length
+
+ return required_len
+
+ @classmethod
+ def parse(cls, buf):
+ buf = six.binary_type(buf) # for convenience
+
+ header_fields, rest = cls.parse_common_header(buf)
+ # timestamp = header_fields[0]
+ type_ = header_fields[1]
+ subtype = header_fields[2]
+ length = header_fields[3]
+
+ sub_cls = MrtRecord._lookup_type(type_)
+ extended_headers, rest = sub_cls.parse_extended_header(rest)
+ header_fields.extend(extended_headers)
+
+ msg_cls = sub_cls.MESSAGE_CLS._lookup_type(subtype)
+ message_bin = rest[:length]
+ message = msg_cls.parse(message_bin)
+
+ return sub_cls(message, *header_fields), rest[length:]
+
+ @abc.abstractmethod
+ def serialize_header(self):
+ pass
+
+ def serialize(self):
+ if self.timestamp is None:
+ self.timestamp = int(time.time())
+
+ buf = self.message.serialize()
+
+ self.length = len(buf) # fixup
+
+ return self.serialize_header() + buf
+
+
+class MrtCommonRecord(MrtRecord):
+ """
+ MRT record using MRT Common Header.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Timestamp |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Type | Subtype |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Length |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Message... (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!IHHI'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+
+ def serialize_header(self):
+ return struct.pack(self._HEADER_FMT,
+ self.timestamp,
+ self.type, self.subtype,
+ self.length)
+
+
+class ExtendedTimestampMrtRecord(MrtRecord):
+ """
+ MRT record using Extended Timestamp MRT Header.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Timestamp |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Type | Subtype |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Length |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Microsecond Timestamp |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Message... (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!IHHII'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+ _EXT_HEADER_FMT = '!I'
+ EXT_HEADER_SIZE = struct.calcsize(_EXT_HEADER_FMT)
+
+ def __init__(self, message, timestamp=None, type_=None, subtype=None,
+ ms_timestamp=None, length=None):
+ super(ExtendedTimestampMrtRecord, self).__init__(
+ message, timestamp, type_, subtype, length)
+ self.ms_timestamp = ms_timestamp
+
+ @classmethod
+ def parse_extended_header(cls, buf):
+ (ms_timestamp,) = struct.unpack_from(cls._EXT_HEADER_FMT, buf)
+
+ return [ms_timestamp], buf[cls.EXT_HEADER_SIZE:]
+
+ def serialize_header(self):
+ return struct.pack(self._HEADER_FMT,
+ self.timestamp,
+ self.type, self.subtype,
+ self.length,
+ self.ms_timestamp)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class MrtMessage(stringify.StringifyMixin, type_desc.TypeDisp):
+ """
+ MRT Message in record.
+ """
+
+ @classmethod
+ @abc.abstractmethod
+ def parse(cls, buf):
+ pass
+
+ @abc.abstractmethod
+ def serialize(self):
+ pass
+
+
+class UnknownMrtMessage(MrtMessage):
+ """
+ MRT Message for the UNKNOWN Type.
+ """
+
+ def __init__(self, buf):
+ self.buf = buf
+
+ @classmethod
+ def parse(cls, buf):
+ return cls(buf)
+
+ def serialize(self):
+ return self.buf
+
+# Registers self to unknown(default) type
+UnknownMrtMessage._UNKNOWN_TYPE = UnknownMrtMessage
+
+
+@MrtRecord.register_unknown_type()
+class UnknownMrtRecord(MrtCommonRecord):
+ """
+ MRT record for the UNKNOWN Type.
+ """
+ MESSAGE_CLS = UnknownMrtMessage
+
+
+class Ospf2MrtMessage(MrtMessage):
+ """
+ MRT Message for the OSPFv2 Type.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Remote IP Address |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Local IP Address |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | OSPF Message Contents (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!4s4s'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+
+ def __init__(self, remote_ip, local_ip, ospf_message):
+ self.remote_ip = remote_ip
+ self.local_ip = local_ip
+ assert isinstance(ospf_message, ospf.OSPFMessage)
+ self.ospf_message = ospf_message
+
+ @classmethod
+ def parse(cls, buf):
+ (remote_ip, local_ip) = struct.unpack_from(cls._HEADER_FMT, buf)
+ remote_ip = addrconv.ipv4.bin_to_text(remote_ip)
+ local_ip = addrconv.ipv4.bin_to_text(local_ip)
+ ospf_message, _, _ = ospf.OSPFMessage.parser(buf[cls.HEADER_SIZE:])
+
+ return cls(remote_ip, local_ip, ospf_message)
+
+ def serialize(self):
+ return (addrconv.ipv4.text_to_bin(self.remote_ip)
+ + addrconv.ipv4.text_to_bin(self.local_ip)
+ + self.ospf_message.serialize())
+
+
+@MrtRecord.register_type(MrtRecord.TYPE_OSPFv2)
+class Ospf2MrtRecord(MrtCommonRecord):
+ """
+ MRT Record for the OSPFv2 Type.
+ """
+ MESSAGE_CLS = Ospf2MrtMessage
+
+ def __init__(self, message, timestamp=None, type_=None, subtype=0,
+ length=None):
+ super(Ospf2MrtRecord, self).__init__(
+ message=message, timestamp=timestamp, type_=type_,
+ subtype=subtype, length=length)
+
+# Registers self to unknown(default) type
+Ospf2MrtMessage._UNKNOWN_TYPE = Ospf2MrtMessage
+
+
+@six.add_metaclass(abc.ABCMeta)
+class TableDumpMrtMessage(MrtMessage):
+ """
+ MRT Message for the TABLE_DUMP Type.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | View Number | Sequence Number |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Prefix (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Prefix Length | Status |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Originated Time |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer IP Address (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer AS | Attribute Length |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | BGP Attribute... (variable)
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '' # should be defined in subclass
+ HEADER_SIZE = 0
+
+ def __init__(self, view_num, seq_num, prefix, prefix_len, status,
+ originated_time, peer_ip, peer_as, bgp_attributes,
+ attr_len=None):
+ self.view_num = view_num
+ self.seq_num = seq_num
+ self.prefix = prefix
+ self.prefix_len = prefix_len
+ # Status in the TABLE_DUMP Type SHOULD be set to 1
+ assert status == 1
+ self.status = status
+ self.originated_time = originated_time
+ self.peer_ip = peer_ip
+ self.peer_as = peer_as
+ self.attr_len = attr_len
+ assert isinstance(bgp_attributes, (list, tuple))
+ for attr in bgp_attributes:
+ assert isinstance(attr, bgp._PathAttribute)
+ self.bgp_attributes = bgp_attributes
+
+ @classmethod
+ def parse(cls, buf):
+ (view_num, seq_num, prefix, prefix_len, status, originated_time,
+ peer_ip, peer_as, attr_len) = struct.unpack_from(cls._HEADER_FMT, buf)
+ prefix = ip.bin_to_text(prefix)
+ peer_ip = ip.bin_to_text(peer_ip)
+
+ bgp_attr_bin = buf[cls.HEADER_SIZE:cls.HEADER_SIZE + attr_len]
+ bgp_attributes = []
+ while bgp_attr_bin:
+ attr, bgp_attr_bin = bgp._PathAttribute.parser(bgp_attr_bin)
+ bgp_attributes.append(attr)
+
+ return cls(view_num, seq_num, prefix, prefix_len, status,
+ originated_time, peer_ip, peer_as, bgp_attributes,
+ attr_len)
+
+ def serialize(self):
+ bgp_attrs_bin = bytearray()
+ for attr in self.bgp_attributes:
+ bgp_attrs_bin += attr.serialize()
+ self.attr_len = len(bgp_attrs_bin) # fixup
+
+ prefix = ip.text_to_bin(self.prefix)
+ peer_ip = ip.text_to_bin(self.peer_ip)
+
+ return struct.pack(self._HEADER_FMT,
+ self.view_num, self.seq_num,
+ prefix,
+ self.prefix_len, self.status,
+ self.originated_time,
+ peer_ip,
+ self.peer_as, self.attr_len) + bgp_attrs_bin
+
+
+@MrtRecord.register_type(MrtRecord.TYPE_TABLE_DUMP)
+class TableDumpMrtRecord(MrtCommonRecord):
+ """
+ MRT Record for the TABLE_DUMP Type.
+ """
+ MESSAGE_CLS = TableDumpMrtMessage
+
+ # MRT Subtype
+ SUBTYPE_AFI_IPv4 = 1
+ SUBTYPE_AFI_IPv6 = 2
+
+
+@TableDumpMrtMessage.register_type(TableDumpMrtRecord.SUBTYPE_AFI_IPv4)
+class TableDumpAfiIPv4MrtMessage(TableDumpMrtMessage):
+ """
+ MRT Message for the TABLE_DUMP Type and the AFI_IPv4 subtype.
+ """
+ _HEADER_FMT = '!HH4sBBI4sHH'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+
+
+@TableDumpMrtMessage.register_type(TableDumpMrtRecord.SUBTYPE_AFI_IPv6)
+class TableDumpAfiIPv6MrtMessage(TableDumpMrtMessage):
+ """
+ MRT Message for the TABLE_DUMP Type and the AFI_IPv6 subtype.
+ """
+ _HEADER_FMT = '!HH16sBBI16sHH'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class TableDump2MrtMessage(MrtMessage):
+ """
+ MRT Message for the TABLE_DUMP_V2 Type.
+ """
+
+
+@MrtRecord.register_type(MrtRecord.TYPE_TABLE_DUMP_V2)
+class TableDump2MrtRecord(MrtCommonRecord):
+ MESSAGE_CLS = TableDump2MrtMessage
+
+ # MRT Subtype
+ SUBTYPE_PEER_INDEX_TABLE = 1
+ SUBTYPE_RIB_IPV4_UNICAST = 2
+ SUBTYPE_RIB_IPV4_MULTICAST = 3
+ SUBTYPE_RIB_IPV6_UNICAST = 4
+ SUBTYPE_RIB_IPV6_MULTICAST = 5
+ SUBTYPE_RIB_GENERIC = 6
+
+
+@TableDump2MrtMessage.register_type(
+ TableDump2MrtRecord.SUBTYPE_PEER_INDEX_TABLE)
+class TableDump2PeerIndexTableMrtMessage(TableDump2MrtMessage):
+ """
+ MRT Message for the TABLE_DUMP_V2 Type and the PEER_INDEX_TABLE subtype.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Collector BGP ID |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | View Name Length | View Name (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer Count | Peer Entries (variable)
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!4sH'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+ _PEER_COUNT_FMT = '!H'
+ PEER_COUNT_SIZE = struct.calcsize(_PEER_COUNT_FMT)
+
+ def __init__(self, bgp_id, peer_entries,
+ view_name='', view_name_len=None, peer_count=None):
+ self.bgp_id = bgp_id
+ assert isinstance(peer_entries, (list, tuple))
+ for p in peer_entries:
+ assert isinstance(p, MrtPeer)
+ self.peer_entries = peer_entries
+ assert isinstance(view_name, str)
+ self.view_name = view_name
+ self.view_name_len = view_name_len
+ self.peer_count = peer_count
+
+ @classmethod
+ def parse(cls, buf):
+ (bgp_id, view_name_len) = struct.unpack_from(cls._HEADER_FMT, buf)
+ bgp_id = addrconv.ipv4.bin_to_text(bgp_id)
+ offset = cls.HEADER_SIZE
+
+ (view_name,) = struct.unpack_from('!%ds' % view_name_len, buf, offset)
+ view_name = str(view_name.decode('utf-8'))
+ offset += view_name_len
+
+ (peer_count,) = struct.unpack_from(cls._PEER_COUNT_FMT, buf, offset)
+ offset += cls.PEER_COUNT_SIZE
+
+ rest = buf[offset:]
+ peer_entries = []
+ for i in range(peer_count):
+ p, rest = MrtPeer.parse(rest)
+ peer_entries.insert(i, p)
+
+ return cls(bgp_id, peer_entries, view_name, view_name_len, peer_count)
+
+ def serialize(self):
+ view_name = self.view_name.encode('utf-8')
+ self.view_name_len = len(view_name) # fixup
+
+ self.peer_count = len(self.peer_entries) # fixup
+
+ buf = struct.pack(self._HEADER_FMT,
+ addrconv.ipv4.text_to_bin(self.bgp_id),
+ self.view_name_len) + view_name
+
+ buf += struct.pack(self._PEER_COUNT_FMT,
+ self.peer_count)
+
+ for p in self.peer_entries:
+ buf += p.serialize()
+
+ return buf
+
+
+class MrtPeer(stringify.StringifyMixin):
+ """
+ MRT Peer.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer Type |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer BGP ID |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer IP Address (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer AS (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!B4s'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+
+ # Peer Type field:
+ #
+ # 0 1 2 3 4 5 6 7
+ # +-+-+-+-+-+-+-+-+
+ # | | | | | | |A|I|
+ # +-+-+-+-+-+-+-+-+
+ #
+ # Bit 6: Peer AS number size: 0 = 2 bytes, 1 = 4 bytes
+ # Bit 7: Peer IP Address family: 0 = IPv4(4 bytes), 1 = IPv6(16 bytes)
+ IP_ADDR_FAMILY_BIT = 1 << 0
+ AS_NUMBER_SIZE_BIT = 1 << 1
+
+ def __init__(self, bgp_id, ip_addr, as_num, type_=0):
+ self.type = type_
+ self.bgp_id = bgp_id
+ self.ip_addr = ip_addr
+ self.as_num = as_num
+
+ @classmethod
+ def parse(cls, buf):
+ (type_, bgp_id) = struct.unpack_from(cls._HEADER_FMT, buf)
+ bgp_id = addrconv.ipv4.bin_to_text(bgp_id)
+ offset = cls.HEADER_SIZE
+
+ if type_ & cls.IP_ADDR_FAMILY_BIT:
+ # IPv6 address family
+ ip_addr_len = 16
+ else:
+ # IPv4 address family
+ ip_addr_len = 4
+ ip_addr = ip.bin_to_text(buf[offset:offset + ip_addr_len])
+ offset += ip_addr_len
+
+ if type_ & cls.AS_NUMBER_SIZE_BIT:
+ # Four octet AS number
+ (as_num,) = struct.unpack_from('!I', buf, offset)
+ offset += 4
+ else:
+ # Two octet AS number
+ (as_num,) = struct.unpack_from('!H', buf, offset)
+ offset += 2
+
+ return cls(bgp_id, ip_addr, as_num, type_), buf[offset:]
+
+ def serialize(self):
+ if netaddr.valid_ipv6(self.ip_addr):
+ # Sets Peer IP Address family bit to IPv6
+ self.type |= self.IP_ADDR_FAMILY_BIT
+ ip_addr = ip.text_to_bin(self.ip_addr)
+
+ if self.type & self.AS_NUMBER_SIZE_BIT or self.as_num > 0xffff:
+ # Four octet AS number
+ self.type |= self.AS_NUMBER_SIZE_BIT
+ as_num = struct.pack('!I', self.as_num)
+ else:
+ # Two octet AS number
+ as_num = struct.pack('!H', self.as_num)
+
+ buf = struct.pack(self._HEADER_FMT,
+ self.type,
+ addrconv.ipv4.text_to_bin(self.bgp_id))
+
+ return buf + ip_addr + as_num
+
+
+@six.add_metaclass(abc.ABCMeta)
+class TableDump2AfiSafiSpecificRibMrtMessage(TableDump2MrtMessage):
+ """
+ MRT Message for the TABLE_DUMP_V2 Type and the AFI/SAFI-specific
+ RIB subtypes.
+
+ The AFI/SAFI-specific RIB subtypes consist of the RIB_IPV4_UNICAST,
+ RIB_IPV4_MULTICAST, RIB_IPV6_UNICAST, and RIB_IPV6_MULTICAST subtypes.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Sequence Number |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Prefix Length |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Prefix (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Entry Count | RIB Entries (variable)
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!I'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+
+ # Parser class to parse the Prefix field
+ _PREFIX_CLS = None # should be defined in subclass
+
+ def __init__(self, seq_num, prefix, rib_entries, entry_count=None):
+ self.seq_num = seq_num
+ assert isinstance(prefix, self._PREFIX_CLS)
+ self.prefix = prefix
+ self.entry_count = entry_count
+ assert isinstance(rib_entries, (list, tuple))
+ for rib_entry in rib_entries:
+ assert isinstance(rib_entry, MrtRibEntry)
+ self.rib_entries = rib_entries
+
+ @classmethod
+ def parse_rib_entries(cls, buf):
+ (entry_count,) = struct.unpack_from('!H', buf)
+
+ rest = buf[2:]
+ rib_entries = []
+ for i in range(entry_count):
+ r, rest = MrtRibEntry.parse(rest)
+ rib_entries.insert(i, r)
+
+ return entry_count, rib_entries, rest
+
+ @classmethod
+ def parse(cls, buf):
+ (seq_num,) = struct.unpack_from(cls._HEADER_FMT, buf)
+ rest = buf[cls.HEADER_SIZE:]
+
+ prefix, rest = cls._PREFIX_CLS.parser(rest)
+
+ entry_count, rib_entries, _ = cls.parse_rib_entries(rest)
+
+ return cls(seq_num, prefix, rib_entries, entry_count)
+
+ def serialize_rib_entries(self):
+ self.entry_count = len(self.rib_entries) # fixup
+
+ rib_entries_bin = bytearray()
+ for r in self.rib_entries:
+ rib_entries_bin += r.serialize()
+
+ return struct.pack('!H', self.entry_count) + rib_entries_bin
+
+ def serialize(self):
+ prefix_bin = self.prefix.serialize()
+
+ rib_bin = self.serialize_rib_entries() # entry_count + rib_entries
+
+ return struct.pack(self._HEADER_FMT,
+ self.seq_num) + prefix_bin + rib_bin
+
+
+@TableDump2MrtMessage.register_type(
+ TableDump2MrtRecord.SUBTYPE_RIB_IPV4_UNICAST)
+@TableDump2MrtMessage.register_type(
+ TableDump2MrtRecord.SUBTYPE_RIB_IPV4_MULTICAST)
+class TableDump2RibIPv4UnicastMrtMessage(TableDump2AfiSafiSpecificRibMrtMessage):
+ """
+ MRT Message for the TABLE_DUMP_V2 Type and the
+ RIB_IPV4_UNICAST/SUBTYPE_RIB_IPV4_MULTICAST subtype.
+ """
+ _PREFIX_CLS = bgp.IPAddrPrefix
+
+
+@TableDump2MrtMessage.register_type(
+ TableDump2MrtRecord.SUBTYPE_RIB_IPV6_UNICAST)
+class TableDump2RibIPv6UnicastMrtMessage(TableDump2AfiSafiSpecificRibMrtMessage):
+ """
+ MRT Message for the TABLE_DUMP_V2 Type and the
+ RIB_IPV6_UNICAST/SUBTYPE_RIB_IPV6_MULTICAST subtype.
+ """
+ _PREFIX_CLS = bgp.IP6AddrPrefix
+
+
+@TableDump2MrtMessage.register_type(
+ TableDump2MrtRecord.SUBTYPE_RIB_GENERIC)
+class TableDump2RibGenericMrtMessage(TableDump2MrtMessage):
+ """
+ MRT Message for the TABLE_DUMP_V2 Type and the RIB_GENERIC subtype.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Sequence Number |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Address Family Identifier |Subsequent AFI |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Network Layer Reachability Information (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Entry Count | RIB Entries (variable)
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!IHB'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+
+ def __init__(self, seq_num, afi, safi, nlri, rib_entries,
+ entry_count=None):
+ self.seq_num = seq_num
+ self.afi = afi
+ self.safi = safi
+ assert isinstance(nlri, bgp._AddrPrefix)
+ self.nlri = nlri
+ self.entry_count = entry_count
+ assert isinstance(rib_entries, (list, tuple))
+ for rib_entry in rib_entries:
+ assert isinstance(rib_entry, MrtRibEntry)
+ self.rib_entries = rib_entries
+
+ @classmethod
+ def parse_rib_entries(cls, buf):
+ (entry_count,) = struct.unpack_from('!H', buf)
+
+ rest = buf[2:]
+ rib_entries = []
+ for i in range(entry_count):
+ r, rest = MrtRibEntry.parse(rest)
+ rib_entries.insert(i, r)
+
+ return entry_count, rib_entries, rest
+
+ @classmethod
+ def parse(cls, buf):
+ (seq_num, afi, safi) = struct.unpack_from(cls._HEADER_FMT, buf)
+ rest = buf[cls.HEADER_SIZE:]
+
+ nlri, rest = bgp.BGPNLRI.parser(rest)
+
+ entry_count, rib_entries, _ = cls.parse_rib_entries(rest)
+
+ return cls(seq_num, afi, safi, nlri, rib_entries, entry_count)
+
+ def serialize_rib_entries(self):
+ self.entry_count = len(self.rib_entries) # fixup
+
+ rib_entries_bin = bytearray()
+ for r in self.rib_entries:
+ rib_entries_bin += r.serialize()
+
+ return struct.pack('!H', self.entry_count) + rib_entries_bin
+
+ def serialize(self):
+ nlri_bin = self.nlri.serialize()
+
+ rib_bin = self.serialize_rib_entries() # entry_count + rib_entries
+
+ return struct.pack(self._HEADER_FMT,
+ self.seq_num,
+ self.afi, self.safi) + nlri_bin + rib_bin
+
+
+class MrtRibEntry(stringify.StringifyMixin):
+ """
+ MRT RIB Entry.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer Index |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Originated Time |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Attribute Length |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | BGP Attributes... (variable)
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!HIH'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+
+ def __init__(self, peer_index, originated_time, bgp_attributes,
+ attr_len=None):
+ self.peer_index = peer_index
+ self.originated_time = originated_time
+ assert isinstance(bgp_attributes, (list, tuple))
+ for attr in bgp_attributes:
+ assert isinstance(attr, bgp._PathAttribute)
+ self.bgp_attributes = bgp_attributes
+ self.attr_len = attr_len
+
+ @classmethod
+ def parse(cls, buf):
+ (peer_index, originated_time, attr_len) = struct.unpack_from(
+ cls._HEADER_FMT, buf)
+
+ bgp_attr_bin = buf[cls.HEADER_SIZE:cls.HEADER_SIZE + attr_len]
+ bgp_attributes = []
+ while bgp_attr_bin:
+ attr, bgp_attr_bin = bgp._PathAttribute.parser(bgp_attr_bin)
+ bgp_attributes.append(attr)
+
+ return cls(peer_index, originated_time, bgp_attributes,
+ attr_len), buf[cls.HEADER_SIZE + attr_len:]
+
+ def serialize(self):
+ bgp_attrs_bin = bytearray()
+ for attr in self.bgp_attributes:
+ bgp_attrs_bin += attr.serialize()
+ self.attr_len = len(bgp_attrs_bin) # fixup
+
+ return struct.pack(self._HEADER_FMT,
+ self.peer_index,
+ self.originated_time,
+ self.attr_len) + bgp_attrs_bin
+
+
+@six.add_metaclass(abc.ABCMeta)
+class Bgp4MpMrtMessage(MrtMessage):
+ """
+ MRT Message for the BGP4MP Type.
+ """
+
+
+@MrtRecord.register_type(MrtRecord.TYPE_BGP4MP)
+class Bgp4MpMrtRecord(MrtCommonRecord):
+ MESSAGE_CLS = Bgp4MpMrtMessage
+
+ # MRT Subtype
+ SUBTYPE_BGP4MP_STATE_CHANGE = 0
+ SUBTYPE_BGP4MP_MESSAGE = 1
+ SUBTYPE_BGP4MP_MESSAGE_AS4 = 4
+ SUBTYPE_BGP4MP_STATE_CHANGE_AS4 = 5
+ SUBTYPE_BGP4MP_MESSAGE_LOCAL = 6
+ SUBTYPE_BGP4MP_MESSAGE_AS4_LOCAL = 7
+
+
+@MrtRecord.register_type(MrtRecord.TYPE_BGP4MP_ET)
+class Bgp4MpEtMrtRecord(ExtendedTimestampMrtRecord):
+ MESSAGE_CLS = Bgp4MpMrtMessage
+
+ # MRT Subtype
+ SUBTYPE_BGP4MP_STATE_CHANGE = 0
+ SUBTYPE_BGP4MP_MESSAGE = 1
+ SUBTYPE_BGP4MP_MESSAGE_AS4 = 4
+ SUBTYPE_BGP4MP_STATE_CHANGE_AS4 = 5
+ SUBTYPE_BGP4MP_MESSAGE_LOCAL = 6
+ SUBTYPE_BGP4MP_MESSAGE_AS4_LOCAL = 7
+
+
+@Bgp4MpMrtMessage.register_type(
+ Bgp4MpMrtRecord.SUBTYPE_BGP4MP_STATE_CHANGE)
+class Bgp4MpStateChangeMrtMessage(Bgp4MpMrtMessage):
+ """
+ MRT Message for the BGP4MP Type and the BGP4MP_STATE_CHANGE subtype.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer AS Number | Local AS Number |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Interface Index | Address Family |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer IP Address (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Local IP Address (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Old State | New State |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!HHHH'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+ _ADDRS_FMT = '!%ds%ds'
+ _STATES_FMT = '!HH'
+ STATES_SIZE = struct.calcsize(_STATES_FMT)
+
+ # FSM states
+ STATE_IDLE = 1
+ STATE_CONNECT = 2
+ STATE_ACTIVE = 3
+ STATE_OPEN_SENT = 4
+ STATE_OPEN_CONFIRM = 5
+ STATE_ESTABLISHED = 6
+
+ # Address Family types
+ AFI_IPv4 = 1
+ AFI_IPv6 = 2
+
+ def __init__(self, peer_as, local_as, if_index,
+ peer_ip, local_ip, old_state, new_state, afi=None):
+ self.peer_as = peer_as
+ self.local_as = local_as
+ self.if_index = if_index
+ self.afi = afi
+ self.peer_ip = peer_ip
+ self.local_ip = local_ip
+ self.old_state = old_state
+ self.new_state = new_state
+
+ @classmethod
+ def parse(cls, buf):
+ (peer_as, local_as, if_index, afi) = struct.unpack_from(
+ cls._HEADER_FMT, buf)
+ offset = cls.HEADER_SIZE
+
+ if afi == cls.AFI_IPv4:
+ # IPv4 Address
+ addrs_fmt = cls._ADDRS_FMT % (4, 4)
+ elif afi == cls.AFI_IPv6:
+ # IPv6 Address
+ addrs_fmt = cls._ADDRS_FMT % (16, 16)
+ else:
+ raise struct.error('Unsupported address family: %d' % afi)
+
+ (peer_ip, local_ip) = struct.unpack_from(addrs_fmt, buf, offset)
+ peer_ip = ip.bin_to_text(peer_ip)
+ local_ip = ip.bin_to_text(local_ip)
+ offset += struct.calcsize(addrs_fmt)
+
+ (old_state, new_state) = struct.unpack_from(
+ cls._STATES_FMT, buf, offset)
+
+ return cls(peer_as, local_as, if_index,
+ peer_ip, local_ip, old_state, new_state, afi)
+
+ def serialize(self):
+ # fixup
+ if (netaddr.valid_ipv4(self.peer_ip)
+ and netaddr.valid_ipv4(self.local_ip)):
+ self.afi = self.AFI_IPv4
+ elif (netaddr.valid_ipv6(self.peer_ip)
+ and netaddr.valid_ipv6(self.local_ip)):
+ self.afi = self.AFI_IPv6
+ else:
+ raise ValueError(
+ 'peer_ip and local_ip must be the same address family: '
+ 'peer_ip=%s, local_ip=%s' % (self.peer_ip, self.local_ip))
+
+ buf = struct.pack(self._HEADER_FMT,
+ self.peer_as, self.local_as,
+ self.if_index, self.afi)
+
+ buf += ip.text_to_bin(self.peer_ip)
+ buf += ip.text_to_bin(self.local_ip)
+
+ buf += struct.pack(self._STATES_FMT,
+ self.old_state, self.new_state)
+
+ return buf
+
+
+@Bgp4MpMrtMessage.register_type(
+ Bgp4MpMrtRecord.SUBTYPE_BGP4MP_MESSAGE)
+class Bgp4MpMessageMrtMessage(Bgp4MpMrtMessage):
+ """
+ MRT Message for the BGP4MP Type and the BGP4MP_MESSAGE subtype.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer AS Number | Local AS Number |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Interface Index | Address Family |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer IP Address (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Local IP Address (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | BGP Message... (variable)
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!HHHH'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+ _ADDRS_FMT = '!%ds%ds'
+
+ # Address Family types
+ AFI_IPv4 = 1
+ AFI_IPv6 = 2
+
+ def __init__(self, peer_as, local_as, if_index,
+ peer_ip, local_ip, bgp_message, afi=None):
+ self.peer_as = peer_as
+ self.local_as = local_as
+ self.if_index = if_index
+ self.peer_ip = peer_ip
+ self.local_ip = local_ip
+ assert isinstance(bgp_message, bgp.BGPMessage)
+ self.bgp_message = bgp_message
+ self.afi = afi
+
+ @classmethod
+ def parse(cls, buf):
+ (peer_as, local_as, if_index, afi) = struct.unpack_from(
+ cls._HEADER_FMT, buf)
+ offset = cls.HEADER_SIZE
+
+ if afi == cls.AFI_IPv4:
+ # IPv4 Address
+ addrs_fmt = cls._ADDRS_FMT % (4, 4)
+ elif afi == cls.AFI_IPv6:
+ # IPv6 Address
+ addrs_fmt = cls._ADDRS_FMT % (16, 16)
+ else:
+ raise struct.error('Unsupported address family: %d' % afi)
+
+ (peer_ip, local_ip) = struct.unpack_from(addrs_fmt, buf, offset)
+ peer_ip = ip.bin_to_text(peer_ip)
+ local_ip = ip.bin_to_text(local_ip)
+ offset += struct.calcsize(addrs_fmt)
+
+ rest = buf[offset:]
+ bgp_message, _, _ = bgp.BGPMessage.parser(rest)
+
+ return cls(peer_as, local_as, if_index,
+ peer_ip, local_ip, bgp_message, afi)
+
+ def serialize(self):
+ # fixup
+ if (netaddr.valid_ipv4(self.peer_ip)
+ and netaddr.valid_ipv4(self.local_ip)):
+ self.afi = self.AFI_IPv4
+ elif (netaddr.valid_ipv6(self.peer_ip)
+ and netaddr.valid_ipv6(self.local_ip)):
+ self.afi = self.AFI_IPv6
+ else:
+ raise ValueError(
+ 'peer_ip and local_ip must be the same address family: '
+ 'peer_ip=%s, local_ip=%s' % (self.peer_ip, self.local_ip))
+
+ buf = struct.pack(self._HEADER_FMT,
+ self.peer_as, self.local_as,
+ self.if_index, self.afi)
+
+ buf += ip.text_to_bin(self.peer_ip)
+ buf += ip.text_to_bin(self.local_ip)
+
+ buf += self.bgp_message.serialize()
+
+ return buf
+
+
+@Bgp4MpMrtMessage.register_type(
+ Bgp4MpMrtRecord.SUBTYPE_BGP4MP_MESSAGE_AS4)
+class Bgp4MpMessageAs4MrtMessage(Bgp4MpMessageMrtMessage):
+ """
+ MRT Message for the BGP4MP Type and the BGP4MP_MESSAGE_AS4 subtype.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer AS Number |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Local AS Number |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Interface Index | Address Family |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer IP Address (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Local IP Address (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | BGP Message... (variable)
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!IIHH'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+
+
+@Bgp4MpMrtMessage.register_type(
+ Bgp4MpMrtRecord.SUBTYPE_BGP4MP_STATE_CHANGE_AS4)
+class Bgp4MpStateChangeAs4MrtMessage(Bgp4MpStateChangeMrtMessage):
+ """
+ MRT Message for the BGP4MP Type and the BGP4MP_STATE_CHANGE_AS4 subtype.
+ """
+ # 0 1 2 3
+ # 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
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer AS Number |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Local AS Number |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Interface Index | Address Family |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Peer IP Address (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Local IP Address (variable) |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ # | Old State | New State |
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ _HEADER_FMT = '!IIHH'
+ HEADER_SIZE = struct.calcsize(_HEADER_FMT)
+
+
+@Bgp4MpMrtMessage.register_type(
+ Bgp4MpMrtRecord.SUBTYPE_BGP4MP_MESSAGE_LOCAL)
+class Bgp4MpMessageLocalMrtMessage(Bgp4MpMessageMrtMessage):
+ """
+ MRT Message for the BGP4MP Type and the BGP4MP_MESSAGE_LOCAL subtype.
+ """
+
+
+@Bgp4MpMrtMessage.register_type(
+ Bgp4MpMrtRecord.SUBTYPE_BGP4MP_MESSAGE_AS4_LOCAL)
+class Bgp4MpMessageAs4LocalMrtMessage(Bgp4MpMessageAs4MrtMessage):
+ """
+ MRT Message for the BGP4MP Type and the BGP4MP_MESSAGE_AS4_LOCAL subtype.
+ """
+
+
+# TODO:
+# Currently, Ryu does not provide the packet library for ISIS protocol.
+# Implement parser for ISIS MRT message.
+# class IsisMrtRecord(MrtCommonRecord):
+# class IsisMrtMessage(MrtMessage):
+
+
+# TODO:
+# Currently, Ryu does not provide the packet library for OSPFv3 protocol.
+# Implement the parser for OSPFv3 MRT message.
+# class Ospf3MrtRecord(MrtCommonRecord):
+# class Ospf3MrtMessage(MrtMessage):
+
+
+class Reader(object):
+ """
+ MRT format file reader.
+
+ ========= ================================================
+ Argument Description
+ ========= ================================================
+ f File object which reading MRT format file
+ in binary mode.
+ ========= ================================================
+
+ Example of Usage::
+
+ import bz2
+ from ryu.lib import mrtlib
+
+ count = 0
+ for record in mrtlib.Reader(
+ bz2.BZ2File('rib.YYYYMMDD.hhmm.bz2', 'rb')):
+ print("%d, %s" % (count, record))
+ count += 1
+ """
+
+ def __init__(self, f):
+ self._f = f
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ header_buf = self._f.read(MrtRecord.HEADER_SIZE)
+ if len(header_buf) < MrtRecord.HEADER_SIZE:
+ raise StopIteration()
+
+ # Hack to avoid eating memory up
+ self._f.seek(-MrtRecord.HEADER_SIZE, 1)
+ required_len = MrtRecord.parse_pre(header_buf)
+ buf = self._f.read(required_len)
+ record, _ = MrtRecord.parse(buf)
+
+ return record
+
+ # for Python 3 compatible
+ __next__ = next
+
+ def close(self):
+ self._f.close()
+
+ def __del__(self):
+ self.close()
+
+
+class Writer(object):
+ """
+ MRT format file writer.
+
+ ========= ================================================
+ Argument Description
+ ========= ================================================
+ f File object which writing MRT format file
+ in binary mode.
+ ========= ================================================
+
+ Example of usage::
+
+ import bz2
+ import time
+ from ryu.lib import mrtlib
+ from ryu.lib.packet import bgp
+
+ mrt_writer = mrtlib.Writer(
+ bz2.BZ2File('rib.YYYYMMDD.hhmm.bz2', 'wb'))
+
+ prefix = bgp.IPAddrPrefix(24, '10.0.0.0')
+
+ rib_entry = mrtlib.MrtRibEntry(
+ peer_index=0,
+ originated_time=int(time.time()),
+ bgp_attributes=[bgp.BGPPathAttributeOrigin(0)])
+
+ message = mrtlib.TableDump2RibIPv4UnicastMrtMessage(
+ seq_num=0,
+ prefix=prefix,
+ rib_entries=[rib_entry])
+
+ record = mrtlib.TableDump2MrtRecord(
+ message=message)
+
+ mrt_writer.write(record)
+ """
+
+ def __init__(self, f):
+ self._f = f
+
+ def write(self, record):
+ if not isinstance(record, MrtRecord):
+ raise ValueError(
+ 'record should be an instance of MrtRecord subclass')
+
+ self._f.write(record.serialize())
+
+ def close(self):
+ self._f.close()
+
+ def __del__(self):
+ self.close()