ntopng/scripts/lua/modules/alerts_api.lua
emanuele-f a3432e00e8 Implement ntopng plugins
Plugins are a convenient way to group together related lua scripts.
Their primary use case is to group user scripts and their alert/status
definition.
The builtin ntopng user scripts and definitions are now
packed into plugins directories. In future, we will support loading of
user created plugins.
Plugins are loaded at startup into some runtime directories and then
used. Other changes provided by this commit include:

- Add sample flow logger plugin
- Initial support for system user scripts
- Rename edge to threshold
- Migrate system probes to user scripts/plugins
- Migrate scripts to more explicit alerts_api.checkThresholdAlert api
2019-12-10 09:25:57 +01:00

1294 lines
42 KiB
Lua

--
-- (C) 2013-19 - ntop.org
--
local dirs = ntop.getDirs()
package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path
local json = require("dkjson")
local alert_endpoints = require("alert_endpoints_utils")
local alert_consts = require("alert_consts")
local os_utils = require("os_utils")
local do_trace = false
local alerts_api = {}
-- Just helpers
local str_2_periodicity = {
["min"] = 60,
["5mins"] = 300,
["hour"] = 3600,
["day"] = 86400,
}
local known_alerts = {}
-- ##############################################
-- Returns a string which identifies an alert
function alerts_api.getAlertId(alert)
return(string.format("%s_%s_%s_%s_%s", alert.alert_type,
alert.alert_subtype or "", alert.alert_granularity or "",
alert.alert_entity, alert.alert_entity_val))
end
-- ##############################################
local function alertErrorTraceback(msg)
traceError(TRACE_ERROR, TRACE_CONSOLE, msg)
traceError(TRACE_ERROR, TRACE_CONSOLE, debug.traceback())
end
-- ##############################################
local function get_alert_triggered_key(type_info)
return(string.format("%d@%s", type_info.alert_type.alert_id, type_info.alert_subtype or ""))
end
-- ##############################################
-- Performs the alert store asynchronously.
-- This is necessary both to avoid paying the database io cost inside
-- the other scripts and as a necessity to avoid a deadlock on the
-- host hash in the host.lua script
function alerts_api.checkPendingStoreAlerts(deadline)
if(not areAlertsEnabled()) then
return(false)
end
-- SQLite Alerts
while(true) do
local alert_json = ntop.popSqliteAlert()
if(not alert_json) then
break
end
local alert = json.decode(alert_json)
if(alert) then
interface.select(string.format("%d", alert.ifid))
if(alert.is_flow_alert) then
interface.storeFlowAlert(alert)
else
interface.storeAlert(
alert.alert_tstamp, alert.alert_tstamp_end, alert.alert_granularity,
alert.alert_type, alert.alert_subtype, alert.alert_severity,
alert.alert_entity, alert.alert_entity_val,
alert.alert_json)
end
end
if(os.time() > deadline) then
return(false)
end
end
return(true)
end
-- ##############################################
--! @brief Stores a single alert (or event) into the alerts database
--! @param entity_info data returned by one of the entity_info building functions
--! @param type_info data returned by one of the type_info building functions
--! @param when (optional) the time when the release event occurs
--! @return true if the alert was successfully stored, false otherwise
function alerts_api.store(entity_info, type_info, when)
local force = false
local alert_json = json.encode(type_info.alert_type_params)
local ifid = interface.getId()
local granularity_sec = type_info.alert_granularity and type_info.alert_granularity.granularity_seconds or 0
local granularity_id = type_info.alert_granularity and type_info.alert_granularity.granularity_id or -1
local alert_json = plain_message or json.encode(type_info.alert_type_params)
local subtype = type_info.alert_subtype or ""
when = when or os.time()
if(not areAlertsEnabled()) then
return(false)
end
if alerts_api.isEntityAlertDisabled(ifid, entity_info.alert_entity.entity_id, entity_info.alert_entity_val, type_info.alert_type.alert_id) then
return(false)
end
-- Here the alert is considered stored. The actual store will be performed
-- asynchronously
-- NOTE: keep in sync with SQLite alert format in AlertsManager.cpp
local alert_to_store = {
ifid = ifid,
action = "store",
alert_type = type_info.alert_type.alert_id,
alert_subtype = subtype,
alert_granularity = granularity_sec,
alert_entity = entity_info.alert_entity.entity_id,
alert_entity_val = entity_info.alert_entity_val,
alert_severity = type_info.alert_severity.severity_id,
alert_tstamp = when,
alert_tstamp_end = when,
alert_json = alert_json,
}
if(entity_info.alert_entity.entity_id == alert_consts.alertEntity("host")) then
-- NOTE: for engaged alerts this operation is performed during trigger in C
interface.incTotalHostAlerts(entity_info.alert_entity_val, type_info.alert_type.alert_id)
end
local alert_json = json.encode(alert_to_store)
ntop.pushSqliteAlert(alert_json)
ntop.pushAlertNotification(alert_json)
return(true)
end
-- ##############################################
--! @brief Determine whether the alert has already been triggered
--! @param candidate_severity the candidate alert severity
--! @param candidate_type the candidate alert type
--! @param candidate_granularity the candidate alert granularity
--! @param candidate_alert_subtype the candidate alert subtype
--! @param cur_alerts a table of currently triggered alerts
--! @return true on if the alert has already been triggered, false otherwise
--!
--! @note Example of cur_alerts
--! cur_alerts table
--! cur_alerts.1 table
--! cur_alerts.1.alert_type number 2
--! cur_alerts.1.alert_subtype string min_bytes
--! cur_alerts.1.alert_entity_val string 192.168.2.222@0
--! cur_alerts.1.alert_granularity number 60
--! cur_alerts.1.alert_severity number 2
--! cur_alerts.1.alert_json string {"metric":"bytes","threshold":1,"value":13727070,"operator":"gt"}
--! cur_alerts.1.alert_tstamp_end number 1571328097
--! cur_alerts.1.alert_tstamp number 1571327460
--! cur_alerts.1.alert_entity number 1
local function already_triggered(cur_alerts, candidate_severity, candidate_type,
candidate_granularity, candidate_alert_subtype)
for i = #cur_alerts, 1, -1 do
local cur_alert = cur_alerts[i]
if candidate_severity == cur_alert.alert_severity
and candidate_type == cur_alert.alert_type
and candidate_granularity == cur_alert.alert_granularity
and candidate_alert_subtype == cur_alert.alert_subtype then
-- Remove from cur_alerts, this will save cycles for
-- subsequent calls of this method.
-- Using .remove is OK here as there won't unnecessarily move memory multiple times:
-- we return immeediately
-- NOTE: see un-removed alerts will be released by releaseEntityAlerts in interface.lua
table.remove(cur_alerts, i)
return true
end
end
return false
end
-- ##############################################
--! @brief Trigger an alert of given type on the entity
--! @param entity_info data returned by one of the entity_info building functions
--! @param type_info data returned by one of the type_info building functions
--! @param when (optional) the time when the release event occurs
--! @param cur_alerts (optional) a table containing triggered alerts for the current entity
--! @return true on if the alert was triggered, false otherwise
--! @note The actual trigger is performed asynchronously
--! @note false is also returned if an existing alert is found and refreshed
function alerts_api.trigger(entity_info, type_info, when, cur_alerts)
local ifid = interface.getId()
local is_disabled = alerts_api.isEntityAlertDisabled(ifid, entity_info.alert_entity.entity_id, entity_info.alert_entity_val, type_info.alert_type.alert_id)
-- Check if the alerts has been disabled and, in case return, before checking already_triggered,
-- so that the alert will be automatically released during the next check.
if is_disabled then
return(true)
end
if(not areAlertsEnabled()) then
return(false)
end
if(type_info.alert_granularity == nil) then
alertErrorTraceback("Missing mandatory granularity")
return(false)
end
-- Apply defaults
local granularity_sec = type_info.alert_granularity and type_info.alert_granularity.granularity_seconds or 0
local granularity_id = type_info.alert_granularity and type_info.alert_granularity.granularity_id or 0 --[[ 0 is aperiodic ]]
local subtype = type_info.alert_subtype or ""
if(cur_alerts and already_triggered(cur_alerts, type_info.alert_severity.severity_id,
type_info.alert_type.alert_id, granularity_sec, subtype) == true) then
return(true)
end
when = when or os.time()
local alert_json = json.encode(type_info.alert_type_params)
local triggered
local alert_key_name = get_alert_triggered_key(type_info)
local params = {
alert_key_name, granularity_id,
type_info.alert_severity.severity_id, type_info.alert_type.alert_id,
subtype, alert_json,
}
if(entity_info.alert_entity.entity_id == alert_consts.alertEntity("host")) then
host.checkContext(entity_info.alert_entity_val)
triggered = host.storeTriggeredAlert(table.unpack(params))
elseif(entity_info.alert_entity.entity_id == alert_consts.alertEntity("interface")) then
interface.checkContext(entity_info.alert_entity_val)
triggered = interface.storeTriggeredAlert(table.unpack(params))
elseif(entity_info.alert_entity.entity_id == alert_consts.alertEntity("network")) then
network.checkContext(entity_info.alert_entity_val)
triggered = network.storeTriggeredAlert(table.unpack(params))
else
triggered = interface.triggerExternalAlert(entity_info.alert_entity.entity_id, entity_info.alert_entity_val, table.unpack(params))
end
if(triggered == nil) then
if(do_trace) then print("[Don't Trigger alert (already triggered?) @ "..granularity_sec.."] "..
entity_info.alert_entity_val .."@"..type_info.alert_type.i18n_title..":".. subtype .. "\n") end
return(false)
else
if(do_trace) then print("[TRIGGER alert @ "..granularity_sec.."] "..
entity_info.alert_entity_val .."@"..type_info.alert_type.i18n_title..":".. subtype .. "\n") end
end
triggered.ifid = ifid
triggered.action = "engage"
local alert_json = json.encode(triggered)
ntop.pushAlertNotification(alert_json)
return(true)
end
-- ##############################################
--! @brief Release an alert of given type on the entity
--! @param entity_info data returned by one of the entity_info building functions
--! @param type_info data returned by one of the type_info building functions
--! @param when (optional) the time when the release event occurs
--! @param cur_alerts (optional) a table containing triggered alerts for the current entity
--! @note The actual release is performed asynchronously
--! @return true on success, false otherwise
function alerts_api.release(entity_info, type_info, when, cur_alerts)
-- Apply defaults
local granularity_sec = type_info.alert_granularity and type_info.alert_granularity.granularity_seconds or 0
local granularity_id = type_info.alert_granularity and type_info.alert_granularity.granularity_id or 0 --[[ 0 is aperiodic ]]
local subtype = type_info.alert_subtype or ""
if(cur_alerts and (not already_triggered(cur_alerts, type_info.alert_severity.severity_id,
type_info.alert_type.alert_id, granularity_sec, subtype))) then
return(true)
end
when = when or os.time()
local alert_key_name = get_alert_triggered_key(type_info)
local ifid = interface.getId()
local params = {alert_key_name, granularity_id, when}
local released = nil
if(not areAlertsEnabled()) then
return(false)
end
if(type_info.alert_severity == nil) then
alertErrorTraceback(string.format("Missing alert_severity [type=%s]", type_info.alert_type and type_info.alert_type.alert_id or ""))
return(false)
end
if(entity_info.alert_entity.entity_id == alert_consts.alertEntity("host")) then
host.checkContext(entity_info.alert_entity_val)
released = host.releaseTriggeredAlert(table.unpack(params))
elseif(entity_info.alert_entity.entity_id == alert_consts.alertEntity("interface")) then
interface.checkContext(entity_info.alert_entity_val)
released = interface.releaseTriggeredAlert(table.unpack(params))
elseif(entity_info.alert_entity.entity_id == alert_consts.alertEntity("network")) then
network.checkContext(entity_info.alert_entity_val)
released = network.releaseTriggeredAlert(table.unpack(params))
else
released = interface.releaseExternalAlert(entity_info.alert_entity.entity_id, entity_info.alert_entity_val, table.unpack(params))
end
if(released == nil) then
if(do_trace) then print("[Dont't Release alert (not triggered?) @ "..granularity_sec.."] "..
entity_info.alert_entity_val .."@"..type_info.alert_type.i18n_title..":".. subtype .. "\n") end
return(false)
else
if(do_trace) then print("[RELEASE alert @ "..granularity_sec.."] "..
entity_info.alert_entity_val .."@"..type_info.alert_type.i18n_title..":".. subtype .. "\n") end
end
released.ifid = ifid
released.action = "release"
local alert_json = json.encode(released)
ntop.pushSqliteAlert(alert_json)
ntop.pushAlertNotification(alert_json)
return(true)
end
-- ##############################################
-- Convenient method to release multiple alerts on an entity
function alerts_api.releaseEntityAlerts(entity_info, alerts)
if(alerts == nil) then
alerts = interface.getEngagedAlerts(entity_info.alert_entity.entity_id, entity_info.alert_entity_val)
end
for _, alert in pairs(alerts) do
alerts_api.release(entity_info, {
alert_type = alert_consts.alert_types[alertTypeRaw(alert.alert_type)],
alert_severity = alert_consts.alert_severities[alertSeverityRaw(alert.alert_severity)],
alert_subtype = alert.alert_subtype,
alert_granularity = alert_consts.alerts_granularities[sec2granularity(alert.alert_granularity)],
}, nil, alerts)
end
end
-- ##############################################
-- entity_info building functions
-- ##############################################
function alerts_api.hostAlertEntity(hostip, hostvlan)
return {
alert_entity = alert_consts.alert_entities.host,
-- NOTE: keep in sync with C (Alertable::setEntityValue)
alert_entity_val = hostinfo2hostkey({ip = hostip, vlan = hostvlan}, nil, true)
}
end
-- ##############################################
function alerts_api.interfaceAlertEntity(ifid)
return {
alert_entity = alert_consts.alert_entities.interface,
-- NOTE: keep in sync with C (Alertable::setEntityValue)
alert_entity_val = string.format("iface_%d", ifid)
}
end
-- ##############################################
function alerts_api.networkAlertEntity(network_cidr)
return {
alert_entity = alert_consts.alert_entities.network,
-- NOTE: keep in sync with C (Alertable::setEntityValue)
alert_entity_val = network_cidr
}
end
-- ##############################################
function alerts_api.snmpInterfaceEntity(snmp_device, snmp_interface)
return {
alert_entity = alert_consts.alert_entities.snmp_device,
alert_entity_val = string.format("%s_ifidx%d", snmp_device, snmp_interface)
}
end
-- ##############################################
function alerts_api.macEntity(mac)
return {
alert_entity = alert_consts.alert_entities.mac,
alert_entity_val = mac
}
end
-- ##############################################
function alerts_api.userEntity(user)
return {
alert_entity = alert_consts.alert_entities.user,
alert_entity_val = user
}
end
-- ##############################################
function alerts_api.processEntity(process)
return {
alert_entity = alert_consts.alert_entities.process,
alert_entity_val = process
}
end
-- ##############################################
function alerts_api.hostPoolEntity(pool_id)
return {
alert_entity = alert_consts.alert_entities.host_pool,
alert_entity_val = tostring(pool_id)
}
end
-- ##############################################
function alerts_api.periodicActivityEntity(activity_path)
return {
alert_entity = alert_consts.alert_entities.periodic_activity,
alert_entity_val = activity_path
}
end
-- ##############################################
function alerts_api.pingedHostEntity(host)
return {
alert_entity = alert_consts.alert_entities.pinged_host,
alert_entity_val = host
}
end
-- ##############################################
function alerts_api.categoryListsEntity(list_name)
return {
alert_entity = alert_consts.alert_entities.category_lists,
alert_entity_val = list_name
}
end
-- ##############################################
function alerts_api.influxdbEntity(dburl)
return {
alert_entity = alert_consts.alert_entities.influx_db,
alert_entity_val = dburl
}
end
-- ##############################################
-- type_info building functions
-- ##############################################
function alerts_api.userActivityType(scope, name, params, remote_addr, status)
return({
alert_type = alert_consts.alert_types.alert_user_activity,
alert_severity = alert_consts.alert_severities.info,
alert_type_params = {
scope = scope, name = name, params = params,
remote_addr = remote_addr, status = status,
}
})
end
-- ##############################################
function alerts_api.loginFailedType()
return({
alert_type = alert_consts.alert_types.alert_login_failed,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {},
})
end
-- ##############################################
function alerts_api.processNotificationType(event_type, severity, msg_details)
return({
alert_type = alert_consts.alert_types.alert_process_notification,
alert_severity = alert_consts.alert_severities[alertSeverityRaw(severity)],
alert_type_params = {
msg_details = msg_details,
event_type = event_type,
},
})
end
-- ##############################################
function alerts_api.listDownloadFailedType(list_name, last_error)
return({
alert_type = alert_consts.alert_types.alert_list_download_failed,
alert_severity = alert_consts.alert_severities.error,
alert_type_params = {
name=list_name, err=last_error
}
})
end
-- ##############################################
function alerts_api.influxdbDroppedPointsType(influxdb_url)
return({
alert_type = alert_consts.alert_types.alert_influxdb_export_failure,
alert_severity = alert_consts.alert_severities.error,
alert_granularity = alert_consts.alerts_granularities.min,
alert_type_params = {
influxdb = influxdb_url,
},
})
end
-- ##############################################
function alerts_api.newDeviceType(device_name)
return({
alert_type = alert_consts.alert_types.alert_new_device,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
device = device_name,
},
})
end
-- ##############################################
function alerts_api.deviceHasConnectedType(device_name)
return({
alert_type = alert_consts.alert_types.alert_device_connection,
alert_severity = alert_consts.alert_severities.info,
alert_type_params = {
device = device_name,
},
})
end
-- ##############################################
function alerts_api.deviceHasDisconnectedType(device_name)
return({
alert_type = alert_consts.alert_types.alert_device_disconnection,
alert_severity = alert_consts.alert_severities.info,
alert_type_params = {
device = device_name,
},
})
end
-- ##############################################
function alerts_api.poolQuotaExceededType(pool, proto, subtype, value, quota)
local host_pools_utils = require("host_pools_utils")
return({
alert_type = alert_consts.alert_types.alert_quota_exceeded,
alert_subtype = subtype,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
pool = host_pools_utils.getPoolName(interface.getId(), pool),
proto = proto, value = value, quota = quota,
},
})
end
-- ##############################################
function alerts_api.poolConnectionType(pool)
local host_pools_utils = require("host_pools_utils")
return({
alert_type = alert_consts.alert_types.alert_host_pool_connection,
alert_severity = alert_consts.alert_severities.info,
alert_type_params = {
pool = host_pools_utils.getPoolName(interface.getId(), pool),
},
})
end
-- ##############################################
function alerts_api.poolDisconnectionType(pool)
local host_pools_utils = require("host_pools_utils")
return({
alert_type = alert_consts.alert_types.alert_host_pool_disconnection,
alert_severity = alert_consts.alert_severities.info,
alert_type_params = {
pool = host_pools_utils.getPoolName(interface.getId(), pool),
},
})
end
-- ##############################################
function alerts_api.macIpAssociationChangeType(device, ip, old_mac, new_mac)
return({
alert_type = alert_consts.alert_types.alert_mac_ip_association_change,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
device = device, ip = ip,
old_mac = old_mac, new_mac = new_mac,
},
})
end
-- ##############################################
function alerts_api.broadcastDomainTooLargeType(src_mac, dst_mac, vlan, spa, tpa)
return({
alert_type = alert_consts.alert_types.alert_broadcast_domain_too_large,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
src_mac = src_mac, dst_mac = dst_mac,
spa = spa, tpa = tpa, vlan_id = vlan,
},
})
end
-- ##############################################
function alerts_api.nfqFlushedType(ifname, pct, tot, dropped)
return({
alert_type = alert_consts.alert_types.alert_nfq_flushed,
alert_severity = alert_consts.alert_severities.error,
alert_type_params = {
ifname = ifname, pct = pct, tot = tot, dropped = dropped,
},
})
end
-- ##############################################
function alerts_api.remoteToRemoteType(host_info, mac)
return({
alert_type = alert_consts.alert_types.alert_remote_to_remote,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
host = getResolvedAddress(host_info),
mac = mac,
},
})
end
-- ##############################################
function alerts_api.slowPeriodicActivityType(duration_ms, max_duration_ms)
return({
alert_type = alert_consts.alert_types.alert_slow_periodic_activity,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
duration_ms = duration_ms,
max_duration_ms = max_duration_ms
},
})
end
-- ##############################################
function alerts_api.ipOutsideDHCPRangeType(router_info, mac, client_mac, sender_mac)
return({
alert_type = alert_consts.alert_types.alert_ip_outsite_dhcp_range,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
router_info = hostinfo2hostkey(router_info),
mac = mac, client_mac = client_mac, sender_mac = sender_mac,
router_host = getResolvedAddress(router_info),
},
})
end
-- ##############################################
function alerts_api.snmpInterfaceStatusChangeType(snmp_interface_info, device, interface, interface_name, status)
local snmp_interface_index = snmp_interface_info["snmp_interface"]["index"]
local snmp_interface_name = snmp_interface_info["snmp_interface"]["name"]
local snmp_interface_status = snmp_interface_info["if_status"]["status"]
local snmp_device_ip = snmp_interface_info["snmp_device_ip"]
local res = {
alert_type = alert_consts.alert_types.alert_port_status_change,
alert_severity = alert_consts.alert_severities.info,
alert_type_params = {
device = snmp_device_ip,
interface = snmp_interface_index,
interface_name = snmp_interface_name,
status = snmp_interface_status,
},
}
return res
end
-- ##############################################
function alerts_api.snmpInterfaceDuplexStatusChangeType(snmp_interface_info)
local snmp_interface_index = snmp_interface_info["snmp_interface"]["index"]
local snmp_interface_name = snmp_interface_info["snmp_interface"]["name"]
local snmp_interface_duplexstatus = snmp_interface_info["if_status"]["duplexstatus"]
local snmp_device_ip = snmp_interface_info["snmp_device_ip"]
local res = {
alert_type = alert_consts.alert_types.alert_port_duplexstatus_change,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
device = snmp_device_ip,
interface = snmp_interface_index,
interface_name = snmp_interface_name,
status = snmp_interface_duplexstatus,
},
}
return res
end
-- ##############################################
function alerts_api.snmpInterfaceErrorsType(snmp_interface_info)
local snmp_interface_index = snmp_interface_info["snmp_interface"]["index"]
local snmp_interface_name = snmp_interface_info["snmp_interface"]["name"]
local snmp_device_ip = snmp_interface_info["snmp_device_ip"]
local res = {
alert_type = alert_consts.alert_types.alert_port_errors,
alert_severity = alert_consts.alert_severities.info,
alert_type_params = {
device = snmp_device_ip,
interface = snmp_interface_index,
interface_name = snmp_interface_name,
},
}
return res
end
-- ##############################################
function alerts_api.snmpPortLoadThresholdExceededType(snmp_interface_info)
local snmp_interface_index = snmp_interface_info["snmp_interface"]["index"]
local snmp_interface_name = snmp_interface_info["snmp_interface"]["name"]
local snmp_device_ip = snmp_interface_info["snmp_device_ip"]
local in_port_load = snmp_interface_info["in_port_load"]
local out_port_load = snmp_interface_info["out_port_load"]
local load_threshold = snmp_interface_info["load_threshold"]
local res = {
alert_type = alert_consts.alert_types.alert_port_load_threshold_exceeded,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
device = snmp_device_ip,
interface = snmp_interface_index,
interface_name = snmp_interface_name,
in_load = in_port_load,
out_load = out_port_load,
load_threshold = load_threshold,
},
}
return res
end
-- ##############################################
function alerts_api.misconfiguredAppType(subtype)
return({
alert_type = alert_consts.alert_types.alert_misconfigured_app,
alert_subtype = subtype,
alert_severity = alert_consts.alert_severities.error,
alert_granularity = alert_consts.alerts_granularities.min,
alert_type_params = {},
})
end
-- ##############################################
function alerts_api.tooManyDropsType(drops, drop_perc, threshold)
return({
alert_type = alert_consts.alert_types.alert_too_many_drops,
alert_severity = alert_consts.alert_severities.error,
alert_granularity = alert_consts.alerts_granularities.min,
alert_type_params = {
drops = drops, drop_perc = drop_perc, edge = threshold,
},
})
end
-- ##############################################
function alerts_api.userScriptCallsDrops(subdir, drops)
return({
alert_type = alert_consts.alert_types.alert_user_script_calls_drops,
alert_severity = alert_consts.alert_severities.error,
alert_granularity = alert_consts.alerts_granularities.min,
alert_subtype = subdir,
alert_type_params = {
drops = drops,
},
})
end
-- ##############################################
function alerts_api.slowPurgeType(idle, idle_perc, threshold)
return({
alert_type = alert_consts.alert_types.alert_slow_purge,
alert_severity = alert_consts.alert_severities.warning,
alert_granularity = alert_consts.alerts_granularities.min,
alert_type_params = {
idle = idle, idle_perc = idle_perc, edge = threshold,
},
})
end
-- ##############################################
function alerts_api.slowStatsUpdateType()
return({
alert_type = alert_consts.alert_types.alert_slow_stats_update,
alert_severity = alert_consts.alert_severities.warning,
alert_granularity = alert_consts.alerts_granularities.min,
alert_type_params = {},
})
end
-- ##############################################
function alerts_api.requestReplyRatioType(key, requests, replies, granularity)
return({
alert_type = alert_consts.alert_types.alert_request_reply_ratio,
alert_subtype = key,
alert_granularity = alert_consts.alerts_granularities[granularity],
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
requests = requests, replies = replies,
}
})
end
-- ##############################################
function alerts_api.anomalousTCPFlagsType(num_syn, num_rst, ratio, is_sent, granularity)
return({
alert_type = alert_consts.alert_types.alert_anomalous_tcp_flags,
alert_subtype = ternary(is_sent, "sent", "rcvd"),
alert_granularity = alert_consts.alerts_granularities[granularity],
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
num_syn = num_syn,
num_rst = num_rst,
is_sent = is_sent,
ratio = ratio,
}
})
end
-- ##############################################
function alerts_api.misbehavingFlowsRatioType(misbehaving_flows, total_flows, ratio, is_sent, granularity)
return({
alert_type = alert_consts.alert_types.alert_misbehaving_flows_ratio,
alert_subtype = ternary(is_sent, "sent", "rcvd"),
alert_granularity = alert_consts.alerts_granularities[granularity],
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
misbehaving_flows = misbehaving_flows,
total_flows = total_flows,
is_sent = is_sent,
ratio = ratio,
}
})
end
-- ##############################################
function alerts_api.ghostNetworkType(network, granularity)
return({
alert_type = alert_consts.alert_types.alert_ghost_network,
alert_subtype = network,
alert_granularity = alert_consts.alerts_granularities[granularity],
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {},
})
end
-- ##############################################
-- TODO: comment
function alerts_api.snmp_device_interface_check_function(params)
local check_res = params.user_script.snmp_device_interface_check(params.granularity, params.entity_info)
if check_res then
local type_builder = params.user_script.type_builder
local check_type = type_builder(params.entity_info)
return alerts_api.store(params.alert_entity, check_type)
end
end
-- ##############################################
-- TODO document
function alerts_api.checkThresholdAlert(params, alert_type, value)
local script = params.user_script
local threshold_config = params.alert_config
local alarmed = false
local threshold_type = {
alert_type = alert_type,
alert_subtype = script.key,
alert_granularity = alert_consts.alerts_granularities[params.granularity],
alert_severity = alert_consts.alert_severities.error,
alert_type_params = {
metric = params.user_script.key,
value = value,
operator = threshold_config.operator,
threshold = threshold_config.threshold,
}
}
if(threshold_config.operator == "lt") then
if(value < threshold_config.threshold) then alarmed = true end
else
if(value > threshold_config.threshold) then alarmed = true end
end
if(alarmed) then
return(alerts_api.trigger(params.alert_entity, threshold_type, nil, params.cur_alerts))
else
return(alerts_api.release(params.alert_entity, threshold_type, nil, params.cur_alerts))
end
end
-- ##############################################
-- An alert check function which checks for anomalies.
-- The user_script key is the type of the anomaly to check.
-- The user_script must implement a anomaly_type_builder(anomaly_key) function
-- which returns a type_info for the given anomaly.
function alerts_api.anomaly_check_function(params)
local anomal_key = params.user_script.key
local type_info = params.user_script.anomaly_type_builder(anomal_key)
if params.entity_info.anomalies[anomal_key] then
return alerts_api.trigger(params.alert_entity, type_info, nil, params.cur_alerts)
else
return alerts_api.release(params.alert_entity, type_info, nil, params.cur_alerts)
end
end
-- ##############################################
-- @brief Performs a difference between the current metric value and
-- the previous saved value and saves the current value for next call.
-- @param reg lua C context pointer to the alertable entity storage
-- @param metric_name name of the metric to retrieve
-- @param granularity the granularity string
-- @param curr_val the current metric value
-- @param skip_first if true, 0 will be returned when no cached value is present
-- @return the difference between current and previous value
local function delta_val(reg, metric_name, granularity, curr_val, skip_first)
local granularity_num = granularity2id(granularity)
local key = string.format("%s:%s", metric_name, granularity_num)
-- Read cached value and purify it
local prev_val = tonumber(reg.getCachedAlertValue(key, granularity_num))
-- Save the value for the next round
reg.setCachedAlertValue(key, tostring(curr_val), granularity_num)
if((skip_first == true) and (prev_val == nil)) then
return(0)
else
return(curr_val - (prev_val or 0))
end
end
-- ##############################################
function alerts_api.host_delta_val(metric_name, granularity, curr_val, skip_first)
return(delta_val(host --[[ the host Lua reg ]], metric_name, granularity, curr_val, skip_first))
end
function alerts_api.interface_delta_val(metric_name, granularity, curr_val, skip_first)
return(delta_val(interface --[[ the interface Lua reg ]], metric_name, granularity, curr_val, skip_first))
end
function alerts_api.network_delta_val(metric_name, granularity, curr_val, skip_first)
return(delta_val(network --[[ the network Lua reg ]], metric_name, granularity, curr_val, skip_first))
end
-- ##############################################
function alerts_api.application_bytes(info, application_name)
local curr_val = 0
if info["ndpi"] and info["ndpi"][application_name] then
curr_val = info["ndpi"][application_name]["bytes.sent"] + info["ndpi"][application_name]["bytes.rcvd"]
end
return curr_val
end
-- ##############################################
function alerts_api.category_bytes(info, category_name)
local curr_val = 0
if info["ndpi_categories"] and info["ndpi_categories"][category_name] then
curr_val = info["ndpi_categories"][category_name]["bytes.sent"] + info["ndpi_categories"][category_name]["bytes.rcvd"]
end
return curr_val
end
-- ##############################################
-- ENTITY DISABLED ALERTS API
-- ##############################################
local function getEntityDisabledAlertsBitmapHash(ifid, entity_type)
-- NOTE: should be able to accept strings for alerts_api.purgeAlertsPrefs
if(type(ifid) == "number") then ifid = string.format("%d", ifid) end
if(type(entity_type) == "number") then entity_type = string.format("%u", entity_type) end
return string.format("ntopng.prefs.alerts.ifid_%s.disabled_alerts.entity_%s", ifid, entity_type)
end
-- ##############################################
-- @brief Get a table containing the disabled alert bitmaps of the alertable entities
-- for the given entity_type
-- @return {entity_key -> disabled_alerts_bitmap}
function alerts_api.getEntityTypeDisabledAlertsBitmap(ifid, entity_type)
local hash = getEntityDisabledAlertsBitmapHash(ifid, entity_type)
local rv = ntop.getHashAllCache(hash) or {}
for k, v in pairs(rv) do
rv[k] = tonumber(v)
end
return(rv)
end
-- ##############################################
-- A cache variable to know if there are configured disabled alerts
local function getInterfaceHasDisabledAlertsKey(ifid)
-- NOTE: should be able to accept strings for alerts_api.purgeAlertsPrefs
if(type(ifid) == "number") then ifid = string.format("%d", ifid) end
return(string.format("ntopng.cache.alerts.ifid_%s.has_disabled_alerts", ifid))
end
-- ##############################################
-- @brief Set the disabled alerts bitmap for the given alertable entity
function alerts_api.setEntityAlertsDisabledBitmap(ifid, entity_type, entity_val, bitmap)
local hash = getEntityDisabledAlertsBitmapHash(ifid, entity_type)
if(bitmap == 0) then
-- No status disabled
ntop.delHashCache(hash, entity_val)
else
ntop.setHashCache(hash, entity_val, string.format("%u", bitmap))
end
-- Invalidate the disabled alerts cache
ntop.delCache(getInterfaceHasDisabledAlertsKey(ifid))
end
-- ##############################################
-- @brief Get the disabled alert bitmap for the given entity
function alerts_api.getEntityAlertsDisabledBitmap(ifid, entity_type, entity_val)
local hash = getEntityDisabledAlertsBitmapHash(ifid, entity_type)
return(tonumber(ntop.getHashCache(hash, entity_val)) or 0)
end
-- ##############################################
-- A cache is used to reduce Redis accesses
local cache_disabled_by_entity_type = {}
-- @brief Check if the alert_type is disabled for the given entity
function alerts_api.isEntityAlertDisabled(ifid, entity_type, entity_val, alert_id)
local entities_disabled = cache_disabled_by_entity_type[entity_type]
if(entities_disabled == nil) then
-- Local from redis
entities_disabled = alerts_api.getEntityTypeDisabledAlertsBitmap(ifid, entity_type)
cache_disabled_by_entity_type[entity_type] = entities_disabled
end
local bitmap = entities_disabled[entity_val]
if((bitmap ~= nil) and ntop.bitmapIsSet(bitmap, alert_id)) then
return(true)
end
return(false)
end
-- ##############################################
-- @brief Check if there are any entities with disabled alerts configured
function alerts_api.hasEntitiesWithAlertsDisabled(ifid)
local has_disabled_cache_key = getInterfaceHasDisabledAlertsKey(ifid)
local cached_val = ntop.getCache(has_disabled_cache_key) or ""
if(cached_val ~= "") then
return(cached_val == "1")
end
-- Slow search
local available_entities = alert_consts.alert_entities
local found = false
for _, entity in pairs(available_entities) do
local keys = ntop.getKeysCache(getEntityDisabledAlertsBitmapHash(ifid, entity.entity_id))
if(not table.empty(keys)) then
found = true
break
end
end
ntop.setCache(has_disabled_cache_key, ternary(found, "1", "0"), 3600 --[[ 1h ]])
return(found)
end
-- ##############################################
-- @brief Get all the disabled alerts by entity
-- @return {entity_type -> {entity_val1 -> bitmap, entity_val2 -> bitmap, ...}, ...}
function alerts_api.getAllEntitiesDisabledAlerts(ifid)
local available_entities = alert_consts.alert_entities
local res = {}
for entity_key, entity in pairs(available_entities) do
local hash = getEntityDisabledAlertsBitmapHash(ifid, entity.entity_id)
local entities_bitmaps = ntop.getHashAllCache(hash) or {}
local is_empty = true
for k, v in pairs(entities_bitmaps) do
entities_bitmaps[k] = tonumber(v)
is_empty = false
end
if(not is_empty) then
res[entity_key] = entities_bitmaps
end
end
return(res)
end
-- ##############################################
-- HOST DISABLED FLOW STATUS API
-- ##############################################
local function getHostDisabledStatusBitmapHash(ifid)
-- NOTE: should be able to accept strings for alerts_api.purgeAlertsPrefs
if(type(ifid) == "number") then ifid = string.format("%d", ifid) end
return(string.format("ntopng.prefs.alerts.ifid_%s.disabled_status", ifid))
end
-- ##############################################
-- @brief Get the bitmap of disabled flow status for an host
function alerts_api.getHostDisabledStatusBitmap(ifid, hostkey)
local hash = getHostDisabledStatusBitmapHash(ifid)
return(tonumber(ntop.getHashCache(hash, hostkey)) or 0)
end
-- ##############################################
-- @brief Set the bitmap of disabled flow status for an host
function alerts_api.setHostDisabledStatusBitmap(ifid, hostkey, bitmap)
local hash = getHostDisabledStatusBitmapHash(ifid)
if(bitmap == 0) then
-- No status disabled
ntop.delHashCache(hash, hostkey)
else
ntop.setHashCache(hash, hostkey, string.format("%u", bitmap))
end
end
-- ##############################################
-- @brief Get all the hosts disabled flow status bitmaps
function alerts_api.getAllHostsDisabledStatusBitmaps(ifid)
local hash = getHostDisabledStatusBitmapHash(ifid)
local rv = ntop.getHashAllCache(hash) or {}
for k, v in pairs(rv) do
rv[k] = tonumber(v)
end
return(rv)
end
-- ##############################################
-- SUPPRESSED ALERTS API
-- ##############################################
local function getSuppressedSetKey(ifid, entity_type)
-- NOTE: should be able to accept strings for alerts_api.purgeAlertsPrefs
if(type(ifid) == "number") then ifid = string.format("%d", ifid) end
if(type(entity_type) == "number") then entity_type = string.format("%u", entity_type) end
return(string.format("ntopng.prefs.alerts.ifid_%s.suppressed_alerts.entity_%s", ifid, entity_type))
end
-- @brief Get the suppressed alertable entities given the entity_type
function alerts_api.getSuppressedEntityAlerts(ifid, entity_type)
local setk = getSuppressedSetKey(ifid, entity_type)
local suppressed_entities = ntop.getMembersCache(setk) or {}
local ret = {}
for _, v in pairs(suppressed_entities) do
ret[v] = true
end
return(ret)
end
-- ##############################################
-- @brief Enable/disable suppressed alerts on the given alertable entity
function alerts_api.setSuppressedAlerts(ifid, entity_type, entity_value, suppressed)
local setk = getSuppressedSetKey(ifid, entity_type)
if(suppressed) then
ntop.setMembersCache(setk, entity_value)
else
ntop.delMembersCache(setk, entity_value)
end
end
-- ##############################################
-- A cache is used to reduce Redis accesses
local cache_suppressed_by_entity_type = {}
-- @brief Check if the given entity has suppressed alerts
function alerts_api.hasSuppressedAlerts(ifid, entity_type, entity_value)
local entities_suppressed = cache_suppressed_by_entity_type[entity_type]
if(entities_suppressed == nil) then
-- Local from redis
entities_suppressed = alerts_api.getSuppressedEntityAlerts(ifid, entity_type)
cache_suppressed_by_entity_type[entity_type] = entities_suppressed
end
return(entities_suppressed[entity_value] ~= nil)
end
-- ##############################################
-- @brief Purge all the alerts prefs set by this module
function alerts_api.purgeAlertsPrefs()
-- Purge all the alerts prefs on all the interfaces
deleteCachePattern(getEntityDisabledAlertsBitmapHash("*", "*"))
deleteCachePattern(getSuppressedSetKey("*", "*"))
deleteCachePattern(getInterfaceHasDisabledAlertsKey("*"))
deleteCachePattern(getHostDisabledStatusBitmapHash("*"))
end
-- ##############################################
return(alerts_api)