383 lines
13 KiB
Lua
383 lines
13 KiB
Lua
|
--- A library supporting parsing and generating a limited subset of the Cisco' EIGRP packets.
|
||
|
--
|
||
|
-- @author Hani Benhabiles
|
||
|
-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
|
||
|
-- Version 0.1
|
||
|
-- 19/07/2012 - First version.
|
||
|
|
||
|
local table = require "table"
|
||
|
local stdnse = require "stdnse"
|
||
|
local strbuf = require "strbuf"
|
||
|
local string = require "string"
|
||
|
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 structured 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.debug1("eigrp.lua: parse input should be string.")
|
||
|
return
|
||
|
end
|
||
|
if #eigrp_raw < 20 then
|
||
|
stdnse.debug1("eigrp.lua: raw packet size lower then 20.")
|
||
|
return
|
||
|
end
|
||
|
local tlv
|
||
|
local eigrp_packet = {}
|
||
|
local index = 1
|
||
|
eigrp_packet.ver,
|
||
|
eigrp_packet.opcode,
|
||
|
eigrp_packet.checksum,
|
||
|
eigrp_packet.flags,
|
||
|
eigrp_packet.seq,
|
||
|
eigrp_packet.ack,
|
||
|
eigrp_packet.routerid,
|
||
|
eigrp_packet.as, index = string.unpack(">BBI2I4I4I4I2I2", eigrp_raw, index)
|
||
|
eigrp_packet.tlvs = {}
|
||
|
while index < #eigrp_raw do
|
||
|
tlv = {}
|
||
|
tlv.type, tlv.length, index = string.unpack(">I2I2", eigrp_raw, index)
|
||
|
if tlv.length == 0x00 then
|
||
|
-- In case someone wants to DoS us :)
|
||
|
stdnse.debug1("eigrp.lua: stopped parsing due to null TLV length.")
|
||
|
break
|
||
|
end
|
||
|
-- TODO: These padding calculations seem suspect, especially the ones
|
||
|
-- that assume a static length for a variable-length field like TLV.SEQ
|
||
|
if tlv.type == TLV.PARAM then
|
||
|
-- Parameters
|
||
|
local k = {}
|
||
|
k[1], k[2], k[3], k[4], k[5], k[6], tlv.htime, index = string.unpack(">BBBBBBI2", eigrp_raw, index)
|
||
|
tlv.k = k
|
||
|
index = index + tlv.length - 12
|
||
|
elseif tlv.type == TLV.AUTH then
|
||
|
tlv.authtype,
|
||
|
tlv.authlen,
|
||
|
tlv.keyid,
|
||
|
tlv.keyseq, index = string.unpack(">I2I2I4I4", eigrp_raw, index)
|
||
|
-- Null pad == tlv.length - What was already parsed - authlen
|
||
|
tlv.digest, index = string.unpack(">I2", eigrp_raw, index + (tlv.length - tlv.authlen - index + 1))
|
||
|
elseif tlv.type == TLV.SEQ then
|
||
|
-- Sequence
|
||
|
tlv.address, index = string.unpack(">s2", eigrp_raw, index)
|
||
|
tlv.address = ipOps.str_to_ip(tlv.address)
|
||
|
index = index + tlv.length - 7
|
||
|
elseif tlv.type == TLV.SWVER then
|
||
|
-- Software version
|
||
|
tlv.majv,
|
||
|
tlv.minv,
|
||
|
tlv.majtlv,
|
||
|
tlv.mintlv, index = string.unpack(">BBBB", eigrp_raw, index)
|
||
|
index = index + tlv.length - 8
|
||
|
elseif tlv.type == TLV.MSEQ then
|
||
|
-- Next Multicast Sequence
|
||
|
tlv.mseq, index = string.unpack(">I4", eigrp_raw, index)
|
||
|
index = index + tlv.length - 8
|
||
|
elseif tlv.type == TLV.STUB then
|
||
|
-- TODO
|
||
|
stdnse.debug1("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.debug1("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.debug1("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.debug1("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
|
||
|
tlv.nexth, index = string.unpack(">I4", eigrp_raw, index)
|
||
|
tlv.nexth = ipOps.fromdword(tlv.nexth)
|
||
|
tlv.mask, index = string.unpack(">I2", 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 = {0,0,0,0}
|
||
|
for i = 1, (4 + tlv.length - 29) do
|
||
|
dst[i], index = string.unpack("B", eigrp_raw, index)
|
||
|
end
|
||
|
tlv.dst = table.concat(dst, '.')
|
||
|
elseif tlv.type == TLV.EXT then
|
||
|
-- External Route
|
||
|
tlv.nexth,
|
||
|
tlv.orouterid,
|
||
|
tlv.oas,
|
||
|
tlv.tag,
|
||
|
tlv.emetric,
|
||
|
-- Skip 2 reserved bytes
|
||
|
tlv.eproto,
|
||
|
tlv.eflags,
|
||
|
tlv.lmetrics,
|
||
|
tlv.mask, index = string.unpack(">I4I4I4I4I4xxBBc16B", eigrp_raw, index)
|
||
|
tlv.nexth = ipOps.fromdword(tlv.nexth)
|
||
|
tlv.orouterid = ipOps.fromdword(tlv.orouterid)
|
||
|
-- 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 = {0,0,0,0}
|
||
|
for i = 1, (4 + tlv.length - 49) do
|
||
|
dst[i], index = string.unpack("B", eigrp_raw, index)
|
||
|
end
|
||
|
tlv.dst = table.concat(dst, '.')
|
||
|
elseif tlv.type == TLV.COM then
|
||
|
-- TODO
|
||
|
stdnse.debug1("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.debug1("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.debug1("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.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
index = index + tlv.length - 4
|
||
|
else
|
||
|
stdnse.debug1("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.debug1("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 = strbuf.new()
|
||
|
data = data .. string.pack(">BBI2I4I4I4I2I2",
|
||
|
self.ver, -- Version 2
|
||
|
self.opcode, -- Opcode: Hello
|
||
|
self.checksum or 0, -- Calculated later.
|
||
|
self.flags, -- Flags
|
||
|
self.seq, -- Sequence 0
|
||
|
self.ack, -- Acknowledge 0
|
||
|
self.routerid, -- Virtual Router ID 0
|
||
|
self.as) -- Autonomous system
|
||
|
|
||
|
for _, tlv in pairs(self.tlvs) do
|
||
|
if tlv.type == TLV.PARAM then
|
||
|
data = data .. string.pack(">I2I2 BBBBBB I2",
|
||
|
TLV.PARAM,
|
||
|
12, -- Length
|
||
|
tlv.k[1], tlv.k[2], tlv.k[3], tlv.k[4], tlv.k[5], tlv.k[6],
|
||
|
tlv.htime)
|
||
|
elseif tlv.type == TLV.AUTH then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.SEQ then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.SWVER then
|
||
|
data = data .. string.pack(">I2I2 BB BB",
|
||
|
TLV.SWVER,
|
||
|
0x0008,
|
||
|
tonumber(tlv.majv), tonumber(tlv.minv),
|
||
|
tonumber(tlv.majtlv), tonumber(tlv.mintlv))
|
||
|
elseif tlv.type == TLV.MSEQ then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.STUB then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.TERM then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.TIDLIST then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.REQ then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.INT then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.EXT then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.COM then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.INT6 then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.EXT6 then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
elseif tlv.type == TLV.COM6 then
|
||
|
-- TODO
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
|
||
|
else
|
||
|
stdnse.debug1("eigrp.lua: TLV type %d unknown.", tlv.type)
|
||
|
end
|
||
|
end
|
||
|
data = strbuf.dump(data)
|
||
|
-- In the end, correct the checksum if not manually set
|
||
|
if not self.checksum then
|
||
|
data = data:sub(1,2) .. string.pack(">I2", packet.in_cksum(data)) .. data:sub(5)
|
||
|
end
|
||
|
return data
|
||
|
end,
|
||
|
}
|
||
|
|
||
|
return _ENV;
|