domotz-remote-pawn
Version:
Domotz Agent
294 lines (237 loc) • 10.4 kB
JavaScript
/** This file is part of Domotz Agent.
* Copyright (C) 2016 Domotz Ltd
*
* Domotz Agent is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Domotz Agent is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Domotz Agent. If not, see <http://www.gnu.org/licenses/>.
*
*This module executes the scanning of the known TCP Ports for the currently available
* devices,
* Created by tom on 18/12/15.
*/
var factory = function (q, proc, nmapCommands, xml2js, engineRequest, configuration, parser, interfacesBindingStorage) {
var servicesByIp = {};
var retryInterval = 2000;
var maxRetry = 3;
var macToIp = {};
var ipToMac = {};
/***
* console overrides
*
**/
var my_console = new Object();
var engineMonitoredServicesList = null;
my_console.debug = console.debug;
my_console.info = console.info;
my_console.warn = console.warn;
my_console.error = console.error;
function postResult() {
var scanning_result = {};
for (var ip in servicesByIp) {
if (servicesByIp.hasOwnProperty(ip)) {
scanning_result[ipToMac[ip]] = {};
for (var port in servicesByIp[ip]) {
if (servicesByIp[ip].hasOwnProperty(port)) {
scanning_result[ipToMac[ip]][port] = servicesByIp[ip][port].curr_status;
}
}
}
}
my_console.info("TCP_SERVICES: ******* FINAL RESULT IS : " + JSON.stringify(scanning_result));
if (!isEmpty(scanning_result)) {
engineRequest.sendWithPromise('/tcp-service/status', 'PUT', scanning_result, function (response) {
my_console.info("TCP_SERVICES: result sucessfully uploaded");
my_console.info('TCP_SERVICES: Monitoring cycle ended');
});
}
else {
my_console.info("TCP_SERVICES: AVOIDING sending update for empty paylod");
}
}
function showServicesStatus() {
my_console.debug('TCP_SERVICES: show services status %s ...', JSON.stringify(servicesByIp));
}
function getParseServiceStatus(result, ip) {
try {
var services_status = parser(result);
my_console.debug('TCP_SERVICES (%s): Got result for ip ', ip, ip, JSON.stringify(services_status));
if (!isEmpty(services_status)) {
for (var port in services_status) {
if (services_status.hasOwnProperty(port)) {
servicesByIp[ip][port]["curr_status"] = services_status[port];
}
}
}
}
catch (err) {
my_console.warn('TCP_SERVICES (%s): WARNING IN PARSING NMAP RESULT for ip ', ip, ip)
my_console.warn('TCP_SERVICES (%s): ', ip, err.message);
}
}
function isEmpty(value) {
return Object.keys(value).length === 0;
}
function getPortStringForIp(ip) {
var portListAsString = Object.keys(servicesByIp[ip]).toString();
my_console.debug("TCP_SERVICES (%s): Port String : %s ", ip, portListAsString);
return portListAsString;
}
function getPortStringForIpOnlyOffLine(ip) {
var portListOffLine = [];
for (var service in servicesByIp[ip]) {
if (servicesByIp[ip].hasOwnProperty(service)) {
if (servicesByIp[ip][service].curr_status !== "ONLINE" &&
servicesByIp[ip][service].prev_status === "ONLINE" || servicesByIp[ip][service].prev_status === "UNKNOWN") {
portListOffLine.push(service);
}
}
}
var portListAsString = portListOffLine.toString();
my_console.debug("TCP_SERVICES (%s): Port String (filtered): %s", ip, portListAsString);
return portListAsString;
}
function discoverServicesStatus(ip, portString) {
var cmd = nmapCommands.getExplicitPortNmapCmd(ip, portString);
my_console.info("TCP_SERVICES (%s): Scanning IP ports of device: %s", ip, ip);
my_console.info("TCP_SERVICES (%s): command is '%s'", ip, cmd);
var execPromise = proc.exec(cmd)
.then(xml2js.toJSON)
.then(function (result) {
my_console.debug("TCP_SERVICES (%s): nmap scanning result: %s", ip, JSON.stringify(result));
getParseServiceStatus(result, ip);
my_console.debug("TCP_SERVICES (%s): resolving promise for ip: %s", ip, ip);
})
.catch(function (error) {
my_console.error('TCP_SERVICES (%s): Error on collecting nmap data for ip %s', ip, ip);
my_console.error(error.stack);
});
return execPromise;
}
function discoverServicesStatusWithRetry(ip, portString) {
var count = maxRetry;
var deferred = q.defer();
function _retry() {
if (count < maxRetry) {
portString = getPortStringForIpOnlyOffLine(ip);
}
if (portString === "") {
my_console.debug("TCP_SERVICES (%s): Exiting retry loop, no further analysis is necessary", ip);
return deferred.resolve();
}
if (count === 0) {
my_console.debug("TCP_SERVICES (%s): Exiting retry loop, count : %s", ip, count);
return deferred.resolve();
}
return q.delay(retryInterval).then(function () {
my_console.debug("TCP_SERVICES (%s): Trying to execute discovery : %s/%s", ip, (maxRetry - count + 1), maxRetry);
count--;
return discoverServicesStatus(ip, portString).then(_retry);
});
}
q.nextTick(_retry);
return deferred.promise;
}
function translateToIp() {
macToIp = {};
ipToMac = {};
var interfaceInfo = interfacesBindingStorage.getInterfaceInfo();
for (var mac in interfaceInfo) {
if (interfaceInfo.hasOwnProperty(mac)) {
macToIp[mac] = interfaceInfo[mac];
ipToMac[interfaceInfo[mac]] = mac;
}
}
my_console.debug('TCP_SERVICES: mac translation table ' + JSON.stringify(macToIp));
}
function invalidateList() {
"use strict";
my_console.info("TCP_SERVICES: invalidating monitored devices list");
engineMonitoredServicesList = null;
}
function remotely_get_service_status(callback) {
var parseResult = function (response) {
my_console.info("TCP_SERVICES: using engine response %s ", JSON.stringify(response.body));
engineMonitoredServicesList = response;
translateToIp();
servicesByIp = {};
if (!isEmpty(macToIp)) {
for (var mac in response.body) {
if (response.body.hasOwnProperty(mac)) {
if (macToIp[mac]) {
servicesByIp[macToIp[mac]] = {};
for (var port in response.body[mac]) {
if (response.body[mac].hasOwnProperty(port)) {
servicesByIp[macToIp[mac]][port] =
{
"prev_status": response.body[mac][port],
"curr_status": "OFFLINE" // as a default, ensures that the final status is OFFLINE even if no data is actually collected about it
};
}
}
}
else {
my_console.warn("TCP_SERVICES: CANNOT TRANSLATE MAC TO IP " + mac);
}
}
}
my_console.debug("TCP_SERVICES: services by id status: " + JSON.stringify(servicesByIp));
callback();
}
else {
my_console.warn("TCP_SERVICES: mac-ip translation table is empty, cannot proceed ");
}
};
if (engineMonitoredServicesList !== null) {
my_console.debug('TCP_SERVICES: using cached tcp service status ...');
parseResult(engineMonitoredServicesList);
}else {
my_console.debug('TCP_SERVICES: requesting tcp service status to engine ...');
engineRequest.sendWithPromise('/tcp-service', 'GET', {}, parseResult);
}
}
var locked = false;
function monitor() {
if (!locked) {
try {
locked = true;
servicesByIp = {};
my_console.info('TCP_SERVICES: Monitoring cycle started ...');
remotely_get_service_status(function () {
my_console.debug("TCP_SERVICES: activating monitor cycle ...");
var promises = [];
for (var ip in servicesByIp) {
if (servicesByIp.hasOwnProperty(ip)) {
var portString = getPortStringForIp(ip);
promises.push(discoverServicesStatusWithRetry(ip, portString));
}
}
q.all(promises)
.then(function () {
showServicesStatus();
postResult();
}).finally(function () {
//my_console.debug(promises);
});
});
} catch (err) {
my_console.error('TCP_SERVICES: ' + err.name + ' ' + err.message);
} finally {
locked = false;
}
}
}
return {
monitor: monitor,
invalidateList: invalidateList
};
};
module.exports.factory = factory;