summaryrefslogtreecommitdiffstats
path: root/scripts/targets-ipv6-multicast-mld.nse
blob: e1187c0f8ce8ba00d9facd2310fc5382427a3ccc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
local ipOps = require "ipOps"
local coroutine = require "coroutine"
local nmap = require "nmap"
local stdnse = require "stdnse"
local tab = require "tab"
local table = require "table"
local tableaux = require "tableaux"
local target = require "target"
local multicast = require "multicast"

description = [[
Attempts to discover available IPv6 hosts on the LAN by sending an MLD
(multicast listener discovery) query to the link-local multicast address
(ff02::1) and listening for any responses.  The query's maximum response delay
set to 1 to provoke hosts to respond immediately rather than waiting for other
responses from their multicast group.
]]

---
-- @usage
-- nmap -6 --script=targets-ipv6-multicast-mld.nse --script-args 'newtargets,interface=eth0'
--
-- @output
-- Pre-scan script results:
-- | targets-ipv6-multicast-mld:
-- |   IP: fe80::5a55:abcd:ef01:2345  MAC: 58:55:ab:cd:ef:01  IFACE: en0
-- |   IP: fe80::9284:0123:4567:89ab  MAC: 90:84:01:23:45:67  IFACE: en0
-- |
-- |_  Use --script-args=newtargets to add the results as targets
--
-- @args targets-ipv6-multicast-mld.timeout timeout to wait for
--       responses (default: 10s)
-- @args targets-ipv6-multicast-mld.interface Interface to send on (default:
--       the interface specified with -e or every available Ethernet interface
--       with an IPv6 address.)
--
-- @xmloutput
-- <table>
--   <table>
--     <elem key="address">fe80::5a55:abcd:ef01:2345</elem>
--     <elem key="mac">58:55:ab:cd:ef:01</elem>
--     <elem key="iface">en0</elem>
--   </table>
--   <table>
--     <elem key="address">fe80::9284:0123:4567:89ab</elem>
--     <elem key="mac">90:84:01:23:45:67</elem>
--     <elem key="iface">en0</elem>
--   </table>
-- </table>

author = {"niteesh", "alegen"}
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery","broadcast"}


local arg_timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. '.timeout'))

prerule = function()
  if ( not(nmap.is_privileged()) ) then
    stdnse.verbose1("not running for lack of privileges.")
    return false
  end
  return true
end


local function get_interfaces()
  local interface_name = stdnse.get_script_args(SCRIPT_NAME .. ".interface")
    or nmap.get_interface()

  -- interfaces list (decide which interfaces to broadcast on)
  local interfaces = {}
  for _, if_table in pairs(nmap.list_interfaces()) do
    if (interface_name == nil or if_table.device == interface_name) -- check for correct interface
      and ipOps.ip_in_range(if_table.address, "fe80::/10") -- link local address
      and if_table.link == "ethernet" then                 -- not the loopback interface
      table.insert(interfaces, if_table)
    end
  end

  return interfaces
end

local function single_interface_broadcast(if_nfo, results)
  stdnse.debug2("Starting " .. SCRIPT_NAME .. " on " .. if_nfo.device)
  local condvar = nmap.condvar(results)

  local reports = multicast.mld_query(if_nfo, arg_timeout or 10)
  for _, r in pairs(reports) do
    local l2reply = r[2]
    local l3reply = r[3]
    local target_str = l3reply.ip_src
    if not results[target_str] then
      if target.ALLOW_NEW_TARGETS then
        target.add(target_str)
      end
      results[target_str] = { address = target_str, mac = stdnse.format_mac(l2reply.mac_src), iface = if_nfo.device }
    end
  end

  condvar("signal")
end

local function format_output(results)
  local output = tab.new()
  local xmlout = {}
  local ips = tableaux.keys(results)
  table.sort(ips)

  for i, ip in ipairs(ips) do
    local record = results[ip]
    xmlout[i] = record
    tab.addrow(output, "  IP: " .. record.address, "MAC: " .. record.mac, "IFACE: " .. record.iface)
  end

  if ( #output > 0 ) then
    output = {"", tab.dump(output) }
    if not target.ALLOW_NEW_TARGETS then
      table.insert(output, "  Use --script-args=newtargets to add the results as targets")
    end
    return xmlout, table.concat(output, "\n")
  end
end

action = function()
  local threads = {}
  local results = {}
  local condvar = nmap.condvar(results)

  for _, if_nfo in ipairs(get_interfaces()) do
    -- create a thread for each interface
    local co = stdnse.new_thread(single_interface_broadcast, if_nfo, results)
    threads[co] = true
  end

  repeat
    for thread in pairs(threads) do
      if coroutine.status(thread) == "dead" then threads[thread] = nil end
    end
    if ( next(threads) ) then
      condvar "wait"
    end
  until next(threads) == nil

  return format_output(results)
end