domotz-remote-pawn
Version:
Domotz Agent
164 lines (147 loc) • 6.72 kB
JavaScript
/** This file is part of Domotz Agent.
* Copyright (C) 2018 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 Andrea Azzara <a.azzara@domotz.com> on 20/03/18.
*/
const SSDP_SEARCH_TIMEOUT = 5000;
const CACHE_EXPIRATION_HOURS = 0.5; // 30 Minutes
module.exports.factory = function (resourceLocator, engineRequest) {
var myConsole = resourceLocator.log.decorateLogs();
var natUpnp = resourceLocator.natUpnp;
var lodash = resourceLocator.lodash;
var async = resourceLocator.async;
var interfacesBindingStorage = resourceLocator.interfacesBindingStorage;
var expirationInNHours = resourceLocator.httpOutCache.expirationInNHours;
function discover() {
myConsole.info('Discover called');
var client = natUpnp.createClient();
var searchUtil = client.ssdp.search('urn:schemas-upnp-org:device:InternetGatewayDevice:1');
setTimeout(function () {
searchUtil.emit('end');
}, SSDP_SEARCH_TIMEOUT);
searchUtil.on('device', function (info) {
var gateway = natUpnp.device.create(info.location);
myConsole.info('Gateway found: %s', gateway.description);
factory._discoverAndReportGatewayMappings(gateway);
});
}
function _discoverAndReportGatewayMappings(gateway) {
if (!gateway || !gateway.description) {
return;
}
myConsole.debug('discoverAndReportGatewayMappings');
var gatewayIpRegexp = /^http[s]?\:\/\/(\d*\.\d*\.\d*\.\d*)\:\d*\/.*$/gm;
var parsedGatewayDescription = gateway.description.match(gatewayIpRegexp);
if (parsedGatewayDescription !== null && parsedGatewayDescription.length > 0) {
var gatewayIp = gatewayIpRegexp.exec(gateway.description)[1];
var gatewayMac = interfacesBindingStorage.getMacFromIp(gatewayIp);
if (gatewayMac !== undefined) {
factory._discoverGatewayMappings(gateway, function (results) {
factory._postResults(gatewayMac, results);
});
} else {
myConsole.debug('No matching MAC for device: %s', gatewayIp);
}
}
}
function _discoverGatewayMappings(gateway, callback) {
var i = 0;
var finished = false;
var results = {};
async.whilst(
function () {
return !finished;
},
function (done) {
gateway.run('GetGenericPortMappingEntry', [['NewPortMappingIndex', i++]], function (err, data) {
if (err) {
// If we got an error on index 0, ignore it in case this router starts indicies on 1
if (i !== 1) {
finished = true;
}
done(null);
return;
}
var key = null;
var found = Object.keys(data).some(function (k) {
if (!/:GetGenericPortMappingEntryResponse/.test(k)) {
return false;
}
key = k;
return true;
});
if (!found) {
done(null);
return;
}
data = data[key];
var hostIp = data.NewInternalClient;
var hostMac = interfacesBindingStorage.getMacFromIp(hostIp);
if (hostMac !== undefined) {
var internalPort = parseInt(data.NewInternalPort, 10);
var externalPort = parseInt(data.NewExternalPort, 10);
var protocol = data.NewProtocol.toUpperCase();
var description = data.NewPortMappingDescription;
myConsole.debug('Found port forwarding: %s %s->%s:%s', protocol, externalPort, hostIp, internalPort);
if (results[hostMac + ':' + externalPort] === undefined) {
results[hostMac + ':' + externalPort] = {
mac: hostMac,
int_port: internalPort,
ext_port: externalPort,
protocol: protocol,
description: data.NewPortMappingDescription,
};
} else {
var protocols = [results[hostMac + ':' + externalPort].protocol, protocol].sort().join('/'); // TCP/UDP
results[hostMac + ':' + externalPort].protocol = protocols;
if (description !== results[hostMac + ':' + externalPort].description) {
var descriptions = [results[hostMac + ':' + externalPort].description, description].sort().join(', ');
results[hostMac + ':' + externalPort].description = descriptions;
}
}
}
done(null);
});
},
function () {
callback(lodash.values(results));
}
);
}
function _postResults(gatewayMac, results) {
myConsole.debug('Preparing engine request with payload: %s', JSON.stringify(results));
var options = {
cache_until: expirationInNHours(CACHE_EXPIRATION_HOURS),
};
engineRequest.cachedSendWithPromise(
'/subnet/ip/device/' + gatewayMac + '/protocol/upnp-igd/forward',
'PUT',
results,
undefined,
undefined,
options
);
}
var factory = {
discover: discover,
_discoverAndReportGatewayMappings: _discoverAndReportGatewayMappings,
_discoverGatewayMappings: _discoverGatewayMappings,
_postResults: _postResults,
};
return factory;
};