mirror of
https://github.com/nmap/nmap.git
synced 2026-08-27 11:55:27 +00:00
Add matter-identify NSE script
Identify Matter smart-home devices via mDNS. Queries three DNS-SD service types on 5353/udp (_matter._tcp, _matterc._udp, _meshcop._udp) and decodes TXT records into vendor/product IDs, device type, fabric and node IDs, discriminator, commissioning mode, session intervals, and pairing hints. Also flags the Matter operational port (5540) when open.
This commit is contained in:
parent
4d50070c7f
commit
d2361d12d0
1 changed files with 526 additions and 0 deletions
526
scripts/matter-identify.nse
Normal file
526
scripts/matter-identify.nse
Normal file
|
|
@ -0,0 +1,526 @@
|
|||
local dns = require "dns"
|
||||
local nmap = require "nmap"
|
||||
local shortport = require "shortport"
|
||||
local stdnse = require "stdnse"
|
||||
local string = require "string"
|
||||
local table = require "table"
|
||||
|
||||
description = [[
|
||||
Identifies Matter smart-home devices via mDNS service discovery.
|
||||
|
||||
Matter (formerly Project CHIP) is the unified IP-based smart-home protocol
|
||||
maintained by the Connectivity Standards Alliance. The script sends three
|
||||
DNS PTR queries to port 5353/udp on the target and decodes the responses:
|
||||
|
||||
_matter._tcp.local commissioned (operational) nodes
|
||||
_matterc._udp.local nodes currently in commissioning / pairing mode
|
||||
_meshcop._udp.local Thread network border routers
|
||||
|
||||
Per-instance attributes are extracted from DNS-SD TXT records, including
|
||||
Vendor ID (looked up against the partial CSA registry shipped in the Matter
|
||||
SDK), Product ID, device type, fabric/node identifiers, discriminator,
|
||||
commissioning mode, TCP support, session intervals, and pairing hints.
|
||||
|
||||
If port 5540 (Matter operational TCP/UDP port) is also reported open, the
|
||||
script flags it; mDNS on 5353/udp provides the richer device information.
|
||||
|
||||
References:
|
||||
* https://csa-iot.org/all-solutions/matter/
|
||||
* https://github.com/project-chip/connectedhomeip
|
||||
* Matter Core Specification, section 4 ("Discovery") and section 5.4
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage nmap -sU -p 5353 --script matter-identify <target>
|
||||
-- @usage nmap -sU -p 5353 --script matter-identify 192.168.0.0/24
|
||||
-- @usage nmap -p T:5540,U:5353 --script matter-identify <target>
|
||||
--
|
||||
-- @output
|
||||
-- PORT STATE SERVICE
|
||||
-- 5353/udp open mdns
|
||||
-- | matter-identify:
|
||||
-- | Commissioned Matter device (_matter._tcp) #1:
|
||||
-- | Instance: 92E5DE45A4357164-0000000000000019
|
||||
-- | Fabric ID: 0x92E5DE45A4357164
|
||||
-- | Node ID: 0x0000000000000019
|
||||
-- | Session Idle Interval: 2000 ms
|
||||
-- | Session Active Interval: 2000 ms
|
||||
-- | Session Active Threshold: 4000 ms
|
||||
-- | Port: 5540
|
||||
-- | Host: DAA69B29F9D12EEE.local
|
||||
-- | Commissioning Matter device (_matterc._udp):
|
||||
-- | Instance: 9BB722E74A9CAEBB
|
||||
-- | Vendor ID: 0x130A
|
||||
-- | Product ID: 0x0050
|
||||
-- | Device type: 0x010A (On/Off Plug-in Unit)
|
||||
-- | Device name: Eve Energy
|
||||
-- | Discriminator: 408
|
||||
-- | Commissioning mode: 2 (enhanced commissioning mode)
|
||||
-- | Rotating device ID: 0C00A65A406F5F8689445667357195EA6FB7
|
||||
-- | Pairing hint: 36 (Administrator; Device manual)
|
||||
-- | Session Idle Interval: 2000 ms
|
||||
-- | Session Active Interval: 2000 ms
|
||||
-- | Session Active Threshold: 4000 ms
|
||||
-- | Port: 5540
|
||||
-- | Host: DAA69B29F9D12EEE.local
|
||||
-- | Thread border router (_meshcop._udp):
|
||||
-- | Instance: New Apple TV
|
||||
-- | Network name: MyHome1004112387
|
||||
-- | Extended PAN ID: \x04\x8E\x89\xF2\xC4+D\x11
|
||||
-- | Thread version: 1.3.0
|
||||
-- | Extended address: \x8A\xD9\x9E,\xDA45\xBC
|
||||
-- | Border agent port: \xB3\xD2\x91I
|
||||
-- | Sequence number:
|
||||
-- | BBR seq number: \xF0\xBF
|
||||
-- | State bitmap: \x00\x00\x0F\xB1
|
||||
-- | Port: 49153
|
||||
-- |_ Host: New-Apple-TV.local
|
||||
--
|
||||
-- @see dns-service-discovery.nse
|
||||
|
||||
author = "Zoltan Balazs"
|
||||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = {"discovery", "safe"}
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Constants
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local MDNS_PORT = 5353
|
||||
local MATTER_PORT = 5540
|
||||
local QUERY_TIMEOUT = 2000 -- ms per service-type query
|
||||
|
||||
-- DNS-SD service types defined by the Matter spec, queried via PTR.
|
||||
local MATTER_SERVICES = {
|
||||
{
|
||||
qname = "_matter._tcp.local",
|
||||
label = "Commissioned Matter device (_matter._tcp)",
|
||||
kind = "operational",
|
||||
},
|
||||
{
|
||||
qname = "_matterc._udp.local",
|
||||
label = "Commissioning Matter device (_matterc._udp)",
|
||||
kind = "commissioning",
|
||||
},
|
||||
{
|
||||
qname = "_meshcop._udp.local",
|
||||
label = "Thread border router (_meshcop._udp)",
|
||||
kind = "meshcop",
|
||||
},
|
||||
}
|
||||
|
||||
-- Vendor IDs from connectedhomeip src/lib/core/CHIPVendorIdentifiers.hpp.
|
||||
-- The full CSA registry is much larger; unknown IDs are shown as raw hex.
|
||||
local VENDOR_NAMES = {
|
||||
[0x0000] = "Common / Unspecified",
|
||||
[0x1349] = "Apple",
|
||||
[0x6006] = "Google",
|
||||
[0xFFF1] = "Test Vendor 1",
|
||||
[0xFFF2] = "Test Vendor 2",
|
||||
[0xFFF3] = "Test Vendor 3",
|
||||
[0xFFF4] = "Test Vendor 4",
|
||||
[0xFFFF] = "Not Specified",
|
||||
}
|
||||
|
||||
-- Device type IDs extracted from connectedhomeip
|
||||
-- src/app/zap-templates/zcl/data-model/chip/matter-devices.xml.
|
||||
local DEVICE_TYPE_NAMES = {
|
||||
[0x000A] = "Door Lock",
|
||||
[0x000B] = "Door Lock Controller",
|
||||
[0x000E] = "Aggregator",
|
||||
[0x000F] = "Generic Switch",
|
||||
[0x0011] = "Power Source",
|
||||
[0x0012] = "OTA Requestor",
|
||||
[0x0013] = "Bridged Node",
|
||||
[0x0014] = "OTA Provider",
|
||||
[0x0015] = "Contact Sensor",
|
||||
[0x0016] = "Root Node",
|
||||
[0x0017] = "Solar Power",
|
||||
[0x0018] = "Battery Storage",
|
||||
[0x0019] = "Secondary Network Interface",
|
||||
[0x0022] = "Speaker",
|
||||
[0x0023] = "Casting Video Player",
|
||||
[0x0024] = "Content App",
|
||||
[0x0027] = "Mode Select",
|
||||
[0x0028] = "Basic Video Player",
|
||||
[0x0029] = "Casting Video Client",
|
||||
[0x002A] = "Video Remote Control",
|
||||
[0x002B] = "Fan",
|
||||
[0x002C] = "Air Quality Sensor",
|
||||
[0x002D] = "Air Purifier",
|
||||
[0x0041] = "Water Freeze Detector",
|
||||
[0x0042] = "Water Valve",
|
||||
[0x0043] = "Water Leak Detector",
|
||||
[0x0044] = "Rain Sensor",
|
||||
[0x0045] = "Soil Sensor",
|
||||
[0x0070] = "Refrigerator",
|
||||
[0x0071] = "Temperature Controlled Cabinet",
|
||||
[0x0072] = "Room Air Conditioner",
|
||||
[0x0073] = "Laundry Washer",
|
||||
[0x0074] = "Robotic Vacuum Cleaner",
|
||||
[0x0075] = "Dishwasher",
|
||||
[0x0076] = "Smoke CO Alarm",
|
||||
[0x0077] = "Cook Surface",
|
||||
[0x0078] = "Cooktop",
|
||||
[0x0079] = "Microwave Oven",
|
||||
[0x007A] = "Extractor Hood",
|
||||
[0x007B] = "Oven",
|
||||
[0x007C] = "Laundry Dryer",
|
||||
[0x0090] = "Network Infrastructure Manager",
|
||||
[0x0091] = "Thread Border Router",
|
||||
[0x0100] = "On/Off Light",
|
||||
[0x0101] = "Dimmable Light",
|
||||
[0x0103] = "On/Off Light Switch",
|
||||
[0x0104] = "Dimmer Switch",
|
||||
[0x0105] = "Color Dimmer Switch",
|
||||
[0x0106] = "Light Sensor",
|
||||
[0x0107] = "Occupancy Sensor",
|
||||
[0x010A] = "On/Off Plug-in Unit",
|
||||
[0x010B] = "Dimmable Plug-in Unit",
|
||||
[0x010C] = "Color Temperature Light",
|
||||
[0x010D] = "Extended Color Light",
|
||||
[0x010F] = "Mounted On/Off Control",
|
||||
[0x0110] = "Mounted Dimmable Load Control",
|
||||
[0x0130] = "Joint Fabric Administrator",
|
||||
[0x0202] = "Window Covering",
|
||||
[0x0203] = "Window Covering Controller",
|
||||
[0x0301] = "Thermostat",
|
||||
[0x0302] = "Temperature Sensor",
|
||||
[0x0303] = "Pump",
|
||||
[0x0304] = "Pump Controller",
|
||||
[0x0305] = "Pressure Sensor",
|
||||
[0x0306] = "Flow Sensor",
|
||||
[0x0307] = "Humidity Sensor",
|
||||
[0x0309] = "Heat Pump",
|
||||
[0x030A] = "Thermostat Controller",
|
||||
[0x050C] = "EVSE",
|
||||
[0x050D] = "Device Energy Management",
|
||||
[0x050F] = "Water Heater",
|
||||
[0x0510] = "Electrical Sensor",
|
||||
[0x0840] = "Control Bridge",
|
||||
[0x0850] = "On/Off Sensor",
|
||||
}
|
||||
|
||||
-- Commissioning mode values, from connectedhomeip
|
||||
-- src/lib/dnssd/Advertiser.h enum CommissioningMode.
|
||||
local CM_MODES = {
|
||||
[0] = "disabled",
|
||||
[1] = "basic commissioning mode",
|
||||
[2] = "enhanced commissioning mode",
|
||||
[3] = "joint fabric commissioning mode",
|
||||
}
|
||||
|
||||
-- Pairing hint bitmap (PH TXT key) bit definitions, from
|
||||
-- Matter Core Specification section 5.4.2.3.2.
|
||||
local PAIRING_HINT_BITS = {
|
||||
{ mask = 0x00001, desc = "Power cycle" },
|
||||
{ mask = 0x00002, desc = "Device manufacturer URL (in PI)" },
|
||||
{ mask = 0x00004, desc = "Administrator" },
|
||||
{ mask = 0x00008, desc = "Settings menu on the device" },
|
||||
{ mask = 0x00010, desc = "Custom instruction (in PI)" },
|
||||
{ mask = 0x00020, desc = "Device manual" },
|
||||
{ mask = 0x00040, desc = "Press reset button" },
|
||||
{ mask = 0x00080, desc = "Press reset button with power" },
|
||||
{ mask = 0x00100, desc = "Press reset button for N seconds" },
|
||||
{ mask = 0x00200, desc = "Press reset button until light blinks" },
|
||||
{ mask = 0x00400, desc = "Press reset button for N seconds with power" },
|
||||
{ mask = 0x00800, desc = "Press reset until light blinks with power" },
|
||||
{ mask = 0x01000, desc = "Press setup button" },
|
||||
{ mask = 0x02000, desc = "Press setup button with power" },
|
||||
{ mask = 0x04000, desc = "Press setup button for N seconds" },
|
||||
{ mask = 0x08000, desc = "Press setup button until light blinks" },
|
||||
{ mask = 0x10000, desc = "Press setup button for N seconds with power" },
|
||||
{ mask = 0x20000, desc = "Press setup until light blinks with power" },
|
||||
}
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Helpers
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
--; Format a Vendor ID with its CSA name (when known).
|
||||
local function fmt_vendor(vid)
|
||||
local name = VENDOR_NAMES[vid]
|
||||
if name then
|
||||
return string.format("0x%04X (%s)", vid, name)
|
||||
end
|
||||
return string.format("0x%04X", vid)
|
||||
end
|
||||
|
||||
--; Format a device type ID with its Matter device-library name (when known).
|
||||
local function fmt_devtype(dt)
|
||||
local name = DEVICE_TYPE_NAMES[dt]
|
||||
if name then
|
||||
return string.format("0x%04X (%s)", dt, name)
|
||||
end
|
||||
return string.format("0x%04X", dt)
|
||||
end
|
||||
|
||||
--; Decode the 32-bit PH bitmap into a human-readable hint list.
|
||||
local function fmt_pairing_hint(ph_str)
|
||||
local ph = tonumber(ph_str)
|
||||
if not ph then return ph_str end
|
||||
local hints = {}
|
||||
for _, entry in ipairs(PAIRING_HINT_BITS) do
|
||||
if ph & entry.mask ~= 0 then
|
||||
hints[#hints + 1] = entry.desc
|
||||
end
|
||||
end
|
||||
if #hints == 0 then return tostring(ph) end
|
||||
return string.format("%d (%s)", ph, table.concat(hints, "; "))
|
||||
end
|
||||
|
||||
--; Strip a service-type FQDN suffix from a service-instance FQDN.
|
||||
-- "Inst._matter._tcp.local" minus "_matter._tcp.local" -> "Inst".
|
||||
local function strip_service_suffix(fqdn, service_qname)
|
||||
local suffix = "." .. service_qname
|
||||
if fqdn:sub(-#suffix) == suffix then
|
||||
return fqdn:sub(1, #fqdn - #suffix)
|
||||
end
|
||||
return fqdn
|
||||
end
|
||||
|
||||
--; Parse a single DNS-SD TXT string ("KEY=VALUE" or bare flag) into kv.
|
||||
local function parse_txt_string(str, kv)
|
||||
local k, v = str:match("^([^=]+)=(.*)$")
|
||||
if k then
|
||||
kv[k] = v
|
||||
else
|
||||
kv[str] = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- mDNS query / response handling
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
--; Send a PTR query for `qname` to `host` on UDP port 5353 (mDNS).
|
||||
-- Returns a decoded packet on success, or nil on timeout / failure.
|
||||
local function query_mdns(host, qname)
|
||||
local status, response = dns.query(qname, {
|
||||
host = (type(host) == "table") and host.ip or host,
|
||||
port = MDNS_PORT,
|
||||
proto = "udp",
|
||||
dtype = "PTR",
|
||||
retPkt = true,
|
||||
retAll = true,
|
||||
sendCount = 1,
|
||||
timeout = QUERY_TIMEOUT,
|
||||
})
|
||||
if not status then return nil end
|
||||
return response
|
||||
end
|
||||
|
||||
--; Walk the additional/answer sections of a decoded mDNS packet and
|
||||
-- collect TXT key/value pairs and the SRV port/target for `instance_fqdn`.
|
||||
local function collect_instance_records(response, instance_fqdn)
|
||||
local txt_kv, srv_port, srv_target = {}, nil, nil
|
||||
|
||||
local function process(rrs)
|
||||
for _, rr in ipairs(rrs or {}) do
|
||||
if rr.dname == instance_fqdn then
|
||||
if rr.dtype == dns.types.TXT and rr.TXT and rr.TXT.text then
|
||||
for _, txt_str in ipairs(rr.TXT.text) do
|
||||
if #txt_str > 0 then parse_txt_string(txt_str, txt_kv) end
|
||||
end
|
||||
elseif rr.dtype == dns.types.SRV and rr.SRV then
|
||||
srv_port = srv_port or rr.SRV.port
|
||||
srv_target = srv_target or rr.SRV.target
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
process(response.answers)
|
||||
process(response.add)
|
||||
|
||||
return txt_kv, srv_port, srv_target
|
||||
end
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Output formatters (one per Matter service kind)
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
--; Build an output table for a commissioned (_matter._tcp) device.
|
||||
local function build_operational_output(instance, txt, srv_port, srv_target)
|
||||
local t = stdnse.output_table()
|
||||
t["Instance"] = instance
|
||||
|
||||
-- Instance name encodes "FabricID-NodeID", each as a 16-hex-char value.
|
||||
local fid, nid = instance:match("^(%x+)%-(%x+)$")
|
||||
if fid and nid then
|
||||
t["Fabric ID"] = "0x" .. fid:upper()
|
||||
t["Node ID"] = "0x" .. nid:upper()
|
||||
end
|
||||
|
||||
if txt["T"] then
|
||||
t["TCP support"] = (txt["T"] == "1") and "yes" or "no"
|
||||
end
|
||||
if txt["SII"] then t["Session Idle Interval"] = txt["SII"] .. " ms" end
|
||||
if txt["SAI"] then t["Session Active Interval"] = txt["SAI"] .. " ms" end
|
||||
if txt["SAT"] then t["Session Active Threshold"] = txt["SAT"] .. " ms" end
|
||||
if txt["ICD"] and txt["ICD"] ~= "0" then
|
||||
t["Intermittently Connected Device"] = "yes"
|
||||
end
|
||||
if srv_port then t["Port"] = tostring(srv_port) end
|
||||
if srv_target then t["Host"] = srv_target end
|
||||
return t
|
||||
end
|
||||
|
||||
--; Build an output table for a commissioning-mode (_matterc._udp) device.
|
||||
local function build_commissioning_output(instance, txt, srv_port, srv_target)
|
||||
local t = stdnse.output_table()
|
||||
t["Instance"] = instance
|
||||
|
||||
if txt["VP"] then
|
||||
-- VP encodes "VendorID+ProductID" in decimal.
|
||||
local vid_s, pid_s = txt["VP"]:match("^(%d+)%+(%d+)$")
|
||||
if vid_s then
|
||||
t["Vendor ID"] = fmt_vendor(tonumber(vid_s))
|
||||
t["Product ID"] = string.format("0x%04X", tonumber(pid_s))
|
||||
else
|
||||
t["VP"] = txt["VP"]
|
||||
end
|
||||
end
|
||||
|
||||
if txt["DT"] then t["Device type"] = fmt_devtype(tonumber(txt["DT"])) end
|
||||
if txt["DN"] then t["Device name"] = txt["DN"] end
|
||||
if txt["D"] then t["Discriminator"] = txt["D"] end
|
||||
if txt["CM"] then
|
||||
local cm = tonumber(txt["CM"]) or 0
|
||||
t["Commissioning mode"] =
|
||||
string.format("%d (%s)", cm, CM_MODES[cm] or "unknown")
|
||||
end
|
||||
if txt["JF"] then t["Joint fabric mode"] = txt["JF"] end
|
||||
if txt["RI"] then t["Rotating device ID"] = txt["RI"] end
|
||||
if txt["PH"] then t["Pairing hint"] = fmt_pairing_hint(txt["PH"]) end
|
||||
if txt["PI"] and txt["PI"] ~= "" then
|
||||
t["Pairing instruction"] = txt["PI"]
|
||||
end
|
||||
if txt["CP"] then t["Commissioner passcode"] = txt["CP"] end
|
||||
if txt["T"] then
|
||||
t["TCP support"] = (txt["T"] == "1") and "yes" or "no"
|
||||
end
|
||||
if txt["SII"] then t["Session Idle Interval"] = txt["SII"] .. " ms" end
|
||||
if txt["SAI"] then t["Session Active Interval"] = txt["SAI"] .. " ms" end
|
||||
if txt["SAT"] then t["Session Active Threshold"] = txt["SAT"] .. " ms" end
|
||||
if txt["ICD"] and txt["ICD"] ~= "0" then
|
||||
t["Intermittently Connected Device"] = "yes"
|
||||
end
|
||||
if srv_port then t["Port"] = tostring(srv_port) end
|
||||
if srv_target then t["Host"] = srv_target end
|
||||
return t
|
||||
end
|
||||
|
||||
--; Build an output table for a Thread border router (_meshcop._udp).
|
||||
local function build_meshcop_output(instance, txt, srv_port, srv_target)
|
||||
local t = stdnse.output_table()
|
||||
t["Instance"] = instance
|
||||
if txt["nn"] then t["Network name"] = txt["nn"] end
|
||||
if txt["xp"] then t["Extended PAN ID"] = txt["xp"] end
|
||||
if txt["tv"] then t["Thread version"] = txt["tv"] end
|
||||
if txt["xa"] then t["Extended address"] = txt["xa"] end
|
||||
if txt["pt"] then t["Border agent port"] = txt["pt"] end
|
||||
if txt["sq"] then t["Sequence number"] = txt["sq"] end
|
||||
if txt["bb"] then t["BBR seq number"] = txt["bb"] end
|
||||
if txt["sb"] then
|
||||
local sb = tonumber(txt["sb"], 16) or tonumber(txt["sb"])
|
||||
t["State bitmap"] = sb and string.format("0x%X", sb) or txt["sb"]
|
||||
end
|
||||
if srv_port then t["Port"] = tostring(srv_port) end
|
||||
if srv_target then t["Host"] = srv_target end
|
||||
return t
|
||||
end
|
||||
|
||||
local OUTPUT_BUILDERS = {
|
||||
operational = build_operational_output,
|
||||
commissioning = build_commissioning_output,
|
||||
meshcop = build_meshcop_output,
|
||||
}
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Discovery driver
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
--; For one Matter service type, query mDNS and return a list of
|
||||
-- { label, info } device tables (possibly empty).
|
||||
local function discover_service(host, svc)
|
||||
local response = query_mdns(host, svc.qname)
|
||||
if not response then return {} end
|
||||
|
||||
local devices = {}
|
||||
|
||||
for _, ans in ipairs(response.answers or {}) do
|
||||
if ans.dtype == dns.types.PTR and ans.dname == svc.qname
|
||||
and ans.domain then
|
||||
local instance_fqdn = ans.domain
|
||||
local instance = strip_service_suffix(instance_fqdn, svc.qname)
|
||||
|
||||
local txt_kv, srv_port, srv_target =
|
||||
collect_instance_records(response, instance_fqdn)
|
||||
|
||||
local builder = OUTPUT_BUILDERS[svc.kind]
|
||||
if builder then
|
||||
devices[#devices + 1] = {
|
||||
label = svc.label,
|
||||
info = builder(instance, txt_kv, srv_port, srv_target),
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return devices
|
||||
end
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- NSE entry points
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
--; Match either mDNS (5353/udp) or Matter operational (5540) ports.
|
||||
portrule = function(host, port)
|
||||
return shortport.portnumber(MDNS_PORT, "udp")(host, port)
|
||||
or port.number == MATTER_PORT
|
||||
end
|
||||
|
||||
action = function(host, port)
|
||||
-- For port 5540 we just flag the open port; mDNS yields richer info.
|
||||
if port.number == MATTER_PORT then
|
||||
return ("Matter operational port is open " ..
|
||||
"(run with -p 5353/udp for mDNS device details)")
|
||||
end
|
||||
|
||||
local all_devices = {}
|
||||
for _, svc in ipairs(MATTER_SERVICES) do
|
||||
for _, dev in ipairs(discover_service(host, svc)) do
|
||||
all_devices[#all_devices + 1] = dev
|
||||
end
|
||||
end
|
||||
|
||||
if #all_devices == 0 then return nil end
|
||||
|
||||
-- Build the top-level output table; number duplicate service labels.
|
||||
local output = stdnse.output_table()
|
||||
local label_seen = {}
|
||||
for _, d in ipairs(all_devices) do
|
||||
label_seen[d.label] = (label_seen[d.label] or 0) + 1
|
||||
end
|
||||
|
||||
local label_idx = {}
|
||||
for _, d in ipairs(all_devices) do
|
||||
local key
|
||||
if label_seen[d.label] == 1 then
|
||||
key = d.label
|
||||
else
|
||||
label_idx[d.label] = (label_idx[d.label] or 0) + 1
|
||||
key = string.format("%s #%d", d.label, label_idx[d.label])
|
||||
end
|
||||
output[key] = d.info
|
||||
end
|
||||
|
||||
return output
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue