UNPKG

domotz-remote-pawn

Version:

Domotz Agent

251 lines (218 loc) 9.23 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/>. **/ /** * * Created by NI Team <dev@domotz.com> on 14/07/2023. */ const LOCK_ID = 'IP_CONFLICT'; const LOCK_INDEX = 1; module.exports.factory = function (resourceLocator) { var myConsole = resourceLocator.log.decorateLogs(); var platform = resourceLocator.utils.platform; var procUtils = resourceLocator.utils.proc; var exec = procUtils.exec; var path = resourceLocator.path; var async = resourceLocator.async; var mutex = resourceLocator.utils.mutex; var lodash = resourceLocator.lodash; var nmapBlackList = resourceLocator.nmapBlackList; var interfaceBindings = resourceLocator.interfacesBindingStorage; var host = resourceLocator.discovery.host; var cloudCaller = require('./cloudCaller')(resourceLocator); var getSettings = require('./getSettings')(resourceLocator); var parser = require('./parser')(resourceLocator); var activeConflicts = []; var solvedConflicts = []; var oldActiveConflicts = null; var options = {}; var iteration = 1; var ipConflictRetries = {}; function unlock() { if (mutex.isLocked(LOCK_ID)) { mutex.unlock(LOCK_ID, LOCK_INDEX); } } function _detectConflict(ipTarget, cb) { var res = platform.getNmapVersion(); res.then(function (data) { var cmd = _createNmapCommand(ipTarget); options.suffix_comment = iteration; exec(cmd, options) .then(function (report) { myConsole.debug('command is %s response nmap %s', cmd, report); var response = parser.parseOutput(report); if (response.length > 0) { data = { ip: ipTarget, mac_addresses: response }; cb(null, data); } else { var ipExistInOldActiveConflicts = lodash.some(oldActiveConflicts, function (element) { return element.ip === ipTarget; }); if (ipExistInOldActiveConflicts && ipConflictRetries[ipTarget] > 0) { ipConflictRetries[ipTarget] -= 1; myConsole.debug('Checking IP conflict resolution on IP %s, remaining checks %s', ipTarget, ipConflictRetries[ipTarget]); _detectConflict(ipTarget, cb); } else { cb(null, null); } } }) .catch(function (err) { myConsole.warn('Error in Ip Conflict detection: %s', err.message); cb('Error', null); }); iteration += 1; }).catch(function (error) { myConsole.error('Error %s', JSON.stringify(error)); cb('Error %s', JSON.stringify(error), null); }); } function _createNmapCommand(ipTarget) { var scriptPath = path.join(process.env.DOMOTZ_REMOTE_PAWN_DIR, 'src', 'network_tools', 'nmap_scripts', 'ip-conflict.nse'); if (platform.isWindows()) { options = { shell: platform.getWindowsShell(), }; scriptPath = "'" + scriptPath + "'"; } var target = ipTarget ? 'target=' + ipTarget + ',' : ''; return ( (platform.needsSudo() ? 'sudo ' : '') + 'domotz_nmap ' + ' --script ' + scriptPath + ' --script-args ' + target + 'timeout=' + getSettings.timeoutMsIpConflict() + '' ); } function _isEnabledIpConflict() { if (getSettings.isBroadcastDiscoveryDisabled()) { myConsole.info('Skipping Ip Conflict Detection'); return false; } if (!getSettings.isIpConflictEnabled()) { myConsole.info('Ip Conflict Detection disabled'); return false; } if (mutex.isLocked(LOCK_ID)) { myConsole.info('IP Conflict Detection in progress: skipping'); return false; } if (resourceLocator.emergencyNetworkRestore.isEmergencyInterfaceEnabled()) { myConsole.info('IP Conflict Detection skipped for emergency interface'); return false; } return true; } function _createCommands(interfaces, blacklist) { var commands = []; var probedIps = []; lodash.forOwn(interfaces, function (internalInterface) { if (!lodash.includes(blacklist, internalInterface.ip)) { if (!lodash.includes(probedIps, internalInterface.ip)) { ipConflictRetries[internalInterface.ip] = getSettings.getIpConflictResolutionRetries(); commands.push(_detectConflict.bind(null, internalInterface.ip)); probedIps.push(internalInterface.ip); } } }); return commands; } function _callbackConflicts(err, conflicts) { if (err) { unlock(); return; } activeConflicts = parser.getActiveConflicts(conflicts); solvedConflicts = parser.getSolvedConflicts(activeConflicts, oldActiveConflicts); myConsole.info( 'Ip Conflict Detection completed, reporting data {"active_conflicts":%s, "solved_conflicts":%s}', JSON.stringify(activeConflicts), JSON.stringify(solvedConflicts) ); if (activeConflicts.length > 0 || solvedConflicts.length > 0) { cloudCaller .sendIpConflict(activeConflicts, solvedConflicts) .then(function () { oldActiveConflicts = activeConflicts; }) .catch(function(error) { myConsole.error('Error while sending ip conflicts to the cloud: %s', error.message); }) .finally(unlock); } else { unlock(); } } function _blacklistBroadcastAddresses(ipBlacklistedList) { var interfaces = host.getInternalInterfaces(); lodash.forOwn(interfaces, function (interfaceData, interfaceName) { myConsole.debug('Blacklisting broadcast address: %s', JSON.stringify(interfaces[interfaceName].broadcastAddress)); ipBlacklistedList.push(interfaces[interfaceName].broadcastAddress); }); } function perform() { activeConflicts = []; solvedConflicts = []; if (!_isEnabledIpConflict()) { return; } if (!oldActiveConflicts) { cloudCaller .retrieveActiveIpConflicts() .then(function (response) { oldActiveConflicts = response; myConsole.info('IP conflicts fetched from the cloud: ', JSON.stringify(oldActiveConflicts)); _startDetection(); }) .catch(function (err) { myConsole.warn('Error while fetching active ip conflicts from the cloud:', err.message); }); } else { _startDetection(); } } function _startDetection() { mutex.lock(LOCK_ID, LOCK_INDEX); var ipBlacklistedList = nmapBlackList.getDeviceToExclude(); var internalInterfaces = lodash.filter(interfaceBindings.getLastSnapshot(), function (iface) { return ( iface.mac.indexOf(interfaceBindings.EXTERNAL_HOST_PREFIX) === -1 && iface.mac.indexOf(interfaceBindings.CUSTOM_NETWORK_PREFIX) === -1 ); }); _blacklistBroadcastAddresses(ipBlacklistedList); var commands = _createCommands(internalInterfaces, ipBlacklistedList); var ipConflictParallelism = getSettings.ipConflictParallelism(); async.parallelLimit(commands, ipConflictParallelism, _callbackConflicts); } function checkIfIpInConflict(ip) { var activeConflictForIp = activeConflicts.filter(function (conflict) { return conflict.ip === ip; }); return activeConflictForIp.length > 0; } return { perform: perform, checkIfIpInConflict: checkIfIpInConflict, _createNmapCommand: _createNmapCommand, //for testing purposes only _callbackConflicts: _callbackConflicts, _blacklistBroadcastAddresses: _blacklistBroadcastAddresses, }; };