mirror of
https://github.com/ntop/ntopng.git
synced 2026-04-29 15:39:33 +00:00
The graph page has been set as the first page wehn a user navigates to the service map page. When a user double clicks a node then the map will be filtered with only the edges linked to the node. The flush data and download buttons have been restored.
4037 lines
127 KiB
Lua
4037 lines
127 KiB
Lua
--
|
|
-- (C) 2014-20 - 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
|
|
|
|
require "lua_trace"
|
|
require "ntop_utils"
|
|
locales_utils = require "locales_utils"
|
|
local os_utils = require "os_utils"
|
|
local format_utils = require "format_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' then
|
|
return false
|
|
end
|
|
|
|
local i, _ = string.find(str, start, 1, is_plain)
|
|
|
|
return(i ~= nil)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function shortenString(name, max_len)
|
|
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) then
|
|
return(name)
|
|
else
|
|
return(string.sub(name, 1, max_len).."...")
|
|
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
|
|
|
|
-- ##############################################
|
|
|
|
-- 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 },
|
|
{ "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, 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)
|
|
info = ntop.getInfo(false)
|
|
local cookie_attr = ntop.getCookieAttributes()
|
|
local lines = {
|
|
'Cache-Control: max-age=0, no-cache, no-store',
|
|
'Server: ntopng '..info["version"]..' ['.. info["platform"]..']',
|
|
'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"),
|
|
}
|
|
|
|
if(_SESSION ~= nil) then
|
|
lines[#lines + 1] = 'Set-Cookie: session='.._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(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 HREF='http://as.robtex.com/as"..asn..".html' title='"..asname.."'>"..asname.."</A> <i class='fas fa-external-link-alt fa-lg'></i>")
|
|
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-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-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(vid)) 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-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
|
|
|
|
-- ##############################################
|
|
|
|
--
|
|
-- 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[2] == proto_name then
|
|
return(proto[3])
|
|
end
|
|
end
|
|
end
|
|
|
|
function l4_proto_to_string(proto_id)
|
|
proto_id = tonumber(proto_id)
|
|
|
|
for _, proto in pairs(l4_keys) do
|
|
if proto[3] == proto_id then
|
|
return proto[1], proto[2]
|
|
end
|
|
end
|
|
|
|
return string.format("%d", proto_id)
|
|
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("%s*<[iI].->(.-)</[iI]>","%1")
|
|
:gsub("<.->(.-)</.->","%1") -- note: this does not handle nested tags
|
|
:gsub("^%s*(.-)%s*$", "%1")
|
|
|
|
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())
|
|
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 mustScanAlerts(ifstats)
|
|
return areAlertsEnabled()
|
|
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 hasNindexSupport()
|
|
if not ntop.isPro() or ntop.isWindows() then
|
|
return false
|
|
end
|
|
|
|
-- TODO optimize
|
|
if prefs == nil then
|
|
prefs = ntop.getPrefs()
|
|
end
|
|
|
|
if prefs.is_nindex_enabled then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- NOTE: global nindex support may be enabled but some disable on some interfaces
|
|
function interfaceHasNindexSupport()
|
|
return(hasNindexSupport() and interface.nIndexEnabled())
|
|
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)
|
|
return(format_utils.formatEpoch(epoch))
|
|
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
|
|
|
|
-- #################################
|
|
|
|
function hostVisualization(ip, name, vlan)
|
|
if (ip ~= name) then
|
|
if isIPv6(ip) then
|
|
name = name.." [IPv6]"
|
|
end
|
|
else
|
|
if vlan ~= nil and tonumber(vlan) > 0 then
|
|
name = name.."@"..vlan
|
|
end
|
|
end
|
|
|
|
return name
|
|
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 hostVisualization(hostinfo["host"], hostname, hostinfo["vlan"])
|
|
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)
|
|
local icon = getApplicationIcon(name)
|
|
|
|
name = name:gsub("^%l", string.upper)
|
|
return(icon.." "..name)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function getCategoryLabel(cat_name)
|
|
if isEmptyString(cat_name) then
|
|
return("")
|
|
end
|
|
|
|
local v = i18n("ndpi_categories." .. cat_name)
|
|
if v then
|
|
-- Localized string found
|
|
return(v)
|
|
end
|
|
|
|
cat_name = cat_name:gsub("^%l", string.upper)
|
|
return(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 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 plugins
|
|
host_key = host_info["host"] -- hostinfo2hostkey(host_info)
|
|
else
|
|
host_key = host_info
|
|
end
|
|
|
|
ntop.setCache(getHostAltNamesKey(host_key), alt_name)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function getDhcpNameKey(ifid, mac)
|
|
return string.format("ntopng.dhcp.%d.cache.%s", ifid, mac)
|
|
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
|
|
--
|
|
function hostinfo2label(host_info)
|
|
local alt_name = nil
|
|
local ip = host_info["ip"] or host_info["host"]
|
|
|
|
-- 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"] ~= false) and (host_info["dhcpHost"] ~= false) then
|
|
alt_name = getHostAltName(host_info["mac"])
|
|
|
|
if not isEmptyString(alt_name) then
|
|
return(alt_name)
|
|
end
|
|
end
|
|
|
|
alt_name = getHostAltName(ip)
|
|
|
|
if not isEmptyString(alt_name) then
|
|
return(alt_name)
|
|
end
|
|
|
|
-- Name info from C (e.g. DHCP name)
|
|
if not isEmptyString(host_info["name"]) then
|
|
return host_info["name"]
|
|
end
|
|
|
|
-- Try to get the resolved name
|
|
local hostname = ntop.getResolvedName(ip)
|
|
local rname = hostVisualization(ip, hostname, host_info["vlan"])
|
|
|
|
if not isEmptyString(rname) then
|
|
return rname
|
|
end
|
|
|
|
-- Fallback: just the IP and VLAN
|
|
return(hostinfo2hostkey(host_info))
|
|
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
|
|
if 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
|
|
|
|
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 interfaceHasNindexSupport() 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 res = ''
|
|
|
|
if not href_check or hostdetails_exists(host_info, href_params) then
|
|
local url_params = table.tconcat(href_params or {}, "=", "&")
|
|
|
|
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
|
|
|
|
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
|
|
-- @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)
|
|
local hostdetails_url = hostinfo2detailsurl(host_info, href_params, href_check)
|
|
|
|
if not isEmptyString(hostdetails_url) then
|
|
res = string.format("<a href='%s' data-toggle='tooltip' title='%s'>%s</a>",
|
|
hostdetails_url, href_tooltip or '', href_value or '')
|
|
else
|
|
res = href_value or ''
|
|
end
|
|
|
|
return res
|
|
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)
|
|
local name
|
|
local orig_name
|
|
|
|
if alerts_view and not hasNindexSupport() then
|
|
-- do not return resolved name as it will hide the IP address
|
|
return(flow_info[host_type..".ip"])
|
|
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") 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"],
|
|
mac = flow_info[host_type..".mac"],
|
|
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 getLocalNetworkAlias(network)
|
|
local alias = ntop.getHashCache(getLocalNetworkAliasKey(), network)
|
|
|
|
if not isEmptyString(alias) then
|
|
return alias
|
|
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 flow2hostinfo(host_info, host_type)
|
|
if host_type == "cli" then
|
|
return({host = host_info["cli.ip"], vlan = host_info["cli.vlan"]})
|
|
elseif host_type == "srv" then
|
|
return({host = host_info["srv.ip"], vlan = host_info["srv.vlan"]})
|
|
end
|
|
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
|
|
|
|
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(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
|
|
|
|
if((host_info["vlan"] ~= nil and host_info["vlan"] ~= 0) or show_vlan) then
|
|
rsp = rsp..'@'..tostring(host_info["vlan"] or 0)
|
|
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
|
|
|
|
-- NOTE: on index based tables using #table is much more performant
|
|
function table.len(table)
|
|
local count = 0
|
|
|
|
if(table == nil) then return(0) end
|
|
|
|
for k,v in pairs(table) do
|
|
count = count + 1
|
|
end
|
|
|
|
return count
|
|
end
|
|
|
|
function table.slice(tbl, first, last, step)
|
|
local sliced = {}
|
|
|
|
for i = first or 1, last or #tbl, step or 1 do
|
|
sliced[#sliced+1] = tbl[i]
|
|
end
|
|
|
|
return sliced
|
|
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."..current_ifname.."_not_idle")
|
|
if(state == "0") then return true else return false end
|
|
end
|
|
|
|
function getThroughputType()
|
|
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.'..ifname..'.speed')
|
|
if not isEmptyString(ifspeed) and tonumber(ifspeed) ~= nil then
|
|
ifspeed = tonumber(ifspeed)
|
|
else
|
|
ifspeed = interface.getMaxIfSpeed(ifid)
|
|
end
|
|
|
|
return ifspeed
|
|
end
|
|
|
|
function getInterfaceRefreshRate(ifid)
|
|
local key = "ntopng.prefs.ifid_"..tostring(ifid)..".refresh_rate"
|
|
local refreshrate = ntop.getCache(key)
|
|
|
|
if isEmptyString(refreshrate) or tonumber(refreshrate) == nil then
|
|
refreshrate = 3
|
|
else
|
|
refreshrate = tonumber(refreshrate)
|
|
end
|
|
|
|
return refreshrate
|
|
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)
|
|
if(interface_name == "__system__") then
|
|
return(i18n("system"))
|
|
elseif tonumber(interface_name) ~= nil then
|
|
-- convert ID to name
|
|
interface_name = getInterfaceName(interface_name)
|
|
else
|
|
-- Parameter is a string, let's take it's id first
|
|
local interface_id = getInterfaceId(interface_name)
|
|
-- and then get the name
|
|
interface_name = getInterfaceName(interface_id)
|
|
end
|
|
|
|
local key = 'ntopng.prefs.'..interface_name..'.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 haveAdminPrivileges(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\"><img src=".. ntop.getHttpPrefix() .. "/img/warning.png> 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)
|
|
if(breed == "Safe") then
|
|
return("<i class='fas fa-lock' alt='Safe Protocol'></i>")
|
|
elseif(breed == "Acceptable") then
|
|
return("<i class='fas fa-thumbs-up' alt='Acceptable Protocol'></i>")
|
|
elseif(breed == "Fun") then
|
|
return("<i class='fas fa-smile' alt='Fun Protocol'></i>")
|
|
elseif(breed == "Unsafe") then
|
|
return("<i class='fas fa-thumbs-down'></i>")
|
|
elseif(breed == "Dangerous") then
|
|
return("<i class='fas fa-exclamation-triangle'></i>")
|
|
else
|
|
return("")
|
|
end
|
|
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() .. "/img/blank.gif' class='flag flag-".. string.lower(country) .."'></a> ")
|
|
end
|
|
end
|
|
|
|
-- GENERIC UTILS
|
|
|
|
-- split
|
|
function split(s, delimiter)
|
|
result = {};
|
|
if(s ~= nil) then
|
|
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
|
|
table.insert(result, match);
|
|
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 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
|
|
|
|
-- 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 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, only_symbolic)
|
|
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(only_symbolic == true) then
|
|
return(magic_short_macs[m].."_"..t)
|
|
else
|
|
return(magic_short_macs[m].."_"..t.." ("..macInfo(mac_address)..")")
|
|
end
|
|
else
|
|
local s = get_mac_classification(m)
|
|
|
|
if(m == s) then
|
|
return '<a href="' .. ntop.getHttpPrefix() .. '/lua/mac_details.lua?host='..mac_address..'">' .. get_mac_classification(m) .. ":" .. t .. '</a>'
|
|
else
|
|
if(only_symbolic == true) then
|
|
return(get_mac_classification(m).."_"..t)
|
|
else
|
|
return(get_mac_classification(m).."_"..t.." ("..macInfo(mac_address)..")")
|
|
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 target=\"_blank\" HREF=\"http://"..site.."\">"..site.."</A> <i class=\"fas fa-external-link-alt\"></i></th>")
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function formatElephantFlowStatus(flowstatus_info, local2remote)
|
|
local threshold = ""
|
|
local res = ""
|
|
|
|
if not flowstatus_info then
|
|
return i18n("flow_details.elephant_flow")
|
|
end
|
|
|
|
if local2remote then
|
|
res = i18n("flow_details.elephant_flow_l2r")
|
|
|
|
if flowstatus_info["elephant.l2r_threshold"] then
|
|
threshold = flowstatus_info["elephant.l2r_threshold"]
|
|
end
|
|
else
|
|
res = i18n("flow_details.elephant_flow_r2l")
|
|
|
|
if flowstatus_info["elephant.r2l_threshold"] then
|
|
threshold = flowstatus_info["elephant.r2l_threshold"]
|
|
end
|
|
end
|
|
|
|
res = string.format("%s<sup><i class='fas fa-info-circle' aria-hidden='true' title='"..i18n("flow_details.elephant_flow_descr").."'></i></sup>", res)
|
|
|
|
if threshold ~= "" then
|
|
res = string.format("%s [%s]", res, i18n("flow_details.elephant_exceeded", {vol = bytesToSize(threshold)}))
|
|
end
|
|
|
|
return res
|
|
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 printTCPFlags(flags)
|
|
if(hasbit(flags,0x01)) then print('<span class="badge badge-info">FIN</span> ') end
|
|
if(hasbit(flags,0x02)) then print('<span class="badge badge-info">SYN</span> ') end
|
|
if(hasbit(flags,0x04)) then print('<span class="badge badge-danger">RST</span> ') end
|
|
if(hasbit(flags,0x08)) then print('<span class="badge badge-info">PUSH</span> ') end
|
|
if(hasbit(flags,0x10)) then print('<span class="badge badge-info">ACK</span> ') end
|
|
if(hasbit(flags,0x20)) then print('<span class="badge badge-info">URG</span> ') end
|
|
if(hasbit(flags,0x40)) then print('<span class="badge badge-info">ECE</span> ') end
|
|
if(hasbit(flags,0x80)) then print('<span class="badge badge-info">CWR</span> ') end
|
|
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)
|
|
if ntop.isPro() and ntop.getPrefs().is_dump_flows_to_mysql_enabled == true then
|
|
local hist_url = ntop.getHttpPrefix().."/lua/pro/db_explorer.lua?search=true&ifid="..ifId
|
|
local now = os.time()
|
|
local ago1h = now - 3600
|
|
|
|
hist_url = hist_url.."&epoch_end="..tostring(now)
|
|
if((host ~= nil) and (host ~= "")) then hist_url = hist_url.."&"..hostinfo2url(host) end
|
|
if((l4_proto ~= nil) and (l4_proto ~= "")) then
|
|
hist_url = hist_url.."&l4proto="..l4_proto
|
|
end
|
|
if((ndpi_proto_id ~= nil) and (ndpi_proto_id ~= "")) then hist_url = hist_url.."&protocol="..ndpi_proto_id end
|
|
if((info ~= nil) and (info ~= "")) then hist_url = hist_url.."&info="..info end
|
|
print(' ')
|
|
-- print('<span class="badge badge-info">')
|
|
print('<a href="'..hist_url..'&epoch_begin='..tostring(ago1h)..'" title="Flows seen in the last hour"><i class="fas fa-history fa-lg"></i></a>')
|
|
-- print('</span>')
|
|
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 btn-group-toggle ]] .. table.concat(extra.classes or {}, "") .. [[" id="]] .. ctrl_id .. [[" data-toggle="buttons" 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
|
|
line[#line+1] = [[<label class="btn]]
|
|
if selected == k then
|
|
line[#line+1] = [[ btn-primary active]]
|
|
else
|
|
line[#line+1] = [[ btn-secondary]]
|
|
end
|
|
line[#line+1] = [[ btn-sm"><input data-resol="]] .. k .. [[" value="]] .. truncate(v.value) .. [[" title="]] .. v.label .. [[" name="opt_resbt_]] .. k .. [[_]] .. ctrl_id .. [[" autocomplete="off" type="radio"]]
|
|
if selected == k then line[#line+1] = [[ checked="checked"]] end
|
|
line[#line+1] = [[/>]] .. v.label .. [[</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")).last();
|
|
}
|
|
|
|
function resol_selector_get_buttons(an_input) {
|
|
return $(".btn-group", $(an_input).closest(".form-group")).first().find("input");
|
|
}
|
|
|
|
/* This function scales values wrt selected resolution */
|
|
function resol_selector_reset_input_range(selected) {
|
|
var selected = $(selected);
|
|
var input = resol_selector_get_input(selected);
|
|
|
|
var raw = parseInt(input.attr("data-min"));
|
|
if (! isNaN(raw))
|
|
input.attr("min", Math.sign(raw) * Math.ceil(Math.abs(raw) / selected.val()));
|
|
|
|
raw = parseInt(input.attr("data-max"));
|
|
if (! isNaN(raw))
|
|
input.attr("max", Math.sign(raw) * Math.ceil(Math.abs(raw) / selected.val()));
|
|
|
|
var step = parseInt(input.attr("data-step-" + selected.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);
|
|
}
|
|
|
|
function resol_selector_change_selection(selected) {
|
|
selected.attr('checked', 'checked')
|
|
.closest("label").removeClass('btn-secondary').addClass('btn-primary')
|
|
.siblings().removeClass('active').removeClass('btn-primary').addClass('btn-secondary').find("input").removeAttr('checked');
|
|
|
|
resol_selector_reset_input_range(selected);
|
|
}
|
|
|
|
function resol_recheck_input_range(input) {
|
|
var value = input.val();
|
|
|
|
if (input[0].hasAttribute("min"))
|
|
value = Math.max(value, input.attr("min"));
|
|
if (input[0].hasAttribute("max"))
|
|
value = Math.min(value, input.attr("max"));
|
|
|
|
var old_val = input.val();
|
|
if ((old_val != "") && (old_val != value))
|
|
input.val(value);
|
|
}
|
|
|
|
function resol_selector_change_callback(event) {
|
|
resol_selector_change_selection($(this));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Helper function to set a selector value by raw value */
|
|
function resol_selector_set_value(input_id, its_value) {
|
|
var input = $(input_id);
|
|
var buttons = resol_selector_get_buttons($(input_id));
|
|
var values = [];
|
|
|
|
buttons.each(function() {
|
|
values.push(parseInt($(this).val()));
|
|
});
|
|
|
|
var new_value;
|
|
var new_i;
|
|
if (its_value > 0) {
|
|
/* highest divisor */
|
|
var highest_i = 0;
|
|
for (var i=1; i<values.length; i++) {
|
|
if(((values[i] > values[highest_i]) && (its_value % values[i] == 0)))
|
|
highest_i = i;
|
|
}
|
|
|
|
new_value = its_value / values[highest_i];
|
|
new_i = highest_i;
|
|
} else {
|
|
/* smallest value */
|
|
new_value = Math.max(its_value, -1);
|
|
new_i = values.indexOf(Math.min.apply(Math, values));
|
|
}
|
|
|
|
/* Set */
|
|
input.val(new_value);
|
|
resol_selector_change_selection($(buttons[new_i]));
|
|
|
|
/* This must be set manually on initialization */
|
|
$(buttons[new_i]).closest("label").addClass("active");
|
|
}
|
|
|
|
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);
|
|
resol_recheck_input_range(input);
|
|
|
|
/* 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, "\n", "")
|
|
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 printntopngRelease(info)
|
|
if info.oem then
|
|
return ""
|
|
end
|
|
|
|
if(info["version.enterprise_l_edition"]) then
|
|
print(" Enterprise L")
|
|
elseif(info["version.enterprise_m_edition"]) then
|
|
print(" Enterprise M")
|
|
elseif(info["version.enterprise_edition"]) or (info["version.nedge_enterprise_edition"]) then
|
|
print(" Enterprise")
|
|
elseif(info["version.nedge_edition"] or info["pro.release"]) then
|
|
print(" Professional")
|
|
else
|
|
print(" Community")
|
|
end
|
|
|
|
if(info["version.embedded_edition"] == true) then
|
|
print("/Embedded")
|
|
end
|
|
|
|
print(" Edition</td></tr>\n")
|
|
end
|
|
|
|
function getNtopngRelease(ntopng_info)
|
|
if ntopng_info.oem or ntopng_info["version.nedge_edition"] then
|
|
return ""
|
|
end
|
|
|
|
if(ntopng_info["version.enterprise_l_edition"]) then
|
|
return "Enterprise L"
|
|
elseif(ntopng_info["version.enterprise_m_edition"]) then
|
|
return "Enterprise M"
|
|
elseif(ntopng_info["version.enterprise_edition"]) or (ntopng_info["version.nedge_enterprise_edition"]) then
|
|
return "Enterprise"
|
|
elseif(ntopng_info["pro.release"]) then
|
|
return "Professional"
|
|
else
|
|
return "Community"
|
|
end
|
|
|
|
if(ntopng_info["version.embedded_edition"] == true) then
|
|
return"/Embedded"
|
|
end
|
|
|
|
return ""
|
|
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 printWarningAlert(message)
|
|
print[[<div class="alert alert-warning alert-dismissable" role="alert">]]
|
|
print[[<a class="close" data-dismiss="alert" aria-label="close">×</a>]]
|
|
print[[<i class="fas fa-exclamation-triangle fa-sm"></i> ]]
|
|
print[[<strong>]] print(i18n("warning")) print[[</strong> ]]
|
|
print(message)
|
|
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;">
|
|
<button type="button" class="close" data-dismiss="alert" aria-label="]]..i18n("close")..[[">
|
|
<span aria-hidden="true">×</span>
|
|
</button>]])
|
|
|
|
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[[
|
|
</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-control ]] .. (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 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 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)
|
|
|
|
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
|
|
|
|
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()
|
|
-- 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
|
|
|
|
-- ###########################################
|
|
|
|
--- 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
|
|
|
|
-- ###########################################
|
|
|
|
local cache = {}
|
|
|
|
function buildHostHREF(ip_address, vlan_id, page)
|
|
local stats = cache[ip_address]
|
|
|
|
if(stats == nil) then
|
|
stats = interface.getHostInfo(ip_address, vlan_id)
|
|
cache[ip_address] = { stats = stats }
|
|
else
|
|
stats = stats.stats
|
|
end
|
|
|
|
if(stats == nil) then
|
|
return(ip_address)
|
|
else
|
|
local name = stats.name
|
|
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 builServiceMapHREF(ip_address, vlan_id)
|
|
|
|
local stats = cache[ip_address]
|
|
|
|
if(stats == nil) then
|
|
stats = interface.getHostInfo(ip_address, vlan_id)
|
|
cache[ip_address] = { stats = stats }
|
|
else
|
|
stats = stats.stats
|
|
end
|
|
|
|
if(stats == nil) then
|
|
return(ip_address)
|
|
else
|
|
local name = stats.name
|
|
local res
|
|
|
|
if((name == nil) or (name == "")) then name = ip_address end
|
|
res = '<a href="'..ntop.getHttpPrefix()..'/lua/service_map.lua?host='..ip_address
|
|
|
|
if(vlan_id and (vlan_id ~= 0)) then res = res .. "@"..vlan_id end
|
|
res = res ..'&page=graph">'..name..'</A>'
|
|
|
|
return(res)
|
|
end
|
|
end
|
|
|
|
--
|
|
-- IMPORTANT
|
|
-- Leave it at the end so it can use the functions
|
|
-- defined in this file
|
|
--
|
|
http_lint = require "http_lint"
|