mirror of
https://github.com/nmap/nmap.git
synced 2025-12-09 14:11:29 +00:00
Similar changes to r30653, but may break compatibility with people using integer millisecond values, which are now treated as number of seconds. To get same behavior, use ms after number, e.g. 5000 becomes 5000ms or 5s
217 lines
5.5 KiB
Lua
217 lines
5.5 KiB
Lua
local bin = require "bin"
|
|
local ipOps = require "ipOps"
|
|
local nmap = require "nmap"
|
|
local stdnse = require "stdnse"
|
|
local tab = require "tab"
|
|
local table = require "table"
|
|
|
|
description = [[
|
|
Discovers hosts and routing information from devices running RIPng on the
|
|
LAN by sending a broadcast RIPng Request command and collecting any responses.
|
|
]]
|
|
|
|
---
|
|
-- @usage
|
|
-- nmap --script broadcast-ripng-discover
|
|
--
|
|
-- @output
|
|
-- | broadcast-ripng-discover:
|
|
-- | fe80::a00:27ff:fe9a:880c
|
|
-- | route metric next hop
|
|
-- | fe80:470:0:0:0:0:0:0/64 1
|
|
-- | fe80:471:0:0:0:0:0:0/64 1
|
|
-- |_ fe80:472:0:0:0:0:0:0/64 1
|
|
--
|
|
-- @args broadcast-ripng-discover.timeout sets the connection timeout
|
|
-- (default: 5s)
|
|
|
|
author = "Patrik Karlsson"
|
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
|
categories = {"broadcast", "safe"}
|
|
|
|
|
|
prerule = function() return ( nmap.address_family() == "inet6" ) end
|
|
|
|
RIPng = {
|
|
|
|
-- Supported RIPng commands
|
|
Command = {
|
|
Request = 1,
|
|
Response = 2,
|
|
},
|
|
|
|
-- Route table entry
|
|
RTE = {
|
|
|
|
-- Creates a new Route Table Entry
|
|
-- @param prefix string containing the ipv6 route prefix
|
|
-- @param tag number containing the route tag
|
|
-- @param prefix_len number containing the length in bits of the
|
|
-- signifcant part of the prefix
|
|
-- @param metric number containing the current metric for the
|
|
-- destination
|
|
new = function(self, prefix, tag, prefix_len, metric)
|
|
local o = {
|
|
prefix = prefix,
|
|
tag = tag,
|
|
prefix_len = prefix_len,
|
|
metric = metric
|
|
}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
-- Parses a byte string and creates an instance of RTE
|
|
-- @param data string of bytes
|
|
-- @return rte instance of RTE
|
|
parse = function(data)
|
|
local rte = RIPng.RTE:new()
|
|
local pos, ip
|
|
|
|
pos, ip, rte.tag, rte.prefix_len, rte.metric = bin.unpack(">A16SCC", data)
|
|
ip = select(2, bin.unpack("B" .. #ip, ip))
|
|
rte.prefix = ipOps.bin_to_ip(ip)
|
|
return rte
|
|
end,
|
|
|
|
-- Converts a RTE instance to string
|
|
-- @return string of bytes to send to the server
|
|
__tostring = function(self)
|
|
local ipstr = ipOps.ip_to_str(self.prefix)
|
|
assert(16 == #ipstr, "Invalid IPv6 address encountered")
|
|
return bin.pack(">ASCC", ipstr, self.tag, self.prefix_len, self.metric)
|
|
end,
|
|
|
|
|
|
},
|
|
|
|
-- The Request class contains functions to build a RIPv2 Request
|
|
Request = {
|
|
|
|
-- Creates a new Request instance
|
|
--
|
|
-- @param command number containing the RIPv2 Command to use
|
|
-- @return o instance of request
|
|
new = function(self, entries)
|
|
local o = {
|
|
command = 1,
|
|
version = 1,
|
|
entries = entries,
|
|
}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
-- Converts the whole request to a string
|
|
__tostring = function(self)
|
|
local RESERVED = 0
|
|
local str = bin.pack(">CCS", self.command, self.version, RESERVED)
|
|
for _, rte in ipairs(self.entries) do
|
|
str = str .. tostring(rte)
|
|
end
|
|
return str
|
|
end,
|
|
|
|
},
|
|
|
|
-- A RIPng Response
|
|
Response = {
|
|
|
|
-- Creates a new Response instance
|
|
-- @return o new instance of Response
|
|
new = function(self)
|
|
local o = { }
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
-- Creates a new Response instance based on a string of bytes
|
|
-- @return resp new instance of Response
|
|
parse = function(data)
|
|
local resp = RIPng.Response:new()
|
|
local pos, _
|
|
|
|
pos, resp.command, resp.version, _ = bin.unpack(">CCS", data)
|
|
resp.entries = {}
|
|
while( pos < #data ) do
|
|
local e = RIPng.RTE.parse(data:sub(pos))
|
|
table.insert(resp.entries, e)
|
|
pos = pos + 20
|
|
end
|
|
|
|
return resp
|
|
end,
|
|
}
|
|
}
|
|
|
|
local function fail(err) return ("\n ERROR: %s"):format(err or "") end
|
|
|
|
-- Parses a RIPng response
|
|
-- @return ret string containing the routing table
|
|
local function parse_response(resp)
|
|
local next_hop
|
|
local result = tab.new(3)
|
|
tab.addrow(result, "route", "metric", "next hop")
|
|
for _, rte in pairs(resp.entries or {}) do
|
|
-- next hop information is specified in a separate RTE according to
|
|
-- RFC 2080 section 2.1.1
|
|
if ( 0xFF == rte.metric ) then
|
|
next_hop = rte.prefix
|
|
else
|
|
tab.addrow(result, ("%s/%d"):format(rte.prefix, rte.prefix_len), rte.metric, next_hop or "")
|
|
end
|
|
end
|
|
return tab.dump(result)
|
|
end
|
|
|
|
action = function()
|
|
|
|
local req = RIPng.Request:new( { RIPng.RTE:new("0::", 0, 0, 16) } )
|
|
local host, port = "FF02::9", { number = 521, protocol = "udp" }
|
|
local iface = nmap.get_interface()
|
|
local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME..".timeout"))
|
|
timeout = (timeout or 5) * 1000
|
|
|
|
local sock = nmap.new_socket("udp")
|
|
sock:bind(nil, 521)
|
|
sock:set_timeout(timeout)
|
|
|
|
local status = sock:sendto(host, port, tostring(req))
|
|
|
|
-- do we need to add the interface name to the address?
|
|
if ( not(status) ) then
|
|
if ( not(iface) ) then
|
|
return fail("Couldn't determine what interface to use, try supplying it with -e")
|
|
end
|
|
status = sock:sendto(host .. "%" .. iface, port, tostring(req))
|
|
end
|
|
|
|
if ( not(status) ) then
|
|
return fail("Failed to send request to server")
|
|
end
|
|
|
|
local responses = {}
|
|
while(true) do
|
|
local status, data = sock:receive()
|
|
if ( not(status) ) then
|
|
break
|
|
else
|
|
local status, _, _, rhost = sock:get_info()
|
|
if ( not(status) ) then
|
|
rhost = "unknown"
|
|
end
|
|
responses[rhost] = RIPng.Response.parse(data)
|
|
end
|
|
end
|
|
|
|
local result = {}
|
|
for ip, resp in pairs(responses) do
|
|
print(ip, resp)
|
|
table.insert(result, { name = ip, parse_response(resp) } )
|
|
end
|
|
return stdnse.format_output(true, result)
|
|
end
|