--- A library supporting parsing and generating a limited subset of the Cisco' EIGRP packets. -- -- @author "Hani Benhabiles" -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html -- Version 0.1 -- 19/07/2012 - First version. local bin = require "bin" local table = require "table" local stdnse = require "stdnse" local ipOps = require "ipOps" local packet = require "packet" _ENV = stdnse.module("eigrp", stdnse.seeall) -- TLV Type constants TLV = { PARAM = 0x0001, AUTH = 0x0002, SEQ = 0x0003, SWVER = 0x0004, MSEQ = 0x0005, STUB = 0x0006, TERM = 0x0007, TIDLIST = 0x0008, REQ = 0x0101, INT = 0x0102, EXT = 0x0103, COM = 0x0104, INT6 = 0x0402, EXT6 = 0x0403, COM6 = 0x0404, } -- External protocols constants EXT_PROTO = { NULL = 0x00, IGRP = 0x01, EIGRP = 0x02, Static = 0x03, RIP = 0x04, HELLO = 0x05, OSPF = 0x06, ISIS = 0x07, EGP = 0x08, BGP = 0x09, IDRP = 0x10, Connected = 0x11, } -- Packets opcode constants OPCODE = { UPDATE = 0x01, RESERVED = 0x02, QUERY = 0x03, REPLY = 0x04, HELLO = 0x05, } -- The EIGRP Class EIGRP = { --- Creates a new instance of EIGRP class. -- @param opcode integer Opcode. Defaults to 5 (Hello) -- @param as integer Autonomous System. Defaults to 0. -- @param routerid integer virtual router ID. defaults to 0. -- @param flags integer flags field value. Defaults to 0. -- @param seq integer sequence value. Defaults to 0. -- @param ack integer acknowledge value. Defaults to 0. -- @param Checksum integer EIGRP packet checksum. Calculated automatically -- if not manually set. -- @param Table TLVs table. -- @return o Instance of EIGRP new = function(self, opcode, as, routerid, flags, seq, ack, checksum, tlvs) local o = { ver = 2, opcode = opcode or TLV.HELLO, as = as or 0, routerid = routerid or 0, flags = flags or 0, seq = seq or 0x00, ack = ack or 0x00, checksum = checksum, tlvs = tlvs or {}, } setmetatable(o, self) self.__index = self return o end, --- Parses a raw eigrp packet and returns a structred response. -- @param eigrp_raw string EIGRP Raw packet. -- @return response table Structured eigrp packet. parse = function(eigrp_raw) if type(eigrp_raw) ~= 'string' then stdnse.print_debug("eigrp.lua: parse input should be string.") return end if #eigrp_raw < 20 then stdnse.print_debug("eigrp.lua: raw packet size lower then 20.") return end local tlv local eigrp_packet = {} local index = 1 index, eigrp_packet.ver = bin.unpack(">C", eigrp_raw, index) index, eigrp_packet.opcode = bin.unpack(">C", eigrp_raw, index) index, eigrp_packet.checksum = bin.unpack(">S", eigrp_raw, index) index, eigrp_packet.flags = bin.unpack(">I", eigrp_raw, index) index, eigrp_packet.seq = bin.unpack(">I", eigrp_raw, index) index, eigrp_packet.ack = bin.unpack(">I", eigrp_raw, index) index, eigrp_packet.routerid = bin.unpack(">S", eigrp_raw, index) index, eigrp_packet.as = bin.unpack(">S", eigrp_raw, index) eigrp_packet.tlvs = {} while index < #eigrp_raw do tlv = {} index, tlv.type = bin.unpack(">S", eigrp_raw, index) index, tlv.length = bin.unpack(">S", eigrp_raw, index) if tlv.length == 0x00 then -- In case someone wants to DoS us :) stdnse.print_debug("eigrp.lua: stopped parsing due to null TLV length.") break end if tlv.type == TLV.PARAM then -- Parameters local k = {} index, k[1], k[2], k[3], k[4], k[5], k[6] = bin.unpack(">CCCCCC", eigrp_raw, index) index, tlv.htime = bin.unpack(">S", eigrp_raw, index) index = index + tlv.length - 12 elseif tlv.type == TLV.AUTH then index, tlv.authtype = bin.unpack(">S", eigrp_raw, index) index, tlv.authlen = bin.unpack(">S", eigrp_raw, index) index, tlv.keyid = bin.unpack(">I", eigrp_raw, index) index, tlv.keyseq = bin.unpack(">I", eigrp_raw, index) -- Null pad == tlv.length - What was already parsed - authlen index, tlv.digest = bin.unpack(">S", eigrp_raw, index + (tlv.length - tlv.authlen - index + 1)) elseif tlv.type == TLV.SEQ then -- Sequence index, tlv.addlen = bin.unpack(">S", eigrp_raw, index) index, tlv.address = bin.unpack("C", eigrp_raw, index) index, tlv.minv = bin.unpack(">C", eigrp_raw, index) index, tlv.majtlv = bin.unpack(">C", eigrp_raw, index) index, tlv.mintlv = bin.unpack(">C", eigrp_raw, index) index = index + tlv.length - 8 elseif tlv.type == TLV.MSEQ then -- Next Multicast Sequence index, tlv.mseq = bin.unpack(">I", eigrp_raw, index) index = index + tlv.length - 8 elseif tlv.type == TLV.STUB then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) index = index + tlv.length - 4 elseif tlv.type == TLV.TERM then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) index = index + tlv.length - 4 elseif tlv.type == TLV.TIDLIST then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) index = index + tlv.length - 4 elseif tlv.type == TLV.REQ then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) index = index + tlv.length - 4 elseif tlv.type == TLV.INT then -- Internal Route index, tlv.nexth = bin.unpack("S", eigrp_raw, index + 15) -- Destination varies in length -- e.g trailing 0's are omitted -- if length = 29 => destination is 4 bytes -- if length = 28 => destination is 3 bytes -- if length = 27 => destination is 2 bytes -- if length = 26 => destination is 1 byte local dst = {} index, dst[1], dst[2], dst[3], dst[4] = bin.unpack(">C" .. 4 + tlv.length - 29, eigrp_raw, index) for i=2,4 do if not dst[i] then dst[i] = '0' end end tlv.dst = dst[1] .. '.' .. dst[2] .. '.' .. dst[3] .. '.' .. dst[4] elseif tlv.type == TLV.EXT then -- External Route index, tlv.nexth = bin.unpack("I", eigrp_raw, index) index, tlv.tag = bin.unpack(">I", eigrp_raw, index) index, tlv.emetric = bin.unpack(">I", eigrp_raw, index) -- Skip 2 reserved bytes index, tlv.eproto = bin.unpack(">C", eigrp_raw, index + 2) index, tlv.eflags = bin.unpack(">C", eigrp_raw, index) index, tlv.lmetrics = bin.unpack(">L"..2, eigrp_raw, index) index, tlv.mask = bin.unpack(">C", eigrp_raw, index) -- Destination varies in length -- if length = 49 => destination is 4 bytes -- if length = 48 => destination is 3 bytes -- if length = 47 => destination is 2 bytes -- if length = 46 => destination is 1 byte local dst = {} index, dst[1], dst[2], dst[3], dst[4] = bin.unpack(">C" .. 4 + tlv.length - 49, eigrp_raw, index) for i=2,4 do if not dst[i] then dst[i] = '0' end end tlv.dst = dst[1] .. '.' .. dst[2] .. '.' .. dst[3] .. '.' .. dst[4] elseif tlv.type == TLV.COM then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) index = index + tlv.length - 4 elseif tlv.type == TLV.INT6 then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) index = index + tlv.length - 4 elseif tlv.type == TLV.EXT6 then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) index = index + tlv.length - 4 elseif tlv.type == TLV.COM6 then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) index = index + tlv.length - 4 else stdnse.print_debug("eigrp.lua: eigrp.lua: TLV type %d unknown.", tlv.type) index = index + tlv.length - 4 end table.insert(eigrp_packet.tlvs, tlv) end return eigrp_packet end, --- Adds a TLV table to the table of TLVs. -- @param tlv TLV table. addTLV = function(self, tlv) if type(tlv) == 'table' then table.insert(self.tlvs, tlv) else stdnse.print_debug("eigrp.lua: TLV should be a table, not %s", type(tlv)) end end, --- Checks if TLV type is one that should contain routing information. -- @param tlvtype integer TLV type integer to check. -- @return status true if tlvtype is a routing information tlv. isRoutingTLV = function(tlvtype) if tlvtype == 0x101 or tlvtype == 0x102 or tlvtype == 0x103 or tlvtype == 0x104 or tlvtype == 0x402 or tlvtype == 0x403 or tlvtype == 0x404 then return true end end, --- Sets the EIGRP version. -- @param ver integer version to set. setVersion = function(self, ver) self.ver = ver end, --- Sets the EIGRP Packet opcode -- @param opcode integer EIGRP opcode. setOpcode = function(self, opcode) self.opcode = opcode end, --- Sets the EIGRP packet checksum -- @param integer checksum Checksum to set. setChecksum = function(self, checksum) self.checksum = checksum end, --- Sets the EIGRP packet flags field. -- @param flags Flags integer value. setFlags = function(self, flags) self.flags = flags end, --- Sets the EIGRP packet sequence field. -- @param seq EIGRP sequence. setSequence = function(self, seq) self.seq = seq end, --- Sets the EIGRP Packet acknowledge field. -- @param ack EIGRP acknowledge. setAcknowledge = function(self, ack) self.ack = ack end, --- Sets the EIGRP Packet Virtual Router ID. -- @param routerid EIGRP Virtual Router ID. setRouterID = function(self, routerid) self.routerid = routerid end, --- Sets the EIGRP Packet Autonomous System. -- @param as EIGRP A.S. setAS = function(self, as) self.as = as end, --- Sets the EIGRP Packet tlvs -- @param tlvs table of EIGRP tlvs. setTlvs = function(self, tlvs) self.tlvs = tlvs end, --- Converts the request to a string suitable to be sent over a socket. -- @return data string containing the complete request to send over the socket __tostring = function(self) local data = bin.pack(">C", self.ver) -- Version 2 data = data .. bin.pack(">C", self.opcode) -- Opcode: Hello -- If checksum not manually. -- set to 0, then calculate it later if self.checksum then data = data .. bin.pack(">S", self.checksum) else data = data .. bin.pack(">S", 0x0000) -- Calculated later. end data = data .. bin.pack(">I", self.flags) -- Flags data = data .. bin.pack(">I", self.seq) -- Sequence 0 data = data .. bin.pack(">I", self.ack) -- Acknowledge 0 data = data .. bin.pack(">S", self.routerid) -- Virtual Router ID 0 data = data .. bin.pack(">S", self.as) -- Autonomous system for _, tlv in pairs(self.tlvs) do if tlv.type == TLV.PARAM then data = data .. bin.pack(">S", TLV.PARAM) data = data .. bin.pack(">S", 0x000c) -- Length: 12 data = data .. bin.pack(">CCCCCC", tlv.k[1],tlv.k[2],tlv.k[3], tlv.k[4],tlv.k[5],tlv.k[6]) data = data .. bin.pack(">S", tlv.htime) elseif tlv.type == TLV.AUTH then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.SEQ then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.SWVER then data = data .. bin.pack(">S", TLV.SWVER) data = data .. bin.pack(">S", 0x0008) data = data .. bin.pack(">CC", tonumber(tlv.majv), tonumber(tlv.minv)) data = data .. bin.pack(">CC", tonumber(tlv.majtlv), tonumber(tlv.mintlv)) elseif tlv.type == TLV.MSEQ then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.STUB then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.TERM then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.TIDLIST then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.REQ then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.INT then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.EXT then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.COM then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.INT6 then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.EXT6 then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) elseif tlv.type == TLV.COM6 then -- TODO stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) else stdnse.print_debug("eigrp.lua: TLV type %d unknown.", tlv.type) end end -- In the end, correct the checksum if not manually set if not self.checksum then data = data:sub(1,2) .. bin.pack(">S", packet.in_cksum(data)) .. data:sub(5) end return data end, } return _ENV;