glass-isc-dhcp/app.js

553 lines
18 KiB
JavaScript

var express = require('express');
var path = require('path');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
const execSync = require('child_process').execSync;
var app = express();
var json_file = require('jsonfile');
var glass_config = json_file.readFileSync('config/glass_config.json');
/**
* Init Express plugins
*/
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
/**
* Check to see if we at least have one subnet allowed
*/
if (glass_config.ip_ranges_to_allow[0] !== "") {
var ip_filter = require('express-ipfilter').IpFilter;
var ips = glass_config.ip_ranges_to_allow;
app.use(ip_filter(ips, {mode: 'allow'}));
}
/**
* Normal web routes
*/
app.use('/', require('./routes/index'));
app.use('/users', require('./routes/users'));
app.use('/get_stats', require('./routes/get_stats'));
app.use('/dhcp_statistics', require('./routes/dhcp_statistics_page'));
app.use('/dhcp_leases', require('./routes/dhcp_leases'));
app.use('/dhcp_lease_search', require('./routes/dhcp_lease_search'));
app.use('/dhcp_log', require('./routes/dhcp_log'));
app.use('/dhcp_config', require('./routes/dhcp_config'));
app.use('/dhcp_config_snapshots', require('./routes/dhcp_config_snapshots'));
app.use('/dhcp_config_snapshot_view', require('./routes/dhcp_config_snapshot_view'));
app.use('/dhcp_config_save', require('./routes/dhcp_config_save'));
app.use('/dhcp_start_stop_restart', require('./routes/dhcp_start_stop_restart'));
app.use('/api_examples', require('./routes/api_examples'));
app.use('/glass_settings', require('./routes/glass_settings'));
app.use('/glass_alerts', require('./routes/glass_alerts'));
app.use('/glass_alert_settings_save', require('./routes/glass_alert_settings_save'));
app.use('/glass_settings_save', require('./routes/glass_settings_save'));
/**
* API Routes
*/
app.use('/api/get_active_leases/', require('./api/get_active_leases'));
app.use('/api/get_subnet_details/', require('./api/get_subnet_details'));
app.use('/api/get_vendor_count/', require('./api/get_vendor_count'));
app.use('/api/get_mac_oui_count_by_vendor/', require('./api/get_mac_oui_count_by_vendor'));
app.use('/api/get_dhcp_requests/', require('./api/get_dhcp_requests'));
app.use('/api/get_server_info/', require('./api/get_server_info'));
app.use('/api/get_mac_oui_list/', require('./api/get_mac_oui_list'));
app.use('/api/get_glass_config/', require('./api/get_glass_config'));
app.use('/api/get_websocket_config/', require('./api/get_websocket_config'));
app.set('view engine', 'html');
/**
* Catch 404
*/
app.use(function (req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
/**
* Error handler
*/
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.send(err.message);
});
module.exports = app;
module.exports.glass_config = glass_config;
/**
* App Globals
*/
global.cpu_utilization = 0;
global.current_leases_per_second = 0;
global.current_time = 0;
global.debug_watch_lease_parse_stream = 0;
global.dhcp_lease_data = {};
global.dhcp_requests = {};
global.leases_last_update_time = 0;
global.leases_per_minute = 0;
global.leases_per_minute_counter = 0;
global.leases_per_minute_data = [];
global.leases_per_second = 0;
global.listening_to_log_file = 0;
global.oui_data = {};
global.total_leases = 0;
global.socket_clients = 0;
/**
* Server hostname
*/
try {
global.host_name = execSync("cat /etc/hostname").toString().replace("\n", "");
} catch (e) {
global.host_name = execSync("/usr/bin/env hostname -s").toString().replace("\n", "");
}
/**
* Pull in core handlers
*/
let oui_reader = require('./core/oui-reader');
let dhcp_leases = require('./core/dhcp-leases');
let glass_config_watcher = require('./core/glass-config-watcher');
let dhcp_log_watcher = require('./core/dhcp-log-watcher');
let app_timers = require('./core/app-timers');
/**
* Run routines
*/
oui_reader.initOuiDatabase();
dhcp_leases.parseLeasesFileOnce(glass_config);
dhcp_leases.startLeaseListener(glass_config);
dhcp_leases.setLeasesCleanTimer();
glass_config_watcher.init();
dhcp_log_watcher.init(glass_config);
/**
* Timers
*/
app_timers.clearStaleWebsocketConnectionsTimer();
app_timers.pollCpuUtilizationTimer();
app_timers.purgeRequestDataCompleteTimer();
app_timers.purgeRequestDataTimer();
app_timers.startDashboardTimer();
app_timers.startLeasesPerMinuteCalculator();
/**
* Websockets
*/
const WebSocket = require('ws');
const ws_port = glass_config.ws_port || 8080;
console.log("[Glass Server] Websocket server starting on port: " + ws_port);
global.wss = new WebSocket.Server({port: ws_port});
wss.on('connection', function connection(ws) {
socket_clients++;
console.log("[WS] CLIENT_CONNECT: Socket clients (" + socket_clients + ")");
if (!listening_to_log_file) {
listening_to_log_file = 1;
}
});
wss.on('close', function close() {
socket_clients--;
console.log("[WS] CLIENT_DISCONNECT: Socket clients (" + socket_clients + ")");
});
wss.on('connection', function connection(ws) {
ws.isAlive = true;
ws.on('pong', heartbeat);
ws.event_subscription = [];
ws.on('message', function incoming(data) {
if (data !== "" && isJson(data)) {
var json = JSON.parse(data);
if (typeof json["event_subscription"] !== "undefined") {
console.log("[WS] Incoming Subscription '%s'", json['event_subscription']);
ws.event_subscription[json["event_subscription"]] = 1;
}
if (typeof json["event_unsubscribe"] !== "undefined") {
console.log("[WS] event_unsubscribe '%s'", json['event_unsubscribe']);
delete ws.event_subscription[json["event_unsubscribe"]];
}
if (typeof json["all_events"] !== "undefined") {
console.log("[WS] event_unsubscribe '%s'", json['event_unsubscribe']);
ws.event_subscription = [];
}
}
});
stale_connections_audit();
});
get_socket_clients_connected_count = function () {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
socket_clients++;
}
});
return socket_clients;
};
wss.broadcast = function broadcast(data) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
};
wss.broadcast_event = function broadcast(data, event) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
if (client.event_subscription[event])
client.send(JSON.stringify({"event": event, "data": data}));
}
});
};
stale_connections_audit = function() {
socket_clients = 0;
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping('', false, true);
socket_clients++;
});
console.log("[WS] STATUS: Socket clients (" + socket_clients + ")");
};
heartbeat = function() {
this.isAlive = true;
};
isJson = function(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
};
are_clients_subscribed_to_ws_event = function(event) {
if (typeof wss === "undefined")
return false;
var is_listening = false;
wss.clients.forEach(function each(ws) {
/**
* Count event listeners
*/
for (var event_listening in ws.event_subscription) {
if (event_listening === event) {
is_listening = true;
return true;
}
}
});
return is_listening;
};
/**
* Alert Checks
*/
alert_status = [];
alert_status['leases_per_minute'] = 0;
setTimeout(function () {
console.log("[Glass Server] Alert loop started");
let alert_check_timer = setInterval(function () {
// console.log("[Timer] Alert Timer check");
if (glass_config.leases_per_minute_threshold > 0) {
// console.log("[Timer] lpm: %s lpm_th: %s", leases_per_minute, glass_config.leases_per_minute_threshold);
if (leases_per_minute <= glass_config.leases_per_minute_threshold && alert_status['leases_per_minute'] === 0) {
alert_status['leases_per_minute'] = 1;
slack_message(":warning: CRITICAL: DHCP leases per minute have dropped below threshold " +
"(" + parseInt(glass_config.leases_per_minute_threshold).toLocaleString('en') + ") " +
"Current (" + parseInt(leases_per_minute).toLocaleString('en') + ")");
email_alert("CRITICAL: Leases Per Minute Threshold", "DHCP leases per minute dropped below critical threshold <br><br>" +
"Threshold: (" + parseInt(glass_config.leases_per_minute_threshold).toLocaleString('en') + ") <br>" +
"Current: (" + parseInt(leases_per_minute).toLocaleString('en') + ") <br><br>" +
"This is usually indicative of a process or hardware problem and needs to be addressed immediately");
}
else if (leases_per_minute >= glass_config.leases_per_minute_threshold && alert_status['leases_per_minute'] === 1) {
alert_status['leases_per_minute'] = 0;
slack_message(":white_check_mark: CLEAR: DHCP leases per minute have returned to above threshold " +
"(" + parseInt(glass_config.leases_per_minute_threshold).toLocaleString('en') + ") " +
"Current (" + parseInt(leases_per_minute).toLocaleString('en') + ")");
email_alert("CLEAR: Leases Per Minute Threshold", "DHCP leases per minute have returned to normal <br><br>" +
"Threshold: (" + parseInt(glass_config.leases_per_minute_threshold).toLocaleString('en') + ") <br>" +
"Current: (" + parseInt(leases_per_minute).toLocaleString('en') + ")"
);
}
}
}, (5 * 1000));
alert_status_networks_warning = [];
alert_status_networks_critical = [];
let alert_subnet_check_timer = setInterval(function () {
// console.log("[Timer] Alert Timer check - subnets");
if (glass_config.shared_network_warning_threshold > 0 || glass_config.shared_network_critical_threshold > 0) {
const execSync = require('child_process').execSync;
output = execSync('./bin/dhcpd-pools -c ' + glass_config.config_file + ' -l ' + glass_config.leases_file + ' -f j -A -s e');
var dhcp_data = JSON.parse(output);
/*
* Iterate through Shared Networks
*/
for (var i = 0; i < dhcp_data['shared-networks'].length; i++) {
utilization = round(parseFloat(dhcp_data['shared-networks'][i].used / dhcp_data['shared-networks'][i].defined) * 100, 2);
if (isNaN(utilization))
utilization = 0;
/* Initialize these array buckets */
if (typeof alert_status_networks_warning[dhcp_data['shared-networks'][i].location] === "undefined")
alert_status_networks_warning[dhcp_data['shared-networks'][i].location] = 0;
if (typeof alert_status_networks_critical[dhcp_data['shared-networks'][i].location] === "undefined")
alert_status_networks_critical[dhcp_data['shared-networks'][i].location] = 0;
/*
console.log("Location: %s", dhcp_data['shared-networks'][i].location);
console.log("Used: %s", dhcp_data['shared-networks'][i].used.toLocaleString('en'));
console.log("Defined: %s", dhcp_data['shared-networks'][i].defined.toLocaleString('en'));
console.log("Free: %s", dhcp_data['shared-networks'][i].free.toLocaleString('en'));
console.log("Utilization: %s", utilization);
console.log(" \n");
*/
/* Check Warnings */
if (glass_config.shared_network_warning_threshold > 0) {
if (
utilization >= glass_config.shared_network_warning_threshold &&
utilization <= glass_config.shared_network_critical_threshold &&
alert_status_networks_warning[dhcp_data['shared-networks'][i].location] === 0
) {
alert_status_networks_warning[dhcp_data['shared-networks'][i].location] = 1;
slack_message(":warning: WARNING: DHCP shared network utilization (" + dhcp_data['shared-networks'][i].location + ") " +
"Current: (" + utilization + "%) " +
"Threshold: (" + glass_config.shared_network_warning_threshold + "%)"
);
email_alert("WARNING: DHCP shared network utilization",
"WARNING: DHCP shared network utilization (" + dhcp_data['shared-networks'][i].location + ") <br><br>" +
"Threshold: (" + glass_config.shared_network_warning_threshold + "%) <br>" +
"Current: (" + utilization + "%)"
);
}
else if (
utilization <= glass_config.shared_network_warning_threshold &&
alert_status_networks_warning[dhcp_data['shared-networks'][i].location] === 1
) {
alert_status_networks_warning[dhcp_data['shared-networks'][i].location] = 0;
slack_message(":white_check_mark: CLEAR: Warning DHCP shared network utilization (" + dhcp_data['shared-networks'][i].location + ") " +
"Current: (" + utilization + "%) " +
"Threshold: (" + glass_config.shared_network_warning_threshold + "%)"
);
email_alert("CLEAR: DHCP shared network utilization warning",
"CLEAR: DHCP shared network utilization (" + dhcp_data['shared-networks'][i].location + ") <br><br>" +
"Threshold: (" + glass_config.shared_network_warning_threshold + "%) <br>" +
"Current: (" + utilization + "%)"
);
}
}
/* Check Critical */
if (glass_config.shared_network_critical_threshold > 0) {
if (
utilization >= glass_config.shared_network_critical_threshold &&
alert_status_networks_critical[dhcp_data['shared-networks'][i].location] === 0
) {
alert_status_networks_critical[dhcp_data['shared-networks'][i].location] = 1;
slack_message(":fire: CRITICAL: DHCP shared network utilization (" + dhcp_data['shared-networks'][i].location + ") " +
"Current: (" + utilization + "%) " +
"Threshold: (" + glass_config.shared_network_critical_threshold + "%)"
);
email_alert("CRITICAL: DHCP shared network utilization",
"CRITICAL: DHCP shared network utilization (" + dhcp_data['shared-networks'][i].location + ") <br><br>" +
"Threshold: (" + glass_config.shared_network_critical_threshold + "%) <br>" +
"Current: (" + utilization + "%)"
);
}
else if (
utilization <= glass_config.shared_network_critical_threshold &&
alert_status_networks_critical[dhcp_data['shared-networks'][i].location] === 1
) {
alert_status_networks_critical[dhcp_data['shared-networks'][i].location] = 0;
slack_message(":white_check_mark: CLEAR: Critical DHCP shared network utilization (" + dhcp_data['shared-networks'][i].location + ") " +
"Current: (" + utilization + "%) " +
"Threshold: (" + glass_config.shared_network_critical_threshold + "%)"
);
email_alert("CLEAR: DHCP shared network utilization",
"CLEAR: DHCP shared network utilization (" + dhcp_data['shared-networks'][i].location + ") <br><br>" +
"Threshold: (" + glass_config.shared_network_critical_threshold + "%) <br>" +
"Current: (" + utilization + "%)"
);
}
}
}
}
}, (5 * 1000));
}, 60 * 1000);
function round(num, places) {
var multiplier = Math.pow(10, places);
return Math.round(num * multiplier) / multiplier;
}
/* Load Mailer */
const nodemailer = require('nodemailer');
let transporter = nodemailer.createTransport(
{
sendmail: true,
newline: 'unix',
path: '/usr/sbin/sendmail'
}
);
function email_alert(alert_title, alert_message) {
if (typeof glass_config.email_alert_to === "undefined" && typeof glass_config.sms_alert_to === "undefined") {
console.log("[Glass Server] E-Mail alert triggered, but no addresses configured...");
return false;
}
console.log("[Glass Server] Loading E-Mail template...");
var fs = require('fs');
var email_body = fs.readFileSync('./public/templates/email_template.html', "utf8");
console.log("[Glass Server] Loading E-Mail template... DONE...");
/* E-Mail Template Load */
console.log("[Glass Server] Sending E-Mail Alert...\n");
if (glass_config.email_alert_to === "" && glass_config.sms_alert_to !== "") {
console.log("[Glass Server] No email_to specified - returning...");
return false;
}
/* Write on top of E-Mail Template */
email_body = email_body.replace("[body_content_placeholder]", alert_message);
email_body = email_body.replace("[alert_title]", alert_title);
email_body = email_body.replace("[local_time]", new Date().toString());
/* Clean extra commas etc. */
glass_config.email_alert_to = glass_config.email_alert_to.replace(/^[,\s]+|[,\s]+$/g, '').replace(/,[,\s]*,/g, ',');
/* Send regular HTML E-Mails */
if (glass_config.email_alert_to.trim() !== "") {
var mailOptions = {
from: "Glass Alerting Monitor glass@noreply.com",
to: glass_config.email_alert_to,
subject: "[Glass] " + "(" + host_name + ") " + alert_title,
html: email_body,
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
}
else {
console.log('Message sent: ' + info.response);
}
});
}
/* Send SMS */
if (glass_config.sms_alert_to.trim() !== "") {
var mailOptions = {
from: "Glass Alerting Monitor glass@noreply.com",
to: glass_config.sms_alert_to,
subject: "[Glass] " + "(" + host_name + ") " + alert_title,
html: (alert_message.substring(0, 130) + "..."),
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
}
else {
console.log('Message sent: ' + info.response);
}
});
}
}
/**
* Slack Hooks
*/
var Slack = require('slack-node');
webhookUri = glass_config.slack_webhook_url;
slack = new Slack();
slack.setWebhook(webhookUri);
function slack_message(message) {
/**
* If webhook is not set in config, return
*/
if (glass_config.slack_webhook_url.trim() === "") {
console.log("[Glass Server] Slack alert triggered, but no webhook configured...");
return;
}
console.log("[Slack] %s", message);
/**
* Send message
*/
slack.webhook(
{
channel: glass_config.slack_alert_channel,
username: "Glass",
icon_emoji: "https://imgur.com/wD3CcBi",
text: "(" + host_name + ") " + message
},
function (err, response) {
console.log(response);
}
);
}
console.log("[Glass Server] Bootup complete");