--- -- A minimalistic OSPF library, currently supporting IPv4 and the following -- OSPF message types: HELLO -- -- The library consists of an OSPF class that contains code to handle OSPFv2 packets. -- -- @author "Patrik Karlsson <patrik@cqure.net>" -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html local bin = require "bin" local bit = require "bit" local math = require "math" local stdnse = require "stdnse" local table = require "table" local ipOps = require "ipOps" local packet = require "packet" _ENV = stdnse.module("ospf", stdnse.seeall) -- The OSPF class. OSPF = { -- Message Type constants Message = { HELLO = 1, DB_DESCRIPTION = 2, LS_UPDATE = 4, }, LSUpdate = { }, Header = { size = 24, new = function(self, type, area_id, router_id, auth_type, auth_data) local o = { ver = 2, type = type, length = 0, router_id = router_id or 0, area_id = area_id or 0, chksum = 0, auth_type = auth_type or 0, auth_data = auth_data or {}, } setmetatable(o, self) self.__index = self return o end, parse = function(data) local header = OSPF.Header:new() local pos pos, header.ver, header.type, header.length = bin.unpack(">CCS", data) assert( header.ver == 2, "Invalid OSPF version detected") pos, header.router_id, header.area_id, header.chksum, header.auth_type = bin.unpack("<I>ISS", data, pos) -- No authentication if header.auth_type == 0x00 then header.auth_data.password = nil -- Clear text password elseif header.auth_type == 0x01 then pos, header.auth_data.password = bin.unpack(">A8", data, pos) -- MD5 hash authentication elseif header.auth_type == 0x02 then local _ _, header.auth_data.keyid = bin.unpack(">C", data, pos+2) _, header.auth_data.length = bin.unpack(">C", data, pos+3) _, header.auth_data.seq = bin.unpack(">C", data, pos+4) _, header.auth_data.hash = bin.unpack(">H"..header.auth_data.length, data, header.length+1) else -- Shouldn't happen stdnse.print_debug("Unknown authentication type " .. header.auth_type) return nil end header.router_id = ipOps.fromdword(header.router_id) return header end, --- Sets the OSPF Area ID -- @param areaid Area ID. setAreaID = function(self, areaid) self.area_id = (type(areaid) == "number") and areaid or ipOps.todword(areaid) end, --- Sets the OSPF Router ID -- @param router_id Router ID. setRouterId = function(self, router_id) self.router_id = ipOps.todword(router_id) end, --- Sets the OSPF Packet length -- @param length Packet length. setLength = function(self, length) self.length = self.size + length end, __tostring = function(self) local hdr = bin.pack(">CCS", self.ver, self.type, self.length ) hdr = hdr .. bin.pack(">IISS", self.router_id, self.area_id, self.chksum, self.auth_type) if self.auth_type == 0x01 then hdr = hdr .. bin.pack(">A8", self.auth_data.password) elseif self.auth_type == 0x02 then hdr = hdr .. bin.pack(">A".. self.auth_data.length, self.auth_data.hash) end return hdr end, }, Hello = { new = function(self) local o = { header = OSPF.Header:new(OSPF.Message.HELLO), options = 0x02, prio = 0, interval = 10, router_dead_interval = 40 } setmetatable(o, self) self.__index = self return o end, --- Adds a neighbor to the list of neighbors. -- @param neighbor IP Address of the neighbor. addNeighbor = function(self, neighbor) table.insert(self.neighbors, bin.pack("<I", ipOps.todword(neighbor))) end, --- Sets the OSPF netmask. -- @param netmask Netmask in A.B.C.D setNetmask = function(self, netmask) self.netmask = ipOps.todword(netmask) end, --- Sets the OSPF designated Router. -- @param router IP address of the designated router. setDesignatedRouter = function(self, router) self.DR = ipOps.todword(router) end, --- Sets the OSPF backup Router. -- @param router IP Address of the backup router. setBackupRouter = function(self, router) self.BDR = ipOps.todword(router) end, __tostring = function(self) self.neighbors = self.neighbors or {} local function tostr() local data = bin.pack(">ISCCIII", self.netmask, self.interval, self.options, self.prio, self.router_dead_interval, self.DR, self.BDR) for _, n in ipairs(self.neighbors) do data = data .. bin.pack("<I", n) end self.header:setLength(#data) return tostring(self.header) .. data end local data = tostr() self.header.chksum = packet.in_cksum(data:sub(1,16) .. data:sub(19)) return tostr() end, parse = function(data) local hello = OSPF.Hello:new() local pos = OSPF.Header.size + 1 hello.header = OSPF.Header.parse(data) assert( #data >= hello.header.length, "OSPF packet too short") pos, hello.netmask, hello.interval, hello.options, hello.prio, hello.router_dead_interval, hello.DR, hello.BDR = bin.unpack("<ISCCIII", data, pos) hello.netmask = ipOps.fromdword(hello.netmask) hello.DR = ipOps.fromdword(hello.DR) hello.BDR = ipOps.fromdword(hello.BDR) if ( ( #data - pos + 1 ) % 4 ~= 0 ) then stdnse.print_debug(2, "Unexpected OSPF packet length, aborting ...") return end local neighbor_count = ( #data - pos + 1 ) / 4 local neighbor hello.neighbors = {} for i=1, neighbor_count do pos, neighbor = bin.unpack("<I", data, pos) neighbor = ipOps.fromdword(neighbor) table.insert(hello.neighbors, neighbor) end return hello end, }, DBDescription = { LSAHeader = { new = function(self) local o = { age = 0, options = 0, type = 1, id = 0, adv_router = 0, sequence = 0, checksum = 0, length = 0, } setmetatable(o, self) self.__index = self return o end, }, new = function(self) local o = { header = OSPF.Header:new(OSPF.Message.DB_DESCRIPTION), mtu = 1500, options = 2, -- external routing capability init = true, more = true, master = true, sequence = math.random(123456789) } setmetatable(o, self) self.__index = self return o end, __tostring = function(self) local function tostr() local flags = 0 if ( self.init ) then flags = flags + 4 end if ( self.more ) then flags = flags + 2 end if ( self.master) then flags= flags + 1 end local data = bin.pack(">SCCI", self.mtu, self.options, flags, self.sequence) self.header:setLength(#data) return tostring(self.header) .. data end local data = tostr() self.header.chksum = packet.in_cksum(data:sub(1,16) .. data:sub(19)) return tostr() end, parse = function(data) local desc = OSPF.DBDescription:new() local pos = OSPF.Header.size + 1 desc.header = OSPF.Header.parse(data) assert( #data == desc.header.length, "OSPF packet too short") local flags = 0 pos, desc.mtu, desc.options, flags, desc.sequence = bin.unpack(">SCCI", data, pos) desc.init = ( bit.band(flags, 4) == 4 ) desc.more = ( bit.band(flags, 2) == 2 ) desc.master = ( bit.band(flags, 1) == 1 ) if ( desc.init or not(desc.more) ) then return desc end return desc end, }, Response = { parse = function(data) local pos, ver, ospf_type = bin.unpack("CC", data) if ( ospf_type == OSPF.Message.HELLO ) then return OSPF.Hello.parse( data ) elseif( ospf_type == OSPF.Message.DB_DESCRIPTION ) then return OSPF.DBDescription.parse(data) end return end, } } return _ENV;