ntopng/scripts/lua/modules/alert_utils.lua
emanuele-f d037f9a9a4 Use new user scripts config and gui
The user scripts configuration can now be configured from the "User Scripts" entry under the cog
icon. It allows the creation of multiple configuration presets to be applied to hosts, networks and
interfaces.
2020-01-03 13:03:34 +01:00

2400 lines
80 KiB
Lua

--
-- (C) 2014-19 - ntop.org
--
-- This file contains the description of all functions
-- used to trigger host alerts
local verbose = ntop.getCache("ntopng.prefs.alerts.debug") == "1"
local callback_utils = require "callback_utils"
local template = require "template_utils"
local json = require("dkjson")
local host_pools_utils = require("host_pools_utils")
local recovery_utils = require "recovery_utils"
local alert_consts = require "alert_consts"
local format_utils = require "format_utils"
local telemetry_utils = require "telemetry_utils"
local tracker = require "tracker"
local alerts_api = require "alerts_api"
local alert_endpoints = require "alert_endpoints_utils"
local flow_consts = require "flow_consts"
local icmp_utils = require "icmp_utils"
local user_scripts = require "user_scripts"
local shaper_utils = nil
if(ntop.isnEdge()) then
package.path = dirs.installdir .. "/pro/scripts/lua/modules/?.lua;" .. package.path
shaper_utils = require("shaper_utils")
end
if ntop.isEnterprise() then
local dirs = ntop.getDirs()
package.path = dirs.installdir .. "/pro/scripts/lua/enterprise/modules/?.lua;" .. package.path
require "enterprise_alert_utils"
end
-- ##############################################
function alertSeverityRaw(severity_id)
severity_id = tonumber(severity_id)
for key, severity_info in pairs(alert_consts.alert_severities) do
if(severity_info.severity_id == severity_id) then
return(key)
end
end
end
function alertSeverityLabel(v, nohtml)
local severity_id = alertSeverityRaw(v)
if(severity_id) then
local severity_info = alert_consts.alert_severities[severity_id]
local title = i18n(severity_info.i18n_title) or severity_info.i18n_title
if(nohtml) then
return(title)
else
return(string.format('<span class="badge %s">%s</span>', severity_info.label, title))
end
end
end
function alertSeverity(v)
return(alert_consts.alert_severities[v].severity_id)
end
-- ##############################################
function alertTypeRaw(type_id)
type_id = tonumber(type_id)
for key, type_info in pairs(alert_consts.alert_types) do
if(type_info.alert_id == type_id) then
return(key)
end
end
end
function alertTypeLabel(v, nohtml)
local alert_id = alertTypeRaw(v)
if(alert_id) then
local type_info = alert_consts.alert_types[alert_id]
local title = i18n(type_info.i18n_title) or type_info.i18n_title
if(nohtml) then
return(title)
else
return(string.format('<i class="fas %s"></i> %s', type_info.icon, title))
end
end
return(i18n("unknown"))
end
function alertType(v)
if(alert_consts.alert_types[v] == nil) then
tprint(debug.traceback())
end
return(alert_consts.alert_types[v].alert_id)
end
function alertTypeDescription(v)
local alert_id = alertTypeRaw(v)
if(alert_id) then
return(alert_consts.alert_types[alert_id].i18n_description)
end
end
-- ##############################################
-- Rename engine -> granulariy
function alertEngineRaw(granularity_id)
granularity_id = tonumber(granularity_id)
for key, granularity_info in pairs(alert_consts.alerts_granularities) do
if(granularity_info.granularity_id == granularity_id) then
return(key)
end
end
end
function alertEngine(v)
if(alert_consts.alerts_granularities[v] == nil) then
tprint(debug.traceback())
end
return(alert_consts.alerts_granularities[v].granularity_id)
end
function alertEngineLabel(v)
local granularity_id = alertEngineRaw(v)
if(granularity_id ~= nil) then
return(i18n(alert_consts.alerts_granularities[granularity_id].i18n_title))
end
end
function alertEngineDescription(v)
local granularity_id = alertEngineRaw(v)
if(granularity_id ~= nil) then
return(i18n(alert_consts.alerts_granularities[granularity_id].i18n_description))
end
end
function granularity2sec(v)
if(alert_consts.alerts_granularities[v] == nil) then
tprint(debug.traceback())
end
return(alert_consts.alerts_granularities[v].granularity_seconds)
end
-- See NetworkInterface::checkHostsAlerts()
function granularity2id(granularity)
-- TODO replace alertEngine
return(alertEngine(granularity))
end
function sec2granularity(seconds)
seconds = tonumber(seconds)
for key, granularity_info in pairs(alert_consts.alerts_granularities) do
if(granularity_info.granularity_seconds == seconds) then
return(key)
end
end
end
-- ##############################################################################
function get_make_room_keys(ifId)
return {flows="ntopng.cache.alerts.ifid_"..ifId..".make_room_flow_alerts",
entities="ntopng.cache.alerts.ifid_"..ifId..".make_room_closed_alerts"}
end
-- #################################
-- This function maps the SQLite table names to the conventional table
-- names used in this script
local function luaTableName(sqlite_table_name)
--~ ALERTS_MANAGER_FLOWS_TABLE_NAME "flows_alerts"
if(sqlite_table_name == "flows_alerts") then
return("historical-flows")
else
return("historical")
end
end
-- #################################
function performAlertsQuery(statement, what, opts, force_query, group_by)
local wargs = {"WHERE", "1=1"}
local oargs = {}
if(group_by ~= nil) then
group_by = " GROUP BY " .. group_by
else
group_by = ""
end
if tonumber(opts.row_id) ~= nil then
wargs[#wargs+1] = 'AND rowid = '..(opts.row_id)
end
if (not isEmptyString(opts.entity)) and (not isEmptyString(opts.entity_val)) then
if(what == "historical-flows") then
if(tonumber(opts.entity) ~= alert_consts.alertEntity("host")) then
return({})
else
-- need to handle differently for flows table
local info = hostkey2hostinfo(opts.entity_val)
wargs[#wargs+1] = 'AND (cli_addr="'..(info.host)..'" OR srv_addr="'..(info.host)..'")'
wargs[#wargs+1] = 'AND vlan_id='..(info.vlan)
end
else
wargs[#wargs+1] = 'AND alert_entity = "'..(opts.entity)..'"'
wargs[#wargs+1] = 'AND alert_entity_val = "'..(opts.entity_val)..'"'
end
elseif (what ~= "historical-flows") then
if (not isEmptyString(opts.entity)) then
wargs[#wargs+1] = 'AND alert_entity = "'..(opts.entity)..'"'
elseif(not isEmptyString(opts.entity_excludes)) then
local excludes = string.split(opts.entity_excludes, ",") or {opts.entity_excludes}
for _, entity in pairs(excludes) do
wargs[#wargs+1] = 'AND alert_entity != "'.. entity ..'"'
end
end
end
if not isEmptyString(opts.origin) then
local info = hostkey2hostinfo(opts.origin)
wargs[#wargs+1] = 'AND cli_addr="'..(info.host)..'"'
wargs[#wargs+1] = 'AND vlan_id='..(info.vlan)
end
if not isEmptyString(opts.target) then
local info = hostkey2hostinfo(opts.target)
wargs[#wargs+1] = 'AND srv_addr="'..(info.host)..'"'
wargs[#wargs+1] = 'AND vlan_id='..(info.vlan)
end
if tonumber(opts.epoch_begin) ~= nil then
wargs[#wargs+1] = 'AND alert_tstamp >= '..(opts.epoch_begin)
end
if tonumber(opts.epoch_end) ~= nil then
wargs[#wargs+1] = 'AND alert_tstamp <= '..(opts.epoch_end)
end
if not isEmptyString(opts.flowhosts_type) then
if opts.flowhosts_type ~= "all_hosts" then
local cli_local, srv_local = 0, 0
if opts.flowhosts_type == "local_only" then cli_local, srv_local = 1, 1
elseif opts.flowhosts_type == "remote_only" then cli_local, srv_local = 0, 0
elseif opts.flowhosts_type == "local_origin_remote_target" then cli_local, srv_local = 1, 0
elseif opts.flowhosts_type == "remote_origin_local_target" then cli_local, srv_local = 0, 1
end
if what == "historical-flows" then
wargs[#wargs+1] = "AND cli_localhost = "..cli_local
wargs[#wargs+1] = "AND srv_localhost = "..srv_local
end
-- TODO cannot apply it to other tables right now
end
end
if tonumber(opts.alert_type) ~= nil then
wargs[#wargs+1] = "AND alert_type = "..(opts.alert_type)
end
if tonumber(opts.alert_severity) ~= nil then
wargs[#wargs+1] = "AND alert_severity = "..(opts.alert_severity)
end
if((not isEmptyString(opts.sortColumn)) and (not isEmptyString(opts.sortOrder))) then
local order_by
if opts.sortColumn == "column_date" then
order_by = "alert_tstamp"
elseif opts.sortColumn == "column_key" then
order_by = "rowid"
elseif opts.sortColumn == "column_severity" then
order_by = "alert_severity"
elseif opts.sortColumn == "column_type" then
order_by = "alert_type"
elseif opts.sortColumn == "column_count" and what ~= "engaged" then
order_by = "alert_counter"
elseif((opts.sortColumn == "column_duration") and (what == "historical")) then
order_by = "(alert_tstamp_end - alert_tstamp)"
else
-- default
order_by = "alert_tstamp"
end
oargs[#oargs+1] = "ORDER BY "..order_by
oargs[#oargs+1] = string.upper(opts.sortOrder)
end
-- pagination
if((tonumber(opts.perPage) ~= nil) and (tonumber(opts.currentPage) ~= nil)) then
local to_skip = (tonumber(opts.currentPage)-1) * tonumber(opts.perPage)
oargs[#oargs+1] = "LIMIT"
oargs[#oargs+1] = to_skip..","..(opts.perPage)
end
local query = table.concat(wargs, " ")
local res
query = query .. " " .. table.concat(oargs, " ") .. group_by
-- Uncomment to debug the queries
--~ tprint(statement.." (from "..what..") "..query)
if((what == "engaged") or (what == "historical")) then
res = interface.queryAlertsRaw(statement, query, force_query)
elseif what == "historical-flows" then
res = interface.queryFlowAlertsRaw(statement, query, force_query)
else
error("Invalid alert subject: "..what)
end
return res
end
-- #################################
local function getNumEngagedAlerts(options)
local entity_type_filter = tonumber(options.entity)
local entity_value_filter = options.entity_val
local res = interface.getEngagedAlertsCount(entity_type_filter, entity_value_filter, options.entity_excludes)
if(res ~= nil) then
return(res.num_alerts)
end
return(0)
end
-- #################################
function getNumAlerts(what, options)
local num = 0
if(what == "engaged") then
num = getNumEngagedAlerts(options)
else
local opts = getUnpagedAlertOptions(options or {})
local res = performAlertsQuery("SELECT COUNT(*) AS count", what, opts)
if((res ~= nil) and (#res == 1) and (res[1].count ~= nil)) then num = tonumber(res[1].count) end
end
return num
end
-- #################################
-- Faster than of getNumAlerts
function hasAlerts(what, options)
if(what == "engaged") then
return(getNumEngagedAlerts(options) > 0)
end
local opts = getUnpagedAlertOptions(options or {})
-- limit 1
opts.perPage = 1
opts.currentPage = 1
local res = performAlertsQuery("SELECT rowid", what, opts)
if((res ~= nil) and (#res == 1)) then
return(true)
else
return(false)
end
end
-- #################################
local function engagedAlertsQuery(params)
local type_filter = tonumber(params.alert_type)
local severity_filter = tonumber(params.alert_severity)
local entity_type_filter = tonumber(params.entity)
local entity_value_filter = params.entity_val
local perPage = tonumber(params.perPage or 10)
local sortColumn = params.sortColumn or "column_"
local sortOrder = params.sortOrder or "desc"
local sOrder = ternary(sortOrder == "desc", rev_insensitive, asc_insensitive)
local currentPage = tonumber(params.currentPage or 1)
local totalRows = 0
--~ tprint(string.format("type=%s sev=%s entity=%s val=%s", type_filter, severity_filter, entity_type_filter, entity_value_filter))
local alerts = interface.getEngagedAlerts(entity_type_filter, entity_value_filter, type_filter, severity_filter, params.entity_excludes)
local sort_2_col = {}
-- Sort
for idx, alert in pairs(alerts) do
if sortColumn == "column_type" then
sort_2_col[idx] = alert.alert_type
elseif sortColumn == "column_severity" then
sort_2_col[idx] = alert.alert_severity
elseif sortColumn == "column_duration" then
sort_2_col[idx] = os.time() - alert.alert_tstamp
else -- column_date
sort_2_col[idx] = alert.alert_tstamp
end
totalRows = totalRows + 1
end
-- Pagination
local to_skip = (currentPage-1) * perPage
local totalRows = #alerts
local res = {}
local i = 0
for idx in pairsByValues(sort_2_col, sOrder) do
if i >= to_skip + perPage then
break
end
if (i >= to_skip) then
res[#res + 1] = alerts[idx]
end
i = i + 1
end
return res, totalRows
end
-- #################################
function getAlerts(what, options, with_counters)
local alerts, num_alerts
if what == "engaged" then
alerts, num_alerts = engagedAlertsQuery(options)
if not with_counters then
num_alerts = nil
end
else
alerts = performAlertsQuery("SELECT rowid, *", what, options)
if with_counters then
num_alerts = getNumAlerts(what, options)
end
end
return alerts, num_alerts
end
-- #################################
local function refreshAlerts(ifid)
ntop.delCache(string.format("ntopng.cache.alerts.ifid_%d.has_alerts", ifid))
ntop.delCache("ntopng.cache.update_alerts_stats_time")
end
-- #################################
function deleteAlerts(what, options)
local opts = getUnpagedAlertOptions(options or {})
performAlertsQuery("DELETE", what, opts)
end
-- #################################
-- this function returns an object with parameters specific for one tab
function getTabParameters(_get, what)
local opts = {}
for k,v in pairs(_get) do opts[k] = v end
-- these options are contextual to the current tab (status)
if _get.status ~= what then
opts.alert_type = nil
opts.alert_severity = nil
end
if not isEmptyString(what) then opts.status = what end
opts.ifid = interface.getId()
return opts
end
-- #################################
-- Remove pagination options from the options
function getUnpagedAlertOptions(options)
local res = {}
local paged_option = { currentPage=1, perPage=1, sortColumn=1, sortOrder=1 }
for k,v in pairs(options) do
if not paged_option[k] then
res[k] = v
end
end
return res
end
-- #################################
local function checkDisableAlerts()
local ifid = interface.getId()
if(_POST["action"] == "disable_alert") then
local entity = _POST["entity"]
local entity_val = _POST["entity_val"]
local alert_type = _POST["alert_type"]
local disabled_alerts = alerts_api.getEntityAlertsDisabledBitmap(ifid, entity, entity_val)
disabled_alerts = ntop.bitmapSet(disabled_alerts, tonumber(alert_type))
alerts_api.setEntityAlertsDisabledBitmap(ifid, entity, entity_val, disabled_alerts)
elseif(_POST["action"] == "enable_alert") then
local entity = _POST["entity"]
local entity_val = _POST["entity_val"]
local alert_type = _POST["alert_type"]
local disabled_alerts = alerts_api.getEntityAlertsDisabledBitmap(ifid, entity, entity_val)
disabled_alerts = ntop.bitmapClear(disabled_alerts, tonumber(alert_type))
alerts_api.setEntityAlertsDisabledBitmap(ifid, entity, entity_val, disabled_alerts)
end
end
-- #################################
function checkDeleteStoredAlerts()
_GET["status"] = _GET["status"] or _POST["status"]
if((_POST["id_to_delete"] ~= nil) and (_GET["status"] ~= nil)) then
if(_POST["id_to_delete"] ~= "__all__") then
_GET["row_id"] = tonumber(_POST["id_to_delete"])
end
deleteAlerts(_GET["status"], _GET)
-- TRACKER HOOK
tracker.log("checkDeleteStoredAlerts", {_GET["status"], _POST["id_to_delete"]})
-- to avoid performing the delete again
_POST["id_to_delete"] = nil
-- to avoid filtering by id
_GET["row_id"] = nil
-- in case of delete "older than" button, resets the time period after the delete took place
if isEmptyString(_GET["epoch_begin"]) then _GET["epoch_end"] = nil end
local has_alerts = hasAlerts(_GET["status"], _GET)
if(not has_alerts) then
-- reset the filter to avoid hiding the tab
_GET["alert_severity"] = nil
_GET["alert_type"] = nil
end
end
checkDisableAlerts()
if(_POST["action"] == "release_alert") then
local entity_info = {
alert_entity = alert_consts.alert_entities[alert_consts.alertEntityRaw(_POST["entity"])],
alert_entity_val = _POST["entity_val"],
}
local type_info = {
alert_type = alert_consts.alert_types[alertTypeRaw(_POST["alert_type"])],
alert_severity = alert_consts.alert_severities[alertSeverityRaw(_POST["alert_severity"])],
alert_subtype = _POST["alert_subtype"],
alert_granularity = alert_consts.alerts_granularities[sec2granularity(_POST["alert_granularity"])],
}
alerts_api.release(entity_info, type_info)
interface.refreshAlerts();
end
end
-- #################################
-- Return more information for the flow alert description
local function getFlowStatusInfo(record, status_info)
local res = ""
local l7proto_name = interface.getnDPIProtoName(tonumber(record["l7_proto"]) or 0)
if l7proto_name == "ICMP" then -- is ICMPv4
-- TODO: old format - remove when the all the flow alers will be generated in lua
local type_code = {type = status_info["icmp.icmp_type"], code = status_info["icmp.icmp_code"]}
if table.empty(type_code) and status_info["icmp"] then
-- This is the new format created when setting the alert from lua
type_code = {type = status_info["icmp"]["type"], code = status_info["icmp"]["code"]}
end
if status_info["icmp.unreach.src_ip"] then -- TODO: old format to be removed
res = string.format("[%s]", i18n("icmp_page.icmp_port_unreachable_extra", {unreach_host=status_info["icmp.unreach.dst_ip"], unreach_port=status_info["icmp.unreach.dst_port"], unreach_protocol = l4_proto_to_string(status_info["icmp.unreach.protocol"])}))
elseif status_info["icmp"] and status_info["icmp"]["unreach"] then -- New format
res = string.format("[%s]", i18n("icmp_page.icmp_port_unreachable_extra", {unreach_host=status_info["icmp"]["unreach"]["dst_ip"], unreach_port=status_info["icmp"]["unreach"]["dst_port"], unreach_protocol = l4_proto_to_string(status_info["icmp"]["unreach"]["protocol"])}))
else
res = string.format("[%s]", icmp_utils.get_icmp_label(4 --[[ ipv4 --]], type_code["type"], type_code["code"]))
end
end
return string.format(" %s", res)
end
-- #################################
local function formatRawFlow(record, flow_json, skip_add_links)
require "flow_utils"
local time_bounds
local add_links = (not skip_add_links)
local host_page = "&page=alerts"
if interfaceHasNindexSupport() and not skip_add_links then
-- only add links if nindex is present
add_links = true
time_bounds = {getAlertTimeBounds(record)}
end
local decoded = json.decode(flow_json) or {}
if((type(decoded["status_info"]) == "string") and
(string.sub(decoded["status_info"], 1, 1) == "{")) then
-- status_info may contain a JSON string or a plain message
decoded["status_info"] = json.decode(decoded["status_info"])
end
local status_info = decoded.status_info
-- active flow lookup
if not interface.isView() and status_info and status_info["ntopng.key"] and status_info["hash_entry_id"] and record["alert_tstamp"] then
-- attempt a lookup on the active flows
local active_flow = interface.findFlowByKeyAndHashId(status_info["ntopng.key"], status_info["hash_entry_id"])
if active_flow and active_flow["seen.first"] < tonumber(record["alert_tstamp"]) then
return string.format("%s [%s: <A HREF='%s/lua/flow_details.lua?flow_key=%u&flow_hash_id=%u'><span class='badge badge-info'>Info</span></A> %s]",
flow_consts.getStatusDescription(tonumber(record["flow_status"]), status_info),
i18n("flow"), ntop.getHttpPrefix(), active_flow["ntopng.key"], active_flow["hash_entry_id"],
getFlowLabel(active_flow, true, true))
end
end
-- pretend record is a flow to reuse getFlowLabel
local flow = {
["cli.ip"] = record["cli_addr"], ["cli.port"] = tonumber(record["cli_port"]),
["cli.blacklisted"] = tostring(record["cli_blacklisted"]) == "1",
["srv.ip"] = record["srv_addr"], ["srv.port"] = tonumber(record["srv_port"]),
["srv.blacklisted"] = tostring(record["srv_blacklisted"]) == "1",
["vlan"] = record["vlan_id"]}
flow = "["..i18n("flow")..": "..(getFlowLabel(flow, false, add_links, time_bounds, host_page) or "").."] "
local l4_proto_label = l4_proto_to_string(record["proto"] or 0) or ""
if not isEmptyString(l4_proto_label) then
flow = flow.."[" .. l4_proto_label .. "] "
end
local l7proto_name = interface.getnDPIProtoName(tonumber(record["l7_proto"]) or 0)
if record["l7_master_proto"] and record["l7_master_proto"] ~= "0" then
local l7proto_master_name = interface.getnDPIProtoName(tonumber(record["l7_master_proto"]))
if l7proto_master_name ~= l7proto_name then
l7proto_name = string.format("%s.%s", l7proto_master_name, l7proto_name)
end
end
if not isEmptyString(l7proto_name) and l4_proto_label ~= l7proto_name then
flow = flow.."["..i18n("application")..": " ..l7proto_name.."] "
end
if decoded ~= nil then
-- render the json
local msg = ""
if not isEmptyString(record["flow_status"]) then
msg = msg..flow_consts.getStatusDescription(tonumber(record["flow_status"]), status_info).." "
end
if not isEmptyString(flow) then
msg = msg..flow.." "
end
if not isEmptyString(decoded["info"]) then
local lb = ""
if (record["flow_status"] == "13") -- blacklisted flow
and (not flow["srv.blacklisted"]) and (not flow["cli.blacklisted"]) then
lb = " <i class='fas fa-ban' aria-hidden='true' title='Blacklisted'></i>"
end
msg = msg.."["..i18n("info")..": "..decoded["info"]..lb.."] "
end
flow = msg
end
if status_info then
flow = flow..getFlowStatusInfo(record, status_info)
end
return flow
end
-- #################################
local function getMenuEntries(status, selection_name, get_params)
local actual_entries = {}
local params = table.clone(get_params)
-- Remove previous filters
params.alert_severity = nil
params.alert_type = nil
if selection_name == "severity" then
actual_entries = performAlertsQuery("select alert_severity id, count(*) count", status, params, nil, "alert_severity" --[[ group by ]])
elseif selection_name == "type" then
actual_entries = performAlertsQuery("select alert_type id, count(*) count", status, params, nil, "alert_type" --[[ group by ]])
end
return(actual_entries)
end
-- #################################
local function dropdownUrlParams(get_params)
local buttons = ""
for param, val in pairs(get_params) do
-- NOTE: exclude the ifid parameter to avoid interface selection issues with system interface alerts
if((param ~= "alert_severity") and (param ~= "alert_type") and (param ~= "status") and (param ~= "ifid")) then
buttons = buttons.."&"..param.."="..val
end
end
return(buttons)
end
-- #################################
local function drawDropdown(status, selection_name, active_entry, entries_table, button_label, get_params, actual_entries)
-- alert_consts.alert_severity_keys and alert_consts.alert_type_keys are defined in lua_utils
local id_to_label
if selection_name == "severity" then
id_to_label = alertSeverityLabel
elseif selection_name == "type" then
id_to_label = alertTypeLabel
end
actual_entries = actual_entries or getMenuEntries(status, selection_name, get_params)
local buttons = '<div class="btn-group">'
button_label = button_label or firstToUpper(selection_name)
if active_entry ~= nil and active_entry ~= "" then
button_label = firstToUpper(active_entry)..'<span class="fas fa-filter"></span>'
end
buttons = buttons..'<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">'..button_label
buttons = buttons..'<span class="caret"></span></button>'
buttons = buttons..'<ul class="dropdown-menu dropdown-menu-right" role="menu">'
local class_active = ""
if active_entry == nil then class_active = ' class="active"' end
buttons = buttons..'<li'..class_active..'><a class="dropdown-item" href="?status='..status..dropdownUrlParams(get_params)..'">All</a></i>'
for _, entry in pairs(actual_entries) do
local id = tonumber(entry["id"])
local count = entry["count"]
if(id >= 0) then
local label = id_to_label(id, true)
class_active = ""
if label == active_entry then class_active = ' class="active"' end
-- buttons = buttons..'<li'..class_active..'><a class="dropdown-item" href="'..ntop.getHttpPrefix()..'/lua/show_alerts.lua?status='..status
buttons = buttons..'<li'..class_active..'><a class="dropdown-item" href="?status='..status
buttons = buttons..dropdownUrlParams(get_params)
buttons = buttons..'&alert_'..selection_name..'='..id..'">'
buttons = buttons..firstToUpper(label)..' ('..count..')</a></li>'
end
end
buttons = buttons..'</ul></div>'
return buttons
end
-- #################################
local function printProbesTab(entity_probes, entity_type, entity_value, page_name, page_params, alt_name, options)
-- TODO migrate probes
--~ local system_scripts = require("system_scripts_utils")
--~ if #entity_probes > 0 then
--~ print[[
--~ <br>
--~ <table class="table table-bordered table-striped">
--~ <tr>
--~ <th width="10%">]] print(i18n("system_stats.probe")) print[[</th>
--~ <th width="25%">]] print(i18n("system_stats.probe_config")) print[[</th>
--~ </tr>]]
--~ for _, probe in ipairs(entity_probes) do
--~ print[[
--~ <tr>
--~ <td>]] print(probe["probe"]["name"]) print[[</td>
--~ <td><a href="]] print(probe["config"]["url"]) print[["><i class="fas fa-cog" aria-hidden="true"></i></a></td>
--~ </tr>]]
--~ end
--~ print[[</table>]]
--~ end
end
-- #################################
local function printConfigTab(entity_type, entity_value, page_name, page_params, alt_name, options)
local trigger_alerts = true
local ifid = interface.getId()
local trigger_alerts_checked
local cur_bitmap
if(entity_type == "host") then
cur_bitmap = alerts_api.getHostDisabledStatusBitmap(ifid, entity_value)
end
local entity_type_id = alert_consts.alertEntity(entity_type)
if _SERVER["REQUEST_METHOD"] == "POST" then
if _POST["trigger_alerts"] ~= "1" then
trigger_alerts = false
else
trigger_alerts = true
end
alerts_api.setSuppressedAlerts(ifid, entity_type_id, entity_value, (not trigger_alerts))
if(entity_type == "host") then
local bitmap = 0
if not isEmptyString(_POST["disabled_status"]) then
local status_selection = split(_POST["disabled_status"], ",") or { _POST["disabled_status"] }
for _, status in pairs(status_selection) do
bitmap = ntop.bitmapSet(bitmap, tonumber(status))
end
end
if(bitmap ~= cur_bitmap) then
alerts_api.setHostDisabledStatusBitmap(ifid, entity_value, bitmap)
cur_bitmap = bitmap
end
end
else
trigger_alerts = (not alerts_api.hasSuppressedAlerts(ifid, entity_type_id, entity_value))
end
if trigger_alerts == false then
trigger_alerts_checked = ""
else
trigger_alerts = true
trigger_alerts_checked = "checked"
end
local enable_label = options.enable_label or i18n("show_alerts.trigger_alert_descr")
print[[
<br>
<form id="alerts-config" class="form-inline" method="post">
<input name="csrf" type="hidden" value="]] print(ntop.getRandomCSRFValue()) print[[" />
<table class="table table-bordered table-striped">]]
print[[<tr>
<th width="25%">]] print(i18n("device_protocols.alert")) print[[</th>
<td>
<input type="checkbox" name="trigger_alerts" value="1" ]] print(trigger_alerts_checked) print[[>
<i class="fas fa-exclamation-triangle fa-lg"></i>
]] print(enable_label) print[[
</input>
</td>
</tr>]]
if(entity_type == "host") then
print[[<tr>
<td width="30%">
<b>]] print(i18n("host_details.status_ignore")) print[[</b> <i class="fas fa-info-circle" title="]] print(i18n("host_details.disabled_flow_status_help")) print[["></i>
</td>
<td>
<input id="status_trigger_alert" name="disabled_status" type="hidden" />
<select onchange="convertMultiSelect()" id="status_trigger_alert_select" multiple class="form-control" style="width:40em; height:10em; display:inline;">]]
for _, status in pairsByKeys(flow_consts.status_types, asc) do
local status_id = status.status_id
if(status_id == flow_consts.status_types.status_normal.status_id) then
goto continue
end
print[[<option value="]] print(string.format("%d", status_id))
if ntop.bitmapIsSet(cur_bitmap, tonumber(status_id)) then
print[[" selected="selected]]
end
print[[">]]
print(i18n(status.i18n_title))
print[[</option>]]
::continue::
end
print[[</select><div style="margin-top:1em;"><i>]] print(i18n("host_details.multiple_selection")) print[[</i></div>
<button type="button" class="btn btn-secondary" style="margin-top:1em;" onclick="resetMultiSelect()">]] print(i18n("reset")) print[[</button>
</td>
</tr>]]
end
print[[</table>
<button class="btn btn-primary" style="float:right; margin-right:1em;" disabled="disabled" type="submit">]] print(i18n("save_configuration")) print[[</button>
</form>
<br><br>
<script>
function convertMultiSelect() {
var values = [];
$("#status_trigger_alert_select option:selected").each(function(idx, item) {
values.push($(item).val());
});
$("#status_trigger_alert").val(values.join(","));
$("#status_trigger_alert").trigger("change");
}
function resetMultiSelect() {
$("#status_trigger_alert_select option:selected").each(function(idx, item) {
item.selected = "";
});
convertMultiSelect();
}
/* Run after page load */
$(convertMultiSelect);
aysHandleForm("#alerts-config");
</script>]]
end
-- #################################
function printAlertTables(entity_type, alert_source, page_name, page_params, alt_name, show_entity, options)
local has_engaged_alerts, has_past_alerts, has_flow_alerts = false,false,false
local has_disabled_alerts = alerts_api.hasEntitiesWithAlertsDisabled(interface.getId())
local tab = _GET["tab"]
local have_nedge = ntop.isnEdge()
options = options or {}
local anomaly_config_key = nil
local flow_rate_alert_thresh, syn_alert_thresh
if entity_type == "host" then
anomaly_config_key = 'ntopng.prefs.'..(options.host_ip)..':'..tostring(options.host_vlan)..'.alerts_config'
end
print('<ul class="nav nav-tabs">')
local function printTab(tab, content, sel_tab)
if(tab == sel_tab) then print("\t<li class='nav-item active show'>") else print("\t<li class='nav-item'>") end
print("<a class='nav-link' href=\""..ntop.getHttpPrefix().."/lua/"..page_name.."?page=alerts&tab="..tab)
for param, value in pairs(page_params) do
print("&"..param.."="..value)
end
print("\">"..content.."</a></li>\n")
end
if(show_entity) then
-- these fields will be used to perform queries
_GET["entity"] = alert_consts.alertEntity(show_entity)
_GET["entity_val"] = alert_source
end
if(show_entity) then
-- possibly process pending delete arguments
checkDeleteStoredAlerts()
-- possibly add a tab if there are alerts configured for the host
has_engaged_alerts = hasAlerts("engaged", getTabParameters(_GET, "engaged"))
has_past_alerts = hasAlerts("historical", getTabParameters(_GET, "historical"))
has_flow_alerts = hasAlerts("historical-flows", getTabParameters(_GET, "historical-flows"))
if(has_engaged_alerts or has_past_alerts or has_flow_alerts) then
if(has_engaged_alerts) then
tab = tab or "alert_list"
printTab("alert_list", i18n("show_alerts.engaged_alerts"), tab)
end
if(has_past_alerts) then
tab = tab or "past_alert_list"
printTab("past_alert_list", i18n("show_alerts.past_alerts"), tab)
end
if(has_flow_alerts) then
tab = tab or "flow_alert_list"
printTab("flow_alert_list", i18n("show_alerts.flow_alerts"), tab)
end
else
-- if there are no alerts, we show the alert settings
if(tab=="alert_list") then tab = nil end
end
end
-- Default tab
if(tab == nil) then tab = "config" end
local is_alert_list_tab = ((tab == "alert_list") or (tab == "past_alert_list") or (tab == "flow_alert_list"))
--[[ TODO migrate probes
local system_scripts = require("system_scripts_utils")
local entity_probes = system_scripts.getEntityProbes(entity_type, alert_source)
if #entity_probes > 0 then
printTab("probes", i18n("system_stats.probes"), tab)
end
]]
printTab("config", '<i class="fas fa-cog" aria-hidden="true"></i> ' .. i18n("traffic_recording.settings"), tab)
print('</ul>')
if((show_entity) and is_alert_list_tab) then
drawAlertTables(has_past_alerts, has_engaged_alerts, has_flow_alerts, has_disabled_alerts, _GET, true, nil, { dont_nest_alerts = true })
elseif(tab == "config") then
printConfigTab(entity_type, alert_source, page_name, page_params, alt_name, options)
elseif(tab == "probes") and #entity_probes > 0 then
printProbesTab(entity_probes, entity_type, alert_source, page_name, page_params, alt_name, options)
end
end
-- #################################
function optimizeAlerts()
if(not areAlertsEnabled()) then
return
end
interface.optimizeAlerts()
end
-- #################################
function housekeepingAlertsMakeRoom(ifId)
local prefs = ntop.getPrefs()
local max_num_alerts_per_entity = prefs.max_num_alerts_per_entity
local max_num_flow_alerts = prefs.max_num_flow_alerts
local k = get_make_room_keys(ifId)
if ntop.getCache(k["entities"]) == "1" then
ntop.delCache(k["entities"])
local res = interface.queryAlertsRaw(
"SELECT alert_entity, alert_entity_val, count(*) count",
"GROUP BY alert_entity, alert_entity_val HAVING COUNT >= "..max_num_alerts_per_entity) or {}
for _, e in pairs(res) do
local to_keep = (max_num_alerts_per_entity * 0.8) -- deletes 20% more alerts than the maximum number
to_keep = round(to_keep, 0)
-- tprint({e=e, total=e.count, to_keep=to_keep, to_delete=to_delete, to_delete_not_discounted=(e.count - max_num_alerts_per_entity)})
local cleanup = interface.queryAlertsRaw(
"DELETE",
"WHERE alert_entity="..e.alert_entity.." AND alert_entity_val=\""..e.alert_entity_val.."\" "
.." AND rowid NOT IN (SELECT rowid FROM alerts WHERE alert_entity="..e.alert_entity.." AND alert_entity_val=\""..e.alert_entity_val.."\" "
.." ORDER BY alert_tstamp DESC LIMIT "..to_keep..")", false)
end
end
if ntop.getCache(k["flows"]) == "1" then
ntop.delCache(k["flows"])
local res = interface.queryFlowAlertsRaw("SELECT count(*) count", "WHERE 1=1") or {}
local count = tonumber(res[1].count)
if count ~= nil and count >= max_num_flow_alerts then
local to_keep = (max_num_flow_alerts * 0.8)
to_keep = round(to_keep, 0)
local cleanup = interface.queryFlowAlertsRaw("DELETE",
"WHERE rowid NOT IN (SELECT rowid FROM flows_alerts ORDER BY alert_tstamp DESC LIMIT "..to_keep..")")
-- tprint({total=count, to_delete=to_delete, cleanup=cleanup})
-- tprint(cleanup)
-- TODO: possibly raise a too many flow alerts
end
end
end
-- #################################
local function menuEntriesToDbFormat(entries)
local res = {}
for entry_id, entry_val in pairs(entries) do
res[#res + 1] = {
id = tostring(entry_id),
count = tostring(entry_val),
}
end
return(res)
end
-- #################################
local function printDisabledAlerts(ifid)
print[[
<script>
$("#table-disabled-alerts").datatable({
url: "]] print(ntop.getHttpPrefix()) print [[/lua/get_disabled_alerts.lua?ifid=]] print(string.format("%d", ifid)) print[[",
showPagination: true,
title: "]] print(i18n("show_alerts.disabled_alerts")) print[[",
columns: [
{
title: "]]print(i18n("show_alerts.alarmable"))print[[",
field: "column_entity_formatted",
sortable: true,
css: {
textAlign: 'center',
whiteSpace: 'nowrap',
width: '35%',
}
},{
title: "]]print(i18n("show_alerts.alert_type"))print[[",
field: "column_type",
sortable: true,
css: {
textAlign: 'center',
whiteSpace: 'nowrap',
}
},{
title: "]]print(i18n("show_alerts.alert_actions")) print[[",
css: {
textAlign: 'center',
}
}], tableCallback: function() {
datatableForEachRow("#table-disabled-alerts", function(row_id) {
datatableAddActionButtonCallback.bind(this)(3, "prepareToggleAlertsDialog('table-disabled-alerts',"+ row_id +"); $('#enable_alert_type').modal('show');", "]] print(i18n("show_alerts.enable_alerts")) print[[");
})
}
});
</script>]]
end
-- #################################
function drawAlertTables(has_past_alerts, has_engaged_alerts, has_flow_alerts, has_disabled_alerts, get_params, hide_extended_title, alt_nav_tabs, options)
local alert_items = {}
local url_params = {}
local options = options or {}
local ifid = interface.getId()
print(
template.gen("modal_confirm_dialog.html", {
dialog={
id = "delete_alert_dialog",
action = "deleteAlertById(delete_alert_id)",
title = i18n("show_alerts.delete_alert"),
message = i18n("show_alerts.confirm_delete_alert").."?",
confirm = i18n("delete"),
confirm_button = "btn-danger",
}
})
)
print(
template.gen("modal_confirm_dialog.html", {
dialog={
id = "release_single_alert",
action = "releaseAlert(alert_to_release)",
title = i18n("show_alerts.release_alert"),
message = i18n("show_alerts.confirm_release_alert"),
confirm = i18n("show_alerts.release_alert_action"),
confirm_button = "btn-primary",
}
})
)
print(
template.gen("modal_confirm_dialog.html", {
dialog={
id = "enable_alert_type",
action = "toggleAlert(false)",
title = i18n("show_alerts.enable_alerts_title"),
message = i18n("show_alerts.enable_alerts_message", {
type = "<span class='toggle-alert-id'></span>",
entity_value = "<span class='toggle-alert-entity-value'></span>"
}),
confirm = i18n("show_alerts.enable_alerts"),
}
})
)
print(
template.gen("modal_confirm_dialog.html", {
dialog={
id = "disable_alert_type",
action = "toggleAlert(true)",
title = i18n("show_alerts.disable_alerts_title"),
message = i18n("show_alerts.disable_alerts_message", {
type = "<span class='toggle-alert-id'></span>",
entity_value = "<span class='toggle-alert-entity-value'></span>"
}),
confirm = i18n("show_alerts.disable_alerts"),
}
})
)
print(
template.gen("modal_confirm_dialog.html", {
dialog={
id = "myModal",
action = "checkModalDelete()",
title = "",
message = i18n("show_alerts.purge_subj_alerts_confirm", {subj = '<span id="modalDeleteContext"></span><span id="modalDeleteAlertsMsg"></span>'}),
confirm = i18n("show_alerts.purge_num_alerts", {
num_alerts = '<img id="alerts-summary-wait" src="'..ntop.getHttpPrefix()..'/img/loading.gif"/><span id="alerts-summary-body"></span>'
}),
}
})
)
for k,v in pairs(get_params) do if k ~= "csrf" then url_params[k] = v end end
if not alt_nav_tabs then
if not options.dont_nest_alerts then
print("<br>")
end
print[[
<ul class="nav nav-tabs" role="tablist" id="alert-tabs" style="]] print(ternary(options.dont_nest_alerts, 'display:none', '')) print[[">
<!-- will be populated later with javascript -->
</ul>
]]
nav_tab_id = "alert-tabs"
else
nav_tab_id = alt_nav_tabs
end
print[[
<script>
function checkAlertActionsPanel() {
/* check if this tab is handled by this script */
if(getCurrentStatus() == "" || getCurrentStatus() == "engaged")
$("#alertsActionsPanel").css("display", "none");
else
$("#alertsActionsPanel").css("display", "");
}
function setActiveHashTab(hash) {
$('#]] print(nav_tab_id) --[[ see "clicked" below for the other part of this logic ]] print[[ a[href="' + hash + '"]').tab('show');
}
/* Handle the current tab */
$(function() {
$("ul.nav-tabs > li > a").on("shown.bs.tab", function(e) {
var id = $(e.target).attr("href").substr(1);
history.replaceState(null, null, "#"+id);
updateDeleteLabel(id);
checkAlertActionsPanel();
});
var hash = window.location.hash;
if (! hash && ]] if(isEmptyString(status) and not isEmptyString(_GET["tab"])) then print("true") else print("false") end print[[)
hash = "#]] print(_GET["tab"] or "") print[[";
if (hash)
setActiveHashTab(hash)
$(function() { checkAlertActionsPanel(); });
});
function getActiveTabId() {
return $("#]] print(nav_tab_id) print[[ > li > a.active").attr('href').substr(1);
}
function updateDeleteLabel(tabid) {
var label = $("#purgeBtnLabel");
var prefix = "]]
if not isEmptyString(_GET["entity"]) then print(alert_consts.alertEntityLabel(_GET["entity"], true).." ") end
print [[";
var val = "";
if (tabid == "tab-table-engaged-alerts")
val = "]] print(i18n("show_alerts.engaged")) print[[ ";
else if (tabid == "tab-table-alerts-history")
val = "]] print(i18n("show_alerts.past")) print[[ ";
else if (tabid == "tab-table-flow-alerts-history")
val = "]] print(i18n("show_alerts.past_flow")) print[[ ";
label.html(prefix + val);
}
function getCurrentStatus() {
var tabid = getActiveTabId();
if (tabid == "tab-table-engaged-alerts")
val = "engaged";
else if (tabid == "tab-table-alerts-history")
val = "historical";
else if (tabid == "tab-table-flow-alerts-history")
val = "historical-flows";
else
val = "";
return val;
}
function deleteAlertById(alert_id) {
var params = {};
params.id_to_delete = alert_id;
params.status = getCurrentStatus();
params.csrf = "]] print(ntop.getRandomCSRFValue()) print[[";
var form = paramsToForm('<form method="post"></form>', params);
form.appendTo('body').submit();
}
var alert_to_toggle = null;
function prepareToggleAlertsDialog(table_id, idx) {
var table_data = $("#" + table_id ).data("datatable").resultset.data;
var row = table_data[idx];
alert_to_toggle = row;
$(".toggle-alert-id").html(noHtml(row.column_type).trim());
$(".toggle-alert-entity-value").html(noHtml(row.column_entity_formatted).trim())
}
var alert_to_release = null;
function releaseAlert(idx) {
var table_data = $("#table-engaged-alerts").data("datatable").resultset.data;
var row = table_data[idx];
var params = {
"action": "release_alert",
"entity": row.column_entity_id,
"entity_val": row.column_entity_val,
"alert_type": row.column_type_id,
"alert_severity": row.column_severity_id,
"alert_subtype": row.column_subtype,
"alert_granularity": row.column_granularity,
"csrf": "]] print(ntop.getRandomCSRFValue()) print[[",
};
var form = paramsToForm('<form method="post"></form>', params);
form.appendTo('body').submit();
}
function toggleAlert(disable) {
var row = alert_to_toggle;
var params = {
"action": disable ? "disable_alert" : "enable_alert",
"entity": row.column_entity_id,
"entity_val": row.column_entity_val,
"alert_type": row.column_type_id,
"csrf": "]] print(ntop.getRandomCSRFValue()) print[[",
};
var form = paramsToForm('<form method="post"></form>', params);
form.appendTo('body').submit();
}
</script>
]]
if not alt_nav_tabs then print [[<div class="tab-content">]] end
local status = _GET["status"]
if(status == nil) then
local tab = _GET["tab"]
if(tab == "past_alert_list") then
status = "historical"
elseif(tab == "flow_alert_list") then
status = "historical-flows"
end
end
local status_reset = (status == nil)
local ts_utils = require "ts_utils"
if(has_engaged_alerts) then
alert_items[#alert_items + 1] = {
["label"] = i18n("show_alerts.engaged_alerts"),
["chart"] = ternary(ts_utils.exists("iface:engaged_alerts", {ifid = ifid}), "iface:engaged_alerts", ""),
["div-id"] = "table-engaged-alerts", ["status"] = "engaged"}
elseif status == "engaged" then
status = nil; status_reset = 1
end
if(has_past_alerts) then
alert_items[#alert_items +1] = {
["label"] = i18n("show_alerts.past_alerts"),
["chart"] = "",
["div-id"] = "table-alerts-history", ["status"] = "historical"}
elseif status == "historical" then
status = nil; status_reset = 1
end
if(has_flow_alerts) then
alert_items[#alert_items +1] = {
["label"] = i18n("show_alerts.flow_alerts"),
["chart"] = "",
["div-id"] = "table-flow-alerts-history", ["status"] = "historical-flows"}
elseif status == "historical-flows" then
status = nil; status_reset = 1
end
if has_disabled_alerts then
alert_items[#alert_items +1] = {
["label"] = i18n("show_alerts.disabled_alerts"),
["chart"] = "",
["div-id"] = "table-disabled-alerts", ["status"] = "disabled-alerts"}
end
for k, t in ipairs(alert_items) do
local clicked = "0"
if((not alt_nav_tabs) and ((k == 1 and status == nil) or (status ~= nil and status == t["status"]))) then
clicked = "1"
end
print [[
<div class="tab-pane in" id="tab-]] print(t["div-id"]) print[[">
<div id="]] print(t["div-id"]) print[["></div>
</div>
<script type="text/javascript">
$("#]] print(nav_tab_id) print[[").append('<li class="nav-item ]] print(ternary(options.dont_nest_alerts, 'hidden', '')) print[["><a class="nav-link" href="#tab-]] print(t["div-id"]) print[[" clicked="]] print(clicked) print[[" role="tab" data-toggle="tab">]] print(t["label"]) print[[</a></li>')
</script>
]]
if t["status"] == "disabled-alerts" then
printDisabledAlerts(ifid)
goto next_menu_item
end
print[[
<script type="text/javascript">
$('a[href="#tab-]] print(t["div-id"]) print[["]').on('shown.bs.tab', function (e) {
// append the li to the tabs
$("#]] print(t["div-id"]) print[[").datatable({
url: "]] print(ntop.getHttpPrefix()) print [[/lua/get_alerts_table_data.lua?" + $.param(]] print(tableToJsObject(getTabParameters(url_params, t["status"]))) print [[),
showFilter: true,
showPagination: true,
buttons: [']]
local title = t["label"]..ternary(t["chart"] ~= "", " <small><A HREF='"..ntop.getHttpPrefix().."/lua/if_stats.lua?ifid="..string.format("%d", ifid).."&page=historical&ts_schema="..t["chart"].."'><i class='fas fa-chart-area fa-sm'></i></A></small>", "")
if(options.hide_filters ~= true) then
-- alert_consts.alert_severity_keys and alert_consts.alert_type_keys are defined in lua_utils
local alert_severities = {}
for s, _ in pairs(alert_consts.alert_severities) do alert_severities[#alert_severities +1 ] = s end
local alert_types = {}
for s, _ in pairs(alert_consts.alert_types) do alert_types[#alert_types +1 ] = s end
local type_menu_entries = nil
local sev_menu_entries = nil
local a_type, a_severity = nil, nil
if clicked == "1" then
if tonumber(_GET["alert_type"]) ~= nil then a_type = alertTypeLabel(_GET["alert_type"], true) end
if tonumber(_GET["alert_severity"]) ~= nil then a_severity = alertSeverityLabel(_GET["alert_severity"], true) end
end
if t["status"] == "engaged" then
local res = interface.getEngagedAlertsCount(tonumber(_GET["entity"]), _GET["entity_val"], _GET["entity_excludes"])
if(res ~= nil) then
type_menu_entries = menuEntriesToDbFormat(res.type)
sev_menu_entries = menuEntriesToDbFormat(res.severities)
end
end
print(drawDropdown(t["status"], "type", a_type, alert_types, i18n("alerts_dashboard.alert_type"), get_params, type_menu_entries))
print(drawDropdown(t["status"], "severity", a_severity, alert_severities, i18n("alerts_dashboard.alert_severity"), get_params, sev_menu_entries))
elseif((not isEmptyString(_GET["entity_val"])) and (not hide_extended_title)) then
if entity == "host" then
title = title .. " - " .. firstToUpper(alert_consts.formatAlertEntity(getInterfaceId(ifname), entity, _GET["entity_val"], nil))
end
end
if options.dont_nest_alerts then
title = ""
end
print[['],
/*
buttons: ['<div class="btn-group"><button class="btn btn-link dropdown-toggle" data-toggle="dropdown">Severity<span class="caret"></span></button><ul class="dropdown-menu" role="menu"><li>test severity</li></ul></div><div class="btn-group"><button class="btn btn-link dropdown-toggle" data-toggle="dropdown">Type<span class="caret"></span></button><ul class="dropdown-menu" role="menu"><li>test type</li></ul></div>'],
*/
]]
if(_GET["currentPage"] ~= nil and _GET["status"] == t["status"]) then
print("currentPage: ".._GET["currentPage"]..",\n")
end
if(_GET["perPage"] ~= nil and _GET["status"] == t["status"]) then
print("perPage: ".._GET["perPage"]..",\n")
end
print ('sort: [ ["' .. getDefaultTableSort("alerts") ..'","' .. getDefaultTableSortOrder("alerts").. '"] ],\n')
print [[
title: "]] print(title) print[[",
columns: [
{
title: "]]print(i18n("show_alerts.alert_datetime"))print[[",
field: "column_date",
sortable: true,
css: {
textAlign: 'center',
whiteSpace: 'nowrap',
}
},
{
title: "]]print(i18n("show_alerts.alert_duration"))print[[",
field: "column_duration",
sortable: true,
css: {
textAlign: 'center',
whiteSpace: 'nowrap',
}
},
{
title: "]]print(i18n("show_alerts.alert_count"))print[[",
field: "column_count",
hidden: ]] print(ternary(t["status"] ~= "historical-flows", "true", "false")) print[[,
sortable: true,
css: {
textAlign: 'center'
}
},
{
title: "]]print(i18n("show_alerts.alert_severity"))print[[",
field: "column_severity",
sortable: true,
css: {
textAlign: 'center'
}
},
{
title: "]]print(i18n("show_alerts.alert_type"))print[[",
field: "column_type",
sortable: true,
css: {
textAlign: 'center',
whiteSpace: 'nowrap',
}
},
{
title: "]]print(i18n("drilldown"))print[[",
field: "column_chart",
sortable: false,
hidden: ]] print(ternary(not interfaceHasNindexSupport() or ntop.isPro(), "false", "true")) print[[,
css: {
textAlign: 'center'
}
},
{
title: "]]print(i18n("show_alerts.alert_description"))print[[",
field: "column_msg",
css: {
textAlign: 'left',
}
},
{
field: "column_key",
hidden: true
},
{
title: "]]print(i18n("show_alerts.alert_actions")) print[[",
css: {
textAlign: 'center',
width: "10%",
}
},
], tableCallback: function() {
var table_data = $("#]] print(t["div-id"]) print[[").data("datatable").resultset.data;
datatableForEachRow("#]] print(t["div-id"]) print[[", function(row_id) {
var alert_key = $("td:nth(7)", this).html().split("|");
var alert_id = alert_key[0];
var data = table_data[row_id];
var explorer_url = data["column_explorer"];
if(explorer_url) {
datatableAddLinkButtonCallback.bind(this)(9, explorer_url, "]] print(i18n("show_alerts.explorer")) print[[");
disable_alerts_dialog = "#disable_flows_alerts";
} else if(!data.column_alert_disabled)
datatableAddActionButtonCallback.bind(this)(9, "prepareToggleAlertsDialog(']] print(t["div-id"]) print[[',"+ row_id +"); $('#disable_alert_type').modal('show');", "]] print(i18n("show_alerts.disable_alerts")) print[[");
else
datatableAddActionButtonCallback.bind(this)(9, "prepareToggleAlertsDialog(']] print(t["div-id"]) print[[',"+ row_id +"); $('#enable_alert_type').modal('show');", "]] print(i18n("show_alerts.enable_alerts")) print[[");
if(]] print(ternary(t["status"] == "engaged", "true", "false")) print[[)
datatableAddActionButtonCallback.bind(this)(9, "alert_to_release = "+ row_id +"; $('#release_single_alert').modal('show');", "]] print(i18n("show_alerts.release_alert_action")) print[[");
if(]] print(ternary(t["status"] ~= "engaged", "true", "false")) print[[)
datatableAddDeleteButtonCallback.bind(this)(9, "delete_alert_id ='" + alert_id + "'; $('#delete_alert_dialog').modal('show');", "]] print(i18n('delete')) print[[");
$("form", this).submit(function() {
// add "status" parameter to the form
var get_params = paramsExtend(]] print(tableToJsObject(getTabParameters(url_params, nil))) print[[, {status:getCurrentStatus()});
$(this).attr("action", "?" + $.param(get_params));
return true;
});
});
}
});
});
]]
if (clicked == "1") then
print[[
// must wait for modalDeleteAlertsStatus to be created
$(function() {
var status_reset = ]] print(tostring(status_reset)) --[[ this is necessary because of status parameter inconsistency after tab switch ]] print[[;
var tabid;
if ((status_reset) || (getCurrentStatus() == "")) {
tabid = "]] print("tab-"..t["div-id"]) print[[";
history.replaceState(null, null, "#"+tabid);
} else {
tabid = getActiveTabId();
}
updateDeleteLabel(tabid);
});
]]
end
print[[
</script>
]]
::next_menu_item::
end
local zoom_vals = {
{ i18n("show_alerts.5_min"), 5*60*1, i18n("show_alerts.older_5_minutes_ago") },
{ i18n("show_alerts.30_min"), 30*60*1, i18n("show_alerts.older_30_minutes_ago") },
{ i18n("show_alerts.1_hour"), 60*60*1, i18n("show_alerts.older_1_hour_ago") },
{ i18n("show_alerts.1_day"), 60*60*24, i18n("show_alerts.older_1_day_ago") },
{ i18n("show_alerts.1_week"), 60*60*24*7, i18n("show_alerts.older_1_week_ago") },
{ i18n("show_alerts.1_month"), 60*60*24*31, i18n("show_alerts.older_1_month_ago") },
{ i18n("show_alerts.6_months"), 60*60*24*31*6, i18n("show_alerts.older_6_months_ago") },
{ i18n("show_alerts.1_year"), 60*60*24*366 , i18n("show_alerts.older_1_year_ago") }
}
if(has_engaged_alerts or has_past_alerts or has_flow_alerts) then
-- trigger the click on the right tab to force table load
print[[
<script type="text/javascript">
$("[clicked=1]").trigger("click");
</script>
]]
if not alt_nav_tabs then print [[</div> <!-- closes tab-content -->]] end
local has_fixed_period = ((not isEmptyString(_GET["epoch_begin"])) or (not isEmptyString(_GET["epoch_end"])))
print('<div id="alertsActionsPanel">')
print('<br>' .. i18n("show_alerts.alerts_to_purge") .. ': ')
print[[<select id="deleteZoomSelector" class="form-control" style="display:]] if has_fixed_period then print("none") else print("inline") end print[[; width:14em; margin:0 1em;">]]
local all_msg = ""
if not has_fixed_period then
print('<optgroup label="' .. i18n("show_alerts.older_than") .. '">')
for k,v in ipairs(zoom_vals) do
print('<option data-older="'..(os.time() - zoom_vals[k][2])..'" data-msg="'.." "..zoom_vals[k][3].. '">'..zoom_vals[k][1]..'</option>\n')
end
print('</optgroup>')
else
all_msg = " " .. i18n("show_alerts.in_the_selected_time_frame")
end
print('<option selected="selected" data-older="0" data-msg="') print(all_msg) print('">' .. i18n("all") .. '</option>\n')
print[[</select>
<form id="modalDeleteForm" class="form-inline" style="display:none;" method="post" onsubmit="return checkModalDelete();">
<input type="hidden" id="modalDeleteAlertsOlderThan" value="-1" />
<input id="csrf" name="csrf" type="hidden" value="]] print(ntop.getRandomCSRFValue()) print[[" />
</form>
]]
-- we need to dynamically modify parameters at js-time because we switch tab
local delete_params = getTabParameters(url_params, nil)
delete_params.epoch_end = -1
print[[<button id="buttonOpenDeleteModal" data-toggle="modal" data-target="#myModal" class="btn btn-secondary"> <span id="purgeBtnMessage">]]
print(i18n("show_alerts.purge_subj_alerts", {subj='<span id="purgeBtnLabel"></span>'}))
print[[</span></button>
</div> <!-- closes alertsActionsPanel -->
<script>
paramsToForm('#modalDeleteForm', ]] print(tableToJsObject(delete_params)) print[[);
function getTabSpecificParams() {
var tab_specific = {status:getCurrentStatus()};
var period_end = $('#modalDeleteAlertsOlderThan').val();
if (parseInt(period_end) > 0)
tab_specific.epoch_end = period_end;
if (tab_specific.status == "]] print(_GET["status"]) print[[") {
tab_specific.alert_severity = ]] if tonumber(_GET["alert_severity"]) ~= nil then print(_GET["alert_severity"]) else print('""') end print[[;
tab_specific.alert_type = ]] if tonumber(_GET["alert_type"]) ~= nil then print(_GET["alert_type"]) else print('""') end print[[;
}
// merge the general parameters to the tab specific ones
return paramsExtend(]] print(tableToJsObject(getTabParameters(url_params, nil))) print[[, tab_specific);
}
function checkModalDelete() {
var get_params = getTabSpecificParams();
var post_params = {};
post_params.csrf = "]] print(ntop.getRandomCSRFValue()) print[[";
post_params.id_to_delete = "__all__";
// this actually performs the request
var form = paramsToForm('<form method="post"></form>', post_params);
form.attr("action", "?" + $.param(get_params));
form.appendTo('body').submit();
return false;
}
var cur_alert_num_req = null;
/* This acts before shown.bs.modal event, avoiding visual fields substitution glitch */
$('#buttonOpenDeleteModal').on('click', function() {
var lb = $("#purgeBtnLabel");
var zoomsel = $("#deleteZoomSelector").find(":selected");
$("#myModal h3").html($("#purgeBtnMessage").html());
$(".modal-body #modalDeleteAlertsMsg").html(zoomsel.data('msg') + ']]
if tonumber(_GET["alert_severity"]) ~= nil then
print(' with severity "'..alertSeverityLabel(_GET["alert_severity"], true)..'" ')
elseif tonumber(_GET["alert_type"]) ~= nil then
print(' with type "'..alertTypeLabel(_GET["alert_type"], true)..'" ')
end
print[[');
if (lb.length == 1)
$(".modal-body #modalDeleteContext").html(" " + lb.html());
$('#modalDeleteAlertsOlderThan').val(zoomsel.data('older'));
cur_alert_num_req = $.ajax({
type: 'GET',
]] print("url: '"..ntop.getHttpPrefix().."/lua/get_num_alerts.lua'") print[[,
data: $.extend(getTabSpecificParams(), {ifid: ]] print(string.format("%d", ifid)) print[[}),
complete: function() {
$("#alerts-summary-wait").hide();
}, error: function() {
$("#alerts-summary-body").html("?");
}, success: function(count){
$("#alerts-summary-body").html(count);
if (count == 0)
$('#myModal button[type="submit"]').attr("disabled", "disabled");
}
});
});
$('#myModal').on('hidden.bs.modal', function () {
if(cur_alert_num_req) {
cur_alert_num_req.abort();
cur_alert_num_req = null;
}
$("#alerts-summary-wait").show();
$("#alerts-summary-body").html("");
$('#myModal button[type="submit"]').removeAttr("disabled");
})
</script>]]
end
end
-- #################################
function drawAlerts(options)
local has_engaged_alerts = hasAlerts("engaged", getTabParameters(_GET, "engaged"))
local has_past_alerts = hasAlerts("historical", getTabParameters(_GET, "historical"))
local has_disabled_alerts = alerts_api.hasEntitiesWithAlertsDisabled(interface.getId())
local has_flow_alerts = false
if _GET["entity"] == nil then
has_flow_alerts = hasAlerts("historical-flows", getTabParameters(_GET, "historical-flows"))
end
checkDeleteStoredAlerts()
checkDisableAlerts()
return drawAlertTables(has_past_alerts, has_engaged_alerts, num_flow_alerts, has_disabled_alerts, _GET, true, nil, options)
end
-- #################################
function newAlertsWorkingStatus(ifstats, granularity)
local res = {
granularity = granularity,
engine = alertEngine(granularity),
ifid = ifstats.id,
now = os.time(),
interval = granularity2sec(granularity),
}
return res
end
-- #################################
-- A redis set with mac addresses as keys
local function getActiveDevicesHashKey(ifid)
return "ntopng.cache.active_devices.ifid_" .. ifid
end
function deleteActiveDevicesKey(ifid)
ntop.delCache(getActiveDevicesHashKey(ifid))
end
-- #################################
local function getSavedDeviceNameKey(mac)
return "ntopng.cache.devnames." .. mac
end
local function setSavedDeviceName(mac, name)
local key = getSavedDeviceNameKey(mac)
ntop.setCache(key, name)
end
local function getSavedDeviceName(mac)
local key = getSavedDeviceNameKey(mac)
return ntop.getCache(key)
end
function check_macs_alerts(ifid)
local alert_new_devices_enabled = ntop.getPref("ntopng.prefs.alerts.device_first_seen_alert") == "1"
local alert_device_connection_enabled = ntop.getPref("ntopng.prefs.alerts.device_connection_alert") == "1"
local active_devices_set = getActiveDevicesHashKey(ifid)
local prev_active_devices = swapKeysValues(ntop.getMembersCache(active_devices_set) or {})
local num_prev_active_devices = table.len(prev_active_devices)
local seen_devices_hash = getFirstSeenDevicesHashKey(ifid)
local seen_devices = ntop.getHashAllCache(seen_devices_hash) or {}
local num_seen_devices = table.len(seen_devices)
local max_active_devices_cardinality = 16384
if(num_seen_devices >= max_active_devices_cardinality) then
traceError(TRACE_INFO, TRACE_CONSOLE, string.format("Too many active devices, discarding %u devices", num_seen_devices))
ntop.delCache(active_devices_set)
prev_active_devices = {}
end
local active_devices = {}
callback_utils.foreachDevice(getInterfaceName(ifid), nil, function(devicename, devicestats, devicebase)
-- note: location is always lan when capturing from a local interface
if (not devicestats.special_mac) and (devicestats.location == "lan") then
local mac = devicestats.mac
active_devices[mac] = 1
if not seen_devices[mac] then
-- First time we see a device
ntop.setHashCache(seen_devices_hash, mac, tostring(os.time()))
if alert_new_devices_enabled then
local name = getDeviceName(mac)
setSavedDeviceName(mac, name)
alerts_api.store(
alerts_api.macEntity(mac),
alerts_api.newDeviceType(name))
end
end
if not prev_active_devices[mac] then
-- Device connection
ntop.setMembersCache(active_devices_set, mac)
-- Do not nofify new connected devices if the prev_active_devices
-- set was empty (cleared or on startup)
if num_prev_active_devices > 0 then
if alert_device_connection_enabled then
local name = getDeviceName(mac)
setSavedDeviceName(mac, name)
alerts_api.store(
alerts_api.macEntity(mac),
alerts_api.deviceHasConnectedType(name))
end
end
end
end
end)
-- Safety check to avoid notifying disconnected devices
-- during shutdown when they are no longer active in ntopng.
if not ntop.isShutdown() then
for mac in pairs(prev_active_devices) do
if not active_devices[mac] then
-- Device disconnection
local name = getSavedDeviceName(mac)
ntop.delMembersCache(active_devices_set, mac)
if alert_device_connection_enabled then
alerts_api.store(
alerts_api.macEntity(mac),
alerts_api.deviceHasDisconnectedType(name))
end
end
end
end
end
-- #################################
-- A redis set with host pools as keys
local function getActivePoolsHashKey(ifid)
return "ntopng.cache.active_pools.ifid_" .. ifid
end
function deleteActivePoolsKey(ifid)
ntop.delCache(getActivePoolsHashKey(ifid))
end
-- #################################
-- Redis hashe with key=pool and value=list of quota_exceed_items, separated by |
local function getPoolsQuotaExceededItemsKey(ifid)
return "ntopng.cache.quota_exceeded_pools.ifid_" .. ifid
end
function deletePoolsQuotaExceededItemsKey(ifid)
ntop.delCache(getPoolsQuotaExceededItemsKey(ifid))
end
-- #################################
function check_host_pools_alerts(ifid)
local active_pools_set = getActivePoolsHashKey(ifid)
local prev_active_pools = swapKeysValues(ntop.getMembersCache(active_pools_set)) or {}
local alert_pool_connection_enabled = ntop.getPref("ntopng.prefs.alerts.pool_connection_alert") == "1"
local alerts_on_quota_exceeded = ntop.isPro() and ntop.getPref("ntopng.prefs.alerts.quota_exceeded_alert") == "1"
local pools_stats = nil
local quota_exceeded_pools_key = getPoolsQuotaExceededItemsKey(ifid)
local quota_exceeded_pools_values = ntop.getHashAllCache(quota_exceeded_pools_key) or {}
local quota_exceeded_pools = {}
local now_active_pools = {}
-- Deserialize quota_exceeded_pools
for pool, v in pairs(quota_exceeded_pools_values) do
quota_exceeded_pools[pool] = {}
for _, group in pairs(split(quota_exceeded_pools_values[pool], "|")) do
local parts = split(group, "=")
if #parts == 2 then
local proto = parts[1]
local quota = parts[2]
local parts = split(quota, ",")
quota_exceeded_pools[pool][proto] = {toboolean(parts[1]), toboolean(parts[2])}
end
end
-- quota_exceeded_pools[pool] is like {Youtube={true, false}}, where true is bytes_exceeded, false is time_exceeded
end
if ntop.isPro() then
pools_stats = interface.getHostPoolsStats()
end
local pools = interface.getHostPoolsInfo()
if(pools ~= nil) and (pools_stats ~= nil) then
for pool, info in pairs(pools.num_members_per_pool) do
local pool_stats = pools_stats[tonumber(pool)]
local pool_exceeded_quotas = quota_exceeded_pools[pool] or {}
-- Pool quota
if((pool_stats ~= nil) and (shaper_utils ~= nil)) then
local quotas_info = shaper_utils.getQuotasInfo(ifid, pool, pool_stats)
for proto, info in pairs(quotas_info) do
local prev_exceeded = pool_exceeded_quotas[proto] or {false,false}
if alerts_on_quota_exceeded then
if info.bytes_exceeded and not prev_exceeded[1] then
alerts_api.store(
alerts_api.hostPoolEntity(pool),
alerts_api.poolQuotaExceededType(pool, proto, "traffic_quota", info.bytes_value, info.bytes_quota)
)
end
if info.time_exceeded and not prev_exceeded[2] then
alerts_api.store(
alerts_api.hostPoolEntity(pool),
alerts_api.poolQuotaExceededType(pool, proto, "time_quota", info.time_value, info.time_quota)
)
end
end
if not info.bytes_exceeded and not info.time_exceeded then
-- delete as no quota is left
pool_exceeded_quotas[proto] = nil
else
-- update/add serialized
pool_exceeded_quotas[proto] = {info.bytes_exceeded, info.time_exceeded}
end
end
if table.empty(pool_exceeded_quotas) then
ntop.delHashCache(quota_exceeded_pools_key, pool)
else
-- Serialize the new quota information for the pool
for proto, value in pairs(pool_exceeded_quotas) do
pool_exceeded_quotas[proto] = table.concat({tostring(value[1]), tostring(value[2])}, ",")
end
ntop.setHashCache(quota_exceeded_pools_key, pool, table.tconcat(pool_exceeded_quotas, "=", "|"))
end
end
-- Pool presence
if (pool ~= host_pools_utils.DEFAULT_POOL_ID) and (info.num_hosts > 0) then
now_active_pools[pool] = 1
if not prev_active_pools[pool] then
-- Pool connection
ntop.setMembersCache(active_pools_set, pool)
if alert_pool_connection_enabled then
alerts_api.store(
alerts_api.hostPoolEntity(pool),
alerts_api.poolConnectionType(pool)
)
end
end
end
end
end
-- Pool presence
for pool in pairs(prev_active_pools) do
if not now_active_pools[pool] then
-- Pool disconnection
ntop.delMembersCache(active_pools_set, pool)
if alert_pool_connection_enabled then
alerts_api.store(
alerts_api.hostPoolEntity(pool),
alerts_api.poolDisconnectionType(pool)
)
end
end
end
end
-- #################################
function disableAlertsGeneration()
if not haveAdminPrivileges() then
return
end
-- Ensure we do not conflict with others
ntop.setPref("ntopng.prefs.disable_alerts_generation", "1")
ntop.reloadPreferences()
if(verbose) then io.write("[Alerts] Disable done\n") end
end
-- #################################
function flushAlertsData()
if not haveAdminPrivileges() then
return
end
local selected_interface = ifname
local ifnames = interface.getIfNames()
local force_query = true
local generation_toggle_backup = ntop.getPref("ntopng.prefs.disable_alerts_generation")
if(verbose) then io.write("[Alerts] Temporary disabling alerts generation...\n") end
ntop.setAlertsTemporaryDisabled(true);
ntop.msleep(3000)
callback_utils.foreachInterface(ifnames, nil, function(ifname, ifstats)
if(verbose) then io.write("[Alerts] Processing interface "..ifname.."...\n") end
if(verbose) then io.write("[Alerts] Flushing SQLite configuration...\n") end
performAlertsQuery("DELETE", "engaged", {}, force_query)
performAlertsQuery("DELETE", "historical", {}, force_query)
performAlertsQuery("DELETE", "historical-flows", {}, force_query)
end)
if(verbose) then io.write("[Alerts] Flushing Redis configuration...\n") end
deleteCachePattern("ntopng.prefs.*alert*")
deleteCachePattern("ntopng.alerts.*")
user_scripts.deleteConfigurations()
alerts_api.purgeAlertsPrefs()
for _, key in pairs(get_make_room_keys("*")) do deleteCachePattern(key) end
if(verbose) then io.write("[Alerts] Enabling alerts generation...\n") end
ntop.setAlertsTemporaryDisabled(false);
ntop.setPref("ntopng.prefs.disable_alerts_generation", generation_toggle_backup)
refreshAlerts(interface.getId())
if(verbose) then io.write("[Alerts] Flush done\n") end
interface.select(selected_interface)
end
-- #################################
function alertNotificationActionToLabel(action)
local label = ""
if action == "engage" then
label = "[Engaged]"
elseif action == "release" then
label = "[Released]"
end
return label
end
-- #################################
function formatAlertMessage(ifid, alert)
local msg
if(alert.alert_entity == alert_consts.alertEntity("flow") or (alert.alert_entity == nil)) then
msg = formatRawFlow(alert, alert["alert_json"])
else
msg = alert["alert_json"]
if isEmptyString(msg) then
msg = {}
elseif(string.sub(msg, 1, 1) == "{") then
msg = json.decode(msg)
end
local description = alertTypeDescription(alert.alert_type)
if(type(description) == "string") then
-- localization string
msg = i18n(description, msg)
elseif(type(description) == "function") then
msg = description(ifid, alert, msg)
end
end
if(type(msg) == "table") then
return("")
end
return(msg)
end
-- #################################
function notification_timestamp_asc(a, b)
return (a.alert_tstamp < b.alert_tstamp)
end
function notification_timestamp_rev(a, b)
return (a.alert_tstamp > b.alert_tstamp)
end
-- Returns a summary of the alert as readable text
function formatAlertNotification(notif, options)
local defaults = {
nohtml = false,
show_severity = true,
}
options = table.merge(defaults, options)
local msg = "[" .. formatEpoch(notif.alert_tstamp or 0) .. "]"
msg = msg .. ternary(options.show_severity == false, "", "[" .. alertSeverityLabel(notif.alert_severity, options.nohtml) .. "]") ..
"[" .. alertTypeLabel(notif.alert_type, options.nohtml) .."]"
-- entity can be hidden for example when one is OK with just the message
if options.show_entity then
msg = msg.."["..alert_consts.alertEntityLabel(notif.alert_entity).."]"
if notif.alert_entity ~= "flow" then
local ev = notif.alert_entity_val
if notif.alert_entity == "host" then
-- suppresses @0 when the vlan is zero
ev = hostinfo2hostkey(hostkey2hostinfo(notif.alert_entity_val))
end
msg = msg.."["..ev.."]"
end
end
-- add the label, that is, engaged or released
msg = msg .. alertNotificationActionToLabel(notif.action).. " "
local alert_message = formatAlertMessage(notif.ifid, notif)
if options.nohtml then
msg = msg .. noHtml(alert_message)
else
msg = msg .. alert_message
end
return msg
end
-- ##############################################
-- Processes queued alerts and returns the information necessary to store them.
-- Alerts are only enqueued by AlertsQueue in C. From lua, the alerts_api
-- can be called directly as slow operations will be postponed
local function processStoreAlertFromQueue(alert)
local entity_info = nil
local type_info = nil
interface.select(tostring(alert.ifid))
if(alert.alert_type == "misconfigured_dhcp_range") then
local router_info = {host = alert.router_ip, vlan = alert.vlan_id}
entity_info = alerts_api.hostAlertEntity(alert.client_ip, alert.vlan_id)
type_info = alerts_api.ipOutsideDHCPRangeType(router_info, alert.mac_address, alert.client_mac, alert.sender_mac)
elseif(alert.alert_type == "slow_periodic_activity") then
entity_info = alerts_api.periodicActivityEntity(alert.path)
type_info = alerts_api.slowPeriodicActivityType(alert.duration_ms, alert.max_duration_ms)
elseif(alert.alert_type == "mac_ip_association_change") then
if(ntop.getPref("ntopng.prefs.ip_reassignment_alerts") == "1") then
local name = getSavedDeviceName(alert.new_mac)
entity_info = alerts_api.macEntity(alert.new_mac)
type_info = alerts_api.macIpAssociationChangeType(name, alert.ip, alert.old_mac, alert.new_mac)
end
elseif(alert.alert_type == "login_failed") then
entity_info = alerts_api.userEntity(alert.user)
type_info = alerts_api.loginFailedType()
elseif(alert.alert_type == "broadcast_domain_too_large") then
entity_info = alerts_api.macEntity(alert.src_mac)
type_info = alerts_api.broadcastDomainTooLargeType(alert.src_mac, alert.dst_mac, alert.vlan_id, alert.spa, alert.tpa)
elseif(alert.alert_type == "remote_to_remote") then
if(ntop.getPref("ntopng.prefs.remote_to_remote_alerts") == "1") then
local host_info = {host = alert.host, vlan = alert.vlan}
entity_info = alerts_api.hostAlertEntity(alert.host, alert.vlan)
type_info = alerts_api.remoteToRemoteType(host_info, alert.mac_address)
end
elseif((alert.alert_type == "user_activity") and (alert.scope == "login")) then
entity_info = alerts_api.userEntity(alert.user)
type_info = alerts_api.userActivityType("login", nil, nil, nil, "authorized")
elseif(alert.alert_type == "nfq_flushed") then
entity_info = alerts_api.interfaceAlertEntity(alert.ifid)
type_info = alerts_api.nfqFlushedType(getInterfaceName(alert.ifid), alert.pct, alert.tot, alert.dropped)
else
traceError(TRACE_ERROR, TRACE_CONSOLE, "Unknown alert type " .. (alert.alert_type or ""))
end
return entity_info, type_info
end
-- ##############################################
-- Global function
-- Check for alerts pushed by the datapath to an internal queue (from C)
-- and store them (push them to the SQLite and Notification queues).
-- NOTE: this is executed in a system VM, with no interfaces references
function checkStoreAlertsFromC(deadline)
if(not areAlertsEnabled()) then
return
end
while(os.time() <= deadline) do
local alert = ntop.popInternalAlerts()
if alert == nil then
break
end
if(verbose) then tprint(alert) end
local entity_info, type_info = processStoreAlertFromQueue(alert)
if((type_info ~= nil) and (entity_info ~= nil)) then
alerts_api.store(entity_info, type_info, alert.alert_tstamp)
end
end
end
-- ##############################################
-- Check for alerts in the notification queue and process them.
-- NOTE: this is executed in a system VM, with no interfaces references
function processAlertNotifications(now, periodic_frequency, force_export)
if(not areAlertsEnabled()) then
return
end
local interfaces = interface.getIfNames()
-- Get new alerts
while(true) do
local json_message = ntop.popAlertNotification()
if((json_message == nil) or (json_message == "")) then
break
end
if(verbose) then
io.write("Alert Notification: " .. json_message .. "\n")
end
local message = json.decode(json_message)
if(not message) then
goto continue
end
local str_ifid = tostring(message.ifid)
if((interfaces[str_ifid] == nil) and (str_ifid ~= getSystemInterfaceId())) then
goto continue
end
interface.select(str_ifid)
if message.is_flow_alert then
-- Silly but necessary due to the notifyFlowAlert
message.alert_entity = alert_consts.alert_entities.flow.entity_id
message.alert_entity_val = "flow"
message.action = nil
json_message = json.encode(message)
end
alert_endpoints.dispatchNotification(message, json_message)
::continue::
end
alert_endpoints.processNotifications(now, periodic_frequency)
end
-- ##############################################
local function notify_ntopng_status(started)
local info = ntop.getInfo()
local severity = alertSeverity("info")
local msg
local msg_details = string.format("%s v.%s (%s) [pid: %s][options: %s]", info.product, info.version, info.OS, info.pid, info.command_line)
local anomalous = false
local event
if(started) then
-- let's check if we are restarting from an anomalous termination
-- e.g., from a crash
if not recovery_utils.check_clean_shutdown() then
-- anomalous termination
msg = string.format("%s %s", i18n("alert_messages.ntopng_anomalous_termination", {url="https://www.ntop.org/support/need-help-2/need-help/"}), msg_details)
severity = alertSeverity("error")
anomalous = true
event = "anomalous_termination"
else
-- normal termination
msg = string.format("%s %s", i18n("alert_messages.ntopng_start"), msg_details)
event = "start"
end
else
msg = string.format("%s %s", i18n("alert_messages.ntopng_stop"), msg_details)
event = "stop"
end
local entity_value = "ntopng"
obj = {
entity_type = alert_consts.alertEntity("process"), entity_value=entity_value,
type = alertType("alert_process_notification"),
severity = severity,
message = msg,
when = os.time() }
if anomalous then
telemetry_utils.notify(obj)
end
local entity_info = alerts_api.processEntity(entity_value)
local type_info = alerts_api.processNotificationType(event, severity, msg_details)
interface.select(getSystemInterfaceId())
return(alerts_api.store(entity_info, type_info))
end
function notify_ntopng_start()
return(notify_ntopng_status(true))
end
function notify_ntopng_stop()
return(notify_ntopng_status(false))
end