d64db265b3
This patch adopts to the following pycodestyle's checks; E275: missing whitespace after keyword E305: expected 2 blank lines after end of function or class Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
1223 lines
42 KiB
Python
1223 lines
42 KiB
Python
# 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 ip.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 ip.valid_ipv4(self.peer_ip) and ip.valid_ipv4(self.local_ip):
|
|
self.afi = self.AFI_IPv4
|
|
elif ip.valid_ipv6(self.peer_ip) and ip.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 ip.valid_ipv4(self.peer_ip) and ip.valid_ipv4(self.local_ip):
|
|
self.afi = self.AFI_IPv4
|
|
elif ip.valid_ipv6(self.peer_ip) and ip.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()
|