ntopng/scripts/lua/modules/lua_utils.lua
2022-08-30 19:32:05 +02:00

3752 lines
123 KiB
Lua

--
-- (C) 2014-22 - ntop.org
--
-- Hack to avoid include loops
if(pragma_once_lua_utils == true) then
-- avoid multiple inclusions
return
end
pragma_once_lua_utils = true
-- ###############################################
local clock_start = os.clock()
dirs = ntop.getDirs()
package.path = dirs.installdir .. "/scripts/lua/modules/i18n/?.lua;" .. package.path
package.path = dirs.installdir .. "/scripts/lua/modules/timeseries/?.lua;" .. package.path
package.path = dirs.installdir .. "/scripts/lua/modules/flow_dbms/?.lua;" .. package.path
package.path = dirs.installdir .. "/scripts/lua/modules/pools/?.lua;" .. package.path
require "lua_trace"
require "lua_utils_generic"
require "ntop_utils"
locales_utils = require "locales_utils"
local os_utils = require "os_utils"
local format_utils = require "format_utils"
local http_utils = require "http_utils"
local dns_utils = require "dns_utils"
local network_utils = require "network_utils"
-- TODO: replace those globals with locals everywhere
secondsToTime = format_utils.secondsToTime
msToTime = format_utils.msToTime
bytesToSize = format_utils.bytesToSize
formatPackets = format_utils.formatPackets
formatFlows = format_utils.formatFlows
formatValue = format_utils.formatValue
pktsToSize = format_utils.pktsToSize
bitsToSize = format_utils.bitsToSize
round = format_utils.round
bitsToSizeMultiplier = format_utils.bitsToSizeMultiplier
-- ##############################################
function isAllowedSystemInterface()
return ntop.isAllowedInterface(tonumber(getSystemInterfaceId()))
end
-- ##############################################
local cached_allowed_networks_set = nil
function hasAllowedNetworksSet()
if(cached_allowed_networks_set == nil) then
local nets = ntop.getAllowedNetworks()
local allowed_nets = string.split(nets, ",") or {nets}
cached_allowed_networks_set = false
for _, net in pairs(allowed_nets) do
if((not isEmptyString(net)) and (net ~= "0.0.0.0/0") and (net ~= "::/0")) then
cached_allowed_networks_set = true
break
end
end
end
return(cached_allowed_networks_set)
end
-- ##############################################
function hasSoftwareUpdatesSupport()
return (not ntop.isOffline()
and isAdministrator()
and ntop.isPackage()
and not ntop.isWindows())
end
-- See Utils::l4proto2name()
l4_keys = {
{ "IP", "ip", 0 },
{ "ICMP", "icmp", 1 },
{ "IGMP", "igmp", 2 },
{ "TCP", "tcp", 6 },
{ "UDP", "udp", 17 },
{ "IPv6", "ipv6", 41 },
{ "RSVP", "rsvp", 46 },
{ "GRE", "gre", 47 },
{ "ESP", "esp", 50 },
{ "IPv6-ICMP", "ipv6icmp", 58 },
{ "EIGRP", "eigrp", 88 },
{ "OSPF", "ospf", 89 },
{ "PIM", "pim", 103 },
{ "VRRP", "vrrp", 112 },
{ "HIP", "hip", 139 },
{ "ICMPv6", "icmpv6", 58 },
{ "IGMP", "igmp", 2 },
{ "Other IP", "other_ip", -1 }
}
function __FILE__() return debug.getinfo(2,'S').source end
function __LINE__() return debug.getinfo(2, 'l').currentline end
-- ##############################################
local http_status_code_map = {
[200] = "OK",
[400] = "Bad Request",
[401] = "Unauthorized",
[403] = "Forbidden",
[404] = "Not Found",
[405] = "Method Not Allowed",
[406] = "Not Acceptable",
[408] = "Request timeout",
[409] = "Conflict",
[410] = "Gone",
[412] = "Precondition Failed",
[415] = "Unsupported Media Type",
[423] = "Locked",
[428] = "Precondition Required",
[429] = "Too many requests",
[500] = "Internal Server Error",
[501] = "Not Implemented",
[503] = "Service Unavailable",
}
-- ##############################################
function sendHTTPHeaderIfName(mime, ifname, maxage, content_disposition, extra_headers, status_code)
local info = ntop.getInfo(false)
local tzname = info.tzname or ''
local cookie_attr = ntop.getCookieAttributes()
local lines = {
'Cache-Control: max-age=0, no-cache, no-store',
'Server: ntopng '..info["version"]..' ['.. info["platform"]..']',
'Set-Cookie: tzname=' .. tzname .. '; path=/' .. cookie_attr,
'Pragma: no-cache',
'X-Frame-Options: DENY',
'X-Content-Type-Options: nosniff',
'Content-Type: '.. mime,
'Last-Modified: '..os.date("!%a, %m %B %Y %X %Z"),
}
local uri = _SERVER.URI
if(starts(uri, "/lua/rest/")) then
--
-- Only for REST calls handle CORS (Cross-Origin Resource Sharing)
--
-- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
-- https://web.dev/cross-origin-resource-sharing/
--
lines[#lines + 1] = 'Access-Control-Allow-Origin: *'
lines[#lines + 1] = 'Access-Control-Allow-Methods: GET, POST, HEAD'
end
if(_SESSION ~= nil) then
local key = "session_"..info.http_port.."_"..info.https_port
lines[#lines + 1] = 'Set-Cookie: '..key..'='.._SESSION["session"]..'; max-age=' .. maxage .. '; path=/; ' .. cookie_attr
end
if(ifname ~= nil) then
lines[#lines + 1] = 'Set-Cookie: ifname=' .. ifname .. '; path=/' .. cookie_attr
end
if(info.timezone ~= nil) then
lines[#lines + 1] = 'Set-Cookie: timezone=' .. info.timezone .. '; path=/' .. cookie_attr
end
if(content_disposition ~= nil) then
lines[#lines + 1] = 'Content-Disposition: '..content_disposition
end
if type(extra_headers) == "table" then
for hname, hval in pairs(extra_headers) do
lines[#lines + 1] = hname..': '..hval
end
end
if not status_code then
status_code = 200
end
local status_descr = http_status_code_map[status_code]
if not status_descr then
status_descr = "Unknown"
end
-- Buffer the HTTP reply and write it in one "print" to avoid fragmenting
-- it into multiple packets, to ease HTTP debugging with wireshark.
print("HTTP/1.1 " .. status_code .. " " .. status_descr .. "\r\n" .. table.concat(lines, "\r\n") .. "\r\n\r\n")
end
-- ##############################################
function sendHTTPHeaderLogout(mime, content_disposition)
sendHTTPHeaderIfName(mime, nil, 0, content_disposition)
end
-- ##############################################
function sendHTTPHeader(mime, content_disposition, extra_headers, status_code)
sendHTTPHeaderIfName(mime, nil, 3600, content_disposition, extra_headers, status_code)
end
-- ##############################################
function sendHTTPContentTypeHeader(content_type, content_disposition, charset, extra_headers, status_code)
local charset = charset or "utf-8"
local mime = content_type.."; charset="..charset
sendHTTPHeader(mime, content_disposition, extra_headers, status_code)
end
-- ##############################################
function findString(str, tofind)
if(str == nil) then return(nil) end
if(tofind == nil) then return(nil) end
str1 = string.lower(string.gsub(str, "-", "_"))
tofind1 = string.lower(string.gsub(tofind, "-", "_"))
return(string.find(str1, tofind1, 1))
end
-- ##############################################
function findStringArray(str, tofind)
if(str == nil) then return(nil) end
if(tofind == nil) then return(nil) end
local rsp = false
for k,v in pairs(tofind) do
str1 = string.gsub(str, "-", "_")
tofind1 = string.gsub(v, "-", "_")
if(str1 == tofind1) then
rsp = true
end
end
return(rsp)
end
-- ##############################################
function urlencode(str)
str = string.gsub (str, "\r?\n", "\r\n")
str = string.gsub (str, "([^%w%-%.%_%~ ])",
function (c) return string.format ("%%%02X", string.byte(c)) end)
str = string.gsub (str, " ", "+")
return str
end
-- ##############################################
--
-- Returns indexes to be used for string shortening. The portion of to_shorten between
-- middle_start and middle_end will be inside the bounds.
--
-- to_shorten: string to be shorten
-- middle_start: middle part begin index
-- middle_end: middle part begin index
-- maxlen: maximum length
--
function shortenInTheMiddle(to_shorten, middle_start, middle_end, maxlen)
local maxlen = maxlen - (middle_end - middle_start)
if maxlen <= 0 then
return 0, string.len(to_shorten)
end
local left_slice = math.max(middle_start - math.floor(maxlen / 2), 1)
maxlen = maxlen - (middle_start - left_slice - 1)
local right_slice = math.min(middle_end + maxlen, string.len(to_shorten))
return left_slice, right_slice
end
-- ##############################################
function shortHostName(name)
local chunks = {name:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")}
if(#chunks == 4) then
return(name)
else
local max_len = ntop.getPref("ntopng.prefs.max_ui_strlen")
max_len = tonumber(max_len)
if(max_len == nil) then max_len = 24 end
chunks = {name:match("%w+:%w+:%w+:%w+:%w+:%w+")}
--io.write(#chunks.."\n")
if(#chunks == 1) then
return(name)
end
if(string.len(name) < max_len) then
return(name)
else
tot = 0
n = 0
ret = ""
for token in string.gmatch(name, "([%w-]+).") do
if(tot < max_len) then
if(n > 0) then ret = ret .. "." end
ret = ret .. token
tot = tot+string.len(token)
n = n + 1
end
end
return(ret .. "...")
end
end
return(name)
end
-- ##############################################
function _handleArray(name, sev)
local id
for id, _ in ipairs(name) do
local l = name[id][1]
local key = name[id][2]
if(string.upper(key) == string.upper(sev)) then
return(l)
end
end
return(firstToUpper(sev))
end
-- ##############################################
function l4Label(proto)
return(_handleArray(l4_keys, proto))
end
-- ##############################################
function l4_proto_to_id(proto_name)
for _, proto in pairs(l4_keys) do
if proto[1] == proto_name or proto[2] == proto_name then
return(proto[3])
end
end
end
-- ##############################################
function l4_proto_to_string(proto_id)
if not proto_id then return "" end
if isEmptyString(proto_id) then return "" end
proto_id_num = tonumber(proto_id)
if proto_id_num == nil then
-- Already string?
return proto_id
end
for _, proto in pairs(l4_keys) do
if proto[3] == proto_id_num then
return proto[1], proto[2]
end
end
return string.format("%d", proto_id_num)
end
-- ##############################################
-- Return the list of L4 proto (key = name, value = id)
function l4_proto_list()
local list = {}
for _, proto in pairs(l4_keys) do
-- add L4 proto only
if proto[2] ~= 'ip' and
proto[2] ~= 'ipv6' then
list[proto[1]] = proto[3]
end
end
return list
end
-- ##############################################
function noHtml(s)
if s == nil then return nil end
local gsub, char = string.gsub, string.char
local entityMap = {lt = "<", gt = ">" , amp = "&", quot ='"', apos = "'"}
local entitySwap = function(orig, n, s)
return (n == '' and entityMap[s])
or (n == "#" and tonumber(s)) and string.char(s)
or (n == "#x" and tonumber(s,16)) and string.char(tonumber(s,16))
or orig
end
local function unescape(str)
return (gsub( str, '(&(#?x?)([%d%a]+);)', entitySwap ))
end
local cleaned = s:gsub("<[aA] .->(.-)</[aA]>","%1")
:gsub("<abbr .->(.-)</abbr>","%1")
:gsub("<span .->(.-)</span>","%1")
:gsub("%s*<[iI].->(.-)</[iI]>","%1")
:gsub("<.->(.-)</.->","%1") -- note: keep as last as this does not handle nested tags
:gsub("^%s*(.-)%s*$", "%1")
:gsub('&nbsp;', " ")
return unescape(cleaned)
end
-- ##############################################
function areAlertsEnabled()
if(__alert_enabled == nil) then
-- Not too nice as changes will be read periodically as new VMs are reloaded
-- but at least we avoid breaking up the performance
__alert_enabled = (ntop.getPref("ntopng.prefs.disable_alerts_generation") ~= "1")
end
return (__alert_enabled)
end
-- ##############################################
function isScoreEnabled()
return(ntop.isEnterpriseM() or ntop.isnEdgeEnterprise())
end
-- ##############################################
function hasTrafficReport()
local ts_utils = require("ts_utils_core")
local is_pcap_dump = interface.isPcapDumpInterface()
return((not is_pcap_dump) and (ts_utils.getDriverName() == "rrd") and ntop.isEnterpriseM())
end
function hasAlertsDisabled()
_POST = _POST or {}
return ((_POST["disable_alerts_generation"] ~= nil) and (_POST["disable_alerts_generation"] == "1")) or
((_POST["disable_alerts_generation"] == nil) and (ntop.getPref("ntopng.prefs.disable_alerts_generation") == "1"))
end
-- ##############################################
function hasClickHouseSupport()
local auth = require "auth"
if not ntop.isPro() or ntop.isWindows() then
return false
end
-- Don't allow nIndex for unauthorized users
if not auth.has_capability(auth.capabilities.historical_flows) then
return false
end
-- TODO optimize
if prefs == nil then
prefs = ntop.getPrefs()
end
if prefs.is_dump_flows_to_clickhouse_enabled then
return true
end
return false
end
-- ##############################################
-- NOTE: global nindex support may be enabled but some disable on some interfaces
function interfaceHasClickHouseSupport()
return(hasClickHouseSupport() and ntop.getPrefs()["is_dump_flows_to_clickhouse_enabled"])
end
--for _key, _value in pairsByKeys(vals, rev) do
-- print(_key .. "=" .. _value .. "\n")
--end
-- ##############################################
function truncate(x)
return x<0 and math.ceil(x) or math.floor(x)
end
-- ##############################################
-- Note that the function below returns a string as returning a number
-- would not help as a new float would be returned
function toint(num)
return string.format("%u", truncate(num))
end
-- ##############################################
function capitalize(str)
return (str:gsub("^%l", string.upper))
end
-- ##############################################
local function starstring(len)
local s = ""
while(len > 0) do
s = s .."*"
len = len -1
end
return(s)
end
-- ##############################################
function obfuscate(str)
local len = string.len(str)
local in_clear = 2
if(len <= in_clear) then
return(starstring(len))
else
return(string.sub(str, 0, in_clear)..starstring(len-in_clear))
end
end
-- ##############################################
function isnumber(str)
if((str ~= nil) and (string.len(str) > 0) and (tonumber(str) ~= nil)) then
return(true)
else
return(false)
end
end
-- ##############################################
function split(pString, pPattern)
local Table = {} -- NOTE: use {n = 0} in Lua-5.0
local fpat = "(.-)" .. pPattern
local last_end = 1
local s, e, cap = pString:find(fpat, 1)
while s do
if s ~= 1 or cap ~= "" then
table.insert(Table,cap)
end
last_end = e+1
s, e, cap = pString:find(fpat, last_end)
end
if last_end <= #pString then
cap = pString:sub(last_end)
table.insert(Table, cap)
end
return Table
end
-- ##############################################
-- returns the MAXIMUM value found in a table t, together with the corresponding
-- index argmax. a pair argmax, max is returned.
function tmax(t)
local argmx, mx = nil, nil
if (type(t) ~= "table") then return nil, nil end
for k, v in pairs(t) do
-- first iteration
if mx == nil and argmx == nil then
mx = v
argmx = k
elseif (v == mx and k > argmx) or v > mx then
-- if there is a tie, prefer the greatest argument
-- otherwise grab the maximum
argmx = k
mx = v
end
end
return argmx, mx
end
-- ##############################################
-- returns the MINIMUM value found in a table t, together with the corresponding
-- index argmin. a pair argmin, min is returned.
function tmin(t)
local argmn, mn = nil, nil
if (type(t) ~= "table") then return nil, nil end
for k, v in pairs(t) do
-- first iteration
if mn == nil and argmn == nil then
mn = v
argmn = k
elseif (v == mn and k > argmn) or v < mn then
-- if there is a tie, prefer the greatest argument
-- otherwise grab the minimum
argmn = k
mn = v
end
end
return argmn, mn
end
-- ##############################################
function formatEpoch(epoch, full_time)
return(format_utils.formatEpoch(epoch, full_time))
end
-- ##############################################
function starts(String,Start)
if((String == nil) or (Start == nil)) then
return(false)
end
return string.sub(String,1,string.len(Start))==Start
end
-- ##############################################
function ends(String,End)
return End=='' or string.sub(String,-string.len(End))==End
end
-- #################################################################
function bit(p)
return 2 ^ (p - 1) -- 1-based indexing
end
-- ##############################################
-- Typical call: if hasbit(x, bit(3)) then ...
function hasbit(x, p)
return x % (p + p) >= p
end
-- ##############################################
function setbit(x, p)
return hasbit(x, p) and x or x + p
end
-- ##############################################
function clearbit(x, p)
return hasbit(x, p) and x - p or x
end
-- ##############################################
function isBroadMulticast(ip)
if(ip == "0.0.0.0") then
return true
end
-- print(ip)
t = string.split(ip, "%.")
-- print(table.concat(t, "\n"))
if(t == nil) then
return false -- Might be an IPv6 address
else
if(tonumber(t[1]) >= 224) then
return true
end
end
return false
end
-- ##############################################
function isBroadcastMulticast(ip)
local ainfo = interface.getAddressInfo(ip)
if(ainfo.is_multicast or ainfo.is_broadcast) then
return true
else
return false
end
end
-- ##############################################
function isIPv4(address)
-- Reuse the for loop to check the address validity
local checkAddress = (function(chunks)
for _, v in pairs(chunks) do
if (tonumber(v) < 0) or (tonumber(v) > 255) then
return false
end
end
return true
end)
local chunks = {address:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")}
local chunksWithPort = {address:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)%:(%d+)$")}
if #chunks == 4 then
return checkAddress(chunks)
elseif #chunksWithPort == 5 then
table.remove(chunksWithPort, 5)
return checkAddress(chunksWithPort)
end
return false
end
-- ##############################################
function isIPv4Network(address)
local parts = split(address, "/")
if #parts == 2 then
local prefix = tonumber(parts[2])
if (prefix == nil) or (math.floor(prefix) ~= prefix) or (prefix < 0) or (prefix > 32) then
return false
end
elseif #parts ~= 1 then
return false
end
return isIPv4(parts[1])
end
-- ##############################################
function addGoogleMapsScript()
local g_maps_key = ntop.getCache('ntopng.prefs.google_apis_browser_key')
if g_maps_key ~= nil and g_maps_key~= "" then
g_maps_key = "&key="..g_maps_key
else
g_maps_key = ""
end
print("<script src=\"https://maps.googleapis.com/maps/api/js?v=3.exp"..g_maps_key.."\"></script>\n")
end
-- ##############################################
function addLogoLightSvg()
return ([[
<div id='ntopng-logo'>
<svg
id="ntopng-logo"
data-name="ntopng logo"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512.74 512.84"
height="56"
width="56">
<defs>
<style>
.cls-1{fill:none;}
.cls-2{fill:#cecece;}
.cls-2,.cls-3,.cls-4{fill-rule:evenodd;}
.cls-3{fill:#b7b7b7;}
.cls-4{fill:#ee751b;}
</style>
</defs>
<title>ntopng logo</title>
<path class="cls-1" d="M1,513.94V1.1H513.78V513.94ZM497.86,172c0-22.84.08-44.16-.07-65.48a100.07,100.07,0,0,0-1.34-16.31c-6.87-40.3-39-71.17-81.23-71.68-105.79-1.27-211.6-.44-317.4-.35a60.46,60.46,0,0,0-14.33,1.62c-39.52,9.78-65.2,42.87-65.34,85-.24,75.82-.07,151.63,0,227.44,0,1.2.25,2.39.47,4.31,2.18-1.71,3.83-2.82,5.26-4.17,14.23-13.54,28.39-27.15,42.67-40.65,2.17-2.06,2.09-3.89,1.38-6.64-1.58-6.05-3.65-12.31-3.45-18.41.91-28.45,25.61-49.36,51.92-48.1,25.77,1.23,50.24,25.65,46.44,55.08-.59,4.64.85,6,4.18,6.6,38.67-46.29,85.62-58.59,141.88-36.58a15.05,15.05,0,0,0,1-1.31c17.37-26.37,34.78-52.72,52-79.21.92-1.42.69-4.55-.28-6.07-9.33-14.54-11.6-29.7-5.46-46.12,8.26-22.11,33.59-36,56.46-30.51,26.38,6.29,42.42,30.41,37.84,57.12-.55,3.25.22,5.15,3.1,6.94,9,5.62,17.81,11.66,26.81,17.34C485.7,165.2,491.33,168.21,497.86,172Zm-87.1,325.77c22.32,1.05,41.19-5.51,57.26-19.17,19-16.16,30.07-36.25,30-62.13-.33-67.15-.11-134.31-.11-201.46v-7.13c-2.44-1.43-4.62-2.59-6.68-3.93-17.43-11.35-34.88-22.65-52.21-34.14-3.45-2.28-6-2.68-9.66,0-10.77,7.8-23.06,9.91-36.05,7.52-3.8-.69-6,.23-8.17,3.57-11.63,18-23.54,35.89-35.31,53.83-5.55,8.46-11,17-16.52,25.5.85.9,1.39,1.52,2,2.09,36.21,34.78,46.28,81.33,27.45,127.7-1.17,2.87-.64,4.43,1.45,6.41q19.77,18.75,39.33,37.71c7.71,7.43,15.11,15.06,17.48,26.13C423.92,474.13,420.3,486.24,410.76,497.74Zm-58.57.12c-16-16.38-31-31.78-46.18-47-1-1-3.94-1.17-5.55-.57a115.89,115.89,0,0,1-53,7c-61.43-6.45-106.54-62-99.74-123.42,1-9,3.34-17.91,5.11-27.08-4.26-3.2-4.21-3.14-8.65.1-16.12,11.79-33.27,13.91-51.71,5.79-1.78-.78-5.22-.36-6.6.91-10.52,9.69-20.74,19.71-31,29.66Q37.1,360.42,19.44,377.69a5.83,5.83,0,0,0-1.3,3.83c-.07,12.5-.27,25,.06,37.48,1.09,41.31,39.69,78.85,80.9,78.85H352.19Zm-92.67-262c-40.78,2.35-73.75,18.09-93.85,55.48-4.3,8-8.56,16.21-11.36,24.81-4.59,14.08-5.25,28.73-3.21,43.46q5.52,39.68,35.05,66.66c18.62,17,40.75,26.23,65.8,28.17A98.42,98.42,0,0,0,301.13,446c3.13-1.44,4.55-1.23,6.73,1,15.72,16.08,31.71,31.91,47.27,48.14,14.32,14.93,38,15,54-1.07,12.43-12.52,12.43-37.07-.58-50.18-8.62-8.7-17.68-17-26.63-25.35-7.53-7-15.19-14-22.78-21-1.8-1.66-3.1-3.34-1.46-6,7.47-12.09,10.36-25.7,10.79-39.49,1.28-41.07-13.57-74.32-49.22-96.78-5.73-3.61-11.25-7.75-17.38-10.52C288.55,238.72,274.35,236,259.52,235.83Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M497.86,172c-6.53-3.76-12.16-6.77-17.56-10.17-9-5.67-17.77-11.72-26.81-17.33-2.88-1.79-3.65-3.69-3.1-6.94,4.58-26.71-11.46-50.84-37.84-57.12-22.87-5.45-48.2,8.4-56.46,30.5-6.14,16.43-3.87,31.58,5.46,46.13,1,1.52,1.19,4.65.28,6.07-17.19,26.49-34.59,52.83-52,79.2a15.5,15.5,0,0,1-1,1.32c-56.27-22-103.21-9.71-141.89,36.58-3.32-.6-4.77-2-4.17-6.6,3.8-29.44-20.67-53.85-46.45-55.08-26.3-1.26-51,19.65-51.91,48.09-.2,6.11,1.87,12.37,3.45,18.42.71,2.75.79,4.58-1.39,6.64-14.27,13.5-28.43,27.11-42.66,40.64-1.43,1.36-3.09,2.47-5.26,4.18-.22-1.92-.47-3.12-.47-4.31,0-75.82-.19-151.63,0-227.44.13-42.12,25.82-75.2,65.34-85a60.46,60.46,0,0,1,14.33-1.62c105.8-.09,211.61-.92,317.4.35,42.2.5,74.36,31.38,81.23,71.67a101.41,101.41,0,0,1,1.34,16.32C497.94,127.81,497.86,149.13,497.86,172Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-3" d="M410.76,497.74c9.54-11.5,13.17-23.61,10.19-37.49-2.37-11.06-9.77-18.7-17.48-26.13q-19.61-18.92-39.33-37.71c-2.09-2-2.62-3.54-1.45-6.41,18.83-46.37,8.76-92.92-27.45-127.7-.59-.57-1.14-1.19-2-2.09,5.51-8.52,11-17,16.52-25.5,11.77-17.94,23.68-35.8,35.31-53.83,2.15-3.34,4.37-4.27,8.17-3.57,13,2.39,25.28.28,36.05-7.52,3.71-2.69,6.21-2.29,9.66,0,17.33,11.48,34.78,22.79,52.21,34.13,2.06,1.35,4.23,2.51,6.68,3.94V215c0,67.15-.22,134.31.11,201.46.12,25.88-11,46-30,62.12C452,492.23,433.08,498.79,410.76,497.74Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-3" d="M352.19,497.86H99.1c-41.21,0-79.81-37.54-80.9-78.85-.33-12.48-.13-25-.06-37.48a5.88,5.88,0,0,1,1.3-3.83q17.61-17.31,35.42-34.43c10.29-9.95,20.51-20,31-29.67,1.37-1.26,4.81-1.68,6.59-.9,18.44,8.12,35.59,6,51.72-5.79,4.43-3.24,4.39-3.3,8.64-.1-1.77,9.17-4.12,18.05-5.11,27.07-6.8,61.43,38.31,117,99.74,123.42a115.75,115.75,0,0,0,53-7c1.61-.6,4.51-.48,5.55.57C321.19,466.08,336.16,481.48,352.19,497.86Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-4" d="M259.52,235.83c14.83.18,29,2.89,42.38,8.92,6.13,2.77,11.65,6.9,17.38,10.51C354.93,277.72,369.79,311,368.5,352c-.43,13.8-3.32,27.41-10.79,39.5-1.64,2.66-.33,4.34,1.47,6,7.59,7,15.24,13.93,22.78,21,8.94,8.38,18,16.65,26.62,25.35,13,13.1,13,37.66.58,50.17-16,16.11-39.71,16-54,1.08-15.57-16.23-31.56-32.06-47.28-48.15-2.18-2.23-3.6-2.43-6.73-1A98.32,98.32,0,0,1,252,454.41c-25-1.94-47.17-11.18-65.8-28.17q-29.52-26.94-35-66.66c-2.05-14.73-1.39-29.38,3.2-43.46,2.81-8.6,7.06-16.8,11.37-24.81C185.77,253.92,218.74,238.18,259.52,235.83Zm74.75,109.92c0-41.77-33.56-74.92-75.46-75.17-43.6-.26-75,36.34-75.13,75.47-.15,42.12,33.35,75.3,75.65,75.23C301.1,421.21,334.32,387.73,334.27,345.75Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-1" d="M334.27,345.75c.05,42-33.17,75.46-74.94,75.53-42.3.07-75.8-33.11-75.65-75.23.15-39.13,31.53-75.73,75.13-75.47C300.71,270.83,334.23,304,334.27,345.75Zm-23.81-51.17C306.21,301,302,307,298.26,313.26a5.89,5.89,0,0,0,.08,5.24c7.05,10,9.43,21,8.85,33-1.21,25.15-26,50.24-57.27,44.86-27-4.64-44.41-28.79-41.21-54.87.19-1.58-.23-4.18-1.28-4.83-5.73-3.55-11.74-6.64-17.77-10-9.73,33.58,7.86,70.39,40,84.81A72.56,72.56,0,0,0,320.85,384C340.92,352,331.39,313.58,310.46,294.58ZM285.84,278.5c-24.8-13.2-67.88-1.62-82.65,22,5.81,3.19,11.73,6.18,17.36,9.64,2.33,1.43,3.62,1.13,5.61-.47,12.3-9.84,26.1-14,41.84-10.79,1.62.33,4.36-.28,5.21-1.45C277.65,291.33,281.66,284.88,285.84,278.5Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M310.46,294.58c20.93,19,30.46,57.41,10.39,89.43a72.56,72.56,0,0,1-91.19,27.52c-32.14-14.42-49.73-51.23-40-84.81,6,3.32,12,6.41,17.77,10,1,.65,1.47,3.25,1.27,4.83-3.19,26.08,14.25,50.23,41.22,54.87,31.26,5.38,56.06-19.71,57.27-44.86.58-12-1.8-23.06-8.85-33a5.89,5.89,0,0,1-.08-5.24C302,307,306.21,301,310.46,294.58Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M285.84,278.5c-4.18,6.38-8.19,12.83-12.63,19-.85,1.17-3.58,1.78-5.21,1.45-15.74-3.18-29.54.95-41.84,10.79-2,1.6-3.28,1.9-5.61.47-5.63-3.46-11.55-6.45-17.36-9.64C218,276.88,261,265.3,285.84,278.5Z" transform="translate(-1.03 -1.1)"/>
</svg>
</div>
]])
end
-- ##############################################
function addLogoDarkSvg()
return ([[
<div id='ntopng-logo'>
<svg
id="ntopng-logo-svg"
data-name="ntopng logo"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512.74 512.84"
height="56"
width="56">
<defs>
<style>
.cls-1{fill:none;}
.cls-2{fill:#333;}
.cls-2,.cls-3{fill-rule:evenodd;}
.cls-3{fill:#ee751b;}
</style>
</defs>
<title>ntopng logo</title>
<path class="cls-1" d="M1,513.94V1.1H513.78V513.94ZM497.86,172c0-22.84.08-44.16-.07-65.48a100.07,100.07,0,0,0-1.34-16.31c-6.87-40.3-39-71.17-81.23-71.68-105.79-1.27-211.6-.44-317.4-.35a60.46,60.46,0,0,0-14.33,1.62c-39.52,9.78-65.2,42.87-65.34,85-.24,75.82-.07,151.63,0,227.44,0,1.2.25,2.39.47,4.31,2.18-1.71,3.83-2.82,5.26-4.17,14.23-13.54,28.39-27.15,42.67-40.65,2.17-2.06,2.09-3.89,1.38-6.64-1.58-6.05-3.65-12.31-3.45-18.41.91-28.45,25.61-49.36,51.92-48.1,25.77,1.23,50.24,25.65,46.44,55.08-.59,4.64.85,6,4.18,6.6,38.67-46.29,85.62-58.59,141.88-36.58a15.05,15.05,0,0,0,1-1.31c17.37-26.37,34.78-52.72,52-79.21.92-1.42.69-4.55-.28-6.07-9.33-14.54-11.6-29.7-5.46-46.12,8.26-22.11,33.59-36,56.46-30.51,26.38,6.29,42.42,30.41,37.84,57.12-.55,3.25.22,5.15,3.1,6.94,9,5.62,17.81,11.66,26.81,17.34C485.7,165.2,491.33,168.21,497.86,172Zm-87.1,325.77c22.32,1.05,41.19-5.51,57.26-19.17,19-16.16,30.07-36.25,30-62.13-.33-67.15-.11-134.31-.11-201.46v-7.13c-2.44-1.43-4.62-2.59-6.68-3.93-17.43-11.35-34.88-22.65-52.21-34.14-3.45-2.28-6-2.68-9.66,0-10.77,7.8-23.06,9.91-36.05,7.52-3.8-.69-6,.23-8.17,3.57-11.63,18-23.54,35.89-35.31,53.83-5.55,8.46-11,17-16.52,25.5.85.9,1.39,1.52,2,2.09,36.21,34.78,46.28,81.33,27.45,127.7-1.17,2.87-.64,4.43,1.45,6.41q19.77,18.75,39.33,37.71c7.71,7.43,15.11,15.06,17.48,26.13C423.92,474.13,420.3,486.24,410.76,497.74Zm-58.57.12c-16-16.38-31-31.78-46.18-47-1-1-3.94-1.17-5.55-.57a115.89,115.89,0,0,1-53,7c-61.43-6.45-106.54-62-99.74-123.42,1-9,3.34-17.91,5.11-27.08-4.26-3.2-4.21-3.14-8.65.1-16.12,11.79-33.27,13.91-51.71,5.79-1.78-.78-5.22-.36-6.6.91-10.52,9.69-20.74,19.71-31,29.66Q37.1,360.42,19.44,377.69a5.83,5.83,0,0,0-1.3,3.83c-.07,12.5-.27,25,.06,37.48,1.09,41.31,39.69,78.85,80.9,78.85H352.19Zm-92.67-262c-40.78,2.35-73.75,18.09-93.85,55.48-4.3,8-8.56,16.21-11.36,24.81-4.59,14.08-5.25,28.73-3.21,43.46q5.52,39.68,35.05,66.66c18.62,17,40.75,26.23,65.8,28.17A98.42,98.42,0,0,0,301.13,446c3.13-1.44,4.55-1.23,6.73,1,15.72,16.08,31.71,31.91,47.27,48.14,14.32,14.93,38,15,54-1.07,12.43-12.52,12.43-37.07-.58-50.18-8.62-8.7-17.68-17-26.63-25.35-7.53-7-15.19-14-22.78-21-1.8-1.66-3.1-3.34-1.46-6,7.47-12.09,10.36-25.7,10.79-39.49,1.28-41.07-13.57-74.32-49.22-96.78-5.73-3.61-11.25-7.75-17.38-10.52C288.55,238.72,274.35,236,259.52,235.83Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M497.86,172c-6.53-3.76-12.16-6.77-17.56-10.17-9-5.67-17.77-11.72-26.81-17.33-2.88-1.79-3.65-3.69-3.1-6.94,4.58-26.71-11.46-50.84-37.84-57.12-22.87-5.45-48.2,8.4-56.46,30.5-6.14,16.43-3.87,31.58,5.46,46.13,1,1.52,1.19,4.65.28,6.07-17.19,26.49-34.59,52.83-52,79.2a15.5,15.5,0,0,1-1,1.32c-56.27-22-103.21-9.71-141.89,36.58-3.32-.6-4.77-2-4.17-6.6,3.8-29.44-20.67-53.85-46.45-55.08-26.3-1.26-51,19.65-51.91,48.09-.2,6.11,1.87,12.37,3.45,18.42.71,2.75.79,4.58-1.39,6.64-14.27,13.5-28.43,27.11-42.66,40.64-1.43,1.36-3.09,2.47-5.26,4.18-.22-1.92-.47-3.12-.47-4.31,0-75.82-.19-151.63,0-227.44.13-42.12,25.82-75.2,65.34-85a60.46,60.46,0,0,1,14.33-1.62c105.8-.09,211.61-.92,317.4.35,42.2.5,74.36,31.38,81.23,71.67a101.41,101.41,0,0,1,1.34,16.32C497.94,127.81,497.86,149.13,497.86,172Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M410.76,497.74c9.54-11.5,13.17-23.61,10.19-37.49-2.37-11.06-9.77-18.7-17.48-26.13q-19.61-18.92-39.33-37.71c-2.09-2-2.62-3.54-1.45-6.41,18.83-46.37,8.76-92.92-27.45-127.7-.59-.57-1.14-1.19-2-2.09,5.51-8.52,11-17,16.52-25.5,11.77-17.94,23.68-35.8,35.31-53.83,2.15-3.34,4.37-4.27,8.17-3.57,13,2.39,25.28.28,36.05-7.52,3.71-2.69,6.21-2.29,9.66,0,17.33,11.48,34.78,22.79,52.21,34.13,2.06,1.35,4.23,2.51,6.68,3.94V215c0,67.15-.22,134.31.11,201.46.12,25.88-11,46-30,62.12C452,492.23,433.08,498.79,410.76,497.74Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M352.19,497.86H99.1c-41.21,0-79.81-37.54-80.9-78.85-.33-12.48-.13-25-.06-37.48a5.88,5.88,0,0,1,1.3-3.83q17.61-17.31,35.42-34.43c10.29-9.95,20.51-20,31-29.67,1.37-1.26,4.81-1.68,6.59-.9,18.44,8.12,35.59,6,51.72-5.79,4.43-3.24,4.39-3.3,8.64-.1-1.77,9.17-4.12,18.05-5.11,27.07-6.8,61.43,38.31,117,99.74,123.42a115.75,115.75,0,0,0,53-7c1.61-.6,4.51-.48,5.55.57C321.19,466.08,336.16,481.48,352.19,497.86Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-3" d="M259.52,235.83c14.83.18,29,2.89,42.38,8.92,6.13,2.77,11.65,6.9,17.38,10.51C354.93,277.72,369.79,311,368.5,352c-.43,13.8-3.32,27.41-10.79,39.5-1.64,2.66-.33,4.34,1.47,6,7.59,7,15.24,13.93,22.78,21,8.94,8.38,18,16.65,26.62,25.35,13,13.1,13,37.66.58,50.17-16,16.11-39.71,16-54,1.08-15.57-16.23-31.56-32.06-47.28-48.15-2.18-2.23-3.6-2.43-6.73-1A98.32,98.32,0,0,1,252,454.41c-25-1.94-47.17-11.18-65.8-28.17q-29.52-26.94-35-66.66c-2.05-14.73-1.39-29.38,3.2-43.46,2.81-8.6,7.06-16.8,11.37-24.81C185.77,253.92,218.74,238.18,259.52,235.83Zm74.75,109.92c0-41.77-33.56-74.92-75.46-75.17-43.6-.26-75,36.34-75.13,75.47-.15,42.12,33.35,75.3,75.65,75.23C301.1,421.21,334.32,387.73,334.27,345.75Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-1" d="M334.27,345.75c.05,42-33.17,75.46-74.94,75.53-42.3.07-75.8-33.11-75.65-75.23.15-39.13,31.53-75.73,75.13-75.47C300.71,270.83,334.23,304,334.27,345.75Zm-23.81-51.17C306.21,301,302,307,298.26,313.26a5.89,5.89,0,0,0,.08,5.24c7.05,10,9.43,21,8.85,33-1.21,25.15-26,50.24-57.27,44.86-27-4.64-44.41-28.79-41.21-54.87.19-1.58-.23-4.18-1.28-4.83-5.73-3.55-11.74-6.64-17.77-10-9.73,33.58,7.86,70.39,40,84.81A72.56,72.56,0,0,0,320.85,384C340.92,352,331.39,313.58,310.46,294.58ZM285.84,278.5c-24.8-13.2-67.88-1.62-82.65,22,5.81,3.19,11.73,6.18,17.36,9.64,2.33,1.43,3.62,1.13,5.61-.47,12.3-9.84,26.1-14,41.84-10.79,1.62.33,4.36-.28,5.21-1.45C277.65,291.33,281.66,284.88,285.84,278.5Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M310.46,294.58c20.93,19,30.46,57.41,10.39,89.43a72.56,72.56,0,0,1-91.19,27.52c-32.14-14.42-49.73-51.23-40-84.81,6,3.32,12,6.41,17.77,10,1,.65,1.47,3.25,1.27,4.83-3.19,26.08,14.25,50.23,41.22,54.87,31.26,5.38,56.06-19.71,57.27-44.86.58-12-1.8-23.06-8.85-33a5.89,5.89,0,0,1-.08-5.24C302,307,306.21,301,310.46,294.58Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M285.84,278.5c-4.18,6.38-8.19,12.83-12.63,19-.85,1.17-3.58,1.78-5.21,1.45-15.74-3.18-29.54.95-41.84,10.79-2,1.6-3.28,1.9-5.61.47-5.63-3.46-11.55-6.45-17.36-9.64C218,276.88,261,265.3,285.84,278.5Z" transform="translate(-1.03 -1.1)"/>
</svg>
</div>
]])
end
-- ##############################################
function addLogoSvg()
return ([[
<div id='ntop-logo'>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
viewBox="0 0 13.758333 13.758334"
height="52"
width="52">
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"></dc:type>
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1">
<g
style="font-style:normal;font-weight:normal;font-size:16.9333px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ff7500;fill-opacity:1;stroke:none;stroke-width:0.264583"
id="text835"
aria-label="n">
<path
d="M 2.7739989,9.5828812 V 4.216811 q 0,-0.9839173 0.3224603,-1.4552054 0.3307285,-0.4795564 1.008722,-0.4795564 0.4051424,0 0.7193345,0.2149735 Q 5.1387078,2.7037281 5.378486,3.1336751 5.808433,2.662387 6.3706715,2.4474135 6.93291,2.2324399 7.7349267,2.2324399 q 1.5792286,0 2.4143183,0.9012352 0.835089,0.9012352 0.835089,2.6210235 v 3.8281826 q 0,0.9839178 -0.330728,1.4634738 -0.330729,0.479556 -1.0087222,0.479556 -0.6779934,0 -1.0087219,-0.479556 Q 8.3054333,10.566799 8.3054333,9.5828812 V 6.5649835 q 0,-1.1162088 -0.3389967,-1.5874969 -0.3307285,-0.4795563 -1.0996723,-0.4795563 -0.7276027,0 -1.0748677,0.4960927 -0.3472649,0.4878246 -0.3472649,1.5378876 v 3.0509706 q 0,0.9839178 -0.3307285,1.4634738 -0.3307286,0.479556 -1.008722,0.479556 -0.6779935,0 -1.008722,-0.479556 Q 2.7739989,10.566799 2.7739989,9.5828812 Z"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'VAGRounded BT';-inkscape-font-specification:'VAGRounded BT';fill:#ff7500;fill-opacity:1;stroke-width:0.264583"
id="path873"></path>
</g>
</g>
</svg>
</div>
]])
end
-- ##############################################
function addGauge(name, url, maxValue, width, height)
if(url ~= nil) then print('<A HREF="'..url..'">') end
print [[
<div class="progress">
<div id="]] print(name) print [[" class="progress-bar bg-warning"></div>
</div>
]]
if(url ~= nil) then print('</A>\n') end
end
-- ##############################################
--
-- Members supported format
-- 192.168.1.10/32@10
-- 00:11:22:33:44:55
--
function isValidPoolMember(member)
if isEmptyString(member) then
return false
end
if isMacAddress(member) then
return true
end
-- vlan is mandatory here
local vlan_idx = string.find(member, "@")
if ((vlan_idx == nil) or (vlan_idx == 1)) then
return false
end
local other = string.sub(member, 1, vlan_idx-1)
local vlan = tonumber(string.sub(member, vlan_idx+1))
if (vlan == nil) or (vlan < 0) then
return false
end
-- prefix is mandatory here
local address, prefix = splitNetworkPrefix(other)
if prefix == nil then
return false
end
if isIPv4(address) and (tonumber(prefix) >= 0) and (tonumber(prefix) <= 32) then
return true
elseif isIPv6(address) and (tonumber(prefix) >= 0) and (tonumber(prefix) <= 128) then
return true
end
return false
end
-- ##############################################
function host2member(ip, vlan, prefix)
if prefix == nil then
if isIPv4(ip) then
prefix = 32
else
prefix = 128
end
end
return ip .. "/" .. tostring(prefix) .. "@" .. tostring(vlan)
end
-- ##############################################
function isLocal(host_ip)
host = interface.getHostInfo(host_ip)
if((host == nil) or (host['localhost'] ~= true)) then
return(false)
else
return(true)
end
end
-- ##############################################
-- Windows fixes for interfaces with "uncommon chars"
function purifyInterfaceName(interface_name)
-- io.write(debug.traceback().."\n")
interface_name = string.gsub(interface_name, "@", "_")
interface_name = string.gsub(interface_name, ":", "_")
interface_name = string.gsub(interface_name, "/", "_")
return(interface_name)
end
-- ##############################################
-- See datatype AggregationType in ntop_typedefs.h
function aggregation2String(value)
if(value == 0) then return("Client Name")
elseif(value == 1) then return("Server Name")
elseif(value == 2) then return("Domain Name")
elseif(value == 3) then return("Operating System")
elseif(value == 4) then return("Registrar Name")
else return(value)
end
end
-- #################################
-- Aggregates items below some edge
-- edge: minimum percentage value to create collision
-- min_col: minimum collision groups to aggregate
function aggregatePie(values, values_sum, edge, min_col)
local edge = edge or 0.09
min_col = min_col or 2
local aggr = {}
local other = i18n("other")
local below_edge = {}
-- Initial lookup
for k,v in pairs(values) do
if v / values_sum <= edge then
-- too small
below_edge[#below_edge + 1] = k
else
aggr[k] = v
end
end
-- Decide if to aggregate
for _,k in pairs(below_edge) do
if #below_edge >= min_col then
-- aggregate
aggr[other] = aggr[other] or 0
aggr[other] = aggr[other] + values[k]
else
-- do not aggregate
aggr[k] = values[k]
end
end
return aggr
end
-- #################################
-- This function actively resolves an host if there is not information about it.
-- NOTE: prefer the host2name on this function
function resolveAddress(hostinfo, allow_empty)
local alt_name = ip2label(hostinfo["host"])
if(not isEmptyString(alt_name) and (alt_name ~= hostinfo["host"])) then
-- The host label has priority
return(alt_name)
end
local hostname = ntop.resolveName(hostinfo["host"])
if isEmptyString(hostname) then
-- Not resolved
if allow_empty == true then
return hostname
else
-- this function will take care of formatting the IP
return hostinfo2label(hostinfo)
end
end
return hostinfo2label(hostinfo)
end
-- ###########################################
function computeL7Stats(stats, show_breed, show_ndpi_category)
local _ifstats = {}
if(show_breed) then
local breed_stats = {}
for key, value in pairs(stats["ndpi"]) do
local b = stats["ndpi"][key]["breed"]
local traffic = stats["ndpi"][key]["bytes.sent"] + stats["ndpi"][key]["bytes.rcvd"]
if(breed_stats[b] == nil) then
breed_stats[b] = traffic
else
breed_stats[b] = breed_stats[b] + traffic
end
end
for key, value in pairs(breed_stats) do
_ifstats[key] = value
end
elseif(show_ndpi_category) then
local ndpi_category_stats = {}
for key, value in pairs(stats["ndpi_categories"]) do
key = getCategoryLabel(key, value.category)
local traffic = value["bytes"]
if(ndpi_category_stats[key] == nil) then
ndpi_category_stats[key] = traffic
else
ndpi_category_stats[key] = ndpi_category_stats[key] + traffic
end
end
for key, value in pairs(ndpi_category_stats) do
_ifstats[key] = value
end
else
-- Add ARP to stats
local arpBytes = 0
if(stats["eth"] ~= nil) then
arpBytes = stats["eth"]["ARP_bytes"]
if(arpBytes > 0) then
_ifstats["ARP"] = arpBytes
end
end
for key, value in pairs(stats["ndpi"]) do
local traffic = value["bytes.sent"] + value["bytes.rcvd"]
if(key == "Unknown") then
traffic = traffic - arpBytes
end
if(traffic > 0) then
if(show_breed) then
_ifstats[value["breed"]] = traffic
else
_ifstats[key] = traffic
end
end
end
end
return _ifstats
end
-- ##############################################
function splitNetworkPrefix(net)
local prefix = tonumber(net:match("/(.+)"))
local address = net:gsub("/.+","")
return address, prefix
end
-- ##############################################
function splitNetworkWithVLANPrefix(net_mask_vlan)
local vlan = tonumber(net_mask_vlan:match("@(.+)"))
local net_mask = net_mask_vlan:gsub("@.+","")
local prefix = tonumber(net_mask:match("/(.+)"))
local address = net_mask:gsub("/.+","")
return address, prefix, vlan
end
-- ##############################################
function splitProtocol(proto_string)
local parts = string.split(proto_string, "%.")
local app_proto
local master_proto
if parts == nil then
master_proto = proto_string
app_proto = nil
else
master_proto = parts[1]
app_proto = parts[2]
end
return master_proto, app_proto
end
-- ##############################################
function setHostAltName(host_info, alt_name)
local host_key
if type(host_info) == "table" then
-- Note: we are not using hostinfo2hostkey which includes the
-- vlan for backward compatibility, compatibility with
-- the backend, and compatibility with the vpn scripts
host_key = host_info["host"] -- hostinfo2hostkey(host_info)
else
host_key = host_info
end
ntop.setCache(getHostAltNamesKey(host_key), alt_name)
end
-- ##############################################
function setHostNotes(host_info, notes)
local host_key
if type(host_info) == "table" then
-- Note: we are not using hostinfo2hostkey which includes the
-- vlan for backward compatibility, compatibility with
-- the backend, and compatibility with the vpn scripts
host_key = host_info["host"] -- hostinfo2hostkey(host_info)
else
host_key = host_info
end
ntop.setCache(getHostNotesKey(host_key), notes)
end
-- ##############################################
local function label2formattedlabel(alt_name, host_info, show_vlan, shorten_len)
if not isEmptyString(alt_name) then
local ip = host_info["ip"] or host_info["host"]
-- Make it shorter
local res = alt_name
-- Special shorting function for IP addresses
if res ~= ip then
if shorten_len == false then
-- Don't touch the string, requested as-is without shortening
elseif tonumber(shorten_len) then
-- Shorten according to the specified length
res = shortenString(res, shorten_len)
else
-- Use the default system-wide setting for the shortening
res = shortenString(res)
end
end
-- Adding the vlan if requested
if show_vlan then
local vlan = tonumber(host_info["vlan"])
if vlan and vlan > 0 then
local full_vlan_name = getFullVlanName(vlan, true --[[ Compact --]])
res = string.format("%s@%s", res, full_vlan_name)
end
end
return res
end
-- Fallback: just the IP and VLAN
return(hostinfo2hostkey(host_info, true))
end
-- ##############################################
-- Attempt at retrieving an host label from an host_info, using local caches and DNS resolution.
-- This can be more expensive if compared to only using information found inside host_info.
local function hostinfo2label_resolved(host_info, show_vlan, shorten_len)
local ip = host_info["ip"] or host_info["host"]
local res
-- If local broadcast domain host and DHCP, try to get the label associated
-- to the MAC address
if host_info["mac"] and (host_info["broadcast_domain_host"] or host_info["dhcpHost"]) then
res = getHostAltName(host_info["mac"])
end
if isEmptyString(res) then
-- Try and get the resolved name
res = ntop.getResolvedName(ip)
if not isEmptyString(res) then
res = string.lower(res)
else
-- Nothing found, just fallback to the IP address
res = ip
end
end
return label2formattedlabel(res, host_info, show_vlan, shorten_len)
end
-- ##############################################
-- Retrieve an host label from an host_info. The minimum fields of
-- the host_info are "host" and "vlan", however a full JSON from Host::lua
-- is needed to provide an accurate result.
--
-- The following order is used to determine the label:
-- MAC label (LBD hosts only), IP label, MDNS/DHCP name from C, resolved IP
--
-- NOTE: The function attempt at labelling an host only using information found in host_info.
-- In case host_info is not enough to label the host, then local caches and DNS resolution kick in
-- to find a label (at the expense of extra time).
function hostinfo2label(host_info, show_vlan, shorten_len)
local alt_name = nil
local ip = host_info["ip"] or host_info["host"]
-- Take the label as found in the host structure
local res = host_info.label
if isEmptyString(res) then
-- Use any user-configured custom name
-- This goes first as a label set by the user MUST take precedance over any other possibly available label
res = getHostAltName(ip)
if isEmptyString(res) then
-- Read what is found inside host `name`, e.g., name as found by dissected traffic such as DHCP
res = host_info["name"]
if isEmptyString(res) then
return hostinfo2label_resolved(host_info, show_vlan, shorten_len)
end
end
end
return label2formattedlabel(res, host_info, show_vlan, shorten_len)
end
-- ##############################################
-- Just a convenience function for hostinfo2label with only IP and VLAN
function ip2label(ip, vlan)
return hostinfo2label({host = ip, vlan = (vlan or 0)})
end
-- ##############################################
function mac2label(mac)
local alt_name = getHostAltName(mac)
if not isEmptyString(alt_name) and (alt_name ~= mac) then
return(alt_name)
end
alt_name = ntop.getCache(getDhcpNameKey(interface.getId(), mac))
if not isEmptyString(alt_name) and (alt_name ~= mac) then
return(alt_name)
end
-- Fallback: just the MAC
return(mac)
end
-- ##############################################
-- Mac Addresses --
local specialMACs = {
"01:00:0C",
"01:80:C2",
"01:00:5E",
"01:0C:CD",
"01:1B:19",
"FF:FF",
"33:33"
}
function isSpecialMac(mac)
for _,key in pairs(specialMACs) do
if(string.contains(mac, key)) then
return true
end
end
return false
end
-- ##############################################
-- @brief Implements the logic to decide whether to show or not the url for a given `host_info`
local function hostdetails_exists(host_info, hostdetails_params)
if not hostdetails_params then
hostdetails_params = {}
end
if hostdetails_params["page"] ~= "historical" and not hostdetails_params["ts_schema"] then
-- If the requested host_details.lua page is not the "historical" page
-- and if no ts_schema has been requested
-- then we check for host existance in memory, to make sure the page host_details.lua
-- won't bring to an empty page.
if not host_info["ipkey"] then
-- host_info hasn't been generated with Host::lua so we can try and
-- see if the host is active
local active_host = interface.getHostInfo(hostinfo2hostkey(host_info))
if not active_host then
return false
end
end
else
-- If the requested page is the "historical" page, or if a ts_schema has been requested,
-- then we assume page host_details.lua
-- exists if the timeseries are enabled and if the requested timeseries exists for the host
if not hostdetails_params["ts_schema"] then
-- Default schema for hosts
hostdetails_params["ts_schema"] = "host:traffic"
end
-- A ts_schema has been requested, let's see if it exists
local ts_utils = require("ts_utils_core")
local tags = table.merge(host_info, hostdetails_params)
if not tags["ifid"] then tags["ifid"] = interface.getId() end
-- If nIndex support is enabled, then there's no need to check for existence of the
-- schema: nIndex flows must be visible from the historical page even when there's no timeseries
-- associated
if not interfaceHasClickHouseSupport() and not ts_utils.exists(hostdetails_params["ts_schema"], tags) then
-- If here, the requested schema, along with its hostdetails_params doesn't exist
return false
end
end
return true
end
-- ##############################################
-- @brief Generates an host_details.lua url (if available)
-- @param host_info A lua table containing at least keys `host` and `vlan` or a full lua table generated with Host::lua
-- @param href_params A lua table containing params host_details.lua params, e.g., {page = "historical"}
-- @param href_check Performs existance checks on the link to avoid generating links to inactive hosts or hosts without timeseries
-- @return A string containing the url (if available) or an empty string when the url is not available
function hostinfo2detailsurl(host_info, href_params, href_check)
local tag_utils = require "tag_utils"
local res = ''
if not href_check or hostdetails_exists(host_info, href_params) then
local auth = require "auth"
local url_params = table.tconcat(href_params or {}, "=", "&")
-- Alerts pages for the host are in alert_stats.lua (Alerts menu)
if href_params and href_params.page == "engaged-alerts" then
if auth.has_capability(auth.capabilities.alerts) then
res = string.format("%s/lua/alert_stats.lua?page=host&status=engaged&ip=%s%s%s",
ntop.getHttpPrefix(),
hostinfo2hostkey(host_info),
tag_utils.SEPARATOR, "eq")
end
elseif href_params and href_params.page == "alerts" then
if auth.has_capability(auth.capabilities.alerts) then
res = string.format("%s/lua/alert_stats.lua?page=host&status=historical&ip=%s%s%s",
ntop.getHttpPrefix(),
hostinfo2hostkey(host_info),
tag_utils.SEPARATOR, "eq")
end
-- All other pages are in host_details.lua
else
res = string.format("%s/lua/host_details.lua?%s%s%s",
ntop.getHttpPrefix(),
hostinfo2url(host_info),
isEmptyString(url_params) and '' or '&',
url_params,
href_value)
end
end
return res
end
-- ##############################################
-- @brief Generates an host_details.lua a href link (if available), starting from an `host_info` structure
-- @param host_info A lua table containing at least keys `host` and `vlan` or a full lua table generated with Host::lua
-- @param href_params A lua table containing params host_details.lua params, e.g., {page = "historical"}
-- @param href_value A string containing the visible value shown between a href tags
-- @param href_tooltip A string containing a tooltip shown when hovering the mouse on the link
-- @param href_check Performs existance checks on the link to avoid generating links to inactive hosts or hosts without timeseries
-- @param href_only_with_ts True means that a HREF is geneated only of there are timeseries for this host
-- @return A string containing the a href link or a plain string without a href
function hostinfo2detailshref(host_info, href_params, href_value, href_tooltip, href_check, href_only_with_ts, show_value_with_no_ref)
local show_href = false
local res = ""
if(href_only_with_ts == true) then
local detailLevel = ntop.getCache("ntopng.prefs.hosts_ts_creation")
if(detailLevel == "full") then
local l7 = ntop.getCache("ntopng.prefs.host_ndpi_timeseries_creation")
if(l7 ~= "none") then
show_href = true
end
end
else
show_href = true
end
if(show_href) then
local hostdetails_url = hostinfo2detailsurl(host_info, href_params, href_check)
if not isEmptyString(hostdetails_url)then
res = string.format("<a href='%s' data-bs-toggle='tooltip' title='%s'>%s</a>",
hostdetails_url, href_tooltip or '', href_value or '')
else
if show_value_with_no_ref == nil or show_value_with_no_ref == true then
res = href_value or ''
end
end
return res
else
return(href_value)
end
end
-- ##############################################
-- @brief Generates an host_details.lua a href link (if available), starting from an ip and a vlan
-- @param ip A string with a valid ip address
-- @param vlan A string or a number with a VLAN or nil when VLAN information is not available
-- @param href_params A lua table containing params host_details.lua params, e.g., {page = "historical"}
-- @param href_value A string containing the visible value shown between a href tags
-- @param href_tooltip A string containing a tooltip shown when hovering the mouse on the link
-- @param href_check Performs existance checks on the link to avoid generating links to inactive hosts or hosts without timeseries
-- @return A string containing the a href link or a plain string without a href
function ip2detailshref(ip, vlan, href_params, href_value, href_tooltip, href_check)
return hostinfo2detailshref({host = ip, vlan = tonumber(vlan) or 0}, href_params, href_value, href_tooltip, href_check)
end
-- ##############################################
-- Flow Utils --
function flowinfo2hostname(flow_info, host_type, alerts_view, add_hostname)
local name
local orig_name
if (alerts_view and not hasClickHouseSupport()) or (add_hostname ~= nil and add_hostname == false) then
-- do not return resolved name as it will hide the IP address
return(flow_info[host_type..".ip"])
end
if(flow_info == nil) then return("") end
if(host_type == "srv") then
if flow_info["host_server_name"] ~= nil and flow_info["host_server_name"] ~= "" and flow_info["host_server_name"]:match("%w") and not isIPv4(flow_info["host_server_name"]) and not isIPv6(flow_info["host_server_name"]) then
-- remove possible ports from the name
return(flow_info["host_server_name"]:gsub(":%d+$", ""))
end
if(flow_info["protos.tls.certificate"] ~= nil and flow_info["protos.tls.certificate"] ~= "") then
return(flow_info["protos.tls.certificate"])
end
end
local hostinfo = {
host = flow_info[host_type..".ip"],
label = flow_info[host_type..".host"],
mac = flow_info[host_type..".mac"],
dhcpHost = flow_info[host_type..".dhcpHost"],
broadcast_domain_host = flow_info[host_type..".broadcast_domain_host"],
vlan = flow_info["vlan"],
}
return(hostinfo2label(hostinfo))
end
-- ##############################################
function flowinfo2process(process, host_info_to_url)
local fmt, proc_name, proc_user_name = '', '', ''
if process then
-- TODO: add links back once restored
if not isEmptyString(process["name"]) then
local full_clean_name = process["name"]:gsub("'",'')
local t = split(full_clean_name, "/")
clean_name = t[#t]
proc_name = string.format("<A HREF='%s/lua/process_details.lua?%s&pid_name=%s&pid=%u'><i class='fas fa-terminal'></i> %s</A>",
ntop.getHttpPrefix(),
host_info_to_url,
full_clean_name,
process["pid"],
clean_name)
end
-- if not isEmptyString(process["user_name"]) then
-- local clean_user_name = process["user_name"]:gsub("'", '')
-- proc_user_name = string.format("<A HREF='%s/lua/username_details.lua?%s&username=%s&uid=%u'><i class='fas fa-linux'></i> %s</A>",
-- ntop.getHttpPrefix(),
-- host_info_to_url,
-- clean_user_name,
-- process["uid"],
-- clean_user_name)
-- end
fmt = string.format("[%s]", table.concat({proc_user_name, proc_name}, ' '))
end
return fmt
end
-- ##############################################
function flowinfo2container(container)
local fmt, cont_name, pod_name = '', '', ''
if container then
cont_name = string.format("<A HREF='%s/lua/flows_stats.lua?container=%s'><i class='fas fa-ship'></i> %s</A>",
ntop.getHttpPrefix(),
container["id"], format_utils.formatContainer(container))
-- local formatted_pod = format_utils.formatPod(container)
-- if not isEmptyString(formatted_pod) then
-- pod_name = string.format("<A HREF='%s/lua/containers_stats.lua?pod=%s'><i class='fas fa-crosshairs'></i> %s</A>",
-- ntop.getHttpPrefix(),
-- formatted_pod,
-- formatted_pod)
-- end
fmt = string.format("[%s]", table.concat({cont_name, pod_name}, ''))
end
return fmt
end
-- ##############################################
function setLocalNetworkAlias(network, alias)
if((network ~= alias) or isEmptyString(alias)) then
ntop.setHashCache(getLocalNetworkAliasKey(), network, alias)
else
ntop.delHashCache(getLocalNetworkAliasKey(), network)
end
end
-- ##############################################
function setVlanAlias(vlan_id, alias)
if((vlan_id ~= alias) or isEmptyString(alias)) then
ntop.setHashCache(getVlanAliasKey(), vlan_id, alias)
else
ntop.delHashCache(getVlanAliasKey(), vlan_id)
end
end
-- ##############################################
function flow2hostinfo(host_info, host_type)
local host_name
local res = interface.getHostMinInfo(host_info[host_type .. ".ip"])
if((res == nil) or (res["name"] == nil)) then
host_name = host_info[host_type .. ".ip"]
else
host_name = res["name"]
end
return({host = host_info[host_type .. ".ip"], vlan = host_info[host_type .. ".vlan"], name = host_name})
end
-- ##############################################
-- URL Util --
--
-- Split the host key (ip@vlan) creating a new lua table.
-- Example:
-- info = hostkey2hostinfo(key)
-- ip = info["host"]
-- vlan = info["vlan"]
--
function hostkey2hostinfo(key)
local host = {}
local info = split(key,"@")
if(info[1] ~= nil) then
host["host"] = info[1]
end
if(info[2] ~= nil) then
host["vlan"] = tonumber(info[2])
else
host["vlan"] = 0
end
return host
end
-- ##############################################
--
-- Analyze the host_info table and return the host key.
-- Example:
-- host_info = interface.getHostInfo("127.0.0.1",0)
-- key = hostinfo2hostkey(host_info)
--
function hostinfo2hostkey(host_info, host_type, show_vlan)
local rsp = ""
if(host_type == "cli") then
local cli_ip = host_info["cli.ip"] or host_info["cli_ip"]
if cli_ip then
rsp = rsp..cli_ip
end
elseif(host_type == "srv") then
local srv_ip = host_info["srv.ip"] or host_info["srv_ip"]
if srv_ip then
rsp = rsp..srv_ip
end
else
if(host_info["ip"] ~= nil) then
rsp = rsp..host_info["ip"]
elseif(host_info["mac"] ~= nil) then
rsp = rsp..host_info["mac"]
elseif(host_info["host"] ~= nil) then
rsp = rsp..host_info["host"]
elseif(host_info["name"] ~= nil) then
rsp = rsp..host_info["name"]
end
end
local vlan_id = tonumber(host_info["vlan"] or host_info["vlan_id"] or 0)
if vlan_id ~= 0 or show_vlan then
rsp = rsp..'@'..tostring(vlan_id)
end
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2URL => ".. rsp .. "\n") end
return rsp
end
-- ##############################################
function member2visual(member)
local info = hostkey2hostinfo(member)
local host = info.host
local hlen = string.len(host)
if string.ends(host, "/32") and isIPv4(string.sub(host, 1, hlen-3)) then
host = string.sub(host, 1, hlen-3)
elseif string.ends(host, "/128") and isIPv6(string.sub(host, 1, hlen-4)) then
host = string.sub(host, 1, hlen-4)
end
return hostinfo2hostkey({host=host, vlan=info.vlan})
end
-- ##############################################
--
-- Analyze the get_info and return a new table containing the url information about an host.
-- Example: url2host(_GET)
--
function url2hostinfo(get_info)
local host = {}
-- Catch when the host key is using as host url parameter
if((get_info["host"] ~= nil) and (string.find(get_info["host"],"@"))) then
get_info = hostkey2hostinfo(get_info["host"])
end
if(get_info["host"] ~= nil) then
host["host"] = get_info["host"]
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"URL2HOST => Host:"..get_info["host"].."\n") end
end
if(get_info["vlan"] ~= nil) then
host["vlan"] = tonumber(get_info["vlan"])
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"URL2HOST => Vlan:"..get_info["vlan"].."\n") end
else
host["vlan"] = 0
end
return host
end
-- ##############################################
--
-- Catch the main information about an host from the host_info table and return the corresponding url.
-- Example:
-- hostinfo2url(host_key), return an url based on the host_key
-- hostinfo2url(host[key]), return an url based on the host value
-- hostinfo2url(flow[key],"cli"), return an url based on the client host information in the flow table
-- hostinfo2url(flow[key],"srv"), return an url based on the server host information in the flow table
--
function hostinfo2url(host_info, host_type, novlan)
local rsp = ''
-- local version = 0
local version = 1
if(host_type == "cli") then
if(host_info["cli.ip"] ~= nil) then
rsp = rsp..'host='..hostinfo2hostkey(flow2hostinfo(host_info, "cli"))
end
elseif(host_type == "srv") then
if(host_info["srv.ip"] ~= nil) then
rsp = rsp..'host='..hostinfo2hostkey(flow2hostinfo(host_info, "srv"))
end
else
if((type(host_info) ~= "table")) then
host_info = hostkey2hostinfo(host_info)
end
if(host_info["host"] ~= nil) then
rsp = rsp..'host='..host_info["host"]
elseif(host_info["ip"] ~= nil) then
rsp = rsp..'host='..host_info["ip"]
elseif(host_info["mac"] ~= nil) then
rsp = rsp..'host='..host_info["mac"]
--Note: the host'name' is not supported (not accepted by lint)
--elseif(host_info["name"] ~= nil) then
-- rsp = rsp..'host='..host_info["name"]
end
end
if(novlan == nil) then
if((host_info["vlan"] ~= nil) and (tonumber(host_info["vlan"]) ~= 0)) then
if(version == 0) then
rsp = rsp..'&vlan='..tostring(host_info["vlan"])
elseif(version == 1) then
rsp = rsp..'@'..tostring(host_info["vlan"])
end
end
end
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2URL => ".. rsp .. "\n") end
return rsp
end
-- ##############################################
--
-- Catch the main information about an host from the host_info table and return the corresponding json.
-- Example:
-- hostinfo2json(host[key]), return a json string based on the host value
-- hostinfo2json(flow[key],"cli"), return a json string based on the client host information in the flow table
-- hostinfo2json(flow[key],"srv"), return a json string based on the server host information in the flow table
--
function hostinfo2json(host_info,host_type)
local rsp = ''
if(host_type == "cli") then
if(host_info["cli.ip"] ~= nil) then
rsp = rsp..'host: "'..host_info["cli.ip"]..'"'
end
elseif(host_type == "srv") then
if(host_info["srv.ip"] ~= nil) then
rsp = rsp..'host: "'..host_info["srv.ip"]..'"'
end
else
if((type(host_info) ~= "table") and (string.find(host_info,"@"))) then
host_info = hostkey2hostinfo(host_info)
end
if(host_info["host"] ~= nil) then
rsp = rsp..'host: "'..host_info["host"]..'"'
elseif(host_info["ip"] ~= nil) then
rsp = rsp..'host: "'..host_info["ip"]..'"'
elseif(host_info["name"] ~= nil) then
rsp = rsp..'host: "'..host_info["name"] ..'"'
elseif(host_info["mac"] ~= nil) then
rsp = rsp..'host: "'..host_info["mac"] ..'"'
end
end
if((host_info["vlan"] ~= nil) and (host_info["vlan"] ~= 0)) then
rsp = rsp..', vlan: "'..tostring(host_info["vlan"]) .. '"'
end
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2JSON => ".. rsp .. "\n") end
return rsp
end
-- ##############################################
--
-- Catch the main information about an host from the host_info table and return the corresponding jqueryid.
-- Example: host 192.168.1.254, vlan0 ==> 1921681254_0
function hostinfo2jqueryid(host_info,host_type)
local rsp = ''
if(host_type == "cli") then
if(host_info["cli.ip"] ~= nil) then
rsp = rsp..''..host_info["cli.ip"]
end
elseif(host_type == "srv") then
if(host_info["srv.ip"] ~= nil) then
rsp = rsp..''..host_info["srv.ip"]
end
else
if((type(host_info) ~= "table") and (string.find(host_info,"@"))) then
host_info = hostkey2hostinfo(host_info)
end
if(host_info["host"] ~= nil) then
rsp = rsp..''..host_info["host"]
elseif(host_info["ip"] ~= nil) then
rsp = rsp..''..host_info["ip"]
elseif(host_info["name"] ~= nil) then
rsp = rsp..''..host_info["name"]
elseif(host_info["mac"] ~= nil) then
rsp = rsp..''..host_info["mac"]
end
end
if((host_info["vlan"] ~= nil) and (host_info["vlan"] ~= 0)) then
rsp = rsp..'@'..tostring(host_info["vlan"])
end
rsp = string.gsub(rsp, "%.", "__")
rsp = string.gsub(rsp, "/", "___")
rsp = string.gsub(rsp, ":", "____")
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2KEY => ".. rsp .. "\n") end
return rsp
end
-- ##############################################
function isPausedInterface(current_ifname)
if(not isEmptyString(_POST["toggle_local"])) then
return(_POST["toggle_local"] == "0")
end
state = ntop.getCache("ntopng.prefs.ifid_"..tostring(interface.name2id(current_ifname)).."_not_idle")
if(state == "0") then return true else return false end
end
-- ##############################################
function tablePreferences(key, value, force_set)
table_key = getRedisPrefix("ntopng.sort.table")
if((value == nil) or (value == "")) and (force_set ~= true) then
-- Get preferences
return ntop.getHashCache(table_key, key)
else
-- Set preferences
ntop.setHashCache(table_key, key, value)
return(value)
end
end
-- ##############################################
function setInterfaceRegreshRate(ifid, refreshrate)
local key = "ntopng.prefs.ifid_"..tostring(ifid)..".refresh_rate"
if isEmptyString(refreshrate) then
ntop.delCache(key)
else
ntop.setCache(key, tostring(refreshrate))
end
end
-- ##############################################
function setCustomnDPIProtoCategory(app_id, new_cat_id)
ntop.setnDPIProtoCategory(app_id, new_cat_id)
local key = getCustomnDPIProtoCategoriesKey(ifid)
-- NOTE: when the ndpi struct changes, the custom associations are
-- reloaded by Ntop::loadProtocolsAssociations
ntop.setHashCache(key, tostring(app_id), tostring(new_cat_id));
end
-- ##############################################
-- "Some Very Long String" -> "Some Ver...g String"
function shortenCollapse(s, max_len)
local replacement = "..."
local r_len = string.len(replacement)
local s_len = string.len(s)
if max_len == nil then
max_len = ntop.getPref("ntopng.prefs.max_ui_strlen")
max_len = tonumber(max_len)
if(max_len == nil) then max_len = 24 end
end
if max_len <= r_len then
return replacement
end
if s_len > max_len then
local half = math.floor((max_len-r_len) / 2)
return string.sub(s, 1, half) .. replacement .. string.sub(s, s_len-half+1)
end
return s
end
-- ##############################################
function unescapeHTML(s)
local unesc = function (h)
local res = string.char(tonumber(h, 16))
return res
end
-- s = string.gsub(s, "+", " ")
s = string.gsub(s, "%%(%x%x)", unesc)
return s
end
-- ##############################################
function unescapeHttpHost(host)
if isEmptyString(host) then
return(host)
end
return string.gsub(string.gsub(host, "http:__", "http://"), "https:__", "https://")
end
-- ##############################################
function harvestUnusedDir(path, min_epoch)
local files = ntop.readdir(path)
-- print("Reading "..path.."<br>\n")
for k,v in pairs(files) do
if(v ~= nil) then
local p = os_utils.fixPath(path .. "/" .. v)
if(ntop.isdir(p)) then
harvestUnusedDir(p, min_epoch)
else
local when = ntop.fileLastChange(path)
if((when ~= -1) and (when < min_epoch)) then
os.remove(p)
end
end
end
end
end
-- ##############################################
function harvestJSONTopTalkers(days)
local when = os.time() - 86400 * days
ifnames = interface.getIfNames()
for _,ifname in pairs(ifnames) do
interface.select(ifname)
local _ifstats = interface.getStats()
local dirs = ntop.getDirs()
local basedir = os_utils.fixPath(dirs.workingdir .. "/" .. _ifstats.id)
harvestUnusedDir(os_utils.fixPath(basedir .. "/top_talkers"), when)
harvestUnusedDir(os_utils.fixPath(basedir .. "/flows"), when)
end
end
-- ##############################################
function isAdministratorOrPrintErr(isJsonResponse)
if (isAdministrator()) then
return(true)
end
local isJson = isJsonResponse or false
if (isJson) then
local json = require("dkjson")
sendHTTPContentTypeHeader('application/json')
print(json.encode({}))
else
local page_utils = require("page_utils")
page_utils.print_header()
dofile(dirs.installdir .. "/scripts/lua/inc/menu.lua")
print("<div class=\"alert alert-danger\"><i class='fas fa-exclamation-triangle fa-lg fa-ntopng-warning'></i> Access forbidden</div>")
end
return(false)
end
-- ##############################################
function formatBreed(breed, is_encrypted)
local ret = ""
if(breed == "Safe") then
if(is_encrypted == false) then ret = "<i class='fas fa-thumbs-up' alt='"..i18n("breed.safe").."'></i>" end
elseif(breed == "Acceptable") then
-- if(is_encrypted == false) then ret = "<i class='fas fa-thumbs-up' alt='"..i18n("breed.acceptable").."'></i>" end
elseif(breed == "Fun") then
ret = "<i class='fas fa-smile' alt='"..i18n("breed.fun").."'></i>"
elseif(breed == "Unsafe") then
ret = "<i class='fas fa-thumbs-down' style='color: red' alt='"..i18n("breed.unsafe").."'></i>"
elseif(breed == "Dangerous") then
ret = "<i class='fas fa-exclamation-triangle' alt='"..i18n("breed.dangerous").."'></i>"
end
if(is_encrypted == true) then
ret = ret .. " <i class='fas fa-lock'></i>"
end
return(ret)
end
-- ###############################################
function macInfoWithSymbName(mac, name)
return(' <A HREF="' .. ntop.getHttpPrefix() .. '/lua/mac_details.lua?host='.. mac ..'">'..name..'</A> ')
end
-- ###############################################
function macInfo(mac)
return(' <A HREF="' .. ntop.getHttpPrefix() .. '/lua/mac_details.lua?host='.. mac ..'">'..mac..'</A> ')
end
-- ###############################################
function intToIPv4(num)
return(math.floor(num / 2^24).. "." ..math.floor((num % 2^24) / 2^16).. "." ..math.floor((num % 2^16) / 2^8).. "." ..num % 2^8)
end
-- ###############################################
function formatWebSite(site)
return("<A class='ntopng-external-link' target=\"_blank\" href=\"http://"..site.."\">"..site.." <i class=\"fas fa-external-link-alt\"></i></A></th>")
end
-- ###############################################
-- prints purged information for hosts / flows
function purgedErrorString()
local info = ntop.getInfo(false)
return i18n("purged_error_message",{url=ntop.getHttpPrefix()..'/lua/admin/prefs.lua?tab=in_memory', product=info["product"]})
end
-- print TCP flags
function formatTCPFlags(flags)
local out = ''
if(hasbit(flags,0x02)) then out = out .. '<span class="badge bg-info" title="SYN">S</span> ' end
if(hasbit(flags,0x10)) then out = out .. '<span class="badge bg-info" title="ACK">A</span> ' end
if(hasbit(flags,0x01)) then out = out .. '<span class="badge bg-info" title="FIN">F</span> ' end
if(hasbit(flags,0x08)) then out = out .. '<span class="badge bg-info" title="PSH">P</span> ' end
if(hasbit(flags,0x04)) then out = out .. '<span class="badge bg-danger" title="RST">R</span> ' end
if(hasbit(flags,0x20)) then out = out .. '<span class="badge bg-primary" title="URG">U</span> ' end
if(hasbit(flags,0x40)) then out = out .. '<span class="badge bg-info" title="ECE">E</span> ' end
if(hasbit(flags,0x80)) then out = out .. '<span class="badge bg-info" title="CWR">C</span> ' end
return out
end
-- convert the integer carrying TCP flags in a more convenient lua table
function TCPFlags2table(flags)
local res = {
["FIN"] = 0, ["SYN"] = 0, ["RST"] = 0,
["PSH"] = 0, ["ACK"] = 0, ["URG"] = 0,
["ECE"] = 0, ["CWR"] = 0,
}
if(hasbit(flags,0x01)) then res["FIN"] = 1 end
if(hasbit(flags,0x02)) then res["SYN"] = 1 end
if(hasbit(flags,0x04)) then res["RST"] = 1 end
if(hasbit(flags,0x08)) then res["PSH"] = 1 end
if(hasbit(flags,0x10)) then res["ACK"] = 1 end
if(hasbit(flags,0x20)) then res["URG"] = 1 end
if(hasbit(flags,0x40)) then res["ECE"] = 1 end
if(hasbit(flags,0x80)) then res["CWR"] = 1 end
return res
end
-- ##########################################
function historicalProtoHostHref(ifId, host, l4_proto, ndpi_proto_id, info, vlan, no_print)
if ntop.isEnterpriseM() then
local now = os.time()
local ago1h = now - 3600
local prefs = ntop.getPrefs()
if prefs.is_dump_flows_to_clickhouse_enabled then
local hist_url = ntop.getHttpPrefix().."/lua/pro/db_search.lua?"
local params = {epoch_end = now, epoch_begin = ago1h, ifid = ifId}
if host then
local host_k = hostinfo2hostkey(host)
if isEmptyString(host_k) then
host_k = host
end
params["ip"] = host_k..";eq"
end
if l4_proto then
params["l4proto"] = l4_proto..";eq"
end
if ndpi_proto_id then
params["l7proto"] = ndpi_proto_id..";eq"
end
if vlan and vlan ~= 0 then
params["vlan_id"] = vlan..";eq"
end
if info then
params["info"] = info..";in"
end
local url_params = table.tconcat(params, "=", "&")
if not no_print then
print('&nbsp;')
-- print('<span class="badge bg-info">')
print('<a href="'..hist_url..url_params..'" title="'..i18n("db_explorer.last_hour_flows")..'"><i class="fas fa-search-plus"></i></a>')
-- print('</span>')
else
return '<a href="'..hist_url..url_params..'" title="'..i18n("db_explorer.last_hour_flows")..'"><i class="fas fa-search-plus"></i></a>'
end
end
end
end
-- #############################################
-- Add here the icons you guess based on the Mac address
-- TODO move to discovery stuff
local guess_icon_keys = {
["dell inc."] = "fas fa-desktop",
["vmware, inc."] = "fas fa-desktop",
["xensource, inc."] = "fas fa-desktop",
["lanner electronics, inc."] = "fas fa-desktop",
["nexcom international co., ltd."] = "fas fa-desktop",
["apple, inc."] = "fab fa-apple",
["cisco systems, inc"] = "fas fa-arrows-alt",
["juniper networks"] = "fas fa-arrows-alt",
["brocade communications systems, inc."] = "fas fa-arrows-alt",
["force10 networks, inc."] = "fas fa-arrows-alt",
["huawei technologies co.,ltd"] = "fas fa-arrows-alt",
["alcatel-lucent ipd"] = "fas fa-arrows-alt",
["arista networks, inc."] = "fas fa-arrows-alt",
["3com corporation"] = "fas fa-arrows-alt",
["routerboard.com"] = "fas fa-arrows-alt",
["extreme networks"] = "fas fa-arrows-alt",
["xerox corporation"] = "fas fa-print"
}
-- #############################################
function guessHostIcon(key)
local m = string.lower(get_manufacturer_mac(key))
local icon = guess_icon_keys[m]
if((icon ~= nil) and (icon ~= "")) then
return(" <i class='"..icon.." fa-lg'></i>")
else
return ""
end
end
-- #############################################
function setCustomDeviceType(mac, device_type)
ntop.setPref(getCustomDeviceKey(mac), tostring(device_type))
end
-- ####################################################
function tableToJsObject(lua_table)
local json = require("dkjson")
return json.encode(lua_table, nil)
end
-- ####################################################
-- @brief The difference, in seconds, between the local time of this instance and GMT
local server_timezone_diff_seconds
-- ####################################################
-- @brief Converts a datetime string into an epoch, adjusted with the client time
function makeTimeStamp(d)
local pattern = "(%d+)%/(%d+)%/(%d+) (%d+):(%d+):(%d+)"
local day, month, year, hour, minute, seconds = string.match(d, pattern);
-- Get the epoch out of d. The epoch gets adjusted by os.time in the server timezone, that is, in
-- the timezone of this running ntopng instance
-- See https://www.lua.org/pil/22.1.html
local server_epoch = os.time({year = year, month = month, day = day, hour = hour, min = minute, sec = seconds});
-- Convert the server_epoch into a gmt_epoch which is adjusted to GMT
local gmt_epoch = server_epoch + get_server_timezone_diff_seconds()
-- Finally, compute a client_epoch by adding the seconds of getFrontendTzSeconds() to the GMT epoch just computed
local client_epoch = gmt_epoch + getFrontendTzSeconds()
-- Now we can compute the deltas to know the extact number of seconds between the server and the client timezone
local server_to_gmt_delta = gmt_epoch - server_epoch
local gmt_to_client_delta = client_epoch - gmt_epoch
local server_to_client_delta = client_epoch - server_epoch
-- Make sure everything is OK...
assert(server_to_client_delta == server_to_gmt_delta + gmt_to_client_delta)
-- tprint({
-- server_ts = server_epoch,
-- gmt_ts = gmt_epoch,
-- server_to_gmt_delta = (server_to_gmt_delta) / 60 / 60,
-- gmt_to_client_delta = (gmt_to_client_delta) / 60 / 60,
-- server_to_client_delta = (server_to_client_delta) / 60 / 60
-- })
-- Return the epoch in the client timezone
return string.format("%u", server_epoch - server_to_client_delta)
end
-- ###########################################
-- Note: the base unit is Kbit/s here
FMT_TO_DATA_RATES_KBPS = {
["k"] = {label="kbit/s", value=1},
["m"] = {label="Mbit/s", value=1000},
["g"] = {label="Gbit/s", value=1000*1000},
}
FMT_TO_DATA_BYTES = {
["b"] = {label="B", value=1},
["k"] = {label="KB", value=1024},
["m"] = {label="MB", value=1024*1024},
["g"] = {label="GB", value=1024*1024*1024},
}
FMT_TO_DATA_TIME = {
["s"] = {label=i18n("metrics.secs"), value=1},
["m"] = {label=i18n("metrics.mins"), value=60},
["h"] = {label=i18n("metrics.hours"), value=3600},
["d"] = {label=i18n("metrics.days"), value=3600*24},
}
-- ###########################################
-- Note: use data-min and data-max to setup ranges
function makeResolutionButtons(fmt_to_data, ctrl_id, fmt, value, extra, max_val)
local extra = extra or {}
local html_lines = {}
local divisors = {}
-- fill in divisors
if tonumber(value) ~= nil then
-- foreach character in format
string.gsub(fmt, ".", function(k)
local v = fmt_to_data[k]
if v ~= nil then
divisors[#divisors + 1] = {k=k, v=v.value}
end
end)
end
local selected = nil
if tonumber(value) ~= 0 then
selected = highestDivisor(divisors, value, "v")
end
if selected ~= nil then
selected = divisors[selected].k
else
selected = string.sub(fmt, 1, 1)
end
local style = table.merge({display="flex"}, extra.style or {})
html_lines[#html_lines+1] = [[<div class="btn-group ]] .. table.concat(extra.classes or {}, "") .. [[" id="]] .. ctrl_id .. [[" role="group" style="]] .. table.tconcat(style, ":", "; ", ";") .. [[">]]
-- foreach character in format
string.gsub(fmt, ".", function(k)
local v = fmt_to_data[k]
if v ~= nil then
local line = {}
if((max_val == nil) or (v.value < max_val)) then
local input_name = ("opt_resbt_%s_%s"):format(k, ctrl_id)
local input = ([[
<input class="btn-check" data-resol="%s" value="%s" title="%s" name="%s" id="input-%s" autocomplete="off" type="radio" %s/>
]]):format(k, truncate(v.value), v.label, input_name, input_name, ternary((selected == k), 'checked="checked"', ""))
local label = ([[
<label class="btn btn-sm %s" for="input-%s">%s</label>
]]):format(ternary((selected == k), "btn-primary", "btn-secondary"), input_name, v.label)
line[#line+1] = input
line[#line+1] = label
html_lines[#html_lines+1] = table.concat(line, "")
end
end
end)
html_lines[#html_lines+1] = [[</div>]]
-- Note: no // comment below, only /* */
local js_init_code = [[
var _resol_inputs = [];
function resol_selector_get_input(a_button) {
return $("input", $(a_button).closest(".form-group.mb-3")).last();
}
function resol_selector_get_buttons(an_input) {
return $(".btn-group", $(an_input).closest(".form-group.mb-3")).first().find("input");
}
/* This function scales values wrt selected resolution */
function resol_selector_reset_input_range($selected) {
let duration = $($selected);
let input = resol_selector_get_input(duration);
let raw = parseInt(input.attr("data-min"));
if (! isNaN(raw))
input.attr("min", Math.sign(raw) * Math.ceil(Math.abs(raw) / duration.val()));
raw = parseInt(input.attr("data-max"));
if (! isNaN(raw))
input.attr("max", Math.sign(raw) * Math.ceil(Math.abs(raw) / duration.val()));
var step = parseInt(input.attr("data-step-" + duration.attr("data-resol")));
if (! isNaN(step)) {
input.attr("step", step);
/* Align value */
input.val(input.val() - input.val() % step);
} else
input.attr("step", "");
resol_recheck_input_range(input);
}
/*
* Remove the checked value inside the radio buttons
* and add it only to the one selected
*/
function resol_selector_change_callback(event) {
$(this).parent().find('label').removeClass('btn-primary').addClass('btn-secondary');
$(this).parent().find('input[type="radio"]').prop('checked', false);
$(this).prop('checked', true).removeClass('btn-secondary').addClass('btn-primary');
$(this).parent().find('label[for="' + $(this).attr('id') + '"]').removeClass('btn-secondary').addClass('btn-primary');
resol_selector_reset_input_range($(this));
}
/* Function used to check the value input range */
function resol_recheck_input_range(input) {
let value = input.val();
if (input[0].hasAttribute("min") && Number.isNaN(input.attr("min")))
value = Math.max(parseInt(input.val()), !input.attr("min"));
if (input[0].hasAttribute("max") && Number.isNaN(input.attr("max")))
value = Math.min(parseInt(input.val()), !input.attr("max"));
if ((input.val() != "") && (input.val() != value))
input.val(value);
}
function resol_selector_on_form_submit(event) {
var form = $(this);
if (event.isDefaultPrevented() || (form.find(".has-error").length > 0))
return false;
resol_selector_finalize(form);
return true;
}
function resol_selector_get_raw(input) {
var buttons = resol_selector_get_buttons(input);
var selected = buttons.filter(":checked");
return parseInt(selected.val()) * parseInt(input.val());
}
function resol_selector_finalize(form) {
$.each(_resol_inputs, function(i, elem) {
/* Skip elements which are not part of the form */
if (! $(elem).closest("form").is(form))
return;
var selected = $(elem).find("input[checked]");
var input = resol_selector_get_input(selected);
/* transform in raw units */
var new_input = $("<input type=\"hidden\"/>");
new_input.attr("name", input.attr("name"));
input.removeAttr("name");
new_input.val(resol_selector_get_raw(input));
new_input.appendTo(form);
});
/* remove added input names */
$("input[name^=opt_resbt_]", form).removeAttr("name");
}]]
local js_specific_code = [[
$("#]] .. ctrl_id .. [[ input").change(resol_selector_change_callback);
$(function() {
var elemid = "#]] .. ctrl_id .. [[";
_resol_inputs.push(elemid);
var selected = $(elemid + " input[checked]");
resol_selector_reset_input_range(selected);
/* setup the form submit callback (only once) */
var form = selected.closest("form");
if (! form.attr("data-options-handler")) {
form.attr("data-options-handler", 1);
form.submit(resol_selector_on_form_submit);
}
});
]]
-- join strings and strip newlines
local html = string.gsub(table.concat(html_lines, " "), "\n", "")
js_init_code = string.gsub(js_init_code, "", "")
js_specific_code = string.gsub(js_specific_code, "\n", "")
if tonumber(value) ~= nil then
-- returns the new value with selected resolution
return {html=html, init=js_init_code, js=js_specific_code, value=tonumber(value) / fmt_to_data[selected].value}
else
return {html=html, init=js_init_code, js=js_specific_code, value=nil}
end
end
-- ###########################################
--
-- Extracts parameters from a lua table.
-- This function performs the inverse conversion of javascript paramsPairsEncode.
--
-- Note: plain parameters (not encoded with paramsPairsEncode) remain unchanged only
-- when strict mode is *not* enabled
--
function paramsPairsDecode(params, strict_mode)
local res = {}
for k,v in pairs(params) do
local sp = split(k, "key_")
if #sp == 2 then
local keyid = sp[2]
local value = "val_"..keyid
if params[value] then
res[v] = params[value]
end
end
if((not strict_mode) and (res[v] == nil)) then
-- this is a plain parameter
res[k] = v
end
end
return res
end
function isBridgeInterface(ifstats)
return ifstats.inline
end
function hasSnmpDevices(ifid)
if (not ntop.isEnterpriseM()) or (not isAdministrator()) then
return false
end
return has_snmp_devices(ifid)
end
function stripVlan(name)
local key = string.split(name, "@")
if((key ~= nil) and (#key == 2)) then
-- Verify that the host is actually an IP address and the VLAN actually
-- a number to avoid stripping things that are not vlans (e.g. part of an host name)
local addr = key[1]
if((tonumber(key[2]) ~= nil) and (isIPv6(addr) or isIPv4(addr))) then
return(addr)
end
end
return(name)
end
-- ###########################################
-- avoids manual HTTP prefix and /lua concatenation
function page_url(path)
return ntop.getHttpPrefix().."/lua/"..path
end
-- extracts a page url from the path
function path_get_page(path)
local prefix = ntop.getHttpPrefix() .. "/lua/"
if string.find(path, prefix) == 1 then
return string.sub(path, string.len(prefix) + 1)
end
return path
end
-- ###########################################
function swapKeysValues(tbl)
local new_tbl = {}
for k, v in pairs(tbl or {}) do
new_tbl[v] = k
end
return new_tbl
end
-- ###########################################
function tsQueryToTags(query)
local tags = {}
for _, part in pairs(split(query, ",")) do
local sep_pos = string.find(part, ":")
if sep_pos then
local k = string.sub(part, 1, sep_pos-1)
local v = string.sub(part, sep_pos+1)
tags[k] = v
end
end
return tags
end
function tsTagsToQuery(tags)
return table.tconcat(tags, ":", ",")
end
-- ###########################################
function splitUrl(url)
local params = {}
local parts = split(url, "?")
if #parts == 2 then
url = parts[1]
parts = split(parts[2], "&")
for _, param in pairs(parts) do
local p = split(param, "=")
if #p == 2 then
params[p[1]] = p[2]
end
end
end
return {
url = url,
params = params,
}
end
-- ###########################################
function visualTsKey(tskey)
if ends(tskey, "_v4") or ends(tskey, "_v6") then
local ver = string.sub(tskey, string.len(tskey)-1, string.len(tskey))
local address = string.sub(tskey, 1, string.len(tskey)-3)
local visual_addr
if ver == "v4" then
visual_addr = address
else
visual_addr = address .. " (" .. ver ..")"
end
return visual_addr
end
return tskey
end
-- ##############################################
--- Return an HTML `select` element with passed options.
--
function generate_select(id, name, is_required, is_disabled, options, additional_classes)
local required_flag = (is_required and "required" or "")
local disabled_flag = (is_disabled and "disabled" or "")
local name_attr = (name ~= "" and "name='" .. name .. "'" or "")
local parsed_options = ""
for i, option in ipairs(options) do
parsed_options = parsed_options .. ([[
<option ]].. (i == 1 and "selected" or "") ..[[ value="]].. option.value ..[[">]].. option.title ..[[</option>
]])
end
return ([[
<select id="]].. id ..[[" class="form-select ]] .. (additional_classes or "") .. [[" ]].. name_attr ..[[ ]].. required_flag ..[[ ]] .. disabled_flag ..[[>
]].. parsed_options ..[[
</select>
]])
end
-- ###########################################
-- Compares IPv4 / IPv6 addresses
function ip_address_asc(a, b)
return(ntop.ipCmp(a, b) < 0)
end
function ip_address_rev(a, b)
return(ntop.ipCmp(a, b) > 0)
end
-- ###########################################
-- @brief Deletes all the cache/prefs keys matching the pattern
function deleteCachePattern(pattern)
local keys = ntop.getKeysCache(pattern)
for key in pairs(keys or {}) do
ntop.delCache(key)
end
end
-- ###########################################
-- NOTE: '~= "0"' is used for prefs which are enabled by default
function areInterfaceTimeseriesEnabled(ifid)
return((ntop.getPref("ntopng.prefs.interface_rrd_creation") ~= "0"))
end
function areInterfaceL7TimeseriesEnabled(ifid)
return(areInterfaceTimeseriesEnabled(ifid) and
(ntop.getPref("ntopng.prefs.interface_ndpi_timeseries_creation") ~= "per_category"))
end
function areInterfaceCategoriesTimeseriesEnabled(ifid)
local rv = ntop.getPref("ntopng.prefs.interface_ndpi_timeseries_creation")
-- note: categories are disabled by default
return(areInterfaceTimeseriesEnabled(ifid) and
((rv == "per_category") or (rv == "both")))
end
function areHostTimeseriesEnabled(ifid)
local rv = ntop.getPref("ntopng.prefs.hosts_ts_creation")
if isEmptyString(rv) then rv = "light" end
return((rv == "light") or (rv == "full"))
end
function areHostL7TimeseriesEnabled(ifid)
local rv = ntop.getPref("ntopng.prefs.host_ndpi_timeseries_creation")
-- note: host protocols are disabled by default
return((ntop.getPref("ntopng.prefs.hosts_ts_creation") == "full") and
((rv == "per_protocol") or (rv == "both")))
end
function areHostCategoriesTimeseriesEnabled(ifid)
local rv = ntop.getPref("ntopng.prefs.host_ndpi_timeseries_creation")
-- note: host protocols are disabled by default
return((ntop.getPref("ntopng.prefs.hosts_ts_creation") == "full") and
((rv == "per_category") or (rv == "both")))
end
function areSystemTimeseriesEnabled()
return(ntop.getPref("ntopng.prefs.system_probes_timeseries") ~= "0")
end
function areHostPoolsTimeseriesEnabled(ifid)
return(ntop.isPro() and (ntop.getPref("ntopng.prefs.host_pools_rrd_creation") == "1"))
end
function areASTimeseriesEnabled(ifid)
return(ntop.getPref("ntopng.prefs.asn_rrd_creation") == "1")
end
function areInternalTimeseriesEnabled(ifid)
-- NOTE: no separate preference so far
return(areSystemTimeseriesEnabled())
end
function areCountryTimeseriesEnabled(ifid)
return((ntop.getPref("ntopng.prefs.country_rrd_creation") == "1"))
end
function areOSTimeseriesEnabled(ifid)
return((ntop.getPref("ntopng.prefs.os_rrd_creation") == "1"))
end
function areVlanTimeseriesEnabled(ifid)
return(ntop.getPref("ntopng.prefs.vlan_rrd_creation") == "1")
end
function areMacsTimeseriesEnabled(ifid)
return(ntop.getPref("ntopng.prefs.l2_device_rrd_creation") == "1")
end
function areContainersTimeseriesEnabled(ifid)
-- NOTE: no separate preference so far
return(true)
end
function areSnmpTimeseriesEnabled(device, port_idx)
return(ntop.getPref("ntopng.prefs.snmp_devices_rrd_creation") == "1")
end
function areFlowdevTimeseriesEnabled(ifid, device)
return(ntop.getPref("ntopng.prefs.flow_device_port_rrd_creation") == "1")
end
-- ###########################################
-- version is major.minor.veryminor
function version2int(v)
if(v == nil) then return(0) end
e = string.split(v, "%.");
if(e ~= nil) then
major = e[1]
minor = e[2]
veryminor = e[3]
if(major == nil or tonumber(major) == nil or type(major) ~= "string") then major = 0 end
if(minor == nil or tonumber(minor) == nil or type(minor) ~= "string") then minor = 0 end
if(veryminor == nil or tonumber(veryminor) == nil or type(veryminor) ~= "string") then veryminor = 0 end
version = tonumber(major)*1000 + tonumber(minor)*100 -- + tonumber(veryminor)
return(version)
else
return(0)
end
end
--- Check if there is a new major release
--- @return string message If there is a new major release then return a non-nil string
--- containing the update message.
function check_latest_major_release()
if ntop.isOffline() then
return nil
end
-- get the latest major release
local latest_version = ntop.getCache("ntopng.cache.major_release")
-- tprint(debug.traceback())
if isEmptyString(latest_version) then
local rsp = ntop.httpGet("https://www.ntop.org/ntopng.version", "", "", 10 --[[ seconds ]])
if (not isEmptyString(rsp)) and (not isEmptyString(rsp["CONTENT"])) then
latest_version = trimSpace(string.gsub(rsp["CONTENT"], "\n", ""))
else
-- a value that won't trigger an update message
latest_version = "0.0.0"
end
ntop.setCache("ntopng.cache.major_release", latest_version, 86400 --[[ recheck interval]])
end
return get_version_update_msg(info, latest_version)
end
-- ###########################################
-- To be called inside the flows tableCallback
function initFlowsRefreshRows()
print[[
datatableInitRefreshRows($("#table-flows"), "key_and_hash", 10000, {
/* List of rows with trend icons */
"column_thpt": ]] print(ternary(getThroughputType() ~= "bps", "NtopUtils.fpackets", "NtopUtils.bitsToSize")) print[[,
"column_bytes": NtopUtils.bytesToSize,
});
$("#dt-bottom-details > .float-left > p").first().append('. ]]
print(i18n('flows_page.idle_flows_not_listed'))
print[[');]]
end
-- ###########################################
function canRestoreHost(ifid, ip, vlan)
local ip_to_mac = string.format("ntopng.ip_to_mac.ifid_%u__%s@%d", ifid, ip, vlan)
local key_to_check
-- Check if there is a MAC address associated
local mac = ntop.getCache(ip_to_mac)
if not isEmptyString(mac) then
key_to_check = string.format("ntopng.serialized_hostsbymac.ifid_%u__%s_%s", ifid, mac, ternary(isIPv4(ip), "v4", "v6"))
else
key_to_check = string.format("ntopng.serialized_hosts.ifid_%u__%s@%d", ifid, ip, vlan)
end
return(not table.empty(ntop.getKeysCache(key_to_check)))
end
-- ###########################################
function build_query_url(excluded)
local query = "?"
for key, value in pairs(_GET) do
if not(table.contains(excluded, key)) then
query = query .. string.format("%s=%s&", key, value)
end
end
return query
end
-- ###########################################
function build_query_params(params)
local query = "?"
local t = {}
for key, value in pairs(params) do
t[#t+1] = string.format("%s=%s", key, value)
end
return query .. table.concat(t, '&')
end
-- ###########################################
function create_ndpi_proto_name(v)
local app = ""
if v["proto.ndpi"] then
app = getApplicationLabel(v["proto.ndpi"])
else
local master_proto = interface.getnDPIProtoName(tonumber(v["l7_master_proto"]))
local app_proto = interface.getnDPIProtoName(tonumber(v["l7_proto"]))
if master_proto == app_proto then
app = app_proto
elseif master_proto == "Unknown" then
app = app_proto
else
app = master_proto
if app_proto ~= "Unknown" then
app = app .. "." .. app_proto
end
end
app = getApplicationLabel(app)
end
return app
end
-- ###########################################
function buildHostHREF(ip_address, vlan_id, page)
local stats
if(stats == nil) then
stats = interface.getHostInfo(ip_address, vlan_id)
else
stats = stats.stats
end
if(stats == nil) then
return(ip_address)
else
local hinfo = hostkey2hostinfo(ip_address)
local name = hostinfo2label(hinfo)
local res
if((name == nil) or (name == "")) then name = ip_address end
res = '<A HREF="'..ntop.getHttpPrefix()..'/lua/host_details.lua?host='..ip_address
if(vlan_id and (vlan_id ~= 0)) then res = res .. "@"..vlan_id end
res = res ..'&page='..page..'">'..name..'</A>'
return(res)
end
end
function builMapHREF(service_peer, map, page)
-- Getting minimal stats to know if the host is still present in memory
local name
local vlan = service_peer.vlan
service_peer.vlan = nil
local map_url = ntop.getHttpPrefix()..'/lua/pro/enterprise/network_maps.lua?map=' .. map .. '&page=' .. page .. '&' ..hostinfo2url(service_peer)
local host_url = ''
local host_icon
if vlan then
map_url = map_url .. '&vlan_id=' .. vlan
end
-- Getting stats and formatting initial href
if (service_peer.ip or service_peer.host) and not service_peer.is_mac then
-- Host URL only if the host is active
host_url = hostinfo2detailsurl({host = service_peer.ip or service_peer.host, vlan = vlan}, nil, true --[[ check of the host is active --]])
local hinfo = interface.getHostMinInfo(service_peer.ip or service_peer.host, vlan)
name = hostinfo2label(hinfo or service_peer)
host_icon = "fa-laptop"
else
local minfo = interface.getMacInfo(service_peer.host)
-- The URL only if the MAC is active
if minfo and table.len(minfo) > 0 then
host_url = ntop.getHttpPrefix()..'/lua/mac_details.lua?'..hostinfo2url(service_peer)
end
if (service_peer.ip and service_peer.is_mac) or not service_peer.is_mac then
local hinfo = interface.getHostMinInfo(service_peer.ip or service_peer.host, vlan)
name = hostinfo2label(hinfo or service_peer)
else
name = mac2label(service_peer.host)
end
if isMacAddress(name) then
name = get_symbolic_mac(name, true)
end
host_icon = "fa-microchip"
end
-- Getting the name if present
name = name or service_peer.host
if vlan and tonumber(vlan) ~= 0 then
name = name .. '@' .. getFullVlanName(vlan)
end
local res
if not isEmptyString(host_url) then
res = string.format('<a href="%s">%s</a> <a href="%s"><i class="fas %s"></i></a>', map_url, name, host_url, host_icon)
else
res = string.format('<a href="%s">%s</a>', map_url, name)
end
return res
end
-- #####################
-- Used by REST v1
function formatAlertAHref(key, value, label)
return "<a class='tag-filter' data-tag-key='" .. key .. "' title='" .. value .. "' data-tag-value='" .. value .. "' data-tag-label='" .. label .. "' href='#'>" .. label .. "</a>"
end
-- ##############################################
function setObsPointAlias(observation_point_id, alias)
if((observation_point_id ~= alias) and not isEmptyString(alias)) then
ntop.setHashCache(getObsPointAliasKey(), observation_point_id, alias)
else
ntop.delHashCache(getObsPointAliasKey(), observation_point_id)
end
end
-- ##############################################
function addScoreToAlertDescr(msg, score)
return (msg .. string.format(" [%s: %s]",
i18n("score"),
format_utils.formatValue(score)))
end
-- ##############################################
function addHTTPInfoToAlertDescr(msg, alert_json, url_only)
if ((alert_json)
and (table.len(alert_json["proto"] or {}) > 0)
and (table.len(alert_json["proto"]["http"]) > 0)) then
local http_info = format_http_info({ http_info = alert_json["proto"]["http"]["last_method"],
last_return_code = alert_json["proto"]["http"]["last_return_code"],
last_user_agent = alert_json["proto"]["http"]["last_user_agent"],
last_url = alert_json["proto"]["http"]["last_url"] })
if http_info["last_method"] then
msg = msg .. string.format(" [ %s: %s ]", i18n("db_explorer.http_method"), http_info["last_method"])
end
if http_info["last_return_code"] then
msg = msg .. string.format(" [ %s: %s ]", i18n("last_response_status_code"), http_info["last_return_code"])
end
if http_info["last_user_agent"] then
msg = msg .. string.format(" [ %s: %s ]", i18n("last_user_agent"), http_info["last_user_agent"])
end
if http_info["last_url"] then
msg = msg .. string.format(" [ %s: %s ]", i18n("last_url"), http_info["last_url"])
end
end
return msg
end
-- ##############################################
function addDNSInfoToAlertDescr(msg, alert_json)
if ((alert_json)
and (table.len(alert_json["proto"] or {}) > 0)
and (table.len(alert_json["proto"]["dns"] or {}) > 0)) then
local dns_info = format_dns_query_info({ last_query_type = alert_json["proto"]["dns"]["last_query_type"],
last_return_code = alert_json["proto"]["dns"]["last_return_code"],
last_query = alert_json["proto"]["dns"]["last_query"] })
if dns_info["last_query_type"] then
msg = msg .. string.format(" [ %s: %s ]",
i18n("last_query_type"),
dns_info["last_query_type"])
end
if dns_info["last_return_code"] then
msg = msg .. string.format(" [ %s: %s ]",
i18n("last_return_code"),
dns_info["last_return_code"])
end
if dns_info["last_query"] then
msg = msg .. string.format(" [ %s: %s ]", i18n("last_url"), dns_info["last_query"])
end
end
return msg
end
-- ##############################################
function addTLSInfoToAlertDescr(msg, alert_json)
if ((alert_json)
and (table.len(alert_json["proto"] or {}) > 0)
and (table.len(alert_json["proto"]["tls"] or {}) > 0)) then
local tls_info = format_tls_info({ notBefore = alert_json["proto"]["tls"]["notBefore"],
notAfter = alert_json["proto"]["tls"]["notAfter"],
client_requested_server_name = alert_json["proto"]["tls"]["client_requested_server_name"],
['ja3.server_unsafe_cipher'] = alert_json["proto"]["tls"]["ja3.server_unsafe_cipher"] })
if tls_info["tls_certificate_validity"] then
msg = msg .. string.format(" [ %s: %s ]", i18n("tls_certificate_validity"), tls_info["tls_certificate_validity"])
end
if tls_info["ja3.server_unsafe_cipher"] then
msg = msg .. string.format(" [ %s: %s ]", i18n("ja3.server_unsafe_cipher"), tls_info["ja3.server_unsafe_cipher"])
end
if tls_info["client_requested_server_name"] then
msg = msg .. string.format(" [ %s: %s ]", i18n("client_requested_server_name"), tls_info["client_requested_server_name"])
end
end
return msg
end
-- ##############################################
function addICMPInfoToAlertDescr(msg, alert_json)
if ((alert_json)
and (table.len(alert_json["proto"] or {}) > 0)
and (table.len(alert_json["proto"]["icmp"] or {}) > 0)) then
local icmp_info = format_icmp_info({ code = alert_json["proto"]["icmp"]["code"],
type = alert_json["proto"]["icmp"]["type"] })
-- Already formatted by the function
if icmp_info["type"] then
msg = msg .. string.format(" [ %s: %s ]", i18n("icmp_type"), icmp_info["type"])
end
if icmp_info["code"] then
msg = msg .. string.format(" [ %s: %s ]", i18n("icmp_code"), icmp_info["code"])
end
end
return msg
end
-- ##############################################
function addBytesInfoToAlertDescr(msg, value)
msg = string.format("%s [ %s: %s | %s: %s ]", msg,
i18n("server_traffic"), bytesToSize(value["srv2cli_bytes"] or 0),
i18n("client_traffic"), bytesToSize(value["cli2srv_bytes"] or 0))
return msg
end
-- ##############################################
function addExtraFlowInfo(alert_json, value)
local msg = ""
msg = addHTTPInfoToAlertDescr(msg, alert_json)
msg = addDNSInfoToAlertDescr(msg, alert_json)
msg = addTLSInfoToAlertDescr(msg, alert_json)
msg = addICMPInfoToAlertDescr(msg, alert_json)
msg = addBytesInfoToAlertDescr(msg, value)
return msg
end
-- ##############################################
function hostnameIsDomain(name)
if not isEmptyString(name) then
local parts = string.split(name, "%.")
if parts and #parts > 1 then
local last = parts[#parts]
if string.len(last) > 0 then
return true
end
end
end
return false
end
-- ##############################################
local iec104_typeids = {
M_SP_NA_1=0x01,
M_SP_TA_1=0x02,
M_DP_NA_1=0x03,
M_DP_TA_1=0x04,
M_ST_NA_1=0x05,
M_ST_TA_1=0x06,
M_BO_NA_1=0x07,
M_BO_TA_1=0x08,
M_ME_NA_1=0x09,
M_ME_TA_1=0x0A,
M_ME_NB_1=0x0B,
M_ME_TB_1=0x0C,
M_ME_NC_1=0x0D,
M_ME_TC_1=0x0E,
M_IT_NA_1=0x0F,
M_IT_TA_1=0x10,
M_EP_TA_1=0x11,
M_EP_TB_1=0x12,
M_EP_TC_1=0x13,
M_PS_NA_1=0x14,
M_ME_ND_1=0x15,
M_SP_TB_1=30,
M_DP_TB_1=31,
M_ST_TB_1=32,
M_BO_TB_1=33,
M_ME_TD_1=34,
M_ME_TE_1=35,
M_ME_TF_1=36,
M_IT_TB_1=37,
M_EP_TD_1=38,
M_EP_TE_1=39,
M_EP_TF_1=40,
ASDU_TYPE_41=41,
ASDU_TYPE_42=42,
ASDU_TYPE_43=43,
ASDU_TYPE_44=44,
C_SC_NA_1=45,
C_DC_NA_1=46,
C_RC_NA_1=47,
C_SE_NA_1=48,
C_SE_NB_1=49,
C_SE_NC_1=50,
C_BO_NA_1=51,
C_SC_TA_1=58,
C_DC_TA_1=59,
C_RC_TA_1=60,
C_SE_TA_1=61,
C_SE_TB_1=62,
C_SE_TC_1=63,
C_BO_TA_1=64,
M_EI_NA_1=70,
C_IC_NA_1=100,
C_CI_NA_1=101,
C_RD_NA_1=102,
C_CS_NA_1=103,
C_TS_NA_1=104,
C_RP_NA_1=105,
C_CD_NA_1=106,
C_TS_TA_1=107,
P_ME_NA_1=110,
P_ME_NB_1=111,
P_ME_NC_1=112,
P_AC_NA_1=113,
F_FR_NA_1=120,
F_SR_NA_1=121,
F_SC_NA_1=122,
F_LS_NA_1=123,
F_FA_NA_1=124,
F_SG_NA_1=125,
F_DR_TA_1=126,
}
function iec104_typeids2str(c)
if(c == nil) then return end
for s, v in pairs(iec104_typeids) do
if(v == tonumber(c)) then
return(s.." (".. v ..")")
end
end
return(c)
end
function add_historical_flow_explorer_button_ref(extra_params)
if (ntop.getPrefs()["is_dump_flows_to_clickhouse_enabled"]) == false then
return ''
end
local base_url = ntop.getHttpPrefix() .. "/lua/pro/db_search.lua?"
for k, v in pairs(extra_params) do
base_url = base_url .. k .. "=" .. v["value"] .. ";" .. v["operator"]
end
local button = '<a href="' .. base_url .. '" data-placement="bottom" class="btn btn-sm btn-primary" title="Historical Flow Explorer"><i class="fas fa-search-plus"></i></a>'
return button
end
function add_delete_obs_point_button()
local button = ''
if isAdministrator() then
button = '<a href="#delete_obs_point_stats" data-placement="bottom" data-bs-toggle="modal" class="btn btn-sm btn-danger" title="Remove"><i class="fas fa fa-trash"></i></a>'
end
return button
end
function format_device_name(device_ip, short_version)
local device_name = device_ip
if ntop.isPro() then
device_name = hostinfo2label(hostkey2hostinfo(device_ip))
if device_name ~= device_ip then
if short_version then
device_name = shortenString(device_name, 32)
end
device_name = string.format('%s [%s]', device_ip, device_name)
end
end
return device_name
end
-- ##############################################
local _snmp_devices = {}
-- @brief This function format the SNMP interface name.
-- @params device_ip: snmp device ip
-- portidx: number or string, interface index to format
-- short_version: boolean, long formatting version (e.g. flow info) or short version (e.g. dropdown menu)
function format_portidx_name(device_ip, portidx, short_version, shorten_string)
local idx_name = portidx
-- SNMP is available only with Pro version at least
if ntop.isPro() then
local cached_dev = _snmp_devices[device_ip]
if(cached_dev == nil) then
local snmp_cached_dev = require "snmp_cached_dev"
cached_dev = snmp_cached_dev:get_interface_names(device_ip)
_snmp_devices[device_ip] = cached_dev
end
if (cached_dev) and (cached_dev["interfaces"]) then
local port_info = cached_dev["interfaces"][tostring(portidx)]
if port_info then
package.path = dirs.installdir .. "/pro/scripts/lua/modules/?.lua;" .. package.path
snmp_location = require "snmp_location"
if not port_info["id"] then
port_info["id"] = portidx
port_info["snmp_device_ip"] = cached_dev["host_ip"]
end
if short_version then
local name = port_info["name"]
if shorten_string then
name = shortenString(name)
end
idx_name = string.format('%s', name);
else
idx_name = string.format('%s',
i18n("snmp.interface_device_2", {
interface=snmp_location.snmp_port_link(port_info, true),
--device=snmp_location.snmp_device_link(cached_dev["host_ip"])
})
)
end
end
end
end
return idx_name
end
-- ##############################################
-- @brief Given a table of values, if available, it's going to format the values with the standard
-- info and then return the same table formatted
function format_dns_query_info(dns_info)
if dns_info.last_query_type then
dns_info.last_query_type = string.format('<span class="badge bg-info">%s</span>', dns_utils.getQueryType(dns_info.last_query_type))
end
if dns_info.last_return_code then
local badge = get_badge(dns_info.last_return_code)
dns_info.last_return_code = string.format('<span class="badge bg-%s">%s</span>', badge, dns_utils.getResponseStatusCode(dns_info.last_return_code))
end
if dns_info.last_query then
local url = dns_info["last_query"]
url = string.gsub(url, " ", "") -- Clean the URL from spaces and %20, spaces in html
if not string.find(url, '*') then
dns_info.last_query = i18n("external_link_url", { proto = 'https', url = url, url_name = dns_info["last_query"] })
end
end
return dns_info
end
-- ##############################################
function format_tls_info(tls_info)
if tls_info.notBefore then
tls_info.notBefore = formatEpoch(tls_info.notBefore)
end
if tls_info.notAfter then
tls_info.notAfter = formatEpoch(tls_info.notAfter)
end
if tls_info.notBefore and tls_info.notAfter then
tls_info["tls_certificate_validity"] = string.format("%s - %s", tls_info.notBefore, tls_info.notAfter)
tls_info.notBefore = nil
tls_info.notAfter = nil
end
if tls_info.tls_version then
if tls_info.tls_version > 0 then
tls_info["tls_version"] = ntop.getTLSVersionName(tls_info.tls_version)
else
tls_info["tls_version"] = nil
end
end
if tls_info.client_requested_server_name then
local url = tls_info["client_requested_server_name"]
url = string.gsub(url, " ", "") -- Clean the URL from spaces and %20, spaces in html
if not string.find(url, '*') then
tls_info["client_requested_server_name"] = i18n("external_link_url", { proto = 'https', url = url, url_name = url})
end
end
if tls_info["ja3.server_cipher"] then
tls_info["ja3.server_cipher"] = nil
end
if tls_info["ja3.server_unsafe_cipher"] then
local badge = get_badge(tls_info["ja3.server_unsafe_cipher"] == "safe")
tls_info["ja3.server_unsafe_cipher"] = string.format('<span class="badge bg-%s">%s</span>', badge, tls_info["ja3.server_unsafe_cipher"])
end
if tls_info["ja3.server_hash"] then
tls_info["ja3.server_hash"] = i18n("copy_button", { full_name = tls_info["ja3.server_hash"], name = tls_info["ja3.server_hash"] })
end
if tls_info["ja3.client_hash"] then
tls_info["ja3.client_hash"] = i18n("copy_button", { full_name = tls_info["ja3.client_hash"], name = tls_info["ja3.client_hash"] })
end
if tls_info["server_names"] then
tls_info["server_names"] = i18n("copy_button", { full_name = tls_info["server_names"], name = shortenString(tls_info["server_names"],128) })
end
return tls_info
end
-- ##############################################
function format_icmp_info(icmp_info)
local icmp_utils = require "icmp_utils"
if icmp_info.code then
icmp_info.code = icmp_utils.get_icmp_code(icmp_info.type, icmp_info.code)
end
if icmp_info.type then
icmp_info.type = icmp_utils.get_icmp_type(icmp_info.type)
end
return icmp_info
end
-- ##############################################
function format_http_info(http_info)
if http_info["last_return_code"] then
local badge = get_badge(http_info.last_return_code == 200)
http_info["last_return_code"] = string.format('<span class="badge bg-%s">%s</span>', badge, http_utils.getResponseStatusCode(http_info["last_return_code"]))
end
if http_info["last_method"] then
http_info["last_method"] = string.format('<span class="badge bg-info">%s</span>', http_info["last_method"])
end
if http_info["last_url"] then
local url = http_info["last_url"]
if string.find(http_info["last_url"], '^/') then
url = (http_info["server_name"] or "") .. http_info["last_url"]
end
url = string.gsub(url, " ", "") -- Clean the URL from spaces and %20, spaces in html
if not string.find(url, '*') then
http_info["last_url"] = i18n("external_link_url", { proto = 'http', url = url, url_name = url})
end
end
if http_info["server_name"] then
http_info["server_name"] = i18n("copy_button", { full_name = http_info["server_name"], name = shortenString(http_info["server_name"], 32)})
end
return http_info
end
-- ##############################################
function format_common_info(flow_info, formatted_info)
local predominant_bytes = i18n("traffic_srv_to_cli")
if (tonumber(flow_info["cli2srv_bytes"] or 0)) > (tonumber(flow_info["srv2cli_bytes"] or 0)) then
predominant_bytes = i18n("traffic_cli_to_srv")
end
formatted_info["predominant_direction"] = predominant_bytes
formatted_info["server_traffic"] = bytesToSize(flow_info["srv2cli_bytes"] or 0)
formatted_info["client_traffic"] = bytesToSize(flow_info["cli2srv_bytes"] or 0)
return formatted_info
end
-- ##############################################
function format_proto_info(proto_info)
local proto_details = {}
for key, value in pairs(proto_info) do
if type(value) ~= "table" then
proto_info[key] = nil
end
end
for proto, info in pairs(proto_info or {}) do
if proto == "tls" then
proto_details[proto] = format_tls_info(info)
elseif proto == "dns" then
proto_details[proto] = format_dns_query_info(info)
elseif proto == "http" then
proto_details[proto] = format_http_info(info)
elseif proto == "icmp" then
proto_details[proto] = format_icmp_info(info)
end
break
end
return proto_details
end
-- ##############################################
-- @brief This function, given an IP and a vlan return the concat of host@vlan
-- @params host_ip: A string containing the IP
-- vlan: A string or a number containing the vlan id
-- @return A string IP@vlan
function format_ip_vlan(ip, vlan)
local host = ip
if (vlan) and (tonumber(vlan) ~= 0) then
host = host .. '@' .. (tonumber(vlan) or vlan)
end
return host
end
-- ##############################################
-- @brief This function, given an alert and "cli" or "srv" string is going to return the formatted hostname
-- @params alert: A table with the alert infos
-- cli_srv: A string "cli" or "srv" used to get the required info
-- @return A string hostname@vlan
function format_alert_hostname(alert, cli_srv)
local host = alert[cli_srv .. "_name"]
if(isEmptyString(host)) then
host = alert[cli_srv .. "_ip"]
end
return format_ip_vlan(shortenString(host, 26), alert["vlan"])
end
-- ##############################################
-- @brief This function format the info field used in tables
-- @params info: A string containing the info field
-- no_html: A boolean, true if no_html is requested (e.g. Download in CSV format),
-- false otherwise
-- @return A string containing the info field formatted
function format_external_link(url, name, no_html, proto)
local external_field = url
proto = ternary(((proto) and (proto == 'http')), 'http', 'https')
if no_html == false then
if not isEmptyString(url) and not string.find(url, '*') then
url = string.gsub(url, " ", "") -- Clean the URL from spaces and %20, spaces in html
external_field = i18n("external_link_url", { proto = proto, url = url, url_name = name})
end
end
return external_field
end
-- ##############################################
function format_confidence_badge(confidence, shorten_string)
local badge = ""
if confidence == 0 then
badge = "<span class=\"badge bg-warning\" title=\"" .. get_confidence(confidence) .. "\">" .. get_confidence(confidence, shorten_string) .. "</span>"
elseif confidence then
badge = "<span class=\"badge bg-success\" title=\"" .. get_confidence(confidence) .. "\">" .. get_confidence(confidence, shorten_string) .. "</span>"
end
return badge
end
-- ##############################################
function format_query_direction(op, val)
local historical_flow_utils = require "historical_flow_utils"
local direction_where = ""
if val == "0" then
direction_where = "(" .. historical_flow_utils.get_flow_column_by_tag("cli_location") .. " " .. op .. " '0' AND " ..
historical_flow_utils.get_flow_column_by_tag("srv_location") .. " " .. op .. " '0')"
elseif val == "1" then
direction_where = "(" .. historical_flow_utils.get_flow_column_by_tag("cli_location") .. " " .. op .. " '1' AND " ..
historical_flow_utils.get_flow_column_by_tag("srv_location") .. " " .. op .. " '1')"
elseif val == "2" then
direction_where = "(" .. historical_flow_utils.get_flow_column_by_tag("cli_location") .. " " .. op .. " '0' AND " ..
historical_flow_utils.get_flow_column_by_tag("srv_location") .. " " .. op .. " '1')"
elseif val == "3" then
direction_where = "(" .. historical_flow_utils.get_flow_column_by_tag("cli_location") .. " " .. op .. " '1' AND " ..
historical_flow_utils.get_flow_column_by_tag("srv_location") .. " " .. op .. " '0')"
end
return direction_where
end
-- ##############################################
function format_confidence_from_json(record)
local json = require "dkjson"
local alert_json = {}
local confidence = nil
if record["ALERT_JSON"] then
alert_json = json.decode(record["ALERT_JSON"])
elseif record["json"] then
alert_json = json.decode(record["json"])
end
if (alert_json) and
(alert_json.proto) and
(alert_json.proto.confidence) and
(not isEmptyString(alert_json.proto.confidence)) then
confidence = get_confidence(alert_json.proto.confidence)
end
return confidence
end
-- ##############################################
function format_location_badge(location)
local loc = string.lower(location) or ""
if loc == "l" then
loc = i18n("details.label_short_local_host_badge")
elseif loc == "r" then
loc = i18n("details.label_short_remote_host_badge")
end
return loc
end
-- ##############################################
function format_name_value(name, value, shorten)
local formatted_name_value = value
if not isEmptyString(name) and name ~= value then
if (shorten) and (shorten == true) then
formatted_name_value = shortenString(name) .. " [" .. value .. "]"
else
formatted_name_value = name .. " [" .. value .. "]"
end
end
return formatted_name_value
end
-- ##############################################
function format_utils.formatSNMPInterface(snmpdevice, interface_index)
local interface_name = format_portidx_name(snmpdevice, interface_index)
return string.format('%s (%s)', interface_index, (interface_name))
end
-- ##############################################
function map_score_to_severity(score)
if score ~= nil then
return ntop.mapScoreToSeverity(score)
end
return ntop.mapScoreToSeverity(0)
end
-- ##############################################
require "lua_utils_print"
require "lua_utils_get"
-- ##############################################
-- Note that ifname can be set by Lua.cpp so don't touch it if already defined
if((ifname == nil) and (_GET ~= nil)) then
ifname = _GET["ifid"]
if(ifname ~= nil) then
if(ifname.."" == tostring(tonumber(ifname)).."") then
-- ifname does not contain the interface name but rather the interface id
ifname = getInterfaceName(ifname, true)
if(ifname == "") then ifname = nil end
end
end
if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => Session:".._SESSION["session"]) end
if((ifname == nil) and (_SESSION ~= nil)) then
if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => set ifname by _SESSION value") end
ifname = _SESSION["ifname"]
if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => ifname:"..ifname) end
else
if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => set ifname by _GET value") end
end
end
--
-- IMPORTANT
-- Leave it at the end so it can use the functions
-- defined in this file
--
http_lint = require "http_lint"
if(trace_script_duration ~= nil) then
io.write(debug.getinfo(1,'S').source .." executed in ".. (os.clock()-clock_start)*1000 .. " ms\n")
end