domotz-remote-pawn
Version:
Domotz Agent
251 lines (218 loc) • 9.23 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/>.
**/
/**
*
* 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,
};
};