mirror of
https://github.com/ntop/ntopng.git
synced 2026-04-28 23:19:33 +00:00
5457 lines
174 KiB
Lua
5457 lines
174 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
|
|
|
|
-- ###############################################
|
|
|
|
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 "ntop_utils"
|
|
locales_utils = require "locales_utils"
|
|
local os_utils = require "os_utils"
|
|
local format_utils = require "format_utils"
|
|
local dscp_consts = require "dscp_consts"
|
|
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
|
|
|
|
-- ##############################################
|
|
|
|
-- Note: Regexs are applied by default. Pass plain=true to disable them.
|
|
function string.contains(str, start, is_plain)
|
|
if type(str) ~= 'string' or type(start) ~= 'string' or isEmptyString(str) or isEmptyString(start) then
|
|
return false
|
|
end
|
|
|
|
local i, _ = string.find(str, start, 1, is_plain)
|
|
|
|
return(i ~= nil)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function string.containsIgnoreCase(str, start, is_plain)
|
|
return string.contains(string.lower(str), string.lower(start), is_plain)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function shortenString(name, max_len)
|
|
local ellipsis = "\u{2026}" -- The unicode ellipsis (takes less space than three separate dots)
|
|
if(name == nil) then return("") end
|
|
|
|
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(string.len(name) < max_len + 1 --[[ The space taken by the ellipsis --]]) then
|
|
return(name)
|
|
else
|
|
return(string.sub(name, 1, max_len)..ellipsis)
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function convertDate(vardate)
|
|
local m,d,y,h,i,s = string.match(vardate, '(%d+)/(%d+)/(%d+) (%d+):(%d+):(%d+)')
|
|
local key = ntop.getPref('ntopng.user.' .. _SESSION["user"] .. '.date_format')
|
|
|
|
if(key == "little_endian") then
|
|
return string.format('%s/%s/%s %s:%s:%s', d,m,y,h,i,s)
|
|
elseif( key == "middle_endian") then
|
|
return string.format('%s/%s/%s %s:%s:%s', m,d,y,h,i,s)
|
|
else
|
|
return string.format('%s/%s/%s %s:%s:%s', y,m,d,h,i,s)
|
|
end
|
|
|
|
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- See also getHumanReadableInterfaceName
|
|
function getInterfaceName(interface_id, windows_skip_description)
|
|
if(interface_id == getSystemInterfaceId()) then
|
|
return(getSystemInterfaceName())
|
|
end
|
|
|
|
local ifnames = interface.getIfNames()
|
|
local iface = ifnames[tostring(interface_id)]
|
|
|
|
if iface ~= nil then
|
|
if(windows_skip_description ~= true and string.contains(iface, "{")) then -- Windows
|
|
local old_iface = interface.getId()
|
|
|
|
-- Use the interface description instead of the name
|
|
interface.select(tostring(iface))
|
|
iface = interface.getStats().description
|
|
|
|
interface.select(tostring(old_iface))
|
|
end
|
|
|
|
return(iface)
|
|
end
|
|
|
|
return("")
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getInterfaceId(interface_name)
|
|
if(interface_name == getSystemInterfaceName()) then
|
|
return(getSystemInterfaceId())
|
|
end
|
|
|
|
local ifnames = interface.getIfNames()
|
|
|
|
for if_id, if_name in pairs(ifnames) do
|
|
if if_name == interface_name then
|
|
return tonumber(if_id)
|
|
end
|
|
end
|
|
|
|
return(-1)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getFirstInterfaceId()
|
|
local ifid = interface.getFirstInterfaceId()
|
|
|
|
if ifid ~= nil then
|
|
return ifid, getInterfaceName(ifid)
|
|
end
|
|
|
|
return -1, ""
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
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
|
|
|
|
-- ##############################################
|
|
|
|
-- 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
|
|
|
|
-- 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 }
|
|
}
|
|
|
|
L4_PROTO_KEYS = {
|
|
tcp=6,
|
|
udp=17,
|
|
icmp=1,
|
|
eigrp=88,
|
|
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 printGETParameters(get)
|
|
for key, value in pairs(get) do
|
|
io.write(key.."="..value.."\n")
|
|
end
|
|
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 printASN(asn, asname)
|
|
asname = asname:gsub('"','')
|
|
if(asn > 0) then
|
|
return("<A class='ntopng-external-link' href='http://as.robtex.com/as"..asn..".html'>"..asname.." <i class='fas fa-external-link-alt fa-lg'></i></A>")
|
|
else
|
|
return(asname)
|
|
end
|
|
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
|
|
|
|
-- ##############################################
|
|
|
|
function getPageUrl(base_url, params)
|
|
if table.empty(params) then
|
|
return base_url
|
|
end
|
|
|
|
local encoded = {}
|
|
|
|
for k, v in pairs(params) do
|
|
encoded[k] = urlencode(v)
|
|
end
|
|
|
|
local delim = "&"
|
|
if not string.find(base_url, "?") then
|
|
delim = "?"
|
|
end
|
|
|
|
return base_url .. delim .. table.tconcat(encoded, "=", "&")
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function printIpVersionDropdown(base_url, page_params)
|
|
local ipversion = _GET["version"]
|
|
local ipversion_filter
|
|
if not isEmptyString(ipversion) then
|
|
ipversion_filter = '<span class="fas fa-filter"></span>'
|
|
else
|
|
ipversion_filter = ''
|
|
end
|
|
|
|
-- table.clone needed to modify some parameters while keeping the original unchanged
|
|
local ipversion_params = table.clone(page_params)
|
|
ipversion_params["version"] = nil
|
|
|
|
print[[\
|
|
<button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.ip_version")) print[[]] print(ipversion_filter) print[[<span class="caret"></span></button>\
|
|
<ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">\
|
|
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, ipversion_params)) print[[">]] print(i18n("flows_page.all_ip_versions")) print[[</a></li>\
|
|
<li><a class="dropdown-item ]] if ipversion == "4" then print('active') end print[[" href="]] ipversion_params["version"] = "4"; print(getPageUrl(base_url, ipversion_params)); print[[">]] print(i18n("flows_page.ipv4_only")) print[[</a></li>\
|
|
<li><a class="dropdown-item ]] if ipversion == "6" then print('active') end print[[" href="]] ipversion_params["version"] = "6"; print(getPageUrl(base_url, ipversion_params)); print[[">]] print(i18n("flows_page.ipv6_only")) print[[</a></li>\
|
|
</ul>]]
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function printVLANFilterDropdown(base_url, page_params)
|
|
local vlans = interface.getVLANsList()
|
|
|
|
if vlans == nil then vlans = {VLANs={}} end
|
|
vlans = vlans["VLANs"]
|
|
|
|
local ids = {}
|
|
for _, vlan in ipairs(vlans) do
|
|
ids[#ids + 1] = vlan["vlan_id"]
|
|
end
|
|
|
|
local vlan_id = _GET["vlan"]
|
|
local vlan_id_filter = ''
|
|
if not isEmptyString(vlan_id) then
|
|
vlan_id_filter = '<span class="fas fa-filter"></span>'
|
|
end
|
|
|
|
-- table.clone needed to modify some parameters while keeping the original unchanged
|
|
local vlan_id_params = table.clone(page_params)
|
|
vlan_id_params["vlan"] = nil
|
|
|
|
print[[\
|
|
<button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.vlan")) print[[]] print(vlan_id_filter) print[[<span class="caret"></span></button>\
|
|
<ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">\
|
|
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, vlan_id_params)) print[[">]] print(i18n("flows_page.all_vlan_ids")) print[[</a></li>\]]
|
|
for _, vid in ipairs(ids) do
|
|
vlan_id_params["vlan"] = vid
|
|
print[[
|
|
<li>\
|
|
<a class="dropdown-item ]] print(vlan_id == tostring(vid) and 'active' or '') print[[" href="]] print(getPageUrl(base_url, vlan_id_params)) print[[">VLAN ]] print(tostring(getFullVlanName(vid))) print[[</a></li>\]]
|
|
end
|
|
print[[
|
|
|
|
</ul>]]
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function printDSCPDropdown(base_url, page_params, dscp_list)
|
|
local dscp = _GET["dscp"]
|
|
local dscp_filter
|
|
if not isEmptyString(dscp) then
|
|
dscp_filter = '<span class="fas fa-filter"></span>'
|
|
else
|
|
dscp_filter = ''
|
|
end
|
|
|
|
-- table.clone needed to modify some parameters while keeping the original unchanged
|
|
local dscp_params = table.clone(page_params)
|
|
dscp_params["dscp"] = nil
|
|
-- Used to possibly remove tcp state filters when selecting a non-TCP l4 protocol
|
|
local dscp_params_non_filter = table.clone(dscp_params)
|
|
if dscp_params_non_filter["dscp"] then
|
|
dscp_params_non_filter["dscp"] = nil
|
|
end
|
|
|
|
local ordered_dscp_list = {}
|
|
|
|
for key, value in pairs(dscp_list) do
|
|
local name = dscp_consts.dscp_descr(key)
|
|
ordered_dscp_list[name] = {}
|
|
ordered_dscp_list[name]["id"] = key
|
|
ordered_dscp_list[name]["count"] = value.count
|
|
end
|
|
|
|
print[[\
|
|
<button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.dscp")) print[[]] print(dscp_filter) print[[<span class="caret"></span></button>\
|
|
<ul class="dropdown-menu dropdown-menu-end scrollable-dropdown" role="menu" id="flow_dropdown">\
|
|
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, dscp_params_non_filter)) print[[">]] print(i18n("flows_page.all_dscp")) print[[</a></li>]]
|
|
|
|
for key, value in pairsByKeys(ordered_dscp_list, asc) do
|
|
print[[<li]]
|
|
|
|
print([[><a class="dropdown-item ]].. (tonumber(dscp) == value.id and 'active' or '') ..[[" href="]])
|
|
|
|
local dscp_table = ternary(key ~= 6, dscp_params_non_filter, dscp_params)
|
|
|
|
dscp_table["dscp"] = value.id
|
|
print(getPageUrl(base_url, dscp_table))
|
|
|
|
print[[">]] print(key) print [[ (]] print(string.format("%d", value.count)) print [[)</a></li>]]
|
|
end
|
|
|
|
print[[</ul>]]
|
|
end
|
|
|
|
-- ###################################
|
|
|
|
function printHostPoolDropdown(base_url, page_params, host_pool_list)
|
|
local host_pools = require "host_pools"
|
|
|
|
local host_pools_instance = host_pools:create()
|
|
local host_pool = _GET["host_pool_id"]
|
|
local host_pool_filter
|
|
if not isEmptyString(host_pool) then
|
|
host_pool_filter = '<span class="fas fa-filter"></span>'
|
|
else
|
|
host_pool_filter = ''
|
|
end
|
|
|
|
-- table.clone needed to modify some parameters while keeping the original unchanged
|
|
local host_pool_params = table.clone(page_params)
|
|
host_pool_params["host_pool_id"] = nil
|
|
-- Used to possibly remove tcp state filters when selecting a non-TCP l4 protocol
|
|
local host_pool_params_non_filter = table.clone(host_pool_params)
|
|
if host_pool_params_non_filter["host_pool_id"] then
|
|
host_pool_params_non_filter["host_pool_id"] = nil
|
|
end
|
|
|
|
local ordered_host_pool_list = {}
|
|
|
|
if host_pool then
|
|
local id = tonumber(host_pool)
|
|
ordered_host_pool_list[id] = {}
|
|
ordered_host_pool_list[id]["count"] = host_pool_list[id]["count"]
|
|
else
|
|
for key, value in pairs(host_pool_list) do
|
|
ordered_host_pool_list[key] = {}
|
|
ordered_host_pool_list[key]["count"] = value.count
|
|
end
|
|
end
|
|
|
|
print[[\
|
|
<button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("details.host_pool")) print[[]] print(host_pool_filter) print[[<span class="caret"></span></button>\
|
|
<ul class="dropdown-menu dropdown-menu-end scrollable-dropdown" role="menu" id="flow_dropdown">\
|
|
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, host_pool_params_non_filter)) print[[">]] print(i18n("flows_page.all_host_pool")) print[[</a></li>]]
|
|
|
|
for key, value in pairsByKeys(ordered_host_pool_list, asc) do
|
|
print[[<li]]
|
|
|
|
print([[><a class="dropdown-item ]].. (tonumber(host_pool) == key and 'active' or '') ..[[" href="]])
|
|
|
|
local host_pool_table = ternary(key ~= 6, host_pool_params_non_filter, host_pool_params)
|
|
|
|
host_pool_table["host_pool_id"] = key
|
|
print(getPageUrl(base_url, host_pool_table))
|
|
|
|
print[[">]] print(host_pools_instance:get_pool_name(key)) print [[ (]] print(string.format("%d", value.count)) print [[)</a></li>]]
|
|
end
|
|
|
|
print[[</ul>]]
|
|
end
|
|
|
|
-- ###################################
|
|
|
|
function printLocalNetworksDropdown(base_url, page_params)
|
|
local networks_stats = interface.getNetworksStats()
|
|
|
|
local ids = {}
|
|
for n, local_network in pairs(networks_stats) do
|
|
local network_name = getFullLocalNetworkName(local_network["network_key"])
|
|
ids[network_name] = local_network
|
|
end
|
|
|
|
local local_network_id = _GET["network"]
|
|
local local_network_id_filter = ''
|
|
if not isEmptyString(local_network_id) then
|
|
local_network_id_filter = '<span class="fas fa-filter"></span>'
|
|
end
|
|
|
|
-- table.clone needed to modify some parameters while keeping the original unchanged
|
|
local local_network_id_params = table.clone(page_params)
|
|
local_network_id_params["network"] = nil
|
|
|
|
print[[\
|
|
<button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.networks")) print[[]] print(local_network_id_filter) print[[<span class="caret"></span></button>\
|
|
<ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">\
|
|
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, local_network_id_params)) print[[">]] print(i18n("flows_page.all_networks")) print[[</a></li>\]]
|
|
|
|
for local_network_name, local_network in pairsByKeys(ids) do
|
|
local cur_id = local_network["network_id"]
|
|
local_network_id_params["network"] = cur_id
|
|
print[[
|
|
<li>\
|
|
<a class="dropdown-item ]] print(local_network_id == tostring(cur_id) and 'active' or '') print[[" href="]] print(getPageUrl(base_url, local_network_id_params)) print[[">]] print(local_network_name) print[[</a></li>\]]
|
|
end
|
|
print[[
|
|
|
|
</ul>]]
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function printTrafficTypeFilterDropdown(base_url, page_params)
|
|
local traffic_type = _GET["traffic_type"]
|
|
local traffic_type_filter = ''
|
|
if not isEmptyString(traffic_type) then
|
|
traffic_type_filter = '<span class="fas fa-filter"></span>'
|
|
end
|
|
|
|
-- table.clone needed to modify some parameters while keeping the original unchanged
|
|
local traffic_type_params = table.clone(page_params)
|
|
traffic_type_params["traffic_type"] = nil
|
|
|
|
print[[\
|
|
<button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.direction")) print[[]] print(traffic_type_filter) print[[<span class="caret"></span></button>\
|
|
<ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">\
|
|
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, traffic_type_params)) print[[">]] print(i18n("hosts_stats.traffic_type_all")) print[[</a></li>\]]
|
|
|
|
-- now forthe one-way
|
|
traffic_type_params["traffic_type"] = "one_way"
|
|
print[[
|
|
<li>\
|
|
<a class="dropdown-item ]] if traffic_type == "one_way" then print('active') end print[[" href="]] print(getPageUrl(base_url, traffic_type_params)) print[[">]] print(i18n("hosts_stats.traffic_type_one_way")) print[[</a></li>\]]
|
|
traffic_type_params["traffic_type"] = "bidirectional"
|
|
print[[
|
|
<li>\
|
|
<a class="dropdown-item ]] if traffic_type == "bidirectional" then print('active') end print[[" href="]] print(getPageUrl(base_url, traffic_type_params)) print[[">]] print(i18n("hosts_stats.traffic_type_two_ways")) print[[</a></li>\]]
|
|
print[[
|
|
</ul>]]
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getProbesName(flowdevs)
|
|
local devips = {}
|
|
|
|
for dip, _ in pairsByValues(flowdevs, asc) do
|
|
devips[dip] = getProbeName(dip)
|
|
end
|
|
|
|
return devips
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getProbeName(exporter_ip)
|
|
local cached_device_name
|
|
local snmp_cached_dev
|
|
|
|
if ntop.isPro() then
|
|
snmp_cached_dev = require "snmp_cached_dev"
|
|
end
|
|
|
|
if snmp_cached_dev then
|
|
cached_device_name = snmp_cached_dev:create(exporter_ip)
|
|
end
|
|
|
|
if cached_device_name then
|
|
cached_device_name = cached_device_name["name"]
|
|
else
|
|
local hinfo = hostkey2hostinfo(exporter_ip)
|
|
local exporter_label = hostinfo2label(hinfo)
|
|
|
|
if not isEmptyString(exporter_label) then
|
|
cached_device_name = exporter_label
|
|
end
|
|
end
|
|
|
|
return cached_device_name
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function printHostsDeviceFilterDropdown(base_url, page_params)
|
|
-- Getting probes
|
|
local flowdevs = interface.getFlowDevices() or {}
|
|
local ordering_fun = pairsByKeys
|
|
local cur_dev = _GET["deviceIP"]
|
|
local cur_dev_filter = ''
|
|
-- table.clone needed to modify some parameters while keeping the original unchanged
|
|
local dev_params = table.clone(page_params)
|
|
local devips = getProbesName(flowdevs)
|
|
local devips_order = ntop.getPref("ntopng.prefs.flow_table_probe_order") == "1" -- Order by Probe Name
|
|
|
|
if devips_order then
|
|
ordering_fun = pairsByValues
|
|
end
|
|
|
|
if not isEmptyString(cur_dev) then
|
|
cur_dev_filter = '<span class="fas fa-filter"></span>'
|
|
end
|
|
|
|
dev_params["deviceIP"] = nil
|
|
|
|
if table.len(devips) > 0 then
|
|
print[[, '<div class="btn-group float-right">]]
|
|
|
|
print[[
|
|
<button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.device_ip")) print[[]] print(cur_dev_filter) print[[<span class="caret"></span></button>\
|
|
<ul class="dropdown-menu dropdown-menu-end scrollable-dropdown" role="menu" id="flow_dropdown">\
|
|
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, dev_params)) print[[">]] print(i18n("flows_page.all_devices")) print[[</a></li>\]]
|
|
|
|
for dev_ip, dev_resolved_name in ordering_fun(devips, asc) do
|
|
local dev_name = dev_ip
|
|
|
|
dev_params["deviceIP"] = dev_name
|
|
|
|
if not isEmptyString(dev_resolved_name) and dev_resolved_name ~= dev_name then
|
|
dev_name = dev_name .. " ["..shortenString(dev_resolved_name).."]"
|
|
end
|
|
|
|
print[[
|
|
<li><a class="dropdown-item ]] print(dev_ip == cur_dev and 'active' or '') print[[" href="]] print(getPageUrl(base_url, dev_params)) print[[">]] print(i18n("flows_page.device_ip").." "..dev_name) print[[</a></li>\]]
|
|
end
|
|
|
|
print[[
|
|
</ul>\
|
|
]]
|
|
|
|
print[[</div>']]
|
|
end
|
|
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(' ', " ")
|
|
|
|
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
|
|
|
|
function getCategoriesWithProtocols()
|
|
local protocol_categories = interface.getnDPICategories()
|
|
|
|
for k,v in pairsByKeys(protocol_categories) do
|
|
protocol_categories[k] = {id=v, protos=interface.getnDPIProtocols(tonumber(v)), count=0}
|
|
|
|
for proto,_ in pairs(protocol_categories[k].protos) do
|
|
protocol_categories[k].count = protocol_categories[k].count + 1
|
|
end
|
|
end
|
|
|
|
return protocol_categories
|
|
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
|
|
|
|
-- Return the first 'howmany' hosts
|
|
function getTopInterfaceHosts(howmany, localHostsOnly)
|
|
hosts_stats = interface.getHostsInfo()
|
|
hosts_stats = hosts_stats["hosts"]
|
|
ret = {}
|
|
sortTable = {}
|
|
n = 0
|
|
for k,v in pairs(hosts_stats) do
|
|
if((not localHostsOnly) or ((v["localhost"] == true) and (v["ip"] ~= nil))) then
|
|
sortTable[v["bytes.sent"]+v["bytes.rcvd"]+n] = k
|
|
n = n +0.01
|
|
end
|
|
end
|
|
|
|
n = 0
|
|
for _v,k in pairsByKeys(sortTable, rev) do
|
|
if(n < howmany) then
|
|
ret[k] = hosts_stats[k]
|
|
n = n+1
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
return(ret)
|
|
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 getIpUrl(ip)
|
|
if isIPv6(ip) then
|
|
-- https://www.ietf.org/rfc/rfc2732.txt
|
|
return "["..ip.."]"
|
|
end
|
|
return ip
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function getApplicationIcon(name)
|
|
local icon = ""
|
|
if(name == nil) then name = "" end
|
|
|
|
if(findString(name, "Skype")) then icon = '<i class=\'fab fa-skype\'></i>'
|
|
elseif(findString(name, "Unknown")) then icon = '<i class=\'fas fa-question\'></i>'
|
|
elseif(findString(name, "Twitter")) then icon = '<i class=\'fab fa-twitter\'></i>'
|
|
elseif(findString(name, "DropBox")) then icon = '<i class=\'fab fa-dropbox\'></i>'
|
|
elseif(findString(name, "Spotify")) then icon = '<i class=\'fab fa-spotify\'></i>'
|
|
elseif(findString(name, "Apple")) then icon = '<i class=\'fab fa-apple\'></i>'
|
|
elseif(findString(name, "Google") or
|
|
findString(name, "Chrome")) then icon = '<i class=\'fab fa-google-plus-g\'></i>'
|
|
elseif(findString(name, "FaceBook")) then icon = '<i class=\'fab fa-facebook\'></i>'
|
|
elseif(findString(name, "Youtube")) then icon = '<i class=\'fab fa-youtube\'></i>'
|
|
elseif(findString(name, "thunderbird")) then icon = '<i class=\'fas fa-paper-plane\'></i>'
|
|
end
|
|
|
|
return(icon)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function getApplicationLabel(name, maxlen)
|
|
local icon = getApplicationIcon(name)
|
|
|
|
if(maxlen == nil) then
|
|
maxlen = 12
|
|
end
|
|
|
|
-- Do not convert to upper case, keep the nDPI case
|
|
--name = name:gsub("^%l", string.upper)
|
|
|
|
return(icon.." "..shortenString(name, maxlen))
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function getCategoryLabel(cat_name, cat_id)
|
|
local categories_utils = require 'categories_utils'
|
|
return categories_utils.getCustomCategoryName(cat_id, cat_name)
|
|
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)
|
|
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 getItemsNumber(n)
|
|
tot = 0
|
|
for k,v in pairs(n) do
|
|
--io.write(k.."\n")
|
|
tot = tot + 1
|
|
end
|
|
|
|
--io.write(tot.."\n")
|
|
return(tot)
|
|
end
|
|
|
|
-- ###########################################
|
|
|
|
function getHostCommaSeparatedList(p_hosts)
|
|
hosts = {}
|
|
hosts_size = 0
|
|
for i,host in pairs(split(p_hosts, ",")) do
|
|
hosts[i] = host
|
|
hosts_size = hosts_size + 1
|
|
end
|
|
return hosts,hosts_size
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getFirstIpFromMac(host_addr)
|
|
local mac_resolved = host_addr
|
|
-- Transforming the MAC into the ip address
|
|
local mac_hosts = interface.getMacHosts(mac_resolved) or {}
|
|
|
|
-- Mapping mac with ip and setting up the right href
|
|
for _, h in pairsByKeys(mac_hosts, rev) do
|
|
if (h.broadcast_domain_host) or (table.len(mac_hosts) == 1) then
|
|
mac_resolved = h.ip
|
|
break
|
|
end
|
|
end
|
|
|
|
return mac_resolved
|
|
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 getHostAltNamesKey(host_key)
|
|
if(host_key == nil) then return(nil) end
|
|
return "ntopng.cache.host_labels."..host_key
|
|
end
|
|
|
|
function getHostAltName(host_info)
|
|
local host_key
|
|
|
|
if type(host_info) == "table" then
|
|
host_key = host_info["host"]
|
|
else
|
|
host_key = host_info
|
|
end
|
|
|
|
local alt_name = ntop.getCache(getHostAltNamesKey(host_key))
|
|
|
|
if isEmptyString(alt_name) and type(host_info) == "table" and host_info["vlan"] then
|
|
-- Check if there is an alias for the host@vlan
|
|
host_key = hostinfo2hostkey(host_info)
|
|
alt_name = ntop.getCache(getHostAltNamesKey(host_key))
|
|
end
|
|
|
|
return alt_name
|
|
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 getHostNotesKey(host_key)
|
|
if(host_key == nil) then return(nil) end
|
|
return "ntopng.cache.host_notes."..host_key
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getHostNotes(host_info)
|
|
local host_key
|
|
|
|
if type(host_info) == "table" then
|
|
host_key = host_info["host"]
|
|
else
|
|
host_key = host_info
|
|
end
|
|
|
|
local notes = ntop.getCache(getHostNotesKey(host_key))
|
|
|
|
if isEmptyString(notes) and type(host_info) == "table" and host_info["vlan"] then
|
|
-- Check if there is an alias for the host@vlan
|
|
host_key = hostinfo2hostkey(host_info)
|
|
notes = ntop.getCache(getHostNotesKey(host_key))
|
|
end
|
|
|
|
if not isEmptyString(notes) then
|
|
notes = string.lower(notes)
|
|
end
|
|
|
|
return notes
|
|
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
|
|
|
|
-- ##############################################
|
|
|
|
|
|
function getDhcpNameKey(ifid, mac)
|
|
return string.format("ntopng.dhcp.%d.cache.%s", ifid, mac)
|
|
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 --
|
|
|
|
-- A function to give a useful device name
|
|
function getDeviceName(device_mac, skip_manufacturer)
|
|
local name = mac2label(device_mac)
|
|
|
|
if name == device_mac then
|
|
-- Not found, try with first host
|
|
local info = interface.getHostsInfo(false, nil, 1, 0, nil, nil, nil, tonumber(vlan), nil,
|
|
nil, device_mac)
|
|
|
|
if (info ~= nil) then
|
|
for x, host in pairs(info.hosts) do
|
|
-- Make sure the IP is in the broadcast domain to avoid setting up names to MACs such as the gateway
|
|
if host.broadcast_domain_host and not isEmptyString(host.name) and host.name ~= host.ip and host.name ~= "NoIP" then
|
|
name = host.name
|
|
elseif host.ip ~= "0.0.0.0" then
|
|
name = ip2label(host.ip)
|
|
if name == host.ip then
|
|
name = nil
|
|
end
|
|
end
|
|
break
|
|
end
|
|
else
|
|
name = nil
|
|
end
|
|
end
|
|
|
|
if isEmptyString(name) then
|
|
if (not skip_manufacturer) then
|
|
name = get_symbolic_mac(device_mac, true)
|
|
else
|
|
-- last resort
|
|
name = device_mac
|
|
end
|
|
end
|
|
|
|
if isEmptyString(name) or name == device_mac then
|
|
return ''
|
|
end
|
|
|
|
return name
|
|
end
|
|
|
|
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 getLocalNetworkAliasKey()
|
|
return "ntopng.network_aliases"
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getLocalNetworkAliasById(network)
|
|
local networks_stats = interface.getNetworksStats()
|
|
local network_id = tonumber(network)
|
|
|
|
-- If network is (u_int8_t)-1 then return an empty value
|
|
if network == nil or network == network_utils.UNKNOWN_NETWORK then
|
|
return ' '
|
|
end
|
|
|
|
local label = ''
|
|
for n, ns in pairs(networks_stats) do
|
|
if ns.network_id == network_id then
|
|
label = getFullLocalNetworkName(ns.network_key)
|
|
end
|
|
end
|
|
return label
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getLocalNetworkAlias(network)
|
|
local alias = ntop.getLocalNetworkAlias(network) or nil
|
|
|
|
if not alias then
|
|
alias = ntop.getHashCache(getLocalNetworkAliasKey(), network)
|
|
end
|
|
|
|
if not isEmptyString(alias) then
|
|
return alias
|
|
end
|
|
|
|
return network
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getLocalNetworkLabel(network)
|
|
local alias = getLocalNetworkAlias(network)
|
|
|
|
if alias ~= network then
|
|
return string.format("%s · %s", alias, network)
|
|
end
|
|
|
|
return network
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getFullLocalNetworkName(network)
|
|
local alias = getLocalNetworkAlias(network)
|
|
|
|
if alias ~= network then
|
|
return string.format("%s [%s]", alias, network)
|
|
end
|
|
|
|
return network
|
|
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 getVlanAliasKey()
|
|
return "ntopng.vlan_aliases"
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getVlanAlias(vlan_id)
|
|
local alias = ntop.getHashCache(getVlanAliasKey(), vlan_id)
|
|
|
|
if not isEmptyString(alias) then
|
|
return alias
|
|
end
|
|
|
|
return tostring(vlan_id)
|
|
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 getFullVlanName(vlan_id, compact)
|
|
local alias = getVlanAlias(vlan_id)
|
|
|
|
-- In case of vlan 0, return empty string as name
|
|
if tonumber(vlan_id) == 0 then
|
|
return ''
|
|
end
|
|
|
|
if not isEmptyString(alias) then
|
|
if not isEmptyString(alias) and alias ~= tostring(vlan_id) then
|
|
if compact then
|
|
alias = shortenString(alias)
|
|
return string.format("%s", alias)
|
|
else
|
|
return string.format("%u [%s]", vlan_id, alias)
|
|
end
|
|
end
|
|
end
|
|
|
|
return vlan_id
|
|
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
|
|
|
|
-- ############################################
|
|
-- Redis Utils
|
|
-- ############################################
|
|
|
|
-- Inpur: General prefix (i.e ntopng.pref)
|
|
-- Output: User based prefix, if it exists
|
|
--
|
|
-- Examples:
|
|
-- With user: ntopng.pref.user_name
|
|
-- Without: ntopng.pref
|
|
function getRedisPrefix(str)
|
|
if not (isEmptyString(_SESSION["user"] )) then
|
|
-- Login enabled
|
|
return (str .. '.' .. _SESSION["user"])
|
|
else
|
|
-- Login disabled
|
|
return (str)
|
|
end
|
|
end
|
|
|
|
----- End of Redis Utils ------
|
|
|
|
|
|
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 getThroughputType()
|
|
local throughput_type = ntop.getCache("ntopng.prefs.thpt_content")
|
|
if throughput_type == "" then throughput_type = "bps" end
|
|
return throughput_type
|
|
end
|
|
|
|
function processColor(proc)
|
|
if(proc == nil) then
|
|
return("")
|
|
elseif(proc["average_cpu_load"] < 33) then
|
|
return("<font color=green>"..proc["name"].."</font>")
|
|
elseif(proc["average_cpu_load"] < 66) then
|
|
return("<font color=orange>"..proc["name"].."</font>")
|
|
else
|
|
return("<font color=red>"..proc["name"].."</font>")
|
|
end
|
|
end
|
|
|
|
-- Table preferences
|
|
|
|
function getDefaultTableSort(table_type)
|
|
local table_key = getRedisPrefix("ntopng.sort.table")
|
|
local value = nil
|
|
|
|
if(table_type ~= nil) then
|
|
value = ntop.getHashCache(table_key, "sort_"..table_type)
|
|
end
|
|
if((value == nil) or (value == "")) then value = 'column_' end
|
|
return(value)
|
|
end
|
|
|
|
function getDefaultTableSortOrder(table_type, force_get)
|
|
local table_key = getRedisPrefix("ntopng.sort.table")
|
|
local value = nil
|
|
|
|
if(table_type ~= nil) then
|
|
value = ntop.getHashCache(table_key, "sort_order_"..table_type)
|
|
end
|
|
if((value == nil) or (value == "")) and (force_get ~= true) then value = 'desc' end
|
|
return(value)
|
|
end
|
|
|
|
function getDefaultTableSize()
|
|
table_key = getRedisPrefix("ntopng.sort.table")
|
|
value = ntop.getHashCache(table_key, "rows_number")
|
|
if((value == nil) or (value == "")) then value = 10 end
|
|
return(tonumber(value))
|
|
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 getInterfaceSpeed(ifid)
|
|
local ifname = getInterfaceName(ifid)
|
|
local ifspeed = ntop.getCache('ntopng.prefs.ifid_'..tostring(ifid)..'.speed')
|
|
if not isEmptyString(ifspeed) and tonumber(ifspeed) ~= nil then
|
|
ifspeed = tonumber(ifspeed)
|
|
else
|
|
ifspeed = interface.getMaxIfSpeed(ifid)
|
|
end
|
|
|
|
return ifspeed
|
|
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
|
|
|
|
local function getCustomnDPIProtoCategoriesKey()
|
|
return "ntop.prefs.custom_nDPI_proto_categories"
|
|
end
|
|
|
|
function getCustomnDPIProtoCategories()
|
|
local ndpi_protos = interface.getnDPIProtocols()
|
|
local key = getCustomnDPIProtoCategoriesKey()
|
|
|
|
local res = {}
|
|
for _, app_id in pairs(ndpi_protos) do
|
|
local custom_category = ntop.getHashCache(key, tostring(app_id))
|
|
if not isEmptyString(custom_category) then
|
|
res[tonumber(app_id)] = tonumber(custom_category)
|
|
end
|
|
end
|
|
|
|
return res
|
|
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 getHumanReadableInterfaceName(interface_name)
|
|
local interface_id = nil
|
|
|
|
if(interface_name == "__system__") then
|
|
return(i18n("system"))
|
|
elseif tonumber(interface_name) ~= nil then
|
|
-- convert ID to name
|
|
interface_id = tonumber(interface_name)
|
|
interface_name = getInterfaceName(interface_name)
|
|
else
|
|
-- Parameter is a string, let's take it's id first
|
|
interface_id = getInterfaceId(interface_name)
|
|
-- and then get the name
|
|
interface_name = getInterfaceName(interface_id)
|
|
end
|
|
|
|
local key = 'ntopng.prefs.ifid_'..tostring(interface_id)..'.name'
|
|
local custom_name = ntop.getCache(key)
|
|
|
|
if not isEmptyString(custom_name) then
|
|
return(shortenCollapse(custom_name))
|
|
end
|
|
|
|
return interface_name
|
|
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 getKeysSortedByValue(tbl, sortFunction)
|
|
local keys = {}
|
|
for key in pairs(tbl) do
|
|
table.insert(keys, key)
|
|
end
|
|
|
|
table.sort(keys, function(a, b)
|
|
return sortFunction(tbl[a], tbl[b])
|
|
end)
|
|
|
|
return keys
|
|
end
|
|
|
|
function getKeys(t, col)
|
|
local keys = {}
|
|
for k,v in pairs(t) do keys[tonumber(v[col])] = k end
|
|
return keys
|
|
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 getFlag(country)
|
|
if((country == nil) or (country == "")) then
|
|
return("")
|
|
else
|
|
return(" <a href='" .. ntop.getHttpPrefix() .. "/lua/hosts_stats.lua?country=".. country .."'><img src='".. ntop.getHttpPrefix() .. "/dist/images/blank.gif' class='flag flag-".. string.lower(country) .."'></a> ")
|
|
end
|
|
end
|
|
|
|
-- GENERIC UTILS
|
|
|
|
-- split
|
|
function split(s, delimiter)
|
|
result = {};
|
|
if(s ~= nil) then
|
|
if delimiter == nil then
|
|
-- No delimiter, split all characters
|
|
for match in s:gmatch"." do
|
|
table.insert(result, match);
|
|
end
|
|
else
|
|
-- Split by delimiter
|
|
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
|
|
table.insert(result, match);
|
|
end
|
|
end
|
|
end
|
|
return result;
|
|
end
|
|
|
|
-- startswith
|
|
function startswith(s, char)
|
|
return string.sub(s, 1, string.len(s)) == char
|
|
end
|
|
|
|
-- strsplit
|
|
|
|
function strsplit(s, delimiter)
|
|
result = {};
|
|
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
|
|
if(match ~= "") then result[match] = true end
|
|
end
|
|
return result;
|
|
end
|
|
|
|
-- isempty
|
|
function isempty(array)
|
|
local count = 0
|
|
for _,__ in pairs(array) do
|
|
count = count + 1
|
|
end
|
|
return (count == 0)
|
|
end
|
|
|
|
-- isin
|
|
function isin(s, array)
|
|
if (s == nil or s == "" or array == nil or isempty(array)) then return false end
|
|
for _, v in pairs(array) do
|
|
if (s == v) then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- hasKey
|
|
function hasKey(key, theTable)
|
|
if((theTable == nil) or (theTable[key] == nil)) then
|
|
return(false)
|
|
else
|
|
return(true)
|
|
end
|
|
end
|
|
|
|
function getUsernameInputPattern()
|
|
-- maximum len must be kept in sync with MAX_PASSWORD_LEN
|
|
return [[^(?=[a-zA-Z0-9._@!-?]{3,30}$)(?!.*[_.]{2})[^_.].*[^_.]$]]
|
|
end
|
|
|
|
function getPasswordInputPattern()
|
|
-- maximum len must be kept in sync with MAX_PASSWORD_LEN
|
|
return [[^[\w\$\\!\/\(\)= \?\^\*@_\-\u0000-\u0019\u0021-\u00ff]{5,31}$]]
|
|
end
|
|
|
|
-- NOTE: keep in sync with validateLicense()
|
|
function getLicensePattern()
|
|
return [[^[a-zA-Z0-9\+/=]+$]]
|
|
end
|
|
|
|
function getIPv4Pattern()
|
|
return "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$"
|
|
end
|
|
|
|
function getACLPattern()
|
|
local ipv4 = "(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
|
|
local netmask = "(\\/([0-9]|[1-2][0-9]|3[0-2]))"
|
|
local cidr = ipv4..netmask
|
|
local yesorno_cidr = "[\\+\\-]"..cidr
|
|
return "^"..yesorno_cidr.."(,"..yesorno_cidr..")*$"
|
|
end
|
|
|
|
function getMacPattern()
|
|
return "^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F]{2}$"
|
|
end
|
|
|
|
function getURLPattern()
|
|
return "^https?://.+$"
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getRestUrl(script, is_pro, is_enterprise)
|
|
if is_enterprise then
|
|
return(ntop.getHttpPrefix() .. "/lua/enterprise/rest/v2/get/" .. script)
|
|
elseif is_pro then
|
|
return(ntop.getHttpPrefix() .. "/lua/pro/rest/v2/get/" .. script)
|
|
else
|
|
return(ntop.getHttpPrefix() .. "/lua/rest/v2/get/" .. script)
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- get_mac_classification
|
|
function get_mac_classification(m, extended_name)
|
|
local short_extended = ntop.getMacManufacturer(m) or {}
|
|
|
|
if extended_name then
|
|
return short_extended.extended or short_extended.short or m
|
|
else
|
|
return short_extended.short or m
|
|
end
|
|
|
|
return m
|
|
end
|
|
|
|
local magic_macs = {
|
|
["00:00:00:00:00:00"] = "",
|
|
["FF:FF:FF:FF:FF:FF"] = "Broadcast",
|
|
["01:00:0C:CC:CC:CC"] = "CDP",
|
|
["01:00:0C:CC:CC:CD"] = "CiscoSTP",
|
|
["01:80:C2:00:00:00"] = "STP",
|
|
["01:80:C2:00:00:00"] = "LLDP",
|
|
["01:80:C2:00:00:03"] = "LLDP",
|
|
["01:80:C2:00:00:0E"] = "LLDP",
|
|
["01:80:C2:00:00:08"] = "STP",
|
|
["01:1B:19:00:00:00"] = "PTP",
|
|
["01:80:C2:00:00:0E"] = "PTP"
|
|
}
|
|
|
|
local magic_short_macs = {
|
|
["01:00:5E"] = "IPv4mcast",
|
|
["33:33:"] = "IPv6mcast"
|
|
}
|
|
|
|
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
|
|
|
|
-- get_symbolic_mac
|
|
function get_symbolic_mac(mac_address, no_href, add_extra_info)
|
|
if(magic_macs[mac_address] ~= nil) then
|
|
return(magic_macs[mac_address])
|
|
else
|
|
local m = string.sub(mac_address, 1, 8)
|
|
local t = string.sub(mac_address, 10, 17)
|
|
|
|
if(magic_short_macs[m] ~= nil) then
|
|
if(add_extra_info == true) then
|
|
return(magic_short_macs[m].."_"..t.." ("..macInfo(mac_address)..")")
|
|
else
|
|
if no_href then
|
|
return(magic_short_macs[m].."_"..t)
|
|
else
|
|
return(macInfoWithSymbName(mac_address, magic_short_macs[m].."_"..t))
|
|
end
|
|
end
|
|
else
|
|
local s = get_mac_classification(m)
|
|
|
|
if(m == s) then
|
|
if no_href then
|
|
return get_mac_classification(m) .. ":" .. t
|
|
else
|
|
return '<a href="' .. ntop.getHttpPrefix() .. '/lua/mac_details.lua?host='..mac_address..'">' .. get_mac_classification(m) .. ":" .. t .. '</a>'
|
|
end
|
|
else
|
|
local href = ""
|
|
if not no_href then
|
|
href = '<a href="' .. ntop.getHttpPrefix() .. '/lua/mac_details.lua?host='..mac_address..'">'
|
|
end
|
|
|
|
if(add_extra_info == true) then
|
|
return(href .. get_mac_classification(m).."_"..t.." ("..macInfo(mac_address)..")" .. "</a>")
|
|
else
|
|
return(href .. get_mac_classification(m).."_"..t .. "</a>")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function get_mac_url(mac)
|
|
local m = get_symbolic_mac(mac, true)
|
|
|
|
if isEmptyString(m) then
|
|
return ""
|
|
end
|
|
|
|
local url = ntop.getHttpPrefix() .."/lua/mac_details.lua?host="..mac
|
|
|
|
return string.format('[ <a href=\"%s\">%s</a> ]', url, m)
|
|
end
|
|
|
|
function get_manufacturer_mac(mac_address)
|
|
local m = string.sub(mac_address, 1, 8)
|
|
local ret = get_mac_classification(m, true --[[ extended name --]])
|
|
|
|
if(ret == m) then ret = "n/a" end
|
|
|
|
if ret and ret ~= "" then
|
|
ret = ret:gsub("'"," ")
|
|
end
|
|
|
|
return ret or "n/a"
|
|
end
|
|
|
|
-- getservbyport
|
|
function getservbyport(port_num, proto)
|
|
if(proto == nil) then proto = "TCP" end
|
|
|
|
port_num = tonumber(port_num)
|
|
|
|
proto = string.lower(proto)
|
|
|
|
-- io.write(port_num.."@"..proto.."\n")
|
|
return(ntop.getservbyport(port_num, proto))
|
|
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 getFlowMaxRate(cli_max_rate, srv_max_rate)
|
|
cli_max_rate = tonumber(cli_max_rate)
|
|
srv_max_rate = tonumber(srv_max_rate)
|
|
|
|
if((cli_max_rate == 0) or (srv_max_rate == 0)) then
|
|
max_rate = 0
|
|
elseif((cli_max_rate == -1) and (srv_max_rate > 0)) then
|
|
max_rate = srv_max_rate
|
|
elseif((cli_max_rate > 0) and (srv_max_rate == -1)) then
|
|
max_rate = cli_max_rate
|
|
else
|
|
max_rate = math.min(cli_max_rate, srv_max_rate)
|
|
end
|
|
|
|
return(max_rate)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
-- removes trailing/leading spaces
|
|
function trimString(s)
|
|
return (s:gsub("^%s*(.-)%s*$", "%1"))
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
-- removes all spaces
|
|
function trimSpace(what)
|
|
if(what == nil) then return("") end
|
|
return(string.gsub(string.gsub(what, "%s+", ""), "+%s", ""))
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
-- TODO: improve this function
|
|
function jsonencode(what)
|
|
what = string.gsub(what, '"', "'")
|
|
-- everything but all ASCII characters from the space to the tilde
|
|
what = string.gsub(what, "[^ -~]", " ")
|
|
-- cleanup line feeds and carriage returns
|
|
what = string.gsub(what, "\n", " ")
|
|
what = string.gsub(what, "\r", " ")
|
|
-- escape all the remaining backslashes
|
|
what = string.gsub(what, "\\", "\\\\")
|
|
-- max 1 sequential whitespace
|
|
what = string.gsub(what, " +"," ")
|
|
return(what)
|
|
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
|
|
|
|
-- print TCP flags
|
|
function printTCPFlags(flags)
|
|
print(formatTCPFlags(flags))
|
|
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(' ')
|
|
-- 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
|
|
|
|
-- ####################################################
|
|
|
|
-- Functions to set/get a device type of user choice
|
|
|
|
local function getCustomDeviceKey(mac)
|
|
return "ntopng.prefs.device_types." .. string.upper(mac)
|
|
end
|
|
|
|
function getCustomDeviceType(mac)
|
|
return tonumber(ntop.getPref(getCustomDeviceKey(mac)))
|
|
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 Compute and return the difference, in seconds, between the local time of this instance and GMT
|
|
-- @return A positive or negative number corresponding to the seconds between local time and GMT
|
|
local function get_server_timezone_diff_seconds()
|
|
if not server_timezone_diff_seconds then
|
|
local tmp_time = os.time()
|
|
local d1 = os.date("*t", tmp_time)
|
|
local d2 = os.date("!*t", tmp_time)
|
|
-- Forcefully set isdst to false otherwise difference won't work during DST
|
|
d1.isdst = false
|
|
-- Use a minus to have the difference between local time and GMT, rather than between GMT and loca ltime
|
|
server_timezone_diff_seconds = -os.difftime(os.time(d1), os.time(d2))
|
|
end
|
|
|
|
return server_timezone_diff_seconds
|
|
end
|
|
|
|
-- ####################################################
|
|
|
|
-- @brief Get the frontend timezone offset in seconds
|
|
-- @return The offset of the frontend timezone
|
|
function getFrontendTzSeconds()
|
|
local frontend_tz_offset = nil
|
|
|
|
if _COOKIE and _COOKIE.tzoffset then
|
|
-- The timezone offset can be passed from the client as a cookie.
|
|
-- This allows to format the dates in the frontend timezone.
|
|
frontend_tz_offset = tonumber(_COOKIE.tzoffset)
|
|
end
|
|
|
|
if frontend_tz_offset == nil then
|
|
-- If timezone is not available in the client _COOKIE,
|
|
-- server timezone is used as fallback
|
|
return -get_server_timezone_diff_seconds()
|
|
end
|
|
|
|
return frontend_tz_offset
|
|
end
|
|
|
|
-- ####################################################
|
|
|
|
-- @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
|
|
|
|
-- ###########################################
|
|
|
|
-- Merges table a and table b into a new table. If some elements are presents in
|
|
-- both a and b, b elements will have precedence.
|
|
-- NOTE: this does *not* perform a deep merge. Only first level is merged.
|
|
function table.merge(a, b)
|
|
local merged = {}
|
|
a = a or {}
|
|
b = b or {}
|
|
|
|
if((a[1] ~= nil) and (b[1] ~= nil)) then
|
|
-- index based tables
|
|
for _, t in ipairs({a, b}) do
|
|
for _,v in pairs(t) do
|
|
merged[#merged + 1] = v
|
|
end
|
|
end
|
|
else
|
|
-- key based tables
|
|
for _, t in ipairs({a, b}) do
|
|
for k,v in pairs(t) do
|
|
merged[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
return merged
|
|
end
|
|
|
|
-- Performs a deep copy of the table.
|
|
function table.clone(orig)
|
|
local orig_type = type(orig)
|
|
local copy
|
|
|
|
if orig_type == 'table' then
|
|
copy = {}
|
|
for orig_key, orig_value in next, orig, nil do
|
|
copy[table.clone(orig_key)] = table.clone(orig_value)
|
|
end
|
|
setmetatable(copy, table.clone(getmetatable(orig)))
|
|
else -- number, string, boolean, etc
|
|
copy = orig
|
|
end
|
|
|
|
return copy
|
|
end
|
|
|
|
-- From http://lua-users.org/lists/lua-l/2014-09/msg00421.html
|
|
-- Returns true if tables are equal
|
|
function table.compare(t1, t2, ignore_mt)
|
|
local ty1 = type(t1)
|
|
local ty2 = type(t2)
|
|
|
|
if ty1 ~= ty2 then return false end
|
|
if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end
|
|
local mt = getmetatable(t1)
|
|
if not ignore_mt and mt and mt.__eq then return t1 == t2 end
|
|
|
|
for k1,v1 in pairs(t1) do
|
|
local v2 = t2[k1]
|
|
if v2 == nil or not table.compare(v1, v2) then return false end
|
|
end
|
|
|
|
for k2,v2 in pairs(t2) do
|
|
local v1 = t1[k2]
|
|
if v1 == nil or not table.compare(v1, v2) then return false end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function toboolean(s)
|
|
if((s == "true") or (s == true)) then
|
|
return true
|
|
elseif((s == "false") or (s == false)) then
|
|
return false
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
--
|
|
-- Find the highest divisor which divides input value.
|
|
-- val_idx can be used to index divisors values.
|
|
-- Returns the highest_idx
|
|
--
|
|
function highestDivisor(divisors, value, val_idx, iterator_fn)
|
|
local highest_idx = nil
|
|
local highest_val = nil
|
|
iterator_fn = iterator_fn or ipairs
|
|
|
|
for i, v in iterator_fn(divisors) do
|
|
local cmp_v
|
|
if val_idx ~= nil then
|
|
v = v[val_idx]
|
|
end
|
|
|
|
if((highest_val == nil) or ((v > highest_val) and (value % v == 0))) then
|
|
highest_val = v
|
|
highest_idx = i
|
|
end
|
|
end
|
|
|
|
return highest_idx
|
|
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 getTopFlowPeers(hostname_vlan, max_hits, detailed, other_options)
|
|
local detailed = detailed or false
|
|
|
|
local paginator_options = {
|
|
sortColumn = "column_bytes",
|
|
a2zSortOrder = false,
|
|
detailedResults = detailed,
|
|
maxHits = max_hits,
|
|
}
|
|
|
|
if other_options ~= nil then
|
|
paginator_options = table.merge(paginator_options, other_options)
|
|
end
|
|
|
|
local res = interface.getFlowsInfo(hostname_vlan, paginator_options)
|
|
if ((res ~= nil) and (res.flows ~= nil)) then
|
|
return res.flows
|
|
else
|
|
return {}
|
|
end
|
|
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
|
|
|
|
function getSafeChildIcon()
|
|
return(" <font color='#5cb85c'><i class='fas fa-lg fa-child' aria-hidden='true'></i></font>")
|
|
end
|
|
|
|
-- ###########################################
|
|
|
|
function getNtopngRelease(ntopng_info, verbose)
|
|
local release
|
|
|
|
if ntopng_info.oem or ntopng_info["version.nedge_edition"] then
|
|
release = ""
|
|
elseif(ntopng_info["version.enterprise_l_edition"]) then
|
|
release = "Enterprise L"
|
|
elseif(ntopng_info["version.enterprise_m_edition"]) then
|
|
release = "Enterprise M"
|
|
elseif(ntopng_info["version.enterprise_edition"]) or (ntopng_info["version.nedge_enterprise_edition"]) then
|
|
release = "Enterprise"
|
|
elseif(ntopng_info["pro.release"]) then
|
|
release = "Professional"
|
|
elseif(ntopng_info["version.embedded_edition"]) then
|
|
release = "/Embedded"
|
|
else
|
|
release = "Community"
|
|
end
|
|
|
|
-- E.g., ntopng edge v.4.3.210112 (Ubuntu 16.04.6 LTS)
|
|
local res = string.format("%s %s v.%s (%s)", ntopng_info.product, release, ntopng_info.version, ntopng_info.OS)
|
|
|
|
if verbose and ntopng_info.revision then
|
|
res = string.format("%s %s v.%s rev.%s (%s)", ntopng_info.product, release, ntopng_info.version, ntopng_info.revision, ntopng_info.OS)
|
|
end
|
|
|
|
if not ntopng_info.oem then
|
|
local vers = string.split(ntopng_info["version.git"], ":")
|
|
|
|
if vers and vers[2] then
|
|
local ntopng_git_url = "<A HREF=\"https://github.com/ntop/ntopng/commit/".. vers[2] .."\"><i class='fab fa-github'></i></A>"
|
|
|
|
res = string.format("%s | %s", res, ntopng_git_url)
|
|
end
|
|
end
|
|
|
|
return res
|
|
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
|
|
|
|
-- ###########################################
|
|
|
|
-- A redis hash mac -> first_seen
|
|
function getFirstSeenDevicesHashKey(ifid)
|
|
return "ntopng.seen_devices.ifid_" .. ifid
|
|
end
|
|
|
|
-- ###########################################
|
|
|
|
function getHideFromTopSet(ifid)
|
|
return "ntopng.prefs.iface_" .. ifid .. ".hide_from_top"
|
|
end
|
|
|
|
-- ###########################################
|
|
|
|
function getGwMacsSet(ifid)
|
|
return "ntopng.prefs.iface_" .. ifid .. ".gw_macs"
|
|
end
|
|
|
|
-- ###########################################
|
|
|
|
function printWarningAlert(message)
|
|
print[[<div class="alert alert-warning alert-dismissable" role="alert">]]
|
|
print[[<i class="fas fa-exclamation-triangle fa-sm"></i> ]]
|
|
print[[<strong>]] print(i18n("warning")) print[[</strong> ]]
|
|
print(message)
|
|
print[[<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>]]
|
|
print[[</div>]]
|
|
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 getDeviceProtocolPoliciesUrl(params_str)
|
|
local url, sep
|
|
|
|
if ntop.isnEdge() then
|
|
url = "/lua/pro/nedge/admin/nf_edit_user.lua?page=device_protocols"
|
|
sep = "&"
|
|
else
|
|
url = "/lua/admin/edit_device_protocols.lua"
|
|
sep = "?"
|
|
end
|
|
|
|
if not isEmptyString(params_str) then
|
|
return ntop.getHttpPrefix() .. url .. sep .. params_str
|
|
end
|
|
|
|
return ntop.getHttpPrefix() .. url
|
|
end
|
|
|
|
-- ###########################################
|
|
|
|
-- Banner format: {type="success|warning|danger", text="..."}
|
|
function printMessageBanners(banners)
|
|
for _, msg in ipairs(banners) do
|
|
print[[
|
|
<div class="alert alert-]] print(msg.type) print([[ alert-dismissible" style="margin-top:2em; margin-bottom:0em;">
|
|
]])
|
|
|
|
if (msg.type == "warning") then
|
|
print("<b>".. i18n("warning") .. "</b>: ")
|
|
elseif (msg.type == "danger") then
|
|
print("<b>".. i18n("error") .. "</b>: ")
|
|
end
|
|
|
|
print(msg.text)
|
|
|
|
print[[
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>]]
|
|
end
|
|
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
|
|
|
|
-- ###########################################
|
|
|
|
-- Returns the size of a folder (size is in bytes)
|
|
--! @param path the path to compute the size for
|
|
--! @param timeout the maxium time to compute the size. If nil, it defaults to 15 seconds.
|
|
function getFolderSize(path, timeout)
|
|
local folder_size_key = "ntopng.cache.folder_size"
|
|
local now = os.time()
|
|
local expiration = 30 -- sec
|
|
local size = nil
|
|
|
|
if ntop.isWindows() then
|
|
size = 0 -- TODO
|
|
else
|
|
local MAX_TIMEOUT = tonumber(timeout) or 15 -- default
|
|
-- Check if timeout is present on the system to cap the execution time of the subsequent du,
|
|
-- which may be very time consuming, especially when the number of files is high
|
|
local has_timeout = ntop.getCache("ntopng.cache.has_gnu_timeout")
|
|
|
|
if isEmptyString(has_timeout) then
|
|
-- Cache the timeout
|
|
-- Check timeout existence with which. If no timeout is found, command will return nil
|
|
has_timeout = (os_utils.execWithOutput("which timeout >/dev/null 2>&1") ~= nil)
|
|
ntop.setCache("ntopng.cache.has_gnu_timeout", tostring(has_timeout), 3600)
|
|
else
|
|
has_timeout = has_timeout == "true"
|
|
end
|
|
|
|
-- Check the cache for a recent value
|
|
local time_size = ntop.getHashCache(folder_size_key, path)
|
|
if not isEmptyString(time_size) then
|
|
local values = split(time_size, ',')
|
|
if #values >= 2 and tonumber(values[1]) >= (now - expiration) then
|
|
size = tonumber(values[2])
|
|
end
|
|
end
|
|
|
|
if size == nil then
|
|
size = 0
|
|
-- Read disk utilization
|
|
local periodic_activities_utils = require "periodic_activities_utils"
|
|
if ntop.isdir(path) and not periodic_activities_utils.have_degraded_performance() then
|
|
local du_cmd = string.format("du -s %s 2>/dev/null", path)
|
|
if has_timeout then
|
|
du_cmd = string.format("timeout %u%s %s", MAX_TIMEOUT, "s", du_cmd)
|
|
end
|
|
|
|
-- use POSIXLY_CORRECT=1 to guarantee results is returned in 512-byte blocks
|
|
-- both on BSD and Linux
|
|
local line = os_utils.execWithOutput(string.format("POSIXLY_CORRECT=1 %s", du_cmd))
|
|
local values = split(line, '\t')
|
|
if #values >= 1 then
|
|
local used = tonumber(values[1])
|
|
if used ~= nil then
|
|
size = math.ceil(used * 512)
|
|
|
|
-- Cache disk utilization
|
|
ntop.setHashCache("ntopng.cache.folder_size", path, now..","..size)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return size
|
|
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
|
|
|
|
-- ###########################################
|
|
|
|
function getHttpUrlPrefix()
|
|
if starts(_SERVER["HTTP_HOST"], 'https://') then
|
|
return "https://"
|
|
else
|
|
return "http://"
|
|
end
|
|
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 getPoolName(pool_id)
|
|
if isEmptyString(pool_id) or pool_id == "0" then
|
|
return "Default"
|
|
else
|
|
local key = "ntopng.prefs.host_pools.details."..pool_id
|
|
|
|
return ntop.getHashCache(key, "name")
|
|
end
|
|
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
|
|
|
|
function get_version_update_msg(info, latest_version)
|
|
if info then
|
|
local version_elems = split(info["version"], " ")
|
|
local new_version = version2int(latest_version)
|
|
local this_version = version2int(version_elems[1])
|
|
|
|
if (new_version > this_version) then
|
|
return i18n("about.new_major_available", {
|
|
product = info["product"], version = latest_version,
|
|
url = "http://www.ntop.org/get-started/download/"
|
|
})
|
|
end
|
|
end
|
|
|
|
return ""
|
|
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
|
|
|
|
-- ###########################################
|
|
|
|
--- Test if each element inside the table t satisfies the predicate function
|
|
--- @param t table The table containing values to test
|
|
--- @param predicate function The function that return a boolean value (true|false)
|
|
--- @return boolean
|
|
function table.all(t, predicate)
|
|
|
|
if type(t) ~= 'table' then
|
|
traceError(TRACE_DEBUG, TRACE_CONSOLE, "the first paramater is not a table!")
|
|
return false
|
|
end
|
|
if type(predicate) ~= 'function' then
|
|
traceError(TRACE_DEBUG, TRACE_CONSOLE, "the passed predicate is not a function!")
|
|
return false
|
|
end
|
|
|
|
if t == nil then return false end
|
|
|
|
for _, value in pairs(t) do
|
|
|
|
-- check if the value satisfies the boolean predicate
|
|
local term = predicate(value)
|
|
|
|
-- if the return value is valid and true then do nothing
|
|
-- otherwise stop the loop and return false
|
|
if term == nil then
|
|
-- inform the client about the nil value
|
|
traceError(TRACE_DEBUG, TRACE_CONSOLE, "a null term has been returned from the predicate function!")
|
|
return false
|
|
elseif not term then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- each entry satisfies the predicate
|
|
return true
|
|
end
|
|
|
|
-- ###########################################
|
|
|
|
--- Perform a linear search to check if an element is inside a table
|
|
--- @param t table The table to scan
|
|
--- @param needle any The element to search
|
|
--- @param comp function The compare function used to compare the searched element with others
|
|
--- @return boolean True if the element is insie the table, False otherwise
|
|
function table.contains(t, needle, comp)
|
|
|
|
if (t == nil) then return false end
|
|
if (type(t) ~= "table") then return false end
|
|
if (#t == 0) then return false end
|
|
|
|
local default_compare = (function(e) return e == needle end)
|
|
comp = comp or default_compare
|
|
|
|
for _, element in ipairs(t) do
|
|
if comp(element) then return true end
|
|
end
|
|
|
|
return false
|
|
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
|
|
|
|
-- ###########################################
|
|
|
|
--- Insert an element inside the table if is not present
|
|
function table.insertIfNotPresent(t, element, comp)
|
|
if table.contains(t, element, comp) then return end
|
|
t[#t+1] = element
|
|
end
|
|
|
|
-- ###########################################
|
|
|
|
--- Fold right table with a custom function
|
|
--- @param t table Table to fold
|
|
--- @param func function Function to execute on table values
|
|
--- @param val any The returned default value
|
|
function table.foldr(t, func, val)
|
|
for i,v in pairs(t) do
|
|
val = func(val, v)
|
|
end
|
|
return val
|
|
end
|
|
|
|
-- ###########################################
|
|
|
|
function table.has_key(table, key)
|
|
return table[key] ~= nil
|
|
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 getObsPointAliasKey()
|
|
return "ntopng.observation_point_aliases"
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getObsPointAlias(observation_point_id, add_id, add_href)
|
|
local alias = ntop.getHashCache(getObsPointAliasKey(), observation_point_id)
|
|
local ret
|
|
|
|
if not isEmptyString(alias) then
|
|
if(add_id == true) then
|
|
ret = observation_point_id .. " [".. alias .."]"
|
|
else
|
|
ret = alias
|
|
end
|
|
else
|
|
ret = tostring(observation_point_id)
|
|
end
|
|
|
|
if(add_href == true) then
|
|
ret = "<A HREF=\"".. ntop.getHttpPrefix() .."/lua/pro/enterprise/observation_points.lua\">"..ret.."</A>"
|
|
end
|
|
|
|
return ret
|
|
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 getFullObsPointName(observation_point_id, compact, add_id)
|
|
local alias = getObsPointAlias(observation_point_id, add_id)
|
|
|
|
if not isEmptyString(observation_point_id) then
|
|
if not isEmptyString(observation_point_id) and alias ~= tostring(observation_point_id) then
|
|
if compact then
|
|
alias = shortenString(alias)
|
|
return string.format("%s", alias)
|
|
else
|
|
return string.format("%u [%s]", observation_point_id, alias)
|
|
end
|
|
end
|
|
end
|
|
|
|
return observation_point_id
|
|
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 getExtraFlowInfoTLSIssuerDN(alert_json)
|
|
if alert_json and alert_json["proto"] and alert_json["proto"]["tls"] and not isEmptyString(alert_json["proto"]["tls"]["issuerDN"]) then
|
|
return alert_json["proto"]["tls"]["issuerDN"]
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getExtraFlowInfoURL(alert_json)
|
|
if alert_json then
|
|
if alert_json["proto"] and alert_json["proto"]["http"] and not isEmptyString(alert_json["proto"]["http"]["last_url"]) then
|
|
return alert_json["proto"]["http"]["last_url"]
|
|
elseif alert_json["proto"] and alert_json["proto"]["dns"] and not isEmptyString(alert_json["proto"]["dns"]["last_query"]) then
|
|
return alert_json["proto"]["dns"]["last_query"]
|
|
elseif alert_json["proto"] and alert_json["proto"]["tls"] and not isEmptyString(alert_json["proto"]["tls"]["client_requested_server_name"]) then
|
|
return alert_json["proto"]["tls"]["client_requested_server_name"]
|
|
end
|
|
end
|
|
return nil
|
|
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 table.slice(t, start_table, end_table)
|
|
if t == nil then
|
|
error("The array to slice cannot be nil!")
|
|
end
|
|
|
|
if end_table > #t then
|
|
end_table = #t
|
|
end
|
|
|
|
if start_table < 1 then
|
|
error("Invalid bounds!")
|
|
end
|
|
|
|
local res = {}
|
|
for i = start_table, end_table, 1 do
|
|
res[#res + 1] = t[i]
|
|
end
|
|
|
|
return res
|
|
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
|
|
|
|
-- ##############################################
|
|
|
|
-- @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)
|
|
local idx_name = portidx
|
|
-- SNMP is available only with Pro version at least
|
|
if ntop.isPro() then
|
|
local snmp_cached_dev = require "snmp_cached_dev"
|
|
local cached_dev = snmp_cached_dev:create(device_ip)
|
|
|
|
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"] = port_info["index"]
|
|
port_info["snmp_device_ip"] = cached_dev["host_ip"]
|
|
end
|
|
|
|
if short_version then
|
|
idx_name = string.format('%s [%s]', port_info["index"], port_info["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
|
|
|
|
-- ##############################################
|
|
|
|
function print_copy_button(id, data)
|
|
print('<button style="" class="btn btn-sm border ms-1" data-placement="bottom" id="btn-copy-' .. id ..'" data="' .. data .. '"><i class="fas fa-copy"></i></button>')
|
|
print("<script>$('#btn-copy-" .. id .. "').click(function(e) { NtopUtils.copyToClipboard($(this).attr('data'), '" .. i18n('copied') .. "', '" .. i18n('request_failed_message') .. "', $(this));});</script>")
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function get_badge(info)
|
|
local badge = 'success'
|
|
|
|
if info ~= true and info ~= 0 then
|
|
badge = 'danger'
|
|
end
|
|
|
|
return badge
|
|
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
|
|
dns_info.last_query = i18n("external_link_url", { proto = 'https', url = url, url_name = dns_info["last_query"] })
|
|
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
|
|
tls_info["client_requested_server_name"] = i18n("external_link_url", { proto = 'https', url = url, url_name = url})
|
|
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
|
|
http_info["last_url"] = i18n("external_link_url", { proto = 'http', url = url, url_name = url})
|
|
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) 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 get_confidence(confidence_id, shorten_string)
|
|
local tag_utils = require "tag_utils"
|
|
local confidence_name = confidence_id
|
|
|
|
if confidence_id and tonumber(confidence_id) then
|
|
confidence_id = tonumber(confidence_id)
|
|
|
|
for _, confidence in pairs(tag_utils.confidence or {}) do
|
|
if confidence.id == confidence_id then
|
|
confidence_name = confidence.label
|
|
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
return confidence_name
|
|
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
|
|
--
|
|
-- IMPORTANT
|
|
-- Leave it at the end so it can use the functions
|
|
-- defined in this file
|
|
--
|
|
http_lint = require "http_lint"
|
|
|