123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- module Mrt
- require 'bindata'
- require 'ipaddr'
- class CommonHeader < BinData::Record
- TYPES = {
- 11 => :OSPFv2,
- 12 => :TABLE_DUMP,
- 13 => :TABLE_DUMP_V2,
- 16 => :BGP4MP,
- 17 => :BGP4MP_ET,
- 32 => :ISIS,
- 33 => :ISIS_ET,
- 48 => :OSPFv3,
- 49 => :OSPFv3_ET
- }.freeze
- SUBTYPES = {
- 13 => {
- # RFC6396
- 1 => :PEER_INDEX_TABLE,
- 2 => :RIB_IPV4_UNICAST,
- 3 => :RIB_IPV4_MULTICAST,
- 4 => :RIB_IPV6_UNICAST,
- 5 => :RIB_IPV6_MULTICAST,
- 6 => :RIB_GENERIC,
- # RFC8050
- 8 => :RIB_IPV4_UNICAST_ADDPATH,
- 9 => :RIB_IPV4_MULTICAST_ADDPATH,
- 10 => :RIB_IPV6_UNICAST_ADDPATH,
- 11 => :RIB_IPV6_MULTICAST_ADDPATH,
- 12 => :RIB_GENERIC_ADDPATH
- }.freeze
- }.freeze
- endian :big
- uint32 :time
- uint16 :type1
- uint16 :subtype
- uint32 :length1
- def get_time
- return Time.at(time)
- end
- def get_type
- return TYPES[type1]
- end
- def get_subtype
- return SUBTYPES[type1][subtype]
- end
- end
- class PeerEntry < BinData::Record
- endian :big
- uint8 :peer_type
- string :peer_bgp_id, length: 4
- string :peer_ip_address, length: -> {
- peer_type[0].zero? ? # Is the seventh bit zero?
- 4 : # Then it is a IPv4
- 16 # else it is a IPv6
- }
- choice :peer_as, selection: -> { peer_type[1] } do # What is the sixth bit?
- uint16 0 # If it is a zero it is a 16bit ASN
- uint32 1 # else it is a 32bit ASN
- end
- def get_peer_router_id
- return IPAddr.ntop peer_bgp_id
- end
- def get_peer_ip_address
- return IPAddr.ntop peer_ip_address
- end
- end
- class PeerIndexTable < BinData::Record
- endian :big
- string :collector_bgp_id, length: 4
- uint16 :view_name_length
- string :view_name, length: -> { view_name_length }
- uint16 :peer_count
- array :peer_entries, read_until: -> { index >= peer_count - 1 }, type: :PeerEntry
- def get_collector_router_id
- return IPAddr.ntop collector_bgp_id
- end
- end
- class BgpAttributeOrigin < BinData::Record
- MEANINGS = {
- 0 => :IGP,
- 1 => :EGP,
- 2 => :INCOMPLETE
- }.freeze
- endian :big
- # RFC4281 5.1.1. / 4.3
- # octet
- uint8 :origin
- def get_origin
- return MEANINGS[origin]
- end
- end
- class BgpAttributeBGPPath < BinData::Record
- TYPES = {
- 1 => :AS_SET,
- 2 => :AS_SEQUENCE
- }.freeze
- endian :big
- # RFC4281 5.1.2. / 4.3
- # RFC6793
- # 1-octet length field
- uint8 :path_segment_type
- # 1-octet length field
- uint8 :path_segment_length
- array :path_segment_value, read_until: -> { index >= path_segment_length - 1 } do
- # 4-octet length field
- uint32
- end
- def get_type
- return TYPES[path_segment_type]
- end
- end
- class BgpAttributeLocalPref < BinData::Record
- endian :big
- # RFC4271 5.1.5. / 4.3
- # as there is no specifc length defined
- # the length is dynamic
- # see https://github.com/dmendel/bindata/issues/150
- uint32 :local_pref
- end
- class BgpAttributeCommunity < BinData::Record
- endian :big
- # RFC1997 COMMUNITIES attribute
- # a set of four octet values
- # attribute_length is in bytes
- # one array entry has 32bit (4byte)
- # i need to iterate it attribute_length / entry_length
- # this results in attrbute_length / 4
- array :communities, read_until: -> { index >= (attribute_length / 4 - 1) } do
- uint16 :asn
- uint16 :value1
- end
- end
- class BgpAttributeLargeCommunity < BinData::Record
- endian :big
- # RFC8092 3.
- # attribute_length is in bytes
- # one array entry has 3 * 32bit (96byte)
- # i need to iterate it attribute_length / entry_length
- # this results in attrbute_length / 12
- array :large_communities, read_until: -> { index >= (attribute_length / 12 - 1) } do
- # four-octet namespace identifier.
- uint32 :global_administrator
- # four-octet operator-defined value.
- uint32 :local_data_part_1
- # four-octet operator-defined value.
- uint32 :local_data_part_2
- end
- end
- class BgpAttributeMpReachNlri < BinData::Record
- endian :big
- # RFC4760 5.
- # 2-tuples of the form <length, prefix>
- uint8 :length1
- string :prefix, length: :length1
- def get_prefix
- return IPAddr.ntop(prefix)
- end
- end
- class BgpAttributeOtc < BinData::Record
- endian :big
- # RFC9234
- # a length of 4 octets
- uint32 :otc
- end
- class BgpAttributeMed < BinData::Record
- endian :big
- # RFC4271 5.1.4.
- # four-octet unsigned number
- uint32 :med
- end
- class BgpAttributeAtomicAggregate < BinData::Record
- # RFC4271 5.1.6
- # No value
- end
- class BgpAttributeNextHop < BinData::Record
- endian :big
- # RFC4271 5.1.3
- string :next_hop, length: :attribute_length
- def get_nexthop
- return IPAddr.ntop next_hop
- end
- end
- class BgpAttribute < BinData::Record
- TYPES = {
- # RFC4271
- 1 => :ORIGIN,
- 2 => :AS_PATH,
- 3 => :NEXT_HOP,
- 4 => :MULTI_EXIT_DISC,
- 5 => :LOCAL_PREF,
- 6 => :ATOMIC_AGGREGATE,
- 7 => :AGGREGATOR,
- # RFC1997
- 8 => :COMMUNITIES,
- # RFC4760
- 14 => :MP_REACH_NLRI,
- 15 => :MP_UNREACH_NLRI,
- # RFC8092
- 32 => :LARGE_COMMUNITY,
- # RFC9234
- 35 => :OTC
- }.freeze
- endian :big
- uint8 :attribute_flags
- uint8 :attribute_type_code
- choice :attribute_length, selection: -> { attribute_flags[4] } do
- uint8 0 # Is not extended length
- uint16 1 # Has extended length
- end
- buffer :attribute, length: :attribute_length do
- choice selection: -> { attribute_type_code } do
- BgpAttributeOrigin 1
- BgpAttributeBGPPath 2
- BgpAttributeNextHop 3
- BgpAttributeMed 4
- BgpAttributeLocalPref 5
- BgpAttributeAtomicAggregate 6
- BgpAttributeCommunity 8
- BgpAttributeMpReachNlri 14
- BgpAttributeLargeCommunity 32
- BgpAttributeOtc 35
- # or use skip?
- # Do I want to save not known attributes?
- string :default, length: :attribute_length
- end
- end
- def get_type
- return TYPES[attribute_type_code]
- end
- def is_optional?
- return ! attribute_flags[7].zero?
- end
- def is_transitive?
- return ! attribute_flags[6].zero?
- end
- def is_partial?
- return ! attribute_flags[5].zero?
- end
- def is_extended_length?
- return ! attribute_flags[4].zero?
- end
- def is_valid?
- return attribute_flags[0..3].zero?
- end
- end
- class RibEntry < BinData::Record
- endian :big
- uint16 :peer_index
- uint32 :originated_time
- uint32 :path_identifier, onlyif: -> { (8..12).include? header.subtype }
- uint16 :attributes_length
- buffer :attributes, length: :attributes_length do
- array read_until: :eof, type: BgpAttribute
- end
- def get_originated_time
- return Time.at(originated_time)
- end
- end
- class RibUnicast < BinData::Record
- endian :big
- uint32 :sequence_number
- uint8 :prefix_length
- # This violates RFC6396 4.3.2
- # "The value of trailing bits is irrelevant."
- # So I would need a kind of bit string
- # Maybe implement later?
- string :prefix, length: -> { (prefix_length.to_i / 8) + ((prefix_length.to_i % 8).zero? ? 0 : 1) }
- uint16 :entry_count
- array :rib_entries, read_until: -> { index >= entry_count - 1 }, type: RibEntry
- end
- class RibIPv4Unicast < RibUnicast
- def get_prefix
- return IPAddr.ntop("#{prefix}#{"\0" * (4 - prefix.length)}")
- end
- end
- class RibIPv6Unicast < RibUnicast
- def get_prefix
- return IPAddr.ntop("#{prefix}#{"\0" * (16 - prefix.length)}")
- end
- end
- class MrtRecord < BinData::Record
- CommonHeader :header
- buffer :data, length: -> { header.length1 } do
- choice selection: -> { [header.type1, header.subtype] } do
- PeerIndexTable [13, 1] # TableDumpV2 (13) -> PeerIndexTable (1)
- RibIPv4Unicast [13, 2] # TableDumpV2 (13) -> RibIPv4Unicast (2)
- RibIPv6Unicast [13, 4] # TableDumpV2 (13) -> RibIPv6Unicast (4)
- RibIPv4Unicast [13, 8] # TableDumpV2 (13) -> RibIPv4UnicastAddpath (8)
- RibIPv6Unicast [13, 10] # TableDumpV2 (13) -> RivIPv6UnicastAddpath(10)
- end
- end
- end
- class MrtFile < BinData::Record
- array :data, read_until: :eof, type: MrtRecord
- end
- class Parser
- attr_reader :result, :transformed_result
- def initialize iostream
- @iostream = iostream
- end
- def parse!
- @result = MrtFile.read @iostream
- return self
- end
- def transform_record dump
- new_dump = {}
- new_dump[:type] = dump.header.get_type
- new_dump[:subtype] = dump.header.get_subtype
- new_dump[:time] = dump.header.get_type
- case new_dump[:type]
- when :TABLE_DUMP_V2
- case new_dump[:subtype]
- when :PEER_INDEX_TABLE
- new_dump[:collector_bgp_id] = IPAddr.new(dump.data.get_collector_router_id)
- new_dump[:table] = dump.data.view_name
- new_dump[:peers] = []
- dump.data.peer_entries.each do |peer|
- new_peer = {}
- new_peer[:router_id] = IPAddr.new(peer.get_peer_router_id)
- new_peer[:address] = IPAddr.new(peer.get_peer_ip_address)
- new_peer[:asn] = peer.peer_as
- new_dump[:peers] << new_peer
- end
- when :RIB_IPV6_UNICAST, :RIB_IPV6_UNICAST_ADDPATH, :RIB_IPV4_UNICAST, :RIB_IPV4_UNICAST_ADDPATH
- new_dump[:sequence_number] = dump.data.sequence_number
- new_dump[:prefix] = IPAddr.new(dump.data.get_prefix)
- new_dump[:prefix].prefix = dump.data.prefix_length.to_i
- new_dump[:rib_entries] = []
- dump.data.rib_entries.each do |rib|
- new_rib = {}
- new_rib[:peer_index] = rib.peer_index
- new_rib[:time] = rib.get_originated_time
- new_rib[:attributes] = []
- rib.attributes.each do |attr|
- new_attr = {}
- new_attr[:type] = attr.get_type
- # Skip invalid attributes
- if attr.is_valid?
- # Skip unknown attributes
- case new_attr[:type]
- when :ORIGIN
- new_attr[:origin] = attr.attribute.origin
- new_rib[:attributes] << new_attr
- when :AS_PATH
- new_attr[:as_path_type] = attr.attribute.get_type
- new_attr[:as_path] = attr.attribute.path_segment_value.to_a
- new_rib[:attributes] << new_attr
- when :LOCAL_PREF
- new_attr[:local_pref] = attr.attribute.local_pref
- new_rib[:attributes] << new_attr
- when :COMMUNITIES
- new_attr[:communities] = []
- attr.attribute.communities.each do |comm|
- new_attr[:communities] << [comm.asn, comm.value1]
- end
- new_rib[:attributes] << new_attr
- when :LARGE_COMMUNITY
- new_attr[:large_communities] = []
- attr.attribute.large_communities.each do |comm|
- new_attr[:large_communities] << [comm.global_administrator, comm.local_data_part_1, comm.local_data_part_2]
- end
- new_rib[:attributes] << new_attr
- when :MP_REACH_NLRI
- new_attr[:prefix] = IPAddr.new(attr.attribute.get_prefix)
- new_rib[:attributes] << new_attr
- when :MULTI_EXIT_DISC
- new_rib[:med] = attr.attribute.med
- new_rib[:attributes] << new_attr
- when :ATOMIC_AGGREGATE
- new_rib[:atomic_aggregate] = true
- new_rib[:attributes] << new_attr
- when :OTC
- new_rib[:otc] = attr.attribute.otc
- new_rib[:attributes] << new_attr
- when :NEXT_HOP
- new_rib[:nexthop] = IPAddr.new(attr.attribute.get_nexthop)
- new_rib[:attributes] << new_attr
- end
- end
- end
- new_dump[:rib_entries] = new_rib
- end
- else
- raise "Not supported subtype: #{new_dump[:subtype].to_s.dump}"
- end
- else
- raise "Not supported type: #{new_dump[:type].to_s.dump}"
- end
- return new_dump
- end
- def transform!
- res = []
- @result.data.each do |dump|
- res << transform_record(dump)
- end
- @transformed_result = res
- return self
- end
- end
- end
|