Another LAN dashboard, with known/unknown hosts and port scan

This node-red flow generate a dashboard with the LAN online hosts. A csv file of known hosts is used to show specific name and icon.

Based on nmap usage, a host port scan is perform when a host is clicked.

You need the following nodes installed :

  • node-red-dashboard
  • node-red-node-ui-list
  • node-red-contrib-arp
  • node-red-contrib-ui-led

"nmap" package is needed, This flow run "nmap" from "exec" nodes.

All parameters are set in the "Set parameter" change node :

  • localinterface : the network interface name to add to the online hosts, see below.
  • networkaddress : the local network address to scan, like : 192.168.1.0
  • networkmask : the local network mask, like : 24
  • filename : path and name of the csv file containing known hosts, like /home/user/knownhost.csv. Three column must be present
    • name : the host name, like "Box SFR"
    • mac : the host MAC address, like "f0:b4:e9:ff:2b:a9"
    • icon_name : the material design icon name associated to the host, like "router"
  • unknowndefault_name : the default to show for unknown host
  • unknowndefault_icon : the default material design icon for unknownhosts

Here is a two line csv for exemple :

name,mac,icon_name
Mi Wifi 3,f0:b4:e9:ff:2b:a9,router
My Desktop PC,fc:5f:f4:f1:a5:90,desktop_windows

When you click on a host in the list, nmap is launch to find open ports, the "Running scan" led in the interface show that nmap is still running.

And that's it !

To find all the LAN hosts, we do a LAN scan with "nmap" and we use the resultant mac address table of the operating system. But, localhost don't appear is the mac address table, so we have to add it. That's why we need the local interface name, in order to add it's mac address and IP address to the detected online hosts.

Warning : Tested on Node-Red 3.0.1
On Node-Red 2.2.2 the "Read file" node don't handle filename from within msg attribute, so you have to set the csv file name directly in the "Csv knownhosts" node.

Dashboard :

name

Flow :

name

[{"id":"5031e4e2412900c5","type":"tab","label":"Network v2","disabled":false,"info":"","env":[]},{"id":"c27da3d55466e13b","type":"inject","z":"5031e4e2412900c5","name":"Start","props":[{"p":"topic","vt":"str"}],"repeat":"300","crontab":"","once":true,"onceDelay":"0.1","topic":"autorun","x":90,"y":80,"wires":[["2c47203bb84d50e5"]]},{"id":"540b4b99720f427a","type":"file in","z":"5031e4e2412900c5","name":"Csv knownhosts","filename":"filename","filenameType":"msg","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":140,"y":180,"wires":[["8c8fc32a2c802b0e"]]},{"id":"8c8fc32a2c802b0e","type":"csv","z":"5031e4e2412900c5","name":"Load csv","sep":",","hdrin":true,"hdrout":"none","multi":"mult","ret":"\\n","temp":"","skip":"0","strings":true,"include_empty_strings":"","include_null_values":"","x":300,"y":180,"wires":[["b6148ade66947e34"]]},{"id":"d5039b75d750ad9f","type":"exec","z":"5031e4e2412900c5","command":"nmap -sn ","addpay":"payload","append":"","useSpawn":"false","timer":"","winHide":false,"oldrc":false,"name":"nmap full LAN","x":880,"y":180,"wires":[["a15a0518c34dbdac","9503b53cf6fe0ca1"],[],[]]},{"id":"a15a0518c34dbdac","type":"arp","z":"5031e4e2412900c5","name":"ARP","macs":"","x":1030,"y":160,"wires":[["5dcf06220cd89c9a"]]},{"id":"87050072f518b240","type":"change","z":"5031e4e2412900c5","name":"","rules":[{"t":"set","p":"knownhosts","pt":"msg","to":"payload","tot":"msg"},{"t":"set","p":"unknownhosts","pt":"msg","to":"[]","tot":"json"},{"t":"set","p":"payload","pt":"msg","to":"networkaddress & \"/\" & networkmask","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":180,"wires":[["d5039b75d750ad9f"]]},{"id":"5dcf06220cd89c9a","type":"function","z":"5031e4e2412900c5","name":"Doing the job","func":"/* -------------------------------------------------*/\n// Convert an IP address to a Long Int value, used for sorting IP address.\nfunction ipv4StringToLong(anIp) {\n    // Based on https://codereview.stackexchange.com/questions/54775/converting-an-ip\n    // RegExp for One Octet\n    var octet = '([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])';\n    // RegExp for one IP V4 Address, based on Octet\n    var ipRegExp = new RegExp('^' + octet + '\\.' + octet + '\\.' + octet + '\\.' + octet + '$');\n\n    var octets = anIp.match(ipRegExp);\n    if (octets === null) {\n        throw 'Invalid IPv4 address!';\n    } else {\n        // The right-shift operators prevent (128 << 24)\n        // from being interpreted as a negative number.\n        return ((octets[1] << 24) >>> 0) +\n            ((octets[2] << 16) >>> 0) +\n            ((octets[3] << 8) >>> 0) +\n            (octets[4] << 0);\n    }\n};\n\n/*---------------------------------------------------------------*/\n\n\nvar findhosts = msg.payload;\n\n// localhost is not shown in arp table, so we add it to the payload\nfindhosts.push({ ip: msg.localipaddress, mac: msg.localmacaddress, iface: msg.localinterface });\n\n// this line add a false unknown host, uncomment for test purpose\n//findhosts.push({ ip: \"192.168.31.3\", mac: \"00:00:00:00:00:00\", iface: msg.payload[0].iface });\n\nvar payloadForUIList = [];\nvar nbOnlineUnknown = 0;\n\nfor (var index = 0, i = findhosts.length; index < i; index++) {\n    // instead of using a \"for\" loop, we used the array initialized in \"Adding mac address list\", may be usefull for treatment optimisation on big network.\n    var knownhostsindex = msg.knownmac.indexOf(findhosts[index].mac);\n    // if current host is not a known host\n    if (knownhostsindex == -1) {\n        msg.unknownhosts.push(findhosts[index]);\n        nbOnlineUnknown++;\n        findhosts[index][\"known\"] = false;\n        findhosts[index][\"icon_name\"] = msg.unknowndefault_icon;\n        findhosts[index][\"name\"] = msg.unknowndefault_name;\n        // this is for building the payload for the ui list.\n        var currentonlinehost = {\n            \"title\": '<font color=\\\"red\\\"><b>' + findhosts[index].name + '</b></font>',\n            \"description\": findhosts[index].ip,\n            \"icon_name\": findhosts[index].icon_name,\n        };\n    } else {\n        var currentknownhost = msg.knownhosts[knownhostsindex];\n        findhosts[index][\"known\"] = true;\n        findhosts[index][\"icon_name\"] = currentknownhost.icon_name;\n        findhosts[index][\"name\"] = currentknownhost.name;\n        // this is for building the payload for the ui list.\n        var currentonlinehost = {\n            \"title\": '<font color=\\\"green\\\"><b>' + findhosts[index].name + '</b></font>',\n            \"description\": findhosts[index].ip,\n            \"icon_name\": findhosts[index].icon_name,\n        };\n    }\n    // payload for the msg dedicated to the ui list\n    payloadForUIList.push(currentonlinehost);\n}\n\n// ordering payloadForUIList by IP address\npayloadForUIList.sort((a,b) => {\n    let da = ipv4StringToLong(a.description),\n        db = ipv4StringToLong(b.description);\n    return da - db;\n});\n\nvar msgForUIList = { payload: payloadForUIList };\nreturn [msg, msgForUIList, {payload: msg.knownhosts.length}, {payload: msg.payload.length}, {payload: nbOnlineUnknown, unknownhosts: msg.unknownhosts}];","outputs":5,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1180,"y":160,"wires":[["b80bd672ef7a944a"],["82f79bc8f77c8ab0"],["7c274bb0e4726628"],["90e086c0e1d6cf68"],["0da22872f14095ae"]],"outputLabels":["All Data","Data for UI List","Nb total known","Nb total Online","Nb  Online Unknown and unknown info"]},{"id":"b6148ade66947e34","type":"function","z":"5031e4e2412900c5","name":"Adding mac address list","func":"/* On récupère un array des seuls attributs mac */\nconst maclist = msg.payload.map(({ mac }) => mac);\n\nmsg.knownmac= maclist;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":180,"wires":[["87050072f518b240"]],"outputLabels":["if new mac unalloawd"]},{"id":"7c840b548d69d16a","type":"ui_list","z":"5031e4e2412900c5","group":"f8e2871b2786bf93","name":"Online hosts list","order":5,"width":"6","height":"8","lineType":"three","actionType":"click","allowHTML":true,"outputs":1,"topic":"","x":200,"y":440,"wires":[["0a05a0550bbf5a67","2f2ac84bff68c430","cb16aa8edd70f918"]]},{"id":"7f9a918e79f79596","type":"debug","z":"5031e4e2412900c5","name":"To Do Managment of knownhosts csv file","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":280,"y":640,"wires":[]},{"id":"0a05a0550bbf5a67","type":"change","z":"5031e4e2412900c5","name":"removing html","rules":[{"t":"set","p":"payload.title","pt":"msg","to":"$replace(payload.title,/.+<b>(.+)<\\/b>.+/, \"$1\")\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":440,"wires":[["6f39876cd25f7a64"]]},{"id":"45e3c3f5d82a605b","type":"ui_text","z":"5031e4e2412900c5","group":"f8e2871b2786bf93","order":3,"width":0,"height":0,"name":"","label":"Total known","format":"{{msg.payload}}","layout":"row-spread","className":"","x":190,"y":480,"wires":[]},{"id":"b34bc89496861b86","type":"ui_text","z":"5031e4e2412900c5","group":"f8e2871b2786bf93","order":2,"width":0,"height":0,"name":"","label":"Total online","format":"{{msg.payload}}","layout":"row-spread","className":"","x":190,"y":520,"wires":[]},{"id":"afcaed6cba47cdb9","type":"ui_text","z":"5031e4e2412900c5","group":"f8e2871b2786bf93","order":4,"width":0,"height":0,"name":"","label":"Total unknown","format":"<font color=\"red\"><b>{{msg.payload}}</b></font>","layout":"row-spread","className":"","x":200,"y":600,"wires":[]},{"id":"82f79bc8f77c8ab0","type":"link out","z":"5031e4e2412900c5","name":"link out 6","mode":"link","links":["9a4a07866fc9563b"],"x":1325,"y":120,"wires":[]},{"id":"9a4a07866fc9563b","type":"link in","z":"5031e4e2412900c5","name":"link in 6","links":["82f79bc8f77c8ab0"],"x":85,"y":440,"wires":[["7c840b548d69d16a"]]},{"id":"7c274bb0e4726628","type":"link out","z":"5031e4e2412900c5","name":"link out 7","mode":"link","links":["013ac253dcc07cb7"],"x":1345,"y":160,"wires":[]},{"id":"013ac253dcc07cb7","type":"link in","z":"5031e4e2412900c5","name":"link in 7","links":["7c274bb0e4726628"],"x":85,"y":480,"wires":[["45e3c3f5d82a605b"]]},{"id":"90e086c0e1d6cf68","type":"link out","z":"5031e4e2412900c5","name":"link out 8","mode":"link","links":["f8771f7e9caaa15f"],"x":1325,"y":200,"wires":[]},{"id":"f8771f7e9caaa15f","type":"link in","z":"5031e4e2412900c5","name":"link in 8","links":["90e086c0e1d6cf68"],"x":85,"y":520,"wires":[["b34bc89496861b86"]]},{"id":"0da22872f14095ae","type":"link out","z":"5031e4e2412900c5","name":"link out 9","mode":"link","links":["8b4dfd7189f7a856"],"x":1305,"y":240,"wires":[]},{"id":"8b4dfd7189f7a856","type":"link in","z":"5031e4e2412900c5","name":"link in 9","links":["0da22872f14095ae"],"x":85,"y":580,"wires":[["afcaed6cba47cdb9","b7b77e1d98e81459"]]},{"id":"b80bd672ef7a944a","type":"link out","z":"5031e4e2412900c5","name":"msg with all data","mode":"link","links":["c2e9999e64794c14"],"x":1305,"y":80,"wires":[]},{"id":"c2e9999e64794c14","type":"link in","z":"5031e4e2412900c5","name":"link in 10","links":["b80bd672ef7a944a"],"x":85,"y":640,"wires":[["7f9a918e79f79596"]]},{"id":"fc797f52d7a6799f","type":"template","z":"5031e4e2412900c5","name":"Set commands","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"ifconfig {{localinterface}} | grep '\\<inet\\>' | sed -n '1p' | tr -s ' ' | cut -d ' ' -f3 | cut -d ':' -f2;\nifconfig {{localinterface}} | grep '\\<inet\\>' | sed -n '1p' | tr -s ' ' | cut -d ' ' -f5;\nifconfig {{localinterface}} | grep 'ether' | tr -s ' ' | cut -d ' ' -f3","output":"str","x":480,"y":80,"wires":[["bf70ba047ec4a620"]]},{"id":"bf70ba047ec4a620","type":"exec","z":"5031e4e2412900c5","command":"","addpay":"payload","append":"","useSpawn":"false","timer":"","winHide":false,"oldrc":false,"name":"Get Interface info","x":670,"y":80,"wires":[["498f0c253c9aa61a"],[],[]]},{"id":"498f0c253c9aa61a","type":"csv","z":"5031e4e2412900c5","name":"","sep":",","hdrin":"","hdrout":"none","multi":"mult","ret":"\\n","temp":"","skip":"0","strings":false,"include_empty_strings":"","include_null_values":"","x":830,"y":60,"wires":[["76b9775bce5de712"]]},{"id":"76b9775bce5de712","type":"change","z":"5031e4e2412900c5","name":"","rules":[{"t":"set","p":"localipaddress","pt":"msg","to":"payload[0].col1","tot":"msg"},{"t":"set","p":"localnetmask","pt":"msg","to":"payload[1].col1","tot":"msg"},{"t":"set","p":"localmacaddress","pt":"msg","to":"payload[2].col1","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":980,"y":60,"wires":[["540b4b99720f427a"]]},{"id":"b7b77e1d98e81459","type":"debug","z":"5031e4e2412900c5","name":"To do - Alert for unknown hosts","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":250,"y":560,"wires":[]},{"id":"6f39876cd25f7a64","type":"change","z":"5031e4e2412900c5","name":"extracting payload data","rules":[{"t":"set","p":"hostname","pt":"msg","to":"payload.title","tot":"msg"},{"t":"set","p":"icon_name","pt":"msg","to":"payload.icon_name","tot":"msg"},{"t":"set","p":"ipaddress","pt":"msg","to":"payload.description","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":440,"wires":[["79a12d939e9c77c5"]]},{"id":"c9844c2808c1cb0b","type":"exec","z":"5031e4e2412900c5","command":"nmap ","addpay":"payload","append":"","useSpawn":"false","timer":"60","winHide":false,"oldrc":false,"name":"","x":950,"y":440,"wires":[["21493d55bb34ec99","06aa0230e3862814"],[],[]]},{"id":"a11f46ae09b00865","type":"ui_template","z":"5031e4e2412900c5","group":"cc5cf4f7d6597e8f","name":"nmap host scan result","order":4,"width":"10","height":"10","format":"<div ng-bind-html=\"msg.payload\"></div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1380,"y":440,"wires":[[]]},{"id":"21493d55bb34ec99","type":"function","z":"5031e4e2412900c5","name":"HTML formating","func":"// doing the minimum formating...\nmsg.payload = msg.payload.replace(/\\n/g, \"<br>\");\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1140,"y":440,"wires":[["a11f46ae09b00865"]]},{"id":"61539547712f0713","type":"ui_led","z":"5031e4e2412900c5","order":1,"group":"cc5cf4f7d6597e8f","width":0,"height":0,"label":"Running scan","labelPlacement":"left","labelAlignment":"left","colorForValue":[{"color":"#ffffff","value":"false","valueType":"bool"},{"color":"#008000","value":"true","valueType":"bool"}],"allowColorForValueInMessage":false,"shape":"circle","showGlow":true,"name":"Running scan led","x":1330,"y":340,"wires":[]},{"id":"2f2ac84bff68c430","type":"change","z":"5031e4e2412900c5","name":"turning led on","rules":[{"t":"set","p":"payload","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":340,"wires":[["61539547712f0713"]]},{"id":"06aa0230e3862814","type":"change","z":"5031e4e2412900c5","name":"turning led off","rules":[{"t":"set","p":"payload","pt":"msg","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":1140,"y":400,"wires":[["61539547712f0713"]]},{"id":"ab9d718973f46667","type":"ui_switch","z":"5031e4e2412900c5","name":"","label":"Detect OS","tooltip":"","group":"f8e2871b2786bf93","order":6,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"topic","topicType":"msg","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":false,"className":"","x":230,"y":260,"wires":[["c55f58afdcd86c9a"]]},{"id":"c55f58afdcd86c9a","type":"change","z":"5031e4e2412900c5","name":"","rules":[{"t":"set","p":"detectos","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":260,"wires":[[]]},{"id":"9365993334302474","type":"inject","z":"5031e4e2412900c5","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":90,"y":260,"wires":[["ab9d718973f46667"]]},{"id":"79a12d939e9c77c5","type":"function","z":"5031e4e2412900c5","name":"detect OS ?","func":"if (flow.get('detectos')) {\n    msg.payload = \" -O \" + msg.ipaddress;\n} else {\n    msg.payload = msg.ipaddress;\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":810,"y":440,"wires":[["c9844c2808c1cb0b"]]},{"id":"e8780d5db16f9f12","type":"ui_text","z":"5031e4e2412900c5","group":"f8e2871b2786bf93","order":1,"width":0,"height":0,"name":"","label":"last scan","format":"{{msg.payload}}","layout":"row-spread","className":"","x":1160,"y":280,"wires":[]},{"id":"9503b53cf6fe0ca1","type":"function","z":"5031e4e2412900c5","name":"date and time","func":"var date = new Date();\n\nmsg.payload = date.getDate() +\n    \"/\" + ('0' + (date.getMonth() + 1)).slice(-2) +\n    \"/\" + date.getFullYear() +\n    \" \" + ('0' + date.getHours()).slice(-2) +\n    \":\" + ('0' + date.getMinutes()).slice(-2) +\n    \":\" + ('0' + date.getSeconds()).slice(-2);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1000,"y":280,"wires":[["e8780d5db16f9f12"]]},{"id":"8eca0f4cc0a16449","type":"comment","z":"5031e4e2412900c5","name":"What you can do","info":"replacing the previous \"To do\" you can add alerting capability when an unkwon host appera\nlike : sending email, pushbullet...\n\nTake a look at the full msg opbject to see the informations you can used.","x":500,"y":560,"wires":[]},{"id":"84f4d763d9e97cb1","type":"comment","z":"5031e4e2412900c5","name":"Here, i want to...","info":"add the capabilty to manage the csv file content directly from the dashboard,\nthat's why the msg object is full data, because I make the choice of not using flow storage","x":560,"y":640,"wires":[]},{"id":"cb16aa8edd70f918","type":"change","z":"5031e4e2412900c5","name":"Cleaning previous result","rules":[{"t":"set","p":"payload","pt":"msg","to":"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":380,"wires":[["0d761213730b3c7f"]]},{"id":"0d761213730b3c7f","type":"link out","z":"5031e4e2412900c5","name":"link out 10","mode":"link","links":["2073921b7f833704"],"x":575,"y":380,"wires":[]},{"id":"2073921b7f833704","type":"link in","z":"5031e4e2412900c5","name":"link in 11","links":["0d761213730b3c7f"],"x":1285,"y":380,"wires":[["a11f46ae09b00865"]]},{"id":"2c47203bb84d50e5","type":"change","z":"5031e4e2412900c5","name":"Set parameters","rules":[{"t":"set","p":"localinterface","pt":"msg","to":"set the local interface name to add to lan hosts, ex : enp4s0f0","tot":"str"},{"t":"set","p":"networkaddress","pt":"msg","to":"Set the network addresse of the network you want to scan ex :192.168.1.0","tot":"str"},{"t":"set","p":"networkmask","pt":"msg","to":"set the netmask of the network to scan, ex : 24","tot":"str"},{"t":"set","p":"filename","pt":"msg","to":"the path and file name of the csv knownhosts","tot":"str"},{"t":"set","p":"unknowndefault_name","pt":"msg","to":"Unknown Host !","tot":"str"},{"t":"set","p":"unknowndefault_icon","pt":"msg","to":"error","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":280,"y":80,"wires":[["fc797f52d7a6799f"]]},{"id":"f8e2871b2786bf93","type":"ui_group","name":"LAN Report","tab":"36684677fbe16d92","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"cc5cf4f7d6597e8f","type":"ui_group","name":"Host Scan Report","tab":"36684677fbe16d92","order":2,"disp":true,"width":"10","collapse":false,"className":""},{"id":"36684677fbe16d92","type":"ui_tab","name":"Network V2","icon":"dashboard","order":21,"disabled":false,"hidden":false}]

Flow Info

Created 2 years, 11 months ago
Rating: 5 1

Actions

Rate:

Node Types

Core
  • change (x9)
  • comment (x2)
  • csv (x2)
  • debug (x2)
  • exec (x3)
  • file in (x1)
  • function (x5)
  • inject (x2)
  • link in (x6)
  • link out (x6)
  • template (x1)
Other
  • arp (x1)
  • tab (x1)
  • ui_group (x2)
  • ui_led (x1)
  • ui_list (x1)
  • ui_switch (x1)
  • ui_tab (x1)
  • ui_template (x1)
  • ui_text (x4)

Tags

  • LAN
  • dashboard
  • nmap
  • IP
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option