UNPKG

domotz-remote-pawn

Version:

Domotz Agent

327 lines (290 loc) 13.7 kB
/** 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 wraps the nmap commands used for IP devices' interfaces binds (mac-ip-hostname) * Created by Iacopo Papalini <iacopo@domotz.com> on 18/05/16. */ module.exports.BindDiscoveryFactory = function (nmapCommands, nmapXml2Js, fastParser, resourceLocator) { 'use strict'; var log = resourceLocator.log; var q = resourceLocator.q; var lodash = resourceLocator.lodash; var proc = resourceLocator.utils.proc; var hostInfo = resourceLocator.discovery.host; var settingsManager = resourceLocator.settingsManager; var async = resourceLocator.async; var platform = resourceLocator.utils.platform; var nmap_max_parallel_processes = lodash.get( resourceLocator, 'configuration.settings.discovery.nmap_max_parallel_processes', platform.isUbuntuCorePrivate() ? 3 : 10 ); var iteration = 0; var baseConsole = log.decorateLogs(); var myConsole = baseConsole; var SPAWN_HARD_TIMEOUT = 180000; function setIteration(i) { iteration = i; myConsole = baseConsole.decorate(String(iteration)); } function executeDiscoveryCommands(commandList, iteration) { myConsole.verbose('executeDiscoveryCommands START '); var tasks = commandList.map(function (command) { return function (callback) { proc.exec(command.nmap_command, { timeout: SPAWN_HARD_TIMEOUT, suffix_comment: iteration - 1 }) .then(nmapXml2Js.toJSON) .then(function (result) { return fastParser.parse(result); }) .then(function (parsedResult) { callback(null, parsedResult); }) .catch(function (err) { myConsole.error('Nmap error %s', err.toString()); if (err && err.code === 'EMFILE') { proc.gracefulShutdown(); } callback(null, { error: err }); }); }; }); var deferred = q.defer(); var rets = []; async.parallelLimit(tasks, nmap_max_parallel_processes, function (err, results) { if (err) { return deferred.reject(err); } for (var i = 0; i < results.length; i++) { var result = results[i]; var deferredResult = q.defer(); if (result && result.error) { myConsole.debug('Error during discovery command execution: %s', JSON.stringify(result)); deferredResult.reject(result.error); } else if (result instanceof Error) { myConsole.debug('Error during discovery command execution. Message: %s, Stacktrace: %s', result.message, result.stack); deferredResult.reject(result); } else { deferredResult.resolve(result); } rets.push(deferredResult.promise); } return deferred.resolve(q.allSettled(rets)); }); return deferred.promise; } function getFulfilledPromisesResults(results) { /** * see * https://github.com/kriskowal/q/wiki/API-Reference#promiseallsettled * for information about the structure returned by allSettled * This function returns only the device bindings for the discoveries that * actually succeeded. */ var fulfilled = lodash.map(results, function (netScanResult) { if (netScanResult.state === 'fulfilled') { return netScanResult.value; } }); return lodash.filter(fulfilled, function (res) { return res !== null; }); } function _checkAllBindingsAreEmpties(bindings) { var areBindingsAllEmpties = lodash.every(bindings, lodash.isEmpty); if (areBindingsAllEmpties) { var err = new Error('All bindings from discovery commands empties'); err.code = 'DISCOVERY_EMPTY'; throw err; } return; } function getAgentHostname() { var tenant = process.env.TENANT; if (tenant === 'adi') { return 'Capture Box'; } return 'Domotz'; } function isNetworkScanEnabled(broadCastDisabled, bigNetwork, iface) { return !broadCastDisabled && !bigNetwork && !iface.forced_ip_list && !iface.forced_ip_range; } function scanAllHostsInNetwork(mac, name, ip, netmask, nmapType, iface, commandList) { if (mac !== null) { myConsole.info('Scanning all layer 2 network: %s %s/%s with nmap type: %s', name, ip, netmask, nmapType); } else { myConsole.info('Scanning all layer 3 network: %s %s/%s with nmap type: %s', name, ip, netmask, nmapType); } var commands = nmapCommands.getFastNmapCmd(ip, netmask, nmapType, name, iface); commandList = commandList.concat(commands); return commandList; } function _calculateIps(ipList, ipRange) { var retValue = []; if (ipList && ipList.length > 0) { retValue = retValue.concat(ipList); } if (ipRange && ipRange.length > 0) { retValue = retValue.concat(ipRange); } return retValue; } function scanAllKnownIPAddresses(iface, name, ip, netmask, commandList, nmapType) { // When ip sweep scan is disabled, or we discover IPs slowly with a Big Network Scan, // we must ping individually all the known IPs to detect if they are online return commandList.concat([ { iface_name: name, iface: iface, nmap_command: nmapCommands.getRetryCmd(iface, _calculateIps(iface.ipList, iface.ipRange), nmapType), }, ]); } function addLocalhostInterface(myInterfacesBindings) { var defaultInterface = hostInfo.getDefaultInterface(); var macUpperCase = defaultInterface.mac.toUpperCase(); var defaultInterfaceIP = defaultInterface.ip; myConsole.debug( 'Internal Interface %s/%s. Add Binding: %s->%s', defaultInterfaceIP, defaultInterface.netmask, macUpperCase, defaultInterfaceIP ); myInterfacesBindings[macUpperCase] = [{ ip: defaultInterfaceIP, host_name: getAgentHostname() }]; } /** * Discovers all the IP devices in the given interfaces. When done the callback is invoked with, as argument, * an Array of Object similar to: * { * 'mac': '[the mac address]', * 'ip': '[the ip address]', * 'host_name': '[the host name]' * } */ function discoverAll() { var interfaces = hostInfo.getAllowedInterfacesList(); var name; var commandList = []; var myInterfacesBindings = {}; var broadCastDisabled = settingsManager.isBroadcastDiscoveryDisabled(); var allowMultipleLocalhostInterfaces = settingsManager.getSetting( 'configuration.settings.discovery.allow_multiple_localhost_interfaces', true ); var monitorLocalhostInterfaces = settingsManager.getSetting('configuration.settings.discovery.monitor_localhost_interfaces', true); var bigNetworkScanner = resourceLocator.discovery.bigNetworkScanner; myConsole.info('Discover ALL START - Interfaces: %s', JSON.stringify(interfaces)); for (name in interfaces) { if (interfaces.hasOwnProperty(name)) { var iface = interfaces[name]; if (!iface.ip && !iface.forced_ip_list && !iface.forced_ip_range) { myConsole.warn('Discover ALL, missing ip address for interface: %s', name); continue; } var ip = iface.ip; var mac = iface.mac; var netmask = iface.netmask; var nmapType = mac !== null ? nmapCommands.NMAP_TYPE.DEFAULT : nmapCommands.NMAP_TYPE.EXTERNAL_SUBNET; var externalDevice = [ { ip: ip, }, ]; if (hostInfo.deviceBelongsToExternalHosts(externalDevice)) { nmapType = nmapCommands.NMAP_TYPE.EXTERNAL_HOST; } var bigNetwork = bigNetworkScanner.isBigNetwork(iface); var enableNetworkScan = isNetworkScanEnabled(broadCastDisabled, bigNetwork, iface); if (bigNetwork) { myConsole.debug('Network %s (%s/%s) is a big network', name, ip, netmask); } if ( nmapType === nmapCommands.NMAP_TYPE.EXTERNAL_HOST || nmapType === nmapCommands.NMAP_TYPE.EXTERNAL_SUBNET || nmapType === nmapCommands.NMAP_TYPE.EXTERNAL_SUBNET_PING_ONLY ) { myConsole.debug('Custom Interface for Monitoring External Network: %s/%s', ip, netmask); } else if (iface.forced_ip_list) { myConsole.debug('Virtual interface %s to scan %s ip addresses', name, iface.ipList.length); } else if (iface.forced_ip_range) { myConsole.debug('Virtual interface %s to scan %s ip address ranges', name, iface.ipRange.length); } else if (allowMultipleLocalhostInterfaces && monitorLocalhostInterfaces) { var macUpperCase = mac.toUpperCase(); myConsole.debug('Real Interface for Monitoring Internal Network %s/%s. Add Binding: %s->%s', ip, netmask, mac, ip); if (!myInterfacesBindings.hasOwnProperty(macUpperCase)) { myInterfacesBindings[macUpperCase] = []; } myInterfacesBindings[macUpperCase].push({ ip: ip, host_name: getAgentHostname() }); } if (enableNetworkScan || netmask === 32) { commandList = scanAllHostsInNetwork(mac, name, ip, netmask, nmapType, iface, commandList); } else if ((iface.ipList && iface.ipList.length > 0) || (iface.ipRange && iface.ipRange.length > 0)) { commandList = scanAllKnownIPAddresses(iface, name, ip, netmask, commandList, nmapType); } else { myConsole.info('Network scan disabled and no known IP addresses for network %s (%s/%s), doing nothing', name, ip, netmask); } } } if (!allowMultipleLocalhostInterfaces && monitorLocalhostInterfaces) { addLocalhostInterface(myInterfacesBindings); } return executeDiscoveryCommands(commandList, iteration).then(function (results) { myConsole.verbose('executeDiscoveryCommands END'); var bindings = getFulfilledPromisesResults(results); _checkAllBindingsAreEmpties(bindings); bindings.push(myInterfacesBindings); myConsole.info('Discover ALL END - Interfaces: %s', JSON.stringify(interfaces)); return bindings; }); } function discover(ipList, nmapType, hostTimeout, iface) { myConsole.info('Discover RETRY called - IP addresses: %s', JSON.stringify(ipList)); var commandList = [{ nmap_command: nmapCommands.getRetryCmd(iface, ipList, nmapType, hostTimeout) }]; return executeDiscoveryCommands(commandList, iteration).then(function (results) { myConsole.debug('Discover retry results %s', JSON.stringify(results)); myConsole.verbose('executeDiscoveryCommands END '); myConsole.info('Discover RETRY END - IP addresses: %s', JSON.stringify(ipList)); return getFulfilledPromisesResults(results); }); } function assureInterfaces() { var deferred = q.defer(); try { // We must not use getAllowedInterfacesList here, because it is possible to have an empty allowed list // as a valid state var interfaces = hostInfo.getInterfacesList(); if (Object.keys(interfaces).length === 0) { myConsole.info('assureInterfaces - No interfaces available, trying to rediscover host info'); hostInfo.recoverHostInfo().finally(function () { deferred.resolve(); }); } else { deferred.resolve(); } } catch (err) { myConsole.info('assureInterfaces - Error trying to get interfaces list %s', err); deferred.resolve(); } return deferred.promise; } return { setIteration: setIteration, discoverAll: discoverAll, discover: discover, assureInterfaces: assureInterfaces, }; };