diff options
author | IWASE Yusuke <iwase.yusuke0@gmail.com> | 2016-11-25 17:02:55 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2016-12-10 22:00:34 +0900 |
commit | b3804a872748bf4ad874f387aecc953e3c857b5c (patch) | |
tree | 04f7657432d8debc32010ff1b4ae8480f616d30e | |
parent | a6dc0bc8a7b8f16775d56500f1ffc677ebd0c04d (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.py | 1224 |
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() |