diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
commit | 0d47952611198ef6b1163f366dc03922d20b1475 (patch) | |
tree | 3d840a3b8c0daef0754707bfb9f5e873b6b1ac13 /nselib/ospf.lua | |
parent | Initial commit. (diff) | |
download | nmap-0d47952611198ef6b1163f366dc03922d20b1475.tar.xz nmap-0d47952611198ef6b1163f366dc03922d20b1475.zip |
Adding upstream version 7.94+git20230807.3be01efb1+dfsg.upstream/7.94+git20230807.3be01efb1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'nselib/ospf.lua')
-rw-r--r-- | nselib/ospf.lua | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/nselib/ospf.lua b/nselib/ospf.lua new file mode 100644 index 0000000..302079c --- /dev/null +++ b/nselib/ospf.lua @@ -0,0 +1,534 @@ +--- +-- A limited OSPF (Open Shortest Path First routing protocol) library, currently supporting IPv4 and the following +-- OSPF message types: HELLO, DB_DESCRIPTION, LS_REQUEST, LS_UPDATE +-- +-- The library consists of an OSPF class that contains code to handle OSPFv2 packets. +-- +-- @author Patrik Karlsson <patrik@cqure.net> +-- @author Emiliano Ticci <emiticci@gmail.com> +-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html + +local math = require "math" +local stdnse = require "stdnse" +local string = require "string" +local table = require "table" +local ipOps = require "ipOps" +local packet = require "packet" +_ENV = stdnse.module("ospf", stdnse.seeall) + +local have_ssl, openssl = pcall(require, "openssl") + +-- The OSPF class. +OSPF = { + + -- Message Type constants + Message = { + HELLO = 1, + DB_DESCRIPTION = 2, + LS_REQUEST = 3, + LS_UPDATE = 4, + }, + + 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 + header.ver, header.type, header.length, pos = string.unpack(">BBI2", data) + assert( header.ver == 2, "Invalid OSPF version detected") + + header.router_id, header.area_id, header.chksum, header.auth_type, pos + = string.unpack(">I4 I4 I2 I2", data, pos) + + -- No authentication + if header.auth_type == 0x00 then + header.auth_data.password = nil + -- Clear text password + elseif header.auth_type == 0x01 then + header.auth_data.password, pos = string.unpack("c8", data, pos) + -- MD5 hash authentication + elseif header.auth_type == 0x02 then + header.auth_data.keyid, header.auth_data.length, header.auth_data.seq = string.unpack(">BB I4", data, pos+2) + local hash = stdnse.tohex(string.sub(data, header.length+1, header.length+1+header.auth_data.length)) + header.auth_data.hash = hash + else + -- Shouldn't happen + stdnse.debug1("Unknown authentication type %s", 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 = 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 auth + if self.auth_type == 0x00 then + auth = string.rep("\0", 8) + elseif self.auth_type == 0x01 then + auth = self.auth_data.password + elseif self.auth_type == 0x02 then + auth = string.pack(">I2 BB I4", 0, self.auth_data.keyid, self.auth_data.length, self.auth_data.seq) + end + local hdr = string.pack(">BB I2 I4 I4 I2 I2", + self.ver, self.type, + self.length, + ipOps.todword(self.router_id), + self.area_id, + self.chksum, + self.auth_type + ) .. auth + 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, + neighbors = {}, + DR = "0.0.0.0", + BDR = "0.0.0.0", + } + 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, neighbor) + end, + + --- Sets the OSPF netmask. + -- @param netmask Netmask in A.B.C.D + setNetmask = function(self, netmask) + if netmask then + self.netmask = netmask + end + end, + + --- Sets the OSPF designated Router. + -- @param router IP address of the designated router. + setDesignatedRouter = function(self, router) + if router then + self.DR = router + end + end, + + --- Sets the OSPF backup Router. + -- @param router IP Address of the backup router. + setBackupRouter = function(self, router) + if router then + self.BDR = router + end + end, + + __tostring = function(self) + self.neighbors = self.neighbors or {} + local function tostr() + local data = { + string.pack(">I4 I2 BB I4 I4 I4", + ipOps.todword(self.netmask), + self.interval, + self.options, self.prio, + self.router_dead_interval, + ipOps.todword(self.DR), + ipOps.todword(self.BDR)) + } + for _, n in ipairs(self.neighbors) do + data[#data+1] = string.pack(">I4", ipOps.todword(n)) + end + data = table.concat(data) + self.header:setLength(#data) + return tostring(self.header) .. data .. (self.header.auth_data.hash or "") + end + local data = tostr() + if have_ssl and self.header.auth_type == 0x02 then + self.header.auth_data.key = self.header.auth_data.key .. string.rep("\0", 16 - #self.header.auth_data.key) + self.header.auth_data.hash = openssl.md5(data .. self.header.auth_data.key) + else + self.header.chksum = packet.in_cksum(data:sub(1,16) .. data:sub(25)) + end + 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") + hello.netmask, hello.interval, hello.options, hello.prio, + hello.router_dead_interval, hello.DR, + hello.BDR, pos = string.unpack(">I4 I2 BB I4 I4 I4", 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.debug2("Unexpected OSPF packet length, aborting ...") + return + end + + local neighbor_count = ( hello.header.length - pos + 1 ) / 4 + local neighbor + + hello.neighbors = {} + for i=1, neighbor_count do + neighbor, pos = string.unpack(">I4", data, pos) + neighbor = ipOps.fromdword(neighbor) + table.insert(hello.neighbors, neighbor) + end + return hello + end, + + }, + + LSA = { + Header = { + size = 20, + 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, + + parse = function(data) + local lsa_h = OSPF.LSA.Header:new() + local pos = 1 + lsa_h.age, lsa_h.options, lsa_h.type, lsa_h.id, lsa_h.adv_router, + lsa_h.sequence, lsa_h.checksum, lsa_h.length, pos = string.unpack(">I2 BB I4 I4 c4 c2 I2", data, pos) + + lsa_h.id = ipOps.fromdword(lsa_h.id) + lsa_h.adv_router = ipOps.fromdword(lsa_h.adv_router) + lsa_h.sequence = stdnse.tohex(lsa_h.sequence) + lsa_h.checksum = stdnse.tohex(lsa_h.checksum) + return lsa_h + end, + + }, + + Link = { + new = function(self) + local o = { + id = 0, + data = 0, + type = 2, + num_metrics = 0, + metric = 10, + } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local lsa_link = OSPF.LSA.Link:new() + local pos = 1 + lsa_link.id, lsa_link.data, lsa_link.type, + lsa_link.num_metrics, lsa_link.metric, pos = string.unpack(">I4 I4 BB I2", data, pos) + lsa_link.id = ipOps.fromdword(lsa_link.id) + lsa_link.data = ipOps.fromdword(lsa_link.data) + return lsa_link + end, + }, + + Router = { + new = function(self) + local o = { + header = OSPF.LSA.Header:new(), + flags = 0, + num_links = 0, + links = {}, + } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local router = OSPF.LSA.Router:new() + local pos = OSPF.LSA.Header.size + 1 + router.header = OSPF.LSA.Header.parse(data) + router.flags, router.num_links, pos = string.unpack(">B x I2", data, pos) + + while ( pos < router.header.length ) do + table.insert(router.links, OSPF.LSA.Link.parse(data:sub(pos, pos + 12))) + pos = pos + 12 + end + + return router + end, + }, + + ASExternal = { + new = function(self) + local o = { + header = OSPF.LSA.Header:new(), + netmask = 0, + ext_type = 1, + metric = 1, + fw_address = 0, + ext_tag = 0, + } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local as_ext = OSPF.LSA.ASExternal:new() + local pos = OSPF.LSA.Header.size + 1 + as_ext.header = OSPF.LSA.Header.parse(data) + + as_ext.netmask, as_ext.metric, as_ext.fw_address, as_ext.ext_tag, pos = string.unpack(">I4 I4 I4 I4", data, pos) + as_ext.netmask = ipOps.fromdword(as_ext.netmask) + as_ext.ext_type = 1 + ((as_ext.metric & 0xFF000000) >> 31) + as_ext.metric = as_ext.metric & 0x00FFFFFF + as_ext.fw_address = ipOps.fromdword(as_ext.fw_address) + + return as_ext + end, + }, + + parse = function(data) + local header = OSPF.LSA.Header.parse(data) + if header.type == 1 then + return OSPF.LSA.Router.parse(data) + elseif header.type == 5 then + return OSPF.LSA.ASExternal.parse(data) + end + return header.length + end, + }, + + DBDescription = { + + 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), + lsa_headers = {} + } + 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 = string.pack(">I2 BB I4", self.mtu, self.options, flags, self.sequence) + self.header:setLength(#data) + return tostring(self.header) .. data .. (self.header.auth_data.hash or "") + end + local data = tostr() + if have_ssl and self.header.auth_type == 0x02 then + self.header.auth_data.key = self.header.auth_data.key .. string.rep("\0", 16 - #self.header.auth_data.key) + self.header.auth_data.hash = openssl.md5(data .. self.header.auth_data.key) + else + self.header.chksum = packet.in_cksum(data:sub(1,16) .. data:sub(25)) + end + 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 + desc.mtu, desc.options, flags, desc.sequence, pos = string.unpack(">I2 BB I4", data, pos) + + desc.init = (flags & 4) == 4 + desc.more = (flags & 2) == 2 + desc.master = (flags & 1) == 1 + + while ( pos < desc.header.length ) do + table.insert(desc.lsa_headers, OSPF.LSA.Header.parse(data:sub(pos, pos + 20))) + pos = pos + 20 + end + + if ( desc.init or not(desc.more) ) then + return desc + end + + return desc + end, + + }, + + LSRequest = { + new = function(self) + local o = { + header = OSPF.Header:new(OSPF.Message.LS_REQUEST), + ls_requests = {}, + } + setmetatable(o, self) + self.__index = self + return o + end, + + --- Adds a request to the list of requests. + -- @param type LS Type. + -- @param id Link State ID + -- @param adv_router Advertising Router + addRequest = function(self, type, id, adv_router) + local request = { + type = type, + id = id, + adv_router = adv_router + } + table.insert(self.ls_requests, request) + end, + + __tostring = function(self) + local function tostr() + local data = {} + for _, req in ipairs(self.ls_requests) do + data[#data+1] = string.pack(">I4 I4 I4", req.type, ipOps.todword(req.id), ipOps.todword(req.adv_router)) + end + data = table.concat(data) + self.header:setLength(#data) + return tostring(self.header) .. data .. (self.header.auth_data.hash or "") + end + local data = tostr() + if have_ssl and self.header.auth_type == 0x02 then + self.header.auth_data.key = self.header.auth_data.key .. string.rep("\0", 16 - #self.header.auth_data.key) + self.header.auth_data.hash = openssl.md5(data .. self.header.auth_data.key) + else + self.header.chksum = packet.in_cksum(data:sub(1,16) .. data:sub(25)) + end + return tostr() + end, + + parse = function(data) + local ls_req = OSPF.LSRequest:new() + local pos = OSPF.Header.size + 1 + ls_req.header = OSPF.Header.parse(data) + assert( #data >= ls_req.header.length, "OSPF packet too short") + + while ( pos < #data ) do + local req = {} + req.type, req.id, req.adv_router, pos = string.unpack(">I4 I4 I4", data, pos) + table.insert(ls_req.ls_requests, req) + end + + return ls_req + end, + }, + + LSUpdate = { + new = function(self) + local o = { + header = OSPF.Header:new(OSPF.Message.LS_UPDATE), + num_lsas = 0, + lsas = {}, + } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local lsu = OSPF.LSUpdate:new() + local pos = OSPF.Header.size + 1 + lsu.header = OSPF.Header.parse(data) + assert( #data >= lsu.header.length, "OSPF packet too short") + + lsu.num_lsas, pos = string.unpack(">I4", data, pos) + + while ( pos < lsu.header.length ) do + local lsa = OSPF.LSA.parse(data:sub(pos)) + if ( type(lsa) == "table" ) then + table.insert(lsu.lsas, lsa) + pos = pos + lsa.header.length + else + pos = pos + lsa + end + end + + return lsu + end, + }, + + Response = { + + parse = function(data) + local ver, ospf_type, pos = string.unpack("BB", 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) + elseif( ospf_type == OSPF.Message.LS_REQUEST ) then + return OSPF.LSRequest.parse(data) + elseif( ospf_type == OSPF.Message.LS_UPDATE ) then + return OSPF.LSUpdate.parse(data) + end + return + end, + + } +} + +return _ENV; |