domotz-remote-pawn
Version:
Domotz Agent
176 lines (158 loc) • 6.39 kB
JavaScript
/** This file is part of Domotz Agent.
* Copyright (C) 2016 Domotz UK LLP
*
* 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 Iacopo Papalini <iacopo@domotz.com>
*
* Listens for broadcast DHCP lease packets and accumulates the data for sending
* to the backend in piggyback with the fast discovery
*
*/
var dhcpDiscoveryFactory = function (dhcpjs, random, resourceLocator) {
var myConsole = resourceLocator.log.decorateLogs();
var domotzPlatform = resourceLocator.utils.platform;
var server = null;
var packetCounter = 0;
const maxRequestsPerSecond = resourceLocator.settingsManager.getSetting('configuration.settings.max_dhcp_requests_per_second', 100);
const floodDetectionPeriod = resourceLocator.settingsManager.getSetting('configuration.settings.dhcp_flood_detection_period_s', 10);
var isFloodDetectionTimerActive = false;
function extractMacIfWellFormed(message) {
var error;
if (!message.op) {
error = "Corrupted package, no 'op' field.";
myConsole.verbose(error);
return [error];
}
if (message.op.value !== 1) {
error = 'Not a BOOTREQUEST. Value: ' + message.op.value;
myConsole.verbose(error);
return [error];
}
if (!message.options) {
error = 'Not a DHCP request';
myConsole.verbose(error);
return [error];
}
var hwaddr = message.options.clientIdentifier || message.options.chaddr;
if (!hwaddr || !hwaddr.type || hwaddr.type.value !== 1) {
error = 'Not an ethernet device';
myConsole.verbose(error);
return [error];
}
return [null, hwaddr.address.toUpperCase()];
}
function extractDataIfWellFormed(message) {
try {
var ret = {
parameterRequestList: message.options.parameterRequestList,
hostName: message.options.hostName,
vendorClassIdentifier: message.options.vendorClassIdentifier,
};
var atLeastOneDefined = false;
Object.keys(ret).forEach(function (key) {
if (ret[key] !== undefined) {
atLeastOneDefined = true;
}
});
if (atLeastOneDefined) {
return [null, ret];
} else {
return ['Message does not contain discovery data', null];
}
} catch (e) {
return ['' + e, null];
}
}
function handleMessage(message) {
packetCounter += 1;
if (!isFloodDetectionTimerActive) {
isFloodDetectionTimerActive = true;
setTimeout(floodDetection, floodDetectionPeriod * resourceLocator.utils.time.SECOND);
}
var res = extractMacIfWellFormed(message);
var error = res[0];
if (error) {
return error;
}
var mac = res[1];
if (mac) {
myConsole.info('DHCPDISCOVER Packet: source mac=%s', mac);
var invalidateBindingsOnDHCP = resourceLocator.settingsManager.getSetting(
'configuration.settings.dhcp.invalidate_bindings_on_dhcp_requests',
true
);
if (invalidateBindingsOnDHCP) {
resourceLocator.interfacesBindingStorage.invalidateBindingByMac(mac.toUpperCase());
}
}
var filteredMessage = extractDataIfWellFormed(message);
if (filteredMessage[0]) {
return filteredMessage[0];
}
if (mac !== false && filteredMessage[1]) {
resourceLocator.discovery.accumulator.store(mac, 'dhcp', filteredMessage[1]);
} else {
myConsole.verbose('Ignoring DHCP message');
}
}
function handleError(error) {
myConsole.error('DHCP server error: ' + error.toString());
}
function onListening(address) {
myConsole.info('listening for DHCP packets on ' + address);
}
function listenForLeasePackets() {
var dhcpOptions = domotzPlatform.isNodeVersionZero() ? 'udp4' : { type: 'udp4', reuseAddr: true };
var enableDHCPServer = resourceLocator.settingsManager.getSetting('configuration.settings.dhcp.enable_dhcp_server', true);
if (!enableDHCPServer) {
myConsole.info('DHCP server disabled by agent settings');
return;
}
server = dhcpjs.createServer(null, dhcpOptions, myConsole);
server.on('listening', onListening);
server.on('message', handleMessage);
server.on('error', handleError);
server.bind();
}
function stopDhcpServer() {
try {
server.server.close();
} catch (e) {
myConsole.warn('exception in dhcp socket deletion %s', JSON.stringify(e));
}
myConsole.error('DHCP server stopped');
resourceLocator.periodicalTasks.stopTask('dhcpRequestsReport');
}
function floodDetection() {
var packetsPerSecond = packetCounter / floodDetectionPeriod;
if (packetsPerSecond > maxRequestsPerSecond) {
myConsole.error('The number of DHCP requests per second %d exceeded the threshold of %d', packetsPerSecond, maxRequestsPerSecond);
stopDhcpServer();
}
packetCounter = 0;
isFloodDetectionTimerActive = false;
}
return {
// ----- TEST -----
_handleMessage: handleMessage,
// ----- TEST -----
listenForLeasePackets: listenForLeasePackets,
// ----- TEST -----
_stopDhcpServer: stopDhcpServer,
// ----- TEST -----
_floodDetection: floodDetection,
};
};
module.exports.factory = dhcpDiscoveryFactory;