diff --git a/CHANGELOG b/CHANGELOG
index 230a65c3a..1a8135451 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added script broadcast-dhcp6-discover and supporting DHCPv6 library.
+ The script retrieves and prints an IPv6 address and some of the DHCP6
+ options. [Patrik]
+
o IPv6 OS detection now includes a novelty detection phase that avoids
printing a match when an observed fingerprint is too different from
fingerprints seen before. As the OS database is still small, this
diff --git a/nselib/dhcp6.lua b/nselib/dhcp6.lua
new file mode 100644
index 000000000..ef2bab078
--- /dev/null
+++ b/nselib/dhcp6.lua
@@ -0,0 +1,617 @@
+---
+-- Minimalistic DHCP6 implementation supporting basic DHCP6 Solicit requests
+-- The library is structured around the following classes:
+-- * DHCP6.Option - DHCP6 options encoders (for requests) and decoders
+-- (for responses)
+-- * DHCP6.Request - DHCP6 request encoder and decoder
+-- * DHCP6.Response - DHCP6 response encoder and decoder
+-- * Helper - The helper class, primary script interface
+--
+-- The following sample code sends a DHCP6 Solicit request and returns a
+-- response suitable for script output:
+--
+-- local helper = DHCP6.Helper:new("eth0")
+-- local status, response = helper:solicit()
+-- if ( status ) then
+-- return stdnse.format_output(true, response)
+-- end
+--
+--
+-- @author "Patrik Karlsson "
+--
+module(... or "dhcp6", package.seeall)
+
+require 'bin'
+require 'bit'
+require 'ipOps'
+
+DHCP6 = {}
+
+-- DHCP6 request and response types
+DHCP6.Type = {
+ SOLICIT = 1,
+ ADVERTISE = 2,
+ REQUEST = 3,
+}
+
+-- DHCP6 type as string
+DHCP6.TypeStr = {
+ [DHCP6.Type.SOLICIT] = "Solicit",
+ [DHCP6.Type.ADVERTISE] = "Advertise",
+ [DHCP6.Type.REQUEST] = "Request",
+}
+
+-- DHCP6 option types
+DHCP6.OptionTypes = {
+ OPTION_CLIENTID = 0x01,
+ OPTION_SERVERID = 0x02,
+ OPTION_IA_NA = 0x03,
+ OPTION_IAADDR = 0x05,
+ OPTION_ELAPSED_TIME = 0x08,
+ OPTION_STATUS_CODE = 0x0d,
+ OPTION_DNS_SERVERS = 0x17,
+ OPTION_DOMAIN_LIST = 0x18,
+ OPTION_IA_PD = 0x19,
+ OPTION_SNTP_SERVERS = 0x1f,
+ OPTION_CLIENT_FQDN = 0x27,
+}
+
+-- DHCP6 options
+DHCP6.Option = {
+
+ [DHCP6.OptionTypes.OPTION_ELAPSED_TIME] = {
+
+ -- Create a new class instance
+ -- @param time in ms since last request
+ -- @return o new instance of class
+ new = function(self, time)
+ local o = {
+ type = DHCP6.OptionTypes.OPTION_ELAPSED_TIME,
+ time = time,
+ -- in case no time was created, we need this to be able to
+ -- calculate time since instantiation
+ created = os.time(),
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Converts option to a string
+ -- @return str string containing the class instance as string
+ __tostring = function(self)
+ local data
+ if ( self.time ) then
+ data = bin.pack(">S", self.time)
+ else
+ data = bin.pack(">S", (os.time() - self.created) * 1000)
+ end
+ return bin.pack(">SP", self.type, data)
+ end,
+
+ },
+
+ [DHCP6.OptionTypes.OPTION_CLIENTID] = {
+
+ -- Create a new class instance
+ -- @param mac string containing the mac address
+ -- @param duid number the duid of the client
+ -- @param hwtype number the hwtype of the client
+ -- @param time number time since 2000-01-01 00:00:00
+ -- @return o new instance of class
+ new = function(self, mac, duid, hwtype, time)
+ local o = {
+ type = DHCP6.OptionTypes.OPTION_CLIENTID,
+ duid = duid or 1,
+ hwtype = hwtype or 1,
+ time = time or os.time() - os.time({year=2000, day=1, month=1, hour=0, min=0, sec=0}),
+ mac = mac,
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Parse the data string and create an instance of the class
+ -- @param data string containing the data as received over the socket
+ -- @return opt new instance of option
+ parse = function(data)
+ local opt = DHCP6.Option[DHCP6.OptionTypes.OPTION_CLIENTID]:new()
+ local pos
+ pos, opt.duid = bin.unpack(">S", data, pos)
+ if ( 1 ~= opt.duid ) then
+ stdnse.print_debug("Unexpected DUID type (%d)", opt.duid)
+ return
+ end
+ pos, opt.hwtype, opt.time, opt.mac = bin.unpack(">SIA" .. (#data - pos - 4 - 2 + 1), data, pos)
+ opt.time = opt.time + os.time({year=2000, day=1, month=1, hour=0, min=0, sec=0})
+ return opt
+ end,
+
+ -- Converts option to a string
+ -- @return str string containing the class instance as string
+ __tostring = function(self)
+ local data = bin.pack(">SSIA", self.duid, self.hwtype, self.time, self.mac)
+ return bin.pack(">SP", self.type, data)
+ end,
+ },
+
+ [DHCP6.OptionTypes.OPTION_SERVERID] = {
+ -- Create a new class instance
+ -- @param mac string containing the mac address
+ -- @param duid number the duid of the client
+ -- @param hwtype number the hwtype of the client
+ -- @param time number time since 2000-01-01 00:00:00
+ -- @return o new instance of class
+ new = function(...) return DHCP6.Option[DHCP6.OptionTypes.OPTION_CLIENTID].new(...) end,
+
+ -- Parse the data string and create an instance of the class
+ -- @param data string containing the data as received over the socket
+ -- @return opt new instance of option
+ parse = function(...) return DHCP6.Option[DHCP6.OptionTypes.OPTION_CLIENTID].parse(...) end,
+
+ -- Converts option to a string
+ -- @return str string containing the class instance as string
+ __tostring = function(...) return DHCP6.Option[DHCP6.OptionTypes.OPTION_CLIENTID].__tostring(...) end,
+ },
+
+ [DHCP6.OptionTypes.OPTION_STATUS_CODE] = {
+
+ -- Create a new class instance
+ -- @param code number containing the error code
+ -- @param msg string containing the error message
+ -- @return o new instance of class
+ new = function(self, code, msg)
+ local o = {
+ type = DHCP6.OptionTypes.OPTION_STATUS_CODE,
+ code = code,
+ msg = msg,
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Parse the data string and create an instance of the class
+ -- @param data string containing the data as received over the socket
+ -- @return opt new instance of option
+ parse = function(data)
+ local opt = DHCP6.Option[DHCP6.OptionTypes.OPTION_STATUS_CODE]:new()
+ local pos
+
+ pos, opt.code, opt.msg = bin.unpack(">SA" .. (#data - 2), data)
+ return opt
+ end,
+
+ },
+
+ [DHCP6.OptionTypes.OPTION_DNS_SERVERS] = {
+
+ -- Create a new class instance
+ -- @param servers table containing DNS servers
+ -- @return o new instance of class
+ new = function(self, servers)
+ local o = {
+ type = DHCP6.OptionTypes.OPTION_DNS_SERVERS,
+ servers = servers or {},
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Parse the data string and create an instance of the class
+ -- @param data string containing the data as received over the socket
+ -- @return opt new instance of option
+ parse = function(data)
+ local opt = DHCP6.Option[DHCP6.OptionTypes.OPTION_DNS_SERVERS]:new()
+ local pos, count = 1, #data/16
+
+ for i=1,count do
+ local srv
+ pos, srv = bin.unpack(">B16", data, pos)
+ table.insert(opt.servers, srv)
+ end
+ return opt
+ end,
+
+ -- Converts option to a string
+ -- @return str string containing the class instance as string
+ __tostring = function(self)
+ local len = #self.servers * 16
+ local data= bin.pack(">SS", self.type, self.len)
+ for _, ipv6 in ipairs(self.servers) do
+ data = data .. ipOps.ip_to_str(ipv6)
+ end
+ return data
+ end
+ },
+
+ [DHCP6.OptionTypes.OPTION_DOMAIN_LIST] = {
+
+ -- Create a new class instance
+ -- @param domain table containing the search domains
+ -- @return o new instance of class
+ new = function(self, domains)
+ local o = {
+ type = DHCP6.OptionTypes.OPTION_DOMAIN_LIST,
+ domains = domains or {},
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Parse the data string and create an instance of the class
+ -- @param data string containing the data as received over the socket
+ -- @return opt new instance of option
+ parse = function(data)
+ local opt = DHCP6.Option[DHCP6.OptionTypes.OPTION_DOMAIN_LIST]:new()
+ local pos = 1
+
+ repeat
+ local domain = {}
+ repeat
+ local part
+ pos, part = bin.unpack("p", data, pos)
+ if ( part ~= "" ) then
+ table.insert(domain, part)
+ end
+ until( part == "" )
+ table.insert(opt.domains, stdnse.strjoin(".", domain))
+ until( pos > #data )
+ return opt
+ end,
+
+
+ },
+
+ [DHCP6.OptionTypes.OPTION_IA_PD] = {
+
+ -- Create a new class instance
+ -- @param iad number containing iad
+ -- @param t1 number containing t1
+ -- @param t2 number containing t2
+ -- @param option string containing any options
+ -- @return o new instance of class
+ new = function(self, iaid, t1, t2, options)
+ local o = {
+ type = DHCP6.OptionTypes.OPTION_IA_PD,
+ iaid = iaid,
+ t1 = t1 or 0,
+ t2 = t2 or 0,
+ options = options or "",
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Converts option to a string
+ -- @return str string containing the class instance as string
+ __tostring = function(self)
+ local data = bin.pack(">IIIA", self.iaid, self.t1, self.t2, self.options)
+ return bin.pack(">SP", self.type, data)
+ end,
+
+ },
+
+ [DHCP6.OptionTypes.OPTION_IA_NA] = {
+
+ -- Create a new class instance
+ -- @param iad number containing iad
+ -- @param t1 number containing t1
+ -- @param t2 number containing t2
+ -- @param option table containing any options
+ -- @return o new instance of class
+ new = function(self, iaid, t1, t2, options)
+ local o = {
+ type = DHCP6.OptionTypes.OPTION_IA_NA,
+ iaid = iaid,
+ t1 = t1 or 0,
+ t2 = t2 or 0,
+ options = options or {},
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Parse the data string and create an instance of the class
+ -- @param data string containing the data as received over the socket
+ -- @return opt new instance of option
+ parse = function(data)
+ local opt = DHCP6.Option[DHCP6.OptionTypes.OPTION_IA_NA]:new()
+ local pos
+
+ pos, opt.iaid, opt.t1, opt.t2 = bin.unpack(">III", data)
+
+ -- do we have any options
+ while ( pos < #data ) do
+ local typ, len, ipv6, pref_lt, valid_lt, options
+ pos, typ, len = bin.unpack(">SS", data, pos)
+
+ if ( 5 == DHCP6.OptionTypes.OPTION_IAADDR ) then
+ local addr = { type = DHCP6.OptionTypes.OPTION_IAADDR }
+ pos, addr.ipv6, addr.pref_lt, addr.valid_lt = bin.unpack(">A16II", data, pos)
+ table.insert(opt.options, addr)
+ else
+ pos = pos + len
+ end
+ end
+ return opt
+ end,
+
+ -- Converts option to a string
+ -- @return str string containing the class instance as string
+ __tostring = function(self)
+ local data = bin.pack(">III", self.iaid, self.t1, self.t2)
+
+ -- TODO: we don't cover self.options here, we should probably add that
+ return bin.pack(">SP", self.type, data)
+ end,
+ },
+
+ [DHCP6.OptionTypes.OPTION_SNTP_SERVERS] = {
+
+ -- Create a new class instance
+ -- @param servers table containing the NTP servers
+ -- @return o new instance of class
+ new = function(self, servers)
+ local o = {
+ type = DHCP6.OptionTypes.OPTION_SNTP_SERVERS,
+ servers = servers or {},
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Parse the data string and create an instance of the class
+ -- @param data string containing the data as received over the socket
+ -- @return opt new instance of option
+ parse = function(data)
+ local opt = DHCP6.Option[DHCP6.OptionTypes.OPTION_SNTP_SERVERS]:new()
+ local pos, server = 1
+
+ repeat
+ pos, server = bin.unpack(">B16", data, pos)
+ table.insert( opt.servers, ipOps.bin_to_ip(server) )
+ until( pos > #data )
+ return opt
+ end,
+ },
+
+}
+
+
+DHCP6.Request = {
+
+ -- Create a new class instance
+ -- @param msgtype number containing the message type
+ -- @param xid number containing the transaction id
+ -- @param opts table containing any request options
+ -- @return o new instance of class
+ new = function(self, msgtype, xid, opts)
+ local o = {
+ type = msgtype,
+ xid = xid or math.random(1048575),
+ opts = opts or {}
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Adds a new DHCP6 option to the request
+ -- @param opt instance of object to add to the request
+ addOption = function(self, opt)
+ table.insert(self.opts, opt)
+ end,
+
+ -- Converts option to a string
+ -- @return str string containing the class instance as string
+ __tostring = function(self)
+ local tmp = bit.lshift(self.type, 24) + self.xid
+ local data = ""
+
+ for _, opt in ipairs(self.opts) do
+ data = data .. tostring(opt)
+ end
+ return bin.pack(">IA", tmp, data)
+ end,
+
+}
+
+-- The Response class handles responses from the server
+DHCP6.Response = {
+
+ -- Creates a new instance of the response class
+ -- @param msgtype number containing the type of DHCP6 message
+ -- @param xid number containing the transaction ID
+ new = function(self, msgtype, xid, opts)
+ local o = {
+ msgtype = msgtype,
+ xid = xid,
+ opts = opts or {},
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Parse the data string and create an instance of the class
+ -- @param data string containing the data as received over the socket
+ -- @return opt new instance of option
+ parse = function(data)
+ local resp = DHCP6.Response:new()
+ local pos, tmp = bin.unpack(">I", data)
+
+ resp.msgtype = bit.band(tmp, 0xFF000000)
+ resp.msgtype = bit.rshift(resp.msgtype, 24)
+ resp.xid = bit.band(tmp, 0x00FFFFFF)
+ while( pos < #data ) do
+ local opt = {}
+ pos, opt.type, opt.data = bin.unpack(">SP", data, pos)
+ if ( DHCP6.Option[opt.type] and DHCP6.Option[opt.type].parse ) then
+ local opt_parsed = DHCP6.Option[opt.type].parse(opt.data)
+ if ( not(opt_parsed) ) then
+ table.insert(resp.opts, { type = opt.type, raw = opt.data })
+ else
+ table.insert(resp.opts, { type = opt.type, resp = opt_parsed })
+ end
+ else
+ stdnse.print_debug(2, "No option decoder for type: %d; len: %d", opt.type, #(opt.data or ""))
+ table.insert(resp.opts, { type = opt.type, raw = opt.data })
+ end
+ end
+ return resp
+ end
+
+}
+
+-- Table of option to string converters
+-- Each option should have it's own function to convert an instance of option
+-- to a printable string.
+--
+-- TODO: These functions could eventually be moved to a method in it's
+-- respective class.
+OptionToString = {
+
+ [DHCP6.OptionTypes.OPTION_CLIENTID] = function(opt)
+ local HWTYPE_ETHER = 1
+ if ( HWTYPE_ETHER == opt.hwtype ) then
+ local mac = stdnse.tohex(opt.mac):upper()
+ mac = mac:gsub("..", "%1:"):sub(1, -2)
+ local tm = os.date("%Y-%m-%d %H:%M:%S", opt.time)
+ return "Client identifier", ("MAC: %s; Time: %s"):format(mac, tm)
+ end
+ end,
+
+ [DHCP6.OptionTypes.OPTION_SERVERID] = function(opt)
+ local topic, str = OptionToString[DHCP6.OptionTypes.OPTION_CLIENTID](opt)
+ return "Server identifier", str
+ end,
+
+ [DHCP6.OptionTypes.OPTION_IA_NA] = function(opt)
+ if ( opt.options and 1 == #opt.options ) then
+ local ipv6 = opt.options[1].ipv6
+ ipv6 = select(2, bin.unpack("B" .. #ipv6, ipv6))
+ ipv6 = ipOps.bin_to_ip(ipv6)
+ return "Non-temporary Address", ipv6
+ end
+ end,
+
+ [DHCP6.OptionTypes.OPTION_DNS_SERVERS] = function(opt)
+ local servers = {}
+ for _, srv in ipairs(opt.servers) do
+ local ipv6 = ipOps.bin_to_ip(srv)
+ table.insert(servers, ipv6)
+ end
+ return "DNS Servers", stdnse.strjoin(",", servers)
+ end,
+
+ [DHCP6.OptionTypes.OPTION_DOMAIN_LIST] = function(opt)
+ return "Domain Search", stdnse.strjoin(", ", opt.domains)
+ end,
+
+ [DHCP6.OptionTypes.OPTION_STATUS_CODE] = function(opt)
+ return "Error", ("Code: %d; Message: %s"):format(opt.code, opt.msg)
+ end,
+
+ [DHCP6.OptionTypes.OPTION_SNTP_SERVERS] = function(opt)
+ return "NTP Servers", stdnse.strjoin(", ", opt.servers)
+ end,
+}
+
+-- The Helper class serves as the main interface to scripts
+Helper = {
+
+ -- Creates a new Helper class instance
+ -- @param iface string containing the interface name
+ -- @param options table containing any options, currently
+ -- timeout - socket timeout in ms
+ -- @return o new instance of Helper
+ new = function(self, iface, options)
+ local o = {
+ iface = iface,
+ options = options or {},
+ }
+ setmetatable(o, self)
+ self.__index = self
+
+ local info, err = nmap.get_interface_info(iface)
+ -- if we faile to get interface info, don't return a helper
+ -- this is true on OS X for interfaces like: p2p0 and vboxnet0
+ if ( not(info) and err ) then
+ return
+ end
+ o.mac = info.mac
+ o.socket = nmap.new_socket("udp")
+ o.socket:bind(nil, 546)
+ o.socket:set_timeout(o.options.timeout or 5000)
+ return o
+ end,
+
+ -- Sends a DHCP6 Solicit message to the server, essentiall requesting a new
+ -- IPv6 non-temporary address
+ -- @return table of results suitable for use with
+ -- stdnse.format_output
+ solicit = function(self)
+ local req = DHCP6.Request:new( DHCP6.Type.SOLICIT )
+ local option = DHCP6.Option
+ req:addOption(option[DHCP6.OptionTypes.OPTION_ELAPSED_TIME]:new())
+ req:addOption(option[DHCP6.OptionTypes.OPTION_CLIENTID]:new(self.mac))
+
+ local iaid = select(2, bin.unpack(">I", self.mac:sub(3)))
+ req:addOption(option[DHCP6.OptionTypes.OPTION_IA_NA]:new(iaid, 3600, 5400))
+
+ self.host, self.port = { ip = "ff02::1:2" }, { number = 547, protocol = "udp"}
+ local status, err = self.socket:sendto( self.host, self.port, tostring(req) )
+ if ( not(status) ) then
+ self.host.ip = ("%s%%%s"):format(self.host.ip, self.iface)
+ status, err = self.socket:sendto( self.host, self.port, tostring(req) )
+ if ( not(status) ) then
+ return false, "Failed to send DHCP6 request to server"
+ end
+ end
+
+ local resp, retries = {}, 3
+ repeat
+ retries = retries - 1
+ local status, data = self.socket:receive()
+ if ( not(status) ) then
+ return false, "Failed to receive DHCP6 request from server"
+ end
+
+ resp = DHCP6.Response.parse(data)
+ if ( not(resp) ) then
+ return false, "Failed to decode DHCP6 response from server"
+ end
+ until( req.xid == resp.xid or retries == 0 )
+
+ if ( req.xid ~= resp.xid ) then
+ return false, "Failed to receive DHCP6 response from server"
+ end
+
+ local result, result_options = {}, { name = "Options" }
+ local resptype = DHCP6.TypeStr[resp.msgtype] or ("Unknown (%d)"):format(resp.msgtype)
+
+ table.insert(result, ("Message type: %s"):format(resptype))
+ table.insert(result, ("Transaction id: %d"):format(resp.xid))
+
+ for _, opt in ipairs(resp.opts or {}) do
+ if ( OptionToString[opt.type] ) then
+ local topic, str = OptionToString[opt.type](opt.resp)
+ if ( topic and str ) then
+ table.insert(result_options, ("%s: %s"):format(topic, str))
+ end
+ else
+ stdnse.print_debug(2, "No decoder for option type: %d", opt.type)
+ end
+ end
+ table.insert(result, result_options)
+ return true, result
+ end,
+}
+
diff --git a/scripts/broadcast-dhcp6-discover.nse b/scripts/broadcast-dhcp6-discover.nse
new file mode 100644
index 000000000..5e71ed8eb
--- /dev/null
+++ b/scripts/broadcast-dhcp6-discover.nse
@@ -0,0 +1,111 @@
+description = [[
+Sends a DHCPv6 request (Solicit) to the DHCPv6 multicast address. It parses the
+response and extracts the address along with any options returned by the
+server.
+
+The script requires Nmap to be run in privileged mode as it binds the socket
+to a privileged port (udp/546).
+]]
+
+---
+-- @usage
+-- nmap -6 --script broadcast-dhcp6-discover
+--
+-- @output
+-- | broadcast-dhcp6-discover:
+-- | Interface: en0
+-- | Message type: Advertise
+-- | Transaction id: 74401
+-- | Options
+-- | Client identifier: MAC: 68:AB:CD:EF:AB:CD; Time: 2012-01-24 20:36:48
+-- | Server identifier: MAC: 08:FE:DC:BA:98:76; Time: 2012-01-20 11:44:58
+-- | Non-temporary Address: 2001:db8:1:2:0:0:0:1000
+-- | DNS Servers: 2001:db8:0:0:0:0:0:35
+-- | Domain Search: example.com, sub.example.com
+-- |_ NTP Servers: 2001:db8:1111:0:0:0:0:123, 2001:db8:1111:0:0:0:0:124
+--
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"broadcast", "safe"}
+
+require 'dhcp6'
+
+prerule = function()
+ if not nmap.is_privileged() then
+ stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME)
+ return false
+ end
+
+ if nmap.address_family() ~= 'inet6' then
+ stdnse.print_debug("%s is IPv6 compatible only.", SCRIPT_NAME)
+ return false
+ end
+ return true
+end
+
+-- Gets a list of available interfaces based on link and up filters
+--
+-- @param link string containing the link type to filter
+-- @param up string containing the interface status to filter
+-- @return result table containing the matching interfaces
+local function getInterfaces(link, up)
+ if( not(nmap.list_interfaces) ) then return end
+ local interfaces, err = nmap.list_interfaces()
+ local result
+ if ( not(err) ) then
+ for _, iface in ipairs(interfaces) do
+ if ( iface.link == link and iface.up == up ) then
+ result = result or {}
+ result[iface.device] = true
+ end
+ end
+ end
+ return result
+end
+
+local function solicit(iface, result)
+ local condvar = nmap.condvar(result)
+ local helper = dhcp6.Helper:new(iface)
+ if ( not(helper) ) then
+ condvar "signal"
+ return
+ end
+
+ local status, response = helper:solicit()
+ if ( status ) then
+ response.name=("Interface: %s"):format(iface)
+ table.insert(result, response )
+ end
+ condvar "signal"
+end
+
+action = function(host, port)
+
+ local iface = nmap.get_interface()
+ local ifs, result, threads = {}, {}, {}
+ local condvar = nmap.condvar(result)
+
+ if ( iface ) then
+ ifs[iface] = true
+ else
+ ifs = getInterfaces("ethernet", "up")
+ end
+
+ for iface in pairs(ifs) do
+ local co = stdnse.new_thread( solicit, iface, result )
+ threads[co] = true
+ end
+
+ -- wait until the probes are all done
+ repeat
+ condvar "wait"
+ for thread in pairs(threads) do
+ if coroutine.status(thread) == "dead" then
+ threads[thread] = nil
+ end
+ end
+ until next(threads) == nil
+
+ return stdnse.format_output(true, result)
+end
\ No newline at end of file
diff --git a/scripts/script.db b/scripts/script.db
index db050f160..82111db09 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -18,6 +18,7 @@ Entry { filename = "bittorrent-discovery.nse", categories = { "discovery", "safe
Entry { filename = "broadcast-avahi-dos.nse", categories = { "broadcast", "dos", "intrusive", "vuln", } }
Entry { filename = "broadcast-db2-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-dhcp-discover.nse", categories = { "broadcast", "safe", } }
+Entry { filename = "broadcast-dhcp6-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-dns-service-discovery.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-dropbox-listener.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-listener.nse", categories = { "broadcast", "safe", } }