parser.rb 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. module Mrt
  2. require 'bindata'
  3. require 'ipaddr'
  4. class CommonHeader < BinData::Record
  5. TYPES = {
  6. 11 => :OSPFv2,
  7. 12 => :TABLE_DUMP,
  8. 13 => :TABLE_DUMP_V2,
  9. 16 => :BGP4MP,
  10. 17 => :BGP4MP_ET,
  11. 32 => :ISIS,
  12. 33 => :ISIS_ET,
  13. 48 => :OSPFv3,
  14. 49 => :OSPFv3_ET
  15. }.freeze
  16. SUBTYPES = {
  17. 13 => {
  18. # RFC6396
  19. 1 => :PEER_INDEX_TABLE,
  20. 2 => :RIB_IPV4_UNICAST,
  21. 3 => :RIB_IPV4_MULTICAST,
  22. 4 => :RIB_IPV6_UNICAST,
  23. 5 => :RIB_IPV6_MULTICAST,
  24. 6 => :RIB_GENERIC,
  25. # RFC8050
  26. 8 => :RIB_IPV4_UNICAST_ADDPATH,
  27. 9 => :RIB_IPV4_MULTICAST_ADDPATH,
  28. 10 => :RIB_IPV6_UNICAST_ADDPATH,
  29. 11 => :RIB_IPV6_MULTICAST_ADDPATH,
  30. 12 => :RIB_GENERIC_ADDPATH
  31. }.freeze
  32. }.freeze
  33. endian :big
  34. uint32 :time
  35. uint16 :type1
  36. uint16 :subtype
  37. uint32 :length1
  38. def get_time
  39. return Time.at(time)
  40. end
  41. def get_type
  42. return TYPES[type1]
  43. end
  44. def get_subtype
  45. return SUBTYPES[type1][subtype]
  46. end
  47. end
  48. class PeerEntry < BinData::Record
  49. endian :big
  50. uint8 :peer_type
  51. string :peer_bgp_id, length: 4
  52. string :peer_ip_address, length: -> {
  53. peer_type[0].zero? ? # Is the seventh bit zero?
  54. 4 : # Then it is a IPv4
  55. 16 # else it is a IPv6
  56. }
  57. choice :peer_as, selection: -> { peer_type[1] } do # What is the sixth bit?
  58. uint16 0 # If it is a zero it is a 16bit ASN
  59. uint32 1 # else it is a 32bit ASN
  60. end
  61. def get_peer_router_id
  62. return IPAddr.ntop peer_bgp_id
  63. end
  64. def get_peer_ip_address
  65. return IPAddr.ntop peer_ip_address
  66. end
  67. end
  68. class PeerIndexTable < BinData::Record
  69. endian :big
  70. string :collector_bgp_id, length: 4
  71. uint16 :view_name_length
  72. string :view_name, length: -> { view_name_length }
  73. uint16 :peer_count
  74. array :peer_entries, read_until: -> { index >= peer_count - 1 }, type: :PeerEntry
  75. def get_collector_router_id
  76. return IPAddr.ntop collector_bgp_id
  77. end
  78. end
  79. class BgpAttributeOrigin < BinData::Record
  80. MEANINGS = {
  81. 0 => :IGP,
  82. 1 => :EGP,
  83. 2 => :INCOMPLETE
  84. }.freeze
  85. endian :big
  86. # RFC4281 5.1.1. / 4.3
  87. # octet
  88. uint8 :origin
  89. def get_origin
  90. return MEANINGS[origin]
  91. end
  92. end
  93. class BgpAttributeBGPPath < BinData::Record
  94. TYPES = {
  95. 1 => :AS_SET,
  96. 2 => :AS_SEQUENCE
  97. }.freeze
  98. endian :big
  99. # RFC4281 5.1.2. / 4.3
  100. # RFC6793
  101. # 1-octet length field
  102. uint8 :path_segment_type
  103. # 1-octet length field
  104. uint8 :path_segment_length
  105. array :path_segment_value, read_until: -> { index >= path_segment_length - 1 } do
  106. # 4-octet length field
  107. uint32
  108. end
  109. def get_type
  110. return TYPES[path_segment_type]
  111. end
  112. end
  113. class BgpAttributeLocalPref < BinData::Record
  114. endian :big
  115. # RFC4271 5.1.5. / 4.3
  116. # as there is no specifc length defined
  117. # the length is dynamic
  118. # see https://github.com/dmendel/bindata/issues/150
  119. uint32 :local_pref
  120. end
  121. class BgpAttributeCommunity < BinData::Record
  122. endian :big
  123. # RFC1997 COMMUNITIES attribute
  124. # a set of four octet values
  125. # attribute_length is in bytes
  126. # one array entry has 32bit (4byte)
  127. # i need to iterate it attribute_length / entry_length
  128. # this results in attrbute_length / 4
  129. array :communities, read_until: -> { index >= (attribute_length / 4 - 1) } do
  130. uint16 :asn
  131. uint16 :value1
  132. end
  133. end
  134. class BgpAttributeLargeCommunity < BinData::Record
  135. endian :big
  136. # RFC8092 3.
  137. # attribute_length is in bytes
  138. # one array entry has 3 * 32bit (96byte)
  139. # i need to iterate it attribute_length / entry_length
  140. # this results in attrbute_length / 12
  141. array :large_communities, read_until: -> { index >= (attribute_length / 12 - 1) } do
  142. # four-octet namespace identifier.
  143. uint32 :global_administrator
  144. # four-octet operator-defined value.
  145. uint32 :local_data_part_1
  146. # four-octet operator-defined value.
  147. uint32 :local_data_part_2
  148. end
  149. end
  150. class BgpAttributeMpReachNlri < BinData::Record
  151. endian :big
  152. # RFC4760 5.
  153. # 2-tuples of the form <length, prefix>
  154. uint8 :length1
  155. string :prefix, length: :length1
  156. def get_prefix
  157. return IPAddr.ntop(prefix)
  158. end
  159. end
  160. class BgpAttributeOtc < BinData::Record
  161. endian :big
  162. # RFC9234
  163. # a length of 4 octets
  164. uint32 :otc
  165. end
  166. class BgpAttributeMed < BinData::Record
  167. endian :big
  168. # RFC4271 5.1.4.
  169. # four-octet unsigned number
  170. uint32 :med
  171. end
  172. class BgpAttributeAtomicAggregate < BinData::Record
  173. # RFC4271 5.1.6
  174. # No value
  175. end
  176. class BgpAttributeNextHop < BinData::Record
  177. endian :big
  178. # RFC4271 5.1.3
  179. string :next_hop, length: :attribute_length
  180. def get_nexthop
  181. return IPAddr.ntop next_hop
  182. end
  183. end
  184. class BgpAttribute < BinData::Record
  185. TYPES = {
  186. # RFC4271
  187. 1 => :ORIGIN,
  188. 2 => :AS_PATH,
  189. 3 => :NEXT_HOP,
  190. 4 => :MULTI_EXIT_DISC,
  191. 5 => :LOCAL_PREF,
  192. 6 => :ATOMIC_AGGREGATE,
  193. 7 => :AGGREGATOR,
  194. # RFC1997
  195. 8 => :COMMUNITIES,
  196. # RFC4760
  197. 14 => :MP_REACH_NLRI,
  198. 15 => :MP_UNREACH_NLRI,
  199. # RFC8092
  200. 32 => :LARGE_COMMUNITY,
  201. # RFC9234
  202. 35 => :OTC
  203. }.freeze
  204. endian :big
  205. uint8 :attribute_flags
  206. uint8 :attribute_type_code
  207. choice :attribute_length, selection: -> { attribute_flags[4] } do
  208. uint8 0 # Is not extended length
  209. uint16 1 # Has extended length
  210. end
  211. buffer :attribute, length: :attribute_length do
  212. choice selection: -> { attribute_type_code } do
  213. BgpAttributeOrigin 1
  214. BgpAttributeBGPPath 2
  215. BgpAttributeNextHop 3
  216. BgpAttributeMed 4
  217. BgpAttributeLocalPref 5
  218. BgpAttributeAtomicAggregate 6
  219. BgpAttributeCommunity 8
  220. BgpAttributeMpReachNlri 14
  221. BgpAttributeLargeCommunity 32
  222. BgpAttributeOtc 35
  223. # or use skip?
  224. # Do I want to save not known attributes?
  225. string :default, length: :attribute_length
  226. end
  227. end
  228. def get_type
  229. return TYPES[attribute_type_code]
  230. end
  231. def is_optional?
  232. return ! attribute_flags[7].zero?
  233. end
  234. def is_transitive?
  235. return ! attribute_flags[6].zero?
  236. end
  237. def is_partial?
  238. return ! attribute_flags[5].zero?
  239. end
  240. def is_extended_length?
  241. return ! attribute_flags[4].zero?
  242. end
  243. def is_valid?
  244. return attribute_flags[0..3].zero?
  245. end
  246. end
  247. class RibEntry < BinData::Record
  248. endian :big
  249. uint16 :peer_index
  250. uint32 :originated_time
  251. uint32 :path_identifier, onlyif: -> { (8..12).include? header.subtype }
  252. uint16 :attributes_length
  253. buffer :attributes, length: :attributes_length do
  254. array read_until: :eof, type: BgpAttribute
  255. end
  256. def get_originated_time
  257. return Time.at(originated_time)
  258. end
  259. end
  260. class RibUnicast < BinData::Record
  261. endian :big
  262. uint32 :sequence_number
  263. uint8 :prefix_length
  264. # This violates RFC6396 4.3.2
  265. # "The value of trailing bits is irrelevant."
  266. # So I would need a kind of bit string
  267. # Maybe implement later?
  268. string :prefix, length: -> { (prefix_length.to_i / 8) + ((prefix_length.to_i % 8).zero? ? 0 : 1) }
  269. uint16 :entry_count
  270. array :rib_entries, read_until: -> { index >= entry_count - 1 }, type: RibEntry
  271. end
  272. class RibIPv4Unicast < RibUnicast
  273. def get_prefix
  274. return IPAddr.ntop("#{prefix}#{"\0" * (4 - prefix.length)}")
  275. end
  276. end
  277. class RibIPv6Unicast < RibUnicast
  278. def get_prefix
  279. return IPAddr.ntop("#{prefix}#{"\0" * (16 - prefix.length)}")
  280. end
  281. end
  282. class MrtRecord < BinData::Record
  283. CommonHeader :header
  284. buffer :data, length: -> { header.length1 } do
  285. choice selection: -> { [header.type1, header.subtype] } do
  286. PeerIndexTable [13, 1] # TableDumpV2 (13) -> PeerIndexTable (1)
  287. RibIPv4Unicast [13, 2] # TableDumpV2 (13) -> RibIPv4Unicast (2)
  288. RibIPv6Unicast [13, 4] # TableDumpV2 (13) -> RibIPv6Unicast (4)
  289. RibIPv4Unicast [13, 8] # TableDumpV2 (13) -> RibIPv4UnicastAddpath (8)
  290. RibIPv6Unicast [13, 10] # TableDumpV2 (13) -> RivIPv6UnicastAddpath(10)
  291. end
  292. end
  293. end
  294. class MrtFile < BinData::Record
  295. array :data, read_until: :eof, type: MrtRecord
  296. end
  297. class Parser
  298. attr_reader :result, :transformed_result
  299. def initialize iostream
  300. @iostream = iostream
  301. end
  302. def parse!
  303. @result = MrtFile.read @iostream
  304. return self
  305. end
  306. def transform_record dump
  307. new_dump = {}
  308. new_dump[:type] = dump.header.get_type
  309. new_dump[:subtype] = dump.header.get_subtype
  310. new_dump[:time] = dump.header.get_type
  311. case new_dump[:type]
  312. when :TABLE_DUMP_V2
  313. case new_dump[:subtype]
  314. when :PEER_INDEX_TABLE
  315. new_dump[:collector_bgp_id] = IPAddr.new(dump.data.get_collector_router_id)
  316. new_dump[:table] = dump.data.view_name
  317. new_dump[:peers] = []
  318. dump.data.peer_entries.each do |peer|
  319. new_peer = {}
  320. new_peer[:router_id] = IPAddr.new(peer.get_peer_router_id)
  321. new_peer[:address] = IPAddr.new(peer.get_peer_ip_address)
  322. new_peer[:asn] = peer.peer_as
  323. new_dump[:peers] << new_peer
  324. end
  325. when :RIB_IPV6_UNICAST, :RIB_IPV6_UNICAST_ADDPATH, :RIB_IPV4_UNICAST, :RIB_IPV4_UNICAST_ADDPATH
  326. new_dump[:sequence_number] = dump.data.sequence_number
  327. new_dump[:prefix] = IPAddr.new(dump.data.get_prefix)
  328. new_dump[:prefix].prefix = dump.data.prefix_length.to_i
  329. new_dump[:rib_entries] = []
  330. dump.data.rib_entries.each do |rib|
  331. new_rib = {}
  332. new_rib[:peer_index] = rib.peer_index
  333. new_rib[:time] = rib.get_originated_time
  334. new_rib[:attributes] = []
  335. rib.attributes.each do |attr|
  336. new_attr = {}
  337. new_attr[:type] = attr.get_type
  338. # Skip invalid attributes
  339. if attr.is_valid?
  340. # Skip unknown attributes
  341. case new_attr[:type]
  342. when :ORIGIN
  343. new_attr[:origin] = attr.attribute.origin
  344. new_rib[:attributes] << new_attr
  345. when :AS_PATH
  346. new_attr[:as_path_type] = attr.attribute.get_type
  347. new_attr[:as_path] = attr.attribute.path_segment_value.to_a
  348. new_rib[:attributes] << new_attr
  349. when :LOCAL_PREF
  350. new_attr[:local_pref] = attr.attribute.local_pref
  351. new_rib[:attributes] << new_attr
  352. when :COMMUNITIES
  353. new_attr[:communities] = []
  354. attr.attribute.communities.each do |comm|
  355. new_attr[:communities] << [comm.asn, comm.value1]
  356. end
  357. new_rib[:attributes] << new_attr
  358. when :LARGE_COMMUNITY
  359. new_attr[:large_communities] = []
  360. attr.attribute.large_communities.each do |comm|
  361. new_attr[:large_communities] << [comm.global_administrator, comm.local_data_part_1, comm.local_data_part_2]
  362. end
  363. new_rib[:attributes] << new_attr
  364. when :MP_REACH_NLRI
  365. new_attr[:prefix] = IPAddr.new(attr.attribute.get_prefix)
  366. new_rib[:attributes] << new_attr
  367. when :MULTI_EXIT_DISC
  368. new_rib[:med] = attr.attribute.med
  369. new_rib[:attributes] << new_attr
  370. when :ATOMIC_AGGREGATE
  371. new_rib[:atomic_aggregate] = true
  372. new_rib[:attributes] << new_attr
  373. when :OTC
  374. new_rib[:otc] = attr.attribute.otc
  375. new_rib[:attributes] << new_attr
  376. when :NEXT_HOP
  377. new_rib[:nexthop] = IPAddr.new(attr.attribute.get_nexthop)
  378. new_rib[:attributes] << new_attr
  379. end
  380. end
  381. end
  382. new_dump[:rib_entries] = new_rib
  383. end
  384. else
  385. raise "Not supported subtype: #{new_dump[:subtype].to_s.dump}"
  386. end
  387. else
  388. raise "Not supported type: #{new_dump[:type].to_s.dump}"
  389. end
  390. return new_dump
  391. end
  392. def transform!
  393. res = []
  394. @result.data.each do |dump|
  395. res << transform_record(dump)
  396. end
  397. @transformed_result = res
  398. return self
  399. end
  400. end
  401. end