ntopng/scripts/lua/modules/alerts_api.lua
2019-10-04 10:48:13 +02:00

1431 lines
47 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 = {}
-- NOTE: sqlite can handle about 10-50 alerts/sec
local MAX_NUM_ENQUEUED_ALERT_PER_INTERFACE = 256
local ALERT_CHECKS_MODULES_BASEDIR = dirs.installdir .. "/scripts/callbacks/interface"
-- Just helpers
local str_2_periodicity = {
["min"] = 60,
["5mins"] = 300,
["hour"] = 3600,
["day"] = 86400,
}
local known_alerts = {}
-- ##############################################
local function getAlertEventQueue(ifid)
return string.format("ntopng.cache.ifid_%d.alerts_events_queue", ifid)
end
-- ##############################################
-- 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 getEntityDisabledAlertsCountersKey(ifid, entity, entity_val)
return(string.format("ntopng.cache.alerts.ifid_%d.%d_%s", ifid, entity, entity_val))
end
local function incDisabledAlertsCount(ifid, granularity_id, entity, entity_val, alert_type)
local key = getEntityDisabledAlertsCountersKey(ifid, entity, entity_val)
-- NOTE: using separate keys based on granularity to avoid concurrency issues
counter_key = string.format("%d_%d", granularity_id, alert_type)
local val = tonumber(ntop.getHashCache(key, counter_key)) or 0
val = val + 1
ntop.setHashCache(key, counter_key, string.format("%d", val))
return(val)
end
-- ##############################################
local function deleteEntityDisabledAlertsCountersKey(ifid, entity, entity_val, target_type)
local key = getEntityDisabledAlertsCountersKey(ifid, entity, entity_val)
local entity_counters = ntop.getHashAllCache(key) or {}
for what, counter in pairs(entity_counters) do
local parts = string.split(what, "_")
if((parts) and (#parts == 2)) then
local alert_type = tonumber(parts[2])
if(alert_type == target_type) then
ntop.delHashCache(key, what)
end
end
end
end
-- ##############################################
function alerts_api.getEntityDisabledAlertsCounters(ifid, entity, entity_val)
local key = getEntityDisabledAlertsCountersKey(ifid, entity, entity_val)
local entity_counters = ntop.getHashAllCache(key) or {}
local by_alert_type = {}
for what, counter in pairs(entity_counters) do
local parts = string.split(what, "_")
if((parts) and (#parts == 2)) then
local granularity_id = tonumber(parts[1])
local alert_type = tonumber(parts[2])
by_alert_type[alert_type] = by_alert_type[alert_type] or 0
by_alert_type[alert_type] = by_alert_type[alert_type] + counter
end
end
return(by_alert_type)
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
-- ##############################################
-- Enqueue a store alert action on the DB to be performed asynchronously
local function enqueueStoreAlert(ifid, alert)
local trim = nil
local alert_json = json.encode(alert)
local queue = getAlertEventQueue(ifid)
if(ntop.llenCache(queue) > MAX_NUM_ENQUEUED_ALERT_PER_INTERFACE) then
trim = math.ceil(MAX_NUM_ENQUEUED_ALERT_PER_INTERFACE/2)
traceError(TRACE_INFO, TRACE_CONSOLE, string.format("Alerts event queue too long: dropping %u alerts", trim))
interface.incNumDroppedAlerts(trim)
end
ntop.rpushCache(queue, alert_json, trim)
return(true)
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
local ifnames = interface.getIfNames()
ifnames[getSystemInterfaceId()] = getSystemInterfaceName()
for ifid, _ in pairs(ifnames) do
interface.select(ifid)
local queue = getAlertEventQueue(ifid)
while(true) do
local alert_json = ntop.lpopCache(queue)
if(not alert_json) then
break
end
local alert = json.decode(alert_json)
if(alert) then
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
if(os.time() > deadline) then
return(false)
end
end
end
return(true)
end
-- ##############################################
local function pushAlertNotification(ifid, action, alert)
alert.ifid = ifid
alert.action = action
ntop.rpushCache("ntopng.alerts.notifications_queue", json.encode(alert))
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
incDisabledAlertsCount(ifid, granularity_id, entity_info.alert_entity.entity_id, entity_info.alert_entity_val, type_info.alert_type.alert_id)
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 = {
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 == 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
enqueueStoreAlert(ifid, alert_to_store)
pushAlertNotification(ifid, "store", alert_to_store)
return(true)
end
-- ##############################################
function alerts_api.storeFlowAlert(alert_type, alert_severity, flow_info)
if flow then
-- flow.storeAlert(alert_type.alert_id, alert_severity.severity_id, json.encode(flow_info))
end
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
--! @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)
when = when or os.time()
local ifid = interface.getId()
if(not areAlertsEnabled()) then
return(false)
end
if(type_info.alert_granularity == nil) then
alertErrorTraceback("Missing mandatory granularity")
return(false)
end
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 ""
local alert_json = json.encode(type_info.alert_type_params)
local is_disabled = alerts_api.isEntityAlertDisabled(ifid, entity_info.alert_entity.entity_id, entity_info.alert_entity_val, type_info.alert_type.alert_id)
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, is_disabled
}
if(entity_info.alert_entity.entity_id == alertEntity("host")) then
host.checkContext(entity_info.alert_entity_val)
triggered = host.storeTriggeredAlert(table.unpack(params))
elseif(entity_info.alert_entity.entity_id == alertEntity("interface")) then
interface.checkContext(entity_info.alert_entity_val)
triggered = interface.storeTriggeredAlert(table.unpack(params))
elseif(entity_info.alert_entity.entity_id == 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)
elseif(is_disabled) then
if(do_trace) then print("[COUNT Disabled alert @ "..granularity_sec.."] "..
entity_info.alert_entity_val .."@"..type_info.alert_type.i18n_title..":".. subtype .. "\n") end
incDisabledAlertsCount(ifid, granularity_id, entity_info.alert_entity.entity_id, entity_info.alert_entity_val, type_info.alert_type.alert_id)
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
pushAlertNotification(ifid, "engage", triggered)
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
--! @note The actual release is performed asynchronously
--! @return true on success, false otherwise
function alerts_api.release(entity_info, type_info, when)
local when = when or os.time()
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 ""
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 == alertEntity("host")) then
host.checkContext(entity_info.alert_entity_val)
released = host.releaseTriggeredAlert(table.unpack(params))
elseif(entity_info.alert_entity.entity_id == alertEntity("interface")) then
interface.checkContext(entity_info.alert_entity_val)
released = interface.releaseTriggeredAlert(table.unpack(params))
elseif(entity_info.alert_entity.entity_id == 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
enqueueStoreAlert(ifid, released)
pushAlertNotification(ifid, "release", released)
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)],
})
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.thresholdCrossType(granularity, metric, value, operator, threshold)
return({
alert_type = alert_consts.alert_types.threshold_cross,
alert_subtype = string.format("%s_%s", granularity, metric),
alert_granularity = alert_consts.alerts_granularities[granularity],
alert_severity = alert_consts.alert_severities.error,
alert_type_params = {
metric = metric, value = value,
operator = operator, threshold = threshold,
}
})
end
-- ##############################################
function alerts_api.synFloodType(granularity, metric, value, operator, threshold)
return({
alert_type = alert_consts.alert_types.tcp_syn_flood,
alert_subtype = metric,
alert_granularity = alert_consts.alerts_granularities[granularity],
alert_severity = alert_consts.alert_severities.error,
alert_type_params = {
value = value,
threshold = threshold,
}
})
end
-- ##############################################
function alerts_api.flowFloodType(granularity, metric, value, operator, threshold)
return({
alert_type = alert_consts.alert_types.flows_flood,
alert_subtype = metric,
alert_granularity = alert_consts.alerts_granularities[granularity],
alert_severity = alert_consts.alert_severities.error,
alert_type_params = {
value = value,
threshold = threshold,
}
})
end
-- ##############################################
function alerts_api.pingIssuesType(value, threshold, ip)
return({
alert_type = alert_consts.alert_types.ping_issues,
alert_severity = alert_consts.alert_severities.warning,
alert_granularity = alert_consts.alerts_granularities.min,
alert_type_params = {
value = value, threshold = threshold, ip = ip,
}
})
end
-- ##############################################
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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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(device, interface, interface_name, status)
return({
alert_type = alert_consts.alert_types.port_status_change,
alert_severity = alert_consts.alert_severities.info,
alert_type_params = {
device = device, interface = interface,
interface_name = interface_name, status = status,
},
})
end
-- ##############################################
function alerts_api.snmpInterfaceDuplexStatusChangeType(device, interface, interface_name, status)
return({
alert_type = alert_consts.alert_types.port_duplexstatus_change,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
device = device, interface = interface,
interface_name = interface_name, status = status,
},
})
end
-- ##############################################
function alerts_api.snmpInterfaceErrorsType(device, interface, interface_name)
return({
alert_type = alert_consts.alert_types.port_errors,
alert_severity = alert_consts.alert_severities.info,
alert_type_params = {
device = device, interface = interface,
interface_name = interface_name,
},
})
end
-- ##############################################
function alerts_api.snmpPortLoadThresholdExceededType(device, interface, interface_name, interface_load, in_direction)
return({
alert_type = alert_consts.alert_types.port_load_threshold_exceeded,
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {
device = device, interface = interface,
interface_name = interface_name,
interface_load = interface_load, in_direction = in_direction,
},
})
end
-- ##############################################
function alerts_api.misconfiguredAppType(subtype)
return({
alert_type = alert_consts.alert_types.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.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.slowStatsUpdateType()
return({
alert_type = alert_consts.alert_types.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.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.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.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.ghost_network,
alert_subtype = network,
alert_granularity = alert_consts.alerts_granularities[granularity],
alert_severity = alert_consts.alert_severities.warning,
alert_type_params = {},
})
end
-- ##############################################
function alerts_api.load_check_modules(subdir, str_granularity)
local checks_dir = os_utils.fixPath(ALERT_CHECKS_MODULES_BASEDIR .. "/" .. subdir)
local is_nedge = ntop.isnEdge()
local available_modules = {}
package.path = checks_dir .. "/?.lua;" .. package.path
for fname in pairs(ntop.readdir(checks_dir)) do
if ends(fname, ".lua") then
local modname = string.sub(fname, 1, string.len(fname) - 4)
local check_module = require(modname)
if check_module.check_function and check_module.key then
if check_module.nedge_exclude and is_nedge then
goto continue
end
if check_module.granularity and str_granularity then
-- When the module specify one or more granularities
-- at which checks have to be run, the module is only
-- loaded after checking the granularity
for _, gran in pairs(check_module.granularity) do
if gran == str_granularity then
available_modules[modname] = check_module
break
end
end
else
-- When no granularity is explicitly specified
-- in the module, then the check it is assumed to
-- be run for every granularity and the module is
-- always loaded
available_modules[modname] = check_module
end
end
end
::continue::
end
return available_modules
end
-- ##############################################
local function get_flow_check_module_enabled_key(check_module_key)
return string.format("ntopng.prefs.flow_check_modules.ifid_%d.%s", interface.getId(), check_module_key)
end
-- ##############################################
local function refresh_flow_check_module_conf(flow_check_module)
if table.len(_POST) > 0 then
local conf_hash = get_flow_check_module_enabled_key(flow_check_module.key)
for k, v in pairs(_POST) do
if k:ends(flow_check_module.key) then
if k == "enabled_"..flow_check_module.key then
if v == "off" then
ntop.setHashCache(conf_hash, "enabled", "false")
elseif v == "on" then
ntop.delHashCache(conf_hash, "enabled")
end
end
end
end
end
end
-- ##############################################
local function load_flow_check_module_conf(flow_check_module)
if not flow_check_module["conf"] then
flow_check_module["conf"] = {}
end
-- if there's a _POST we try and refresh the module conf with possibly new
-- submitted values
refresh_flow_check_module_conf(flow_check_module)
local k = get_flow_check_module_enabled_key(flow_check_module.key)
local enabled = ntop.getHashCache(k, "enabled")
if enabled ~= "false" then
flow_check_module["conf"]["enabled"] = true
else
flow_check_module["conf"]["enabled"] = false
end
end
-- ##############################################
local function flow_check_modules_benchmarks_key(mod_k)
local ifid = interface.getId()
return string.format("ntopng.cache.ifid_%d.flow_check_modules_benchmarks.mod_%s", ifid, mod_k)
end
-- ##############################################
function alerts_api.store_flow_check_modules_benchmarks(benchmarks)
for mod_k, modules in pairs(benchmarks or {}) do
local k = flow_check_modules_benchmarks_key(mod_k)
for mod_fn, mod_benchmark in pairs(modules) do
ntop.setHashCache(k, mod_fn, json.encode(mod_benchmark))
end
end
end
-- ##############################################
function alerts_api.get_flow_check_module_benchmarks(mod_k)
local k = flow_check_modules_benchmarks_key(mod_k)
-- ntop.delCache(k)
local res = ntop.getHashAllCache(k)
for mod_fn, benchmark in pairs(res or {}) do
res[mod_fn] = json.decode(benchmark)
end
return res
end
-- ##############################################
--
-- Flow check modules are lua scripts located into the following locations:
-- - scripts/callbacks/interface/flow for community scripts
-- - pro/scripts/callbacks/interface/flow for pro/enterprise scripts
--
-- A script must return a lua table, with the following fields:
-- - setup(): a function to call once before processing any flow. It must return true
-- if the module is enabled, false otherwise.
-- - protocolDetected(info) (optional): a function which will be called once the flow protocol
-- has been detected or detection has been aborted. This should happen once per flow.
-- - statusChanged(info) (optional): a function which will be called *after* the protocolDetected()
-- if the flow status changes.
--
function alerts_api.load_flow_check_modules(enabled_only)
local available_modules = {protocolDetected = {}, statusChanged = {}, idle = {}, periodicUpdate = {}, all = {}}
local check_dirs = {
os_utils.fixPath(ALERT_CHECKS_MODULES_BASEDIR .. "/flow"),
}
if ntop.isPro() then
check_dirs[#check_dirs + 1] = os_utils.fixPath(dirs.installdir .. "/pro/scripts/callbacks/interface/flow")
end
for _, checks_dir in pairs(check_dirs) do
package.path = checks_dir .. "/?.lua;" .. package.path
for fname in pairs(ntop.readdir(checks_dir)) do
if ends(fname, ".lua") then
local modname = string.sub(fname, 1, string.len(fname) - 4)
local check_module = require(modname)
load_flow_check_module_conf(check_module)
check_module["benchmark"] = alerts_api.get_flow_check_module_benchmarks(modname)
if check_module.setup then
local is_enabled = check_module["conf"]["enabled"]
if is_enabled or not enabled_only then
local setup_ok = check_module.setup()
if setup_ok then
if check_module.protocolDetected then available_modules["protocolDetected"][modname] = check_module end
if check_module.statusChanged then available_modules["statusChanged"][modname] = check_module end
if check_module.idle then available_modules["idle"][modname] = check_module end
if check_module.periodicUpdate then available_modules["periodicUpdate"][modname] = check_module end
available_modules["all"][modname] = check_module
end
end
end
end
end
end
return available_modules
end
-- ##############################################
-- @brief Get the default alert configuration value for the given check module
-- and granularity.
-- @param check_module a check_module returned by alerts_api.load_check_modules
-- @param granularity_str the target granularity
-- @return nil if there is not default value, the given value otherwise
function alerts_api.getCheckModuleDefaultValue(check_module, granularity_str)
if((check_module.default_values ~= nil) and (check_module.default_values[granularity_str] ~= nil)) then
-- granularity specific default
return(check_module.default_values[granularity_str])
end
-- global default
return(check_module.default_value)
end
-- ##############################################
-- An alert check function which performs threshold checks of a value
-- against a configured threshold and generates a threshold_cross alert
-- if the value is above the threshold.
-- A check_module must implement:
-- get_threshold_value(granularity, entity_info)
-- A function, which returns the current value to be compared agains the threshold
-- The check_module may implement an additional threshold_type_builder function which
-- which returns a type_info. Check alerts_api.thresholdCrossType for the threshold_type_builder signature.
function alerts_api.threshold_check_function(params)
local alarmed = false
local value = params.check_module.get_threshold_value(params.granularity, params.entity_info)
local threshold_config = params.alert_config
local threshold_edge = tonumber(threshold_config.edge)
local threshold_builder = ternary(params.check_module.threshold_type_builder, params.check_module.threshold_type_builder, alerts_api.thresholdCrossType)
local threshold_type = threshold_builder(params.granularity, params.check_module.key, value, threshold_config.operator, threshold_edge)
if(threshold_config.operator == "lt") then
if(value < threshold_edge) then alarmed = true end
else
if(value > threshold_edge) then alarmed = true end
end
if(alarmed) then
return(alerts_api.trigger(params.alert_entity, threshold_type))
else
return(alerts_api.release(params.alert_entity, threshold_type))
end
end
-- ##############################################
-- An alert check function which checks for anomalies.
-- The check_module key is the type of the anomaly to check.
-- The check_module 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.check_module.key
local type_info = params.check_module.anomaly_type_builder(anomal_key)
if params.entity_info.anomalies[anomal_key] then
return alerts_api.trigger(params.alert_entity, type_info)
else
return alerts_api.release(params.alert_entity, type_info)
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
-- ##############################################
function alerts_api.threshold_cross_input_builder(gui_conf, input_id, value)
value = value or {}
local gt_selected = ternary((value.operator or gui_conf.field_operator) == "gt", ' selected="selected"', '')
local lt_selected = ternary((value.operator or gui_conf.field_operator) == "lt", ' selected="selected"', '')
local input_op = "op_" .. input_id
local input_val = "value_" .. input_id
return(string.format([[<select name="%s">
<option value="gt"%s ]] .. (ternary(gui_conf.field_operator == "lt", "hidden", "")) .. [[>&gt;</option>
<option value="lt"%s ]] .. (ternary(gui_conf.field_operator == "gt", "hidden", "")) .. [[>&lt;</option>
</select> <input type="number" class="text-right form-control" min="%s" max="%s" step="%s" style="display:inline; width:12em;" name="%s" value="%s"/> <span>%s</span>]],
input_op, gt_selected, lt_selected,
gui_conf.field_min or "0", gui_conf.field_max or "", gui_conf.field_step or "1",
input_val, value.edge, i18n(gui_conf.i18n_field_unit))
)
end
-- ##############################################
local function build_on_off_toggle(submit_field, active)
local on_value = "on"
local off_value = "off"
local value
local on_color = "success"
local off_color = "danger"
local on_active
local off_active
if active then
value = on_value
on_active = "btn-"..on_color.." active"
off_active = "btn-default"
else
value = off_value
on_active = "btn-default"
off_active = "btn-"..off_color.." active"
end
return [[
<div class="btn-group btn-toggle">
<button type="button" onclick="]]..submit_field..[[_on_fn()" id="]]..submit_field..[[_on_id" class="btn btn-sm ]]..on_active..[[">On</button>
<button type="button" onclick="]]..submit_field..[[_off_fn()" id="]]..submit_field..[[_off_id" class="btn btn-sm ]]..off_active..[[">Off</button>
</div>
<input type=hidden id="]]..submit_field..[[_input" name="]]..submit_field..[[" value="]]..value..[["/>
<script>
function ]]..submit_field..[[_on_fn() {
var class_on = document.getElementById("]]..submit_field..[[_on_id");
var class_off = document.getElementById("]]..submit_field..[[_off_id");
class_on.removeAttribute("class");
class_off.removeAttribute("class");
class_on.setAttribute("class", "btn btn-sm btn-]]..on_color..[[ active");
class_off.setAttribute("class", "btn btn-sm btn-default");
$("#]]..submit_field..[[_input").val("]]..on_value..[[").trigger('change');
}
function ]]..submit_field..[[_off_fn() {
var class_on = document.getElementById("]]..submit_field..[[_on_id");
var class_off = document.getElementById("]]..submit_field..[[_off_id");
class_on.removeAttribute("class");
class_off.removeAttribute("class");
class_on.setAttribute("class", "btn btn-sm btn-default");
class_off.setAttribute("class", "btn btn-sm btn-]]..off_color..[[ active");
$("#]]..submit_field..[[_input").val("]]..off_value..[[").trigger('change');
}
</script>
]]
end
-- ##############################################
function alerts_api.checkbox_input_builder(gui_conf, input_id, value)
local built = build_on_off_toggle(input_id, value == 1)
return built
end
-- ##############################################
function alerts_api.flow_checkbox_input_builder(check_module)
local input_id = string.format("enabled_%s", check_module.key)
local built = build_on_off_toggle(input_id, check_module.conf.enabled)
return built
end
-- ##############################################
local function getEntityDisabledAlertsBitmapKey(ifid, entity, entity_val)
return string.format("ntopng.prefs.alerts.ifid_%d.disabled_alerts.__%s__%s", ifid, entity, entity_val)
end
-- ##############################################
function alerts_api.getEntityAlertsDisabled(ifid, entity, entity_val)
local bitmap = tonumber(ntop.getPref(getEntityDisabledAlertsBitmapKey(ifid, entity, entity_val))) or 0
-- traceError(TRACE_NORMAL, TRACE_CONSOLE, string.format("ifid: %d, entity: %s, val: %s -> bitmap=%x", ifid, alertEntityRaw(entity), entity_val, bitmap))
return(bitmap)
end
-- ##############################################
function alerts_api.setEntityAlertsDisabled(ifid, entity, entity_val, bitmap)
local key = getEntityDisabledAlertsBitmapKey(ifid, entity, entity_val)
if(bitmap == 0) then
ntop.delCache(key)
else
ntop.setPref(key, string.format("%u", bitmap))
end
end
-- ##############################################
local function toggleEntityAlert(ifid, entity, entity_val, alert_type, disable)
alert_type = tonumber(alert_type)
bitmap = alerts_api.getEntityAlertsDisabled(ifid, entity, entity_val)
if(disable) then
bitmap = ntop.bitmapSet(bitmap, alert_type)
else
bitmap = ntop.bitmapClear(bitmap, alert_type)
deleteEntityDisabledAlertsCountersKey(ifid, entity, entity_val, alert_type)
end
alerts_api.setEntityAlertsDisabled(ifid, entity, entity_val, bitmap)
return(bitmap)
end
-- ##############################################
function alerts_api.disableEntityAlert(ifid, entity, entity_val, alert_type)
return(toggleEntityAlert(ifid, entity, entity_val, alert_type, true))
end
-- ##############################################
function alerts_api.enableEntityAlert(ifid, entity, entity_val, alert_type)
return(toggleEntityAlert(ifid, entity, entity_val, alert_type, false))
end
-- ##############################################
function alerts_api.isEntityAlertDisabled(ifid, entity, entity_val, alert_type)
local bitmap = alerts_api.getEntityAlertsDisabled(ifid, entity, entity_val)
return(ntop.bitmapIsSet(bitmap, tonumber(alert_type)))
end
-- ##############################################
function alerts_api.hasEntitiesWithAlertsDisabled(ifid)
return(table.len(ntop.getKeysCache(getEntityDisabledAlertsBitmapKey(ifid, "*", "*"))) > 0)
end
-- ##############################################
function alerts_api.listEntitiesWithAlertsDisabled(ifid)
local keys = ntop.getKeysCache(getEntityDisabledAlertsBitmapKey(ifid, "*", "*")) or {}
local res = {}
for key in pairs(keys) do
local parts = string.split(key, "__")
if((parts) and (#parts == 3)) then
local entity = tonumber(parts[2])
local entity_val = parts[3]
res[entity] = res[entity] or {}
res[entity][entity_val] = true
end
end
return(res)
end
-- ##############################################
return(alerts_api)