-- -- (C) 2014-22 - ntop.org -- -- Hack to avoid include loops if(pragma_once_lua_utils == true) then -- avoid multiple inclusions return end pragma_once_lua_utils = true -- ############################################### dirs = ntop.getDirs() package.path = dirs.installdir .. "/scripts/lua/modules/i18n/?.lua;" .. package.path package.path = dirs.installdir .. "/scripts/lua/modules/timeseries/?.lua;" .. package.path package.path = dirs.installdir .. "/scripts/lua/modules/flow_dbms/?.lua;" .. package.path package.path = dirs.installdir .. "/scripts/lua/modules/pools/?.lua;" .. package.path require "lua_trace" require "ntop_utils" locales_utils = require "locales_utils" local os_utils = require "os_utils" local format_utils = require "format_utils" local dscp_consts = require "dscp_consts" local http_utils = require "http_utils" local dns_utils = require "dns_utils" local network_utils = require "network_utils" -- TODO: replace those globals with locals everywhere secondsToTime = format_utils.secondsToTime msToTime = format_utils.msToTime bytesToSize = format_utils.bytesToSize formatPackets = format_utils.formatPackets formatFlows = format_utils.formatFlows formatValue = format_utils.formatValue pktsToSize = format_utils.pktsToSize bitsToSize = format_utils.bitsToSize round = format_utils.round bitsToSizeMultiplier = format_utils.bitsToSizeMultiplier -- ############################################## -- Note: Regexs are applied by default. Pass plain=true to disable them. function string.contains(str, start, is_plain) if type(str) ~= 'string' or type(start) ~= 'string' or isEmptyString(str) or isEmptyString(start) then return false end local i, _ = string.find(str, start, 1, is_plain) return(i ~= nil) end -- ############################################## function string.containsIgnoreCase(str, start, is_plain) return string.contains(string.lower(str), string.lower(start), is_plain) end -- ############################################## function shortenString(name, max_len) local ellipsis = "\u{2026}" -- The unicode ellipsis (takes less space than three separate dots) if(name == nil) then return("") end if max_len == nil then max_len = ntop.getPref("ntopng.prefs.max_ui_strlen") max_len = tonumber(max_len) if(max_len == nil) then max_len = 24 end end if(string.len(name) < max_len + 1 --[[ The space taken by the ellipsis --]]) then return(name) else return(string.sub(name, 1, max_len)..ellipsis) end end -- ############################################## function convertDate(vardate) local m,d,y,h,i,s = string.match(vardate, '(%d+)/(%d+)/(%d+) (%d+):(%d+):(%d+)') local key = ntop.getPref('ntopng.user.' .. _SESSION["user"] .. '.date_format') if(key == "little_endian") then return string.format('%s/%s/%s %s:%s:%s', d,m,y,h,i,s) elseif( key == "middle_endian") then return string.format('%s/%s/%s %s:%s:%s', m,d,y,h,i,s) else return string.format('%s/%s/%s %s:%s:%s', y,m,d,h,i,s) end end -- ############################################## -- See also getHumanReadableInterfaceName function getInterfaceName(interface_id, windows_skip_description) if(interface_id == getSystemInterfaceId()) then return(getSystemInterfaceName()) end local ifnames = interface.getIfNames() local iface = ifnames[tostring(interface_id)] if iface ~= nil then if(windows_skip_description ~= true and string.contains(iface, "{")) then -- Windows local old_iface = interface.getId() -- Use the interface description instead of the name interface.select(tostring(iface)) iface = interface.getStats().description interface.select(tostring(old_iface)) end return(iface) end return("") end -- ############################################## function getInterfaceId(interface_name) if(interface_name == getSystemInterfaceName()) then return(getSystemInterfaceId()) end local ifnames = interface.getIfNames() for if_id, if_name in pairs(ifnames) do if if_name == interface_name then return tonumber(if_id) end end return(-1) end -- ############################################## function getFirstInterfaceId() local ifid = interface.getFirstInterfaceId() if ifid ~= nil then return ifid, getInterfaceName(ifid) end return -1, "" end -- ############################################## function isAllowedSystemInterface() return ntop.isAllowedInterface(tonumber(getSystemInterfaceId())) end -- ############################################## local cached_allowed_networks_set = nil function hasAllowedNetworksSet() if(cached_allowed_networks_set == nil) then local nets = ntop.getAllowedNetworks() local allowed_nets = string.split(nets, ",") or {nets} cached_allowed_networks_set = false for _, net in pairs(allowed_nets) do if((not isEmptyString(net)) and (net ~= "0.0.0.0/0") and (net ~= "::/0")) then cached_allowed_networks_set = true break end end end return(cached_allowed_networks_set) end -- ############################################## function hasSoftwareUpdatesSupport() return (not ntop.isOffline() and isAdministrator() and ntop.isPackage() and not ntop.isWindows()) end -- ############################################## -- Note that ifname can be set by Lua.cpp so don't touch it if already defined if((ifname == nil) and (_GET ~= nil)) then ifname = _GET["ifid"] if(ifname ~= nil) then if(ifname.."" == tostring(tonumber(ifname)).."") then -- ifname does not contain the interface name but rather the interface id ifname = getInterfaceName(ifname, true) if(ifname == "") then ifname = nil end end end if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => Session:".._SESSION["session"]) end if((ifname == nil) and (_SESSION ~= nil)) then if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => set ifname by _SESSION value") end ifname = _SESSION["ifname"] if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => ifname:"..ifname) end else if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => set ifname by _GET value") end end end -- See Utils::l4proto2name() l4_keys = { { "IP", "ip", 0 }, { "ICMP", "icmp", 1 }, { "IGMP", "igmp", 2 }, { "TCP", "tcp", 6 }, { "UDP", "udp", 17 }, { "IPv6", "ipv6", 41 }, { "RSVP", "rsvp", 46 }, { "GRE", "gre", 47 }, { "ESP", "esp", 50 }, { "IPv6-ICMP", "ipv6icmp", 58 }, { "EIGRP", "eigrp", 88 }, { "OSPF", "ospf", 89 }, { "PIM", "pim", 103 }, { "VRRP", "vrrp", 112 }, { "HIP", "hip", 139 }, { "ICMPv6", "icmpv6", 58 }, { "IGMP", "igmp", 2 }, { "Other IP", "other_ip", -1 } } L4_PROTO_KEYS = { tcp=6, udp=17, icmp=1, eigrp=88, other_ip=-1 } function __FILE__() return debug.getinfo(2,'S').source end function __LINE__() return debug.getinfo(2, 'l').currentline end -- ############################################## local http_status_code_map = { [200] = "OK", [400] = "Bad Request", [401] = "Unauthorized", [403] = "Forbidden", [404] = "Not Found", [405] = "Method Not Allowed", [406] = "Not Acceptable", [408] = "Request timeout", [409] = "Conflict", [410] = "Gone", [412] = "Precondition Failed", [415] = "Unsupported Media Type", [423] = "Locked", [428] = "Precondition Required", [429] = "Too many requests", [500] = "Internal Server Error", [501] = "Not Implemented", [503] = "Service Unavailable", } -- ############################################## function sendHTTPHeaderIfName(mime, ifname, maxage, content_disposition, extra_headers, status_code) info = ntop.getInfo(false) local tzname = info.tzname or '' local cookie_attr = ntop.getCookieAttributes() local lines = { 'Cache-Control: max-age=0, no-cache, no-store', 'Server: ntopng '..info["version"]..' ['.. info["platform"]..']', 'Set-Cookie: tzname=' .. tzname .. '; path=/' .. cookie_attr, 'Pragma: no-cache', 'X-Frame-Options: DENY', 'X-Content-Type-Options: nosniff', 'Content-Type: '.. mime, 'Last-Modified: '..os.date("!%a, %m %B %Y %X %Z"), } if(_SESSION ~= nil) then local key = "session_"..info.http_port.."_"..info.https_port lines[#lines + 1] = 'Set-Cookie: '..key..'='.._SESSION["session"]..'; max-age=' .. maxage .. '; path=/; ' .. cookie_attr end if(ifname ~= nil) then lines[#lines + 1] = 'Set-Cookie: ifname=' .. ifname .. '; path=/' .. cookie_attr end if(info.timezone ~= nil) then lines[#lines + 1] = 'Set-Cookie: timezone=' .. info.timezone .. '; path=/' .. cookie_attr end if(content_disposition ~= nil) then lines[#lines + 1] = 'Content-Disposition: '..content_disposition end if type(extra_headers) == "table" then for hname, hval in pairs(extra_headers) do lines[#lines + 1] = hname..': '..hval end end if not status_code then status_code = 200 end local status_descr = http_status_code_map[status_code] if not status_descr then status_descr = "Unknown" end -- Buffer the HTTP reply and write it in one "print" to avoid fragmenting -- it into multiple packets, to ease HTTP debugging with wireshark. print("HTTP/1.1 " .. status_code .. " " .. status_descr .. "\r\n" .. table.concat(lines, "\r\n") .. "\r\n\r\n") end -- ############################################## function sendHTTPHeaderLogout(mime, content_disposition) sendHTTPHeaderIfName(mime, nil, 0, content_disposition) end -- ############################################## function sendHTTPHeader(mime, content_disposition, extra_headers, status_code) sendHTTPHeaderIfName(mime, nil, 3600, content_disposition, extra_headers, status_code) end -- ############################################## function sendHTTPContentTypeHeader(content_type, content_disposition, charset, extra_headers, status_code) local charset = charset or "utf-8" local mime = content_type.."; charset="..charset sendHTTPHeader(mime, content_disposition, extra_headers, status_code) end -- ############################################## function printGETParameters(get) for key, value in pairs(get) do io.write(key.."="..value.."\n") end end -- ############################################## function findString(str, tofind) if(str == nil) then return(nil) end if(tofind == nil) then return(nil) end str1 = string.lower(string.gsub(str, "-", "_")) tofind1 = string.lower(string.gsub(tofind, "-", "_")) return(string.find(str1, tofind1, 1)) end -- ############################################## function findStringArray(str, tofind) if(str == nil) then return(nil) end if(tofind == nil) then return(nil) end local rsp = false for k,v in pairs(tofind) do str1 = string.gsub(str, "-", "_") tofind1 = string.gsub(v, "-", "_") if(str1 == tofind1) then rsp = true end end return(rsp) end -- ############################################## function printASN(asn, asname) asname = asname:gsub('"','') if(asn > 0) then return(""..asname.." ") 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 = '' 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[[\ \ ]] 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 = '' 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[[\ \ ]] end -- ############################################## function printDSCPDropdown(base_url, page_params, dscp_list) local dscp = _GET["dscp"] local dscp_filter if not isEmptyString(dscp) then dscp_filter = '' else dscp_filter = '' end -- table.clone needed to modify some parameters while keeping the original unchanged local dscp_params = table.clone(page_params) dscp_params["dscp"] = nil -- Used to possibly remove tcp state filters when selecting a non-TCP l4 protocol local dscp_params_non_filter = table.clone(dscp_params) if dscp_params_non_filter["dscp"] then dscp_params_non_filter["dscp"] = nil end local ordered_dscp_list = {} for key, value in pairs(dscp_list) do local name = dscp_consts.dscp_descr(key) ordered_dscp_list[name] = {} ordered_dscp_list[name]["id"] = key ordered_dscp_list[name]["count"] = value.count end print[[\ \ ]] end -- ################################### function printHostPoolDropdown(base_url, page_params, host_pool_list) local host_pools = require "host_pools" local host_pools_instance = host_pools:create() local host_pool = _GET["host_pool_id"] local host_pool_filter if not isEmptyString(host_pool) then host_pool_filter = '' else host_pool_filter = '' end -- table.clone needed to modify some parameters while keeping the original unchanged local host_pool_params = table.clone(page_params) host_pool_params["host_pool_id"] = nil -- Used to possibly remove tcp state filters when selecting a non-TCP l4 protocol local host_pool_params_non_filter = table.clone(host_pool_params) if host_pool_params_non_filter["host_pool_id"] then host_pool_params_non_filter["host_pool_id"] = nil end local ordered_host_pool_list = {} if host_pool then local id = tonumber(host_pool) ordered_host_pool_list[id] = {} ordered_host_pool_list[id]["count"] = host_pool_list[id]["count"] else for key, value in pairs(host_pool_list) do ordered_host_pool_list[key] = {} ordered_host_pool_list[key]["count"] = value.count end end print[[\ \ ]] end -- ################################### function printLocalNetworksDropdown(base_url, page_params) local networks_stats = interface.getNetworksStats() local ids = {} for n, local_network in pairs(networks_stats) do local network_name = getFullLocalNetworkName(local_network["network_key"]) ids[network_name] = local_network end local local_network_id = _GET["network"] local local_network_id_filter = '' if not isEmptyString(local_network_id) then local_network_id_filter = '' end -- table.clone needed to modify some parameters while keeping the original unchanged local local_network_id_params = table.clone(page_params) local_network_id_params["network"] = nil print[[\ \ ]] 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 = '' 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[[\ \ ]] end -- ############################################## function getProbesName(flowdevs) local devips = {} for dip, _ in pairsByValues(flowdevs, asc) do devips[dip] = getProbeName(dip) end return devips end -- ############################################## function getProbeName(exporter_ip) local cached_device_name local snmp_cached_dev if ntop.isPro() then snmp_cached_dev = require "snmp_cached_dev" end if snmp_cached_dev then cached_device_name = snmp_cached_dev:create(exporter_ip) end if cached_device_name then cached_device_name = cached_device_name["name"] else local hinfo = hostkey2hostinfo(exporter_ip) local exporter_label = hostinfo2label(hinfo) if not isEmptyString(exporter_label) then cached_device_name = exporter_label end end return cached_device_name end -- ############################################## function printHostsDeviceFilterDropdown(base_url, page_params) -- Getting probes local flowdevs = interface.getFlowDevices() or {} local ordering_fun = pairsByKeys local cur_dev = _GET["deviceIP"] local cur_dev_filter = '' -- table.clone needed to modify some parameters while keeping the original unchanged local dev_params = table.clone(page_params) local devips = getProbesName(flowdevs) local devips_order = ntop.getPref("ntopng.prefs.flow_table_probe_order") == "1" -- Order by Probe Name if devips_order then ordering_fun = pairsByValues end if not isEmptyString(cur_dev) then cur_dev_filter = '' end dev_params["deviceIP"] = nil if table.len(devips) > 0 then print[[, '
]] print[[ \ \ ]] print[[
']] end end -- ############################################## -- -- Returns indexes to be used for string shortening. The portion of to_shorten between -- middle_start and middle_end will be inside the bounds. -- -- to_shorten: string to be shorten -- middle_start: middle part begin index -- middle_end: middle part begin index -- maxlen: maximum length -- function shortenInTheMiddle(to_shorten, middle_start, middle_end, maxlen) local maxlen = maxlen - (middle_end - middle_start) if maxlen <= 0 then return 0, string.len(to_shorten) end local left_slice = math.max(middle_start - math.floor(maxlen / 2), 1) maxlen = maxlen - (middle_start - left_slice - 1) local right_slice = math.min(middle_end + maxlen, string.len(to_shorten)) return left_slice, right_slice end -- ############################################## function shortHostName(name) local chunks = {name:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")} if(#chunks == 4) then return(name) else local max_len = ntop.getPref("ntopng.prefs.max_ui_strlen") max_len = tonumber(max_len) if(max_len == nil) then max_len = 24 end chunks = {name:match("%w+:%w+:%w+:%w+:%w+:%w+")} --io.write(#chunks.."\n") if(#chunks == 1) then return(name) end if(string.len(name) < max_len) then return(name) else tot = 0 n = 0 ret = "" for token in string.gmatch(name, "([%w-]+).") do if(tot < max_len) then if(n > 0) then ret = ret .. "." end ret = ret .. token tot = tot+string.len(token) n = n + 1 end end return(ret .. "...") end end return(name) end -- ############################################## function _handleArray(name, sev) local id for id, _ in ipairs(name) do local l = name[id][1] local key = name[id][2] if(string.upper(key) == string.upper(sev)) then return(l) end end return(firstToUpper(sev)) end -- ############################################## function l4Label(proto) return(_handleArray(l4_keys, proto)) end function l4_proto_to_id(proto_name) for _, proto in pairs(l4_keys) do if proto[1] == proto_name or proto[2] == proto_name then return(proto[3]) end end end function l4_proto_to_string(proto_id) if not proto_id then return "" end if isEmptyString(proto_id) then return "" end proto_id_num = tonumber(proto_id) if proto_id_num == nil then -- Already string? return proto_id end for _, proto in pairs(l4_keys) do if proto[3] == proto_id_num then return proto[1], proto[2] end end return string.format("%d", proto_id_num) end -- Return the list of L4 proto (key = name, value = id) function l4_proto_list() local list = {} for _, proto in pairs(l4_keys) do -- add L4 proto only if proto[2] ~= 'ip' and proto[2] ~= 'ipv6' then list[proto[1]] = proto[3] end end return list end -- ############################################## function noHtml(s) if s == nil then return nil end local gsub, char = string.gsub, string.char local entityMap = {lt = "<", gt = ">" , amp = "&", quot ='"', apos = "'"} local entitySwap = function(orig, n, s) return (n == '' and entityMap[s]) or (n == "#" and tonumber(s)) and string.char(s) or (n == "#x" and tonumber(s,16)) and string.char(tonumber(s,16)) or orig end local function unescape(str) return (gsub( str, '(&(#?x?)([%d%a]+);)', entitySwap )) end local cleaned = s:gsub("<[aA] .->(.-)","%1") :gsub("(.-)","%1") :gsub("(.-)","%1") :gsub("%s*<[iI].->(.-)","%1") :gsub("<.->(.-)","%1") -- note: keep as last as this does not handle nested tags :gsub("^%s*(.-)%s*$", "%1") :gsub(' ', " ") return unescape(cleaned) end -- ############################################## function areAlertsEnabled() if(__alert_enabled == nil) then -- Not too nice as changes will be read periodically as new VMs are reloaded -- but at least we avoid breaking up the performance __alert_enabled = (ntop.getPref("ntopng.prefs.disable_alerts_generation") ~= "1") end return (__alert_enabled) end -- ############################################## function isScoreEnabled() return(ntop.isEnterpriseM() or ntop.isnEdgeEnterprise()) end -- ############################################## function hasTrafficReport() local ts_utils = require("ts_utils_core") local is_pcap_dump = interface.isPcapDumpInterface() return((not is_pcap_dump) and (ts_utils.getDriverName() == "rrd") and ntop.isEnterpriseM()) end function hasAlertsDisabled() _POST = _POST or {} return ((_POST["disable_alerts_generation"] ~= nil) and (_POST["disable_alerts_generation"] == "1")) or ((_POST["disable_alerts_generation"] == nil) and (ntop.getPref("ntopng.prefs.disable_alerts_generation") == "1")) end function hasClickHouseSupport() local auth = require "auth" if not ntop.isPro() or ntop.isWindows() then return false end -- Don't allow nIndex for unauthorized users if not auth.has_capability(auth.capabilities.historical_flows) then return false end -- TODO optimize if prefs == nil then prefs = ntop.getPrefs() end if prefs.is_dump_flows_to_clickhouse_enabled then return true end return false end -- NOTE: global nindex support may be enabled but some disable on some interfaces function interfaceHasClickHouseSupport() return(hasClickHouseSupport() and ntop.getPrefs()["is_dump_flows_to_clickhouse_enabled"]) end --for _key, _value in pairsByKeys(vals, rev) do -- print(_key .. "=" .. _value .. "\n") --end function truncate(x) return x<0 and math.ceil(x) or math.floor(x) end -- Note that the function below returns a string as returning a number -- would not help as a new float would be returned function toint(num) return string.format("%u", truncate(num)) end function capitalize(str) return (str:gsub("^%l", string.upper)) end local function starstring(len) local s = "" while(len > 0) do s = s .."*" len = len -1 end return(s) end function obfuscate(str) local len = string.len(str) local in_clear = 2 if(len <= in_clear) then return(starstring(len)) else return(string.sub(str, 0, in_clear)..starstring(len-in_clear)) end end function isnumber(str) if((str ~= nil) and (string.len(str) > 0) and (tonumber(str) ~= nil)) then return(true) else return(false) end end function split(pString, pPattern) local Table = {} -- NOTE: use {n = 0} in Lua-5.0 local fpat = "(.-)" .. pPattern local last_end = 1 local s, e, cap = pString:find(fpat, 1) while s do if s ~= 1 or cap ~= "" then table.insert(Table,cap) end last_end = e+1 s, e, cap = pString:find(fpat, last_end) end if last_end <= #pString then cap = pString:sub(last_end) table.insert(Table, cap) end return Table end -- returns the MAXIMUM value found in a table t, together with the corresponding -- index argmax. a pair argmax, max is returned. function tmax(t) local argmx, mx = nil, nil if (type(t) ~= "table") then return nil, nil end for k, v in pairs(t) do -- first iteration if mx == nil and argmx == nil then mx = v argmx = k elseif (v == mx and k > argmx) or v > mx then -- if there is a tie, prefer the greatest argument -- otherwise grab the maximum argmx = k mx = v end end return argmx, mx end -- returns the MINIMUM value found in a table t, together with the corresponding -- index argmin. a pair argmin, min is returned. function tmin(t) local argmn, mn = nil, nil if (type(t) ~= "table") then return nil, nil end for k, v in pairs(t) do -- first iteration if mn == nil and argmn == nil then mn = v argmn = k elseif (v == mn and k > argmn) or v < mn then -- if there is a tie, prefer the greatest argument -- otherwise grab the minimum argmn = k mn = v end end return argmn, mn end function formatEpoch(epoch, full_time) return(format_utils.formatEpoch(epoch, full_time)) end function starts(String,Start) if((String == nil) or (Start == nil)) then return(false) end return string.sub(String,1,string.len(Start))==Start end function ends(String,End) return End=='' or string.sub(String,-string.len(End))==End end -- ################################################################# function bit(p) return 2 ^ (p - 1) -- 1-based indexing end -- Typical call: if hasbit(x, bit(3)) then ... function hasbit(x, p) return x % (p + p) >= p end function setbit(x, p) return hasbit(x, p) and x or x + p end function clearbit(x, p) return hasbit(x, p) and x - p or x end function isBroadMulticast(ip) if(ip == "0.0.0.0") then return true end -- print(ip) t = string.split(ip, "%.") -- print(table.concat(t, "\n")) if(t == nil) then return false -- Might be an IPv6 address else if(tonumber(t[1]) >= 224) then return true end end return false end function isBroadcastMulticast(ip) local ainfo = interface.getAddressInfo(ip) if(ainfo.is_multicast or ainfo.is_broadcast) then return true else return false end end function isIPv4(address) -- Reuse the for loop to check the address validity local checkAddress = (function(chunks) for _, v in pairs(chunks) do if (tonumber(v) < 0) or (tonumber(v) > 255) then return false end end return true end) local chunks = {address:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")} local chunksWithPort = {address:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)%:(%d+)$")} if #chunks == 4 then return checkAddress(chunks) elseif #chunksWithPort == 5 then table.remove(chunksWithPort, 5) return checkAddress(chunksWithPort) end return false end function isIPv4Network(address) local parts = split(address, "/") if #parts == 2 then local prefix = tonumber(parts[2]) if (prefix == nil) or (math.floor(prefix) ~= prefix) or (prefix < 0) or (prefix > 32) then return false end elseif #parts ~= 1 then return false end return isIPv4(parts[1]) end function addGoogleMapsScript() local g_maps_key = ntop.getCache('ntopng.prefs.google_apis_browser_key') if g_maps_key ~= nil and g_maps_key~= "" then g_maps_key = "&key="..g_maps_key else g_maps_key = "" end print("\n") end function addLogoLightSvg() return ([[ ]]) end function addLogoDarkSvg() return ([[ ]]) end function addLogoSvg() return ([[ ]]) end function addGauge(name, url, maxValue, width, height) if(url ~= nil) then print('') end print [[
]] if(url ~= nil) then print('
\n') end end function getCategoriesWithProtocols() local protocol_categories = interface.getnDPICategories() for k,v in pairsByKeys(protocol_categories) do protocol_categories[k] = {id=v, protos=interface.getnDPIProtocols(tonumber(v)), count=0} for proto,_ in pairs(protocol_categories[k].protos) do protocol_categories[k].count = protocol_categories[k].count + 1 end end return protocol_categories end -- -- Members supported format -- 192.168.1.10/32@10 -- 00:11:22:33:44:55 -- function isValidPoolMember(member) if isEmptyString(member) then return false end if isMacAddress(member) then return true end -- vlan is mandatory here local vlan_idx = string.find(member, "@") if ((vlan_idx == nil) or (vlan_idx == 1)) then return false end local other = string.sub(member, 1, vlan_idx-1) local vlan = tonumber(string.sub(member, vlan_idx+1)) if (vlan == nil) or (vlan < 0) then return false end -- prefix is mandatory here local address, prefix = splitNetworkPrefix(other) if prefix == nil then return false end if isIPv4(address) and (tonumber(prefix) >= 0) and (tonumber(prefix) <= 32) then return true elseif isIPv6(address) and (tonumber(prefix) >= 0) and (tonumber(prefix) <= 128) then return true end return false end function host2member(ip, vlan, prefix) if prefix == nil then if isIPv4(ip) then prefix = 32 else prefix = 128 end end return ip .. "/" .. tostring(prefix) .. "@" .. tostring(vlan) end function isLocal(host_ip) host = interface.getHostInfo(host_ip) if((host == nil) or (host['localhost'] ~= true)) then return(false) else return(true) end end -- Return the first 'howmany' hosts function getTopInterfaceHosts(howmany, localHostsOnly) hosts_stats = interface.getHostsInfo() hosts_stats = hosts_stats["hosts"] ret = {} sortTable = {} n = 0 for k,v in pairs(hosts_stats) do if((not localHostsOnly) or ((v["localhost"] == true) and (v["ip"] ~= nil))) then sortTable[v["bytes.sent"]+v["bytes.rcvd"]+n] = k n = n +0.01 end end n = 0 for _v,k in pairsByKeys(sortTable, rev) do if(n < howmany) then ret[k] = hosts_stats[k] n = n+1 else break end end return(ret) end -- Windows fixes for interfaces with "uncommon chars" function purifyInterfaceName(interface_name) -- io.write(debug.traceback().."\n") interface_name = string.gsub(interface_name, "@", "_") interface_name = string.gsub(interface_name, ":", "_") interface_name = string.gsub(interface_name, "/", "_") return(interface_name) end -- See datatype AggregationType in ntop_typedefs.h function aggregation2String(value) if(value == 0) then return("Client Name") elseif(value == 1) then return("Server Name") elseif(value == 2) then return("Domain Name") elseif(value == 3) then return("Operating System") elseif(value == 4) then return("Registrar Name") else return(value) end end -- ################################# -- Aggregates items below some edge -- edge: minimum percentage value to create collision -- min_col: minimum collision groups to aggregate function aggregatePie(values, values_sum, edge, min_col) local edge = edge or 0.09 min_col = min_col or 2 local aggr = {} local other = i18n("other") local below_edge = {} -- Initial lookup for k,v in pairs(values) do if v / values_sum <= edge then -- too small below_edge[#below_edge + 1] = k else aggr[k] = v end end -- Decide if to aggregate for _,k in pairs(below_edge) do if #below_edge >= min_col then -- aggregate aggr[other] = aggr[other] or 0 aggr[other] = aggr[other] + values[k] else -- do not aggregate aggr[k] = values[k] end end return aggr end -- ################################# -- This function actively resolves an host if there is not information about it. -- NOTE: prefer the host2name on this function function resolveAddress(hostinfo, allow_empty) local alt_name = ip2label(hostinfo["host"]) if(not isEmptyString(alt_name) and (alt_name ~= hostinfo["host"])) then -- The host label has priority return(alt_name) end local hostname = ntop.resolveName(hostinfo["host"]) if isEmptyString(hostname) then -- Not resolved if allow_empty == true then return hostname else -- this function will take care of formatting the IP return hostinfo2label(hostinfo) end end return hostinfo2label(hostinfo) end -- ################################# function getIpUrl(ip) if isIPv6(ip) then -- https://www.ietf.org/rfc/rfc2732.txt return "["..ip.."]" end return ip end -- ################################# function getApplicationIcon(name) local icon = "" if(name == nil) then name = "" end if(findString(name, "Skype")) then icon = '' elseif(findString(name, "Unknown")) then icon = '' elseif(findString(name, "Twitter")) then icon = '' elseif(findString(name, "DropBox")) then icon = '' elseif(findString(name, "Spotify")) then icon = '' elseif(findString(name, "Apple")) then icon = '' elseif(findString(name, "Google") or findString(name, "Chrome")) then icon = '' elseif(findString(name, "FaceBook")) then icon = '' elseif(findString(name, "Youtube")) then icon = '' elseif(findString(name, "thunderbird")) then icon = '' end return(icon) end -- ################################# function getApplicationLabel(name, maxlen) local icon = getApplicationIcon(name) if(maxlen == nil) then maxlen = 12 end -- Do not convert to upper case, keep the nDPI case --name = name:gsub("^%l", string.upper) return(icon.." "..shortenString(name, maxlen)) end -- ################################# function getCategoryLabel(cat_name, cat_id) local categories_utils = require 'categories_utils' return categories_utils.getCustomCategoryName(cat_id, cat_name) end -- ########################################### function computeL7Stats(stats, show_breed, show_ndpi_category) local _ifstats = {} if(show_breed) then local breed_stats = {} for key, value in pairs(stats["ndpi"]) do local b = stats["ndpi"][key]["breed"] local traffic = stats["ndpi"][key]["bytes.sent"] + stats["ndpi"][key]["bytes.rcvd"] if(breed_stats[b] == nil) then breed_stats[b] = traffic else breed_stats[b] = breed_stats[b] + traffic end end for key, value in pairs(breed_stats) do _ifstats[key] = value end elseif(show_ndpi_category) then local ndpi_category_stats = {} for key, value in pairs(stats["ndpi_categories"]) do key = getCategoryLabel(key) local traffic = value["bytes"] if(ndpi_category_stats[key] == nil) then ndpi_category_stats[key] = traffic else ndpi_category_stats[key] = ndpi_category_stats[key] + traffic end end for key, value in pairs(ndpi_category_stats) do _ifstats[key] = value end else -- Add ARP to stats local arpBytes = 0 if(stats["eth"] ~= nil) then arpBytes = stats["eth"]["ARP_bytes"] if(arpBytes > 0) then _ifstats["ARP"] = arpBytes end end for key, value in pairs(stats["ndpi"]) do local traffic = value["bytes.sent"] + value["bytes.rcvd"] if(key == "Unknown") then traffic = traffic - arpBytes end if(traffic > 0) then if(show_breed) then _ifstats[value["breed"]] = traffic else _ifstats[key] = traffic end end end end return _ifstats end -- ########################################### function getItemsNumber(n) tot = 0 for k,v in pairs(n) do --io.write(k.."\n") tot = tot + 1 end --io.write(tot.."\n") return(tot) end -- ########################################### function getHostCommaSeparatedList(p_hosts) hosts = {} hosts_size = 0 for i,host in pairs(split(p_hosts, ",")) do hosts[i] = host hosts_size = hosts_size + 1 end return hosts,hosts_size end -- ############################################## function getFirstIpFromMac(host_addr) local mac_resolved = host_addr -- Transforming the MAC into the ip address local mac_hosts = interface.getMacHosts(mac_resolved) or {} -- Mapping mac with ip and setting up the right href for _, h in pairsByKeys(mac_hosts, rev) do if (h.broadcast_domain_host) or (table.len(mac_hosts) == 1) then mac_resolved = h.ip break end end return mac_resolved end -- ############################################## function splitNetworkPrefix(net) local prefix = tonumber(net:match("/(.+)")) local address = net:gsub("/.+","") return address, prefix end -- ############################################## function splitNetworkWithVLANPrefix(net_mask_vlan) local vlan = tonumber(net_mask_vlan:match("@(.+)")) local net_mask = net_mask_vlan:gsub("@.+","") local prefix = tonumber(net_mask:match("/(.+)")) local address = net_mask:gsub("/.+","") return address, prefix, vlan end -- ############################################## function splitProtocol(proto_string) local parts = string.split(proto_string, "%.") local app_proto local master_proto if parts == nil then master_proto = proto_string app_proto = nil else master_proto = parts[1] app_proto = parts[2] end return master_proto, app_proto end -- ############################################## function getHostAltNamesKey(host_key) if(host_key == nil) then return(nil) end return "ntopng.cache.host_labels."..host_key end function getHostAltName(host_info) local host_key if type(host_info) == "table" then host_key = host_info["host"] else host_key = host_info end local alt_name = ntop.getCache(getHostAltNamesKey(host_key)) if isEmptyString(alt_name) and type(host_info) == "table" and host_info["vlan"] then -- Check if there is an alias for the host@vlan host_key = hostinfo2hostkey(host_info) alt_name = ntop.getCache(getHostAltNamesKey(host_key)) end return alt_name end function setHostAltName(host_info, alt_name) local host_key if type(host_info) == "table" then -- Note: we are not using hostinfo2hostkey which includes the -- vlan for backward compatibility, compatibility with -- the backend, and compatibility with the vpn scripts host_key = host_info["host"] -- hostinfo2hostkey(host_info) else host_key = host_info end ntop.setCache(getHostAltNamesKey(host_key), alt_name) end -- ############################################## function getHostNotesKey(host_key) if(host_key == nil) then return(nil) end return "ntopng.cache.host_notes."..host_key end -- ############################################## function getHostNotes(host_info) local host_key if type(host_info) == "table" then host_key = host_info["host"] else host_key = host_info end local notes = ntop.getCache(getHostNotesKey(host_key)) if isEmptyString(notes) and type(host_info) == "table" and host_info["vlan"] then -- Check if there is an alias for the host@vlan host_key = hostinfo2hostkey(host_info) notes = ntop.getCache(getHostNotesKey(host_key)) end if not isEmptyString(notes) then notes = string.lower(notes) end return notes end -- ############################################## function setHostNotes(host_info, notes) local host_key if type(host_info) == "table" then -- Note: we are not using hostinfo2hostkey which includes the -- vlan for backward compatibility, compatibility with -- the backend, and compatibility with the vpn scripts host_key = host_info["host"] -- hostinfo2hostkey(host_info) else host_key = host_info end ntop.setCache(getHostNotesKey(host_key), notes) end -- ############################################## function getDhcpNameKey(ifid, mac) return string.format("ntopng.dhcp.%d.cache.%s", ifid, mac) end -- ############################################## local function label2formattedlabel(alt_name, host_info, show_vlan, shorten_len) if not isEmptyString(alt_name) then local ip = host_info["ip"] or host_info["host"] -- Make it shorter local res = alt_name -- Special shorting function for IP addresses if res ~= ip then if shorten_len == false then -- Don't touch the string, requested as-is without shortening elseif tonumber(shorten_len) then -- Shorten according to the specified length res = shortenString(res, shorten_len) else -- Use the default system-wide setting for the shortening res = shortenString(res) end end -- Adding the vlan if requested if show_vlan then local vlan = tonumber(host_info["vlan"]) if vlan and vlan > 0 then local full_vlan_name = getFullVlanName(vlan, true --[[ Compact --]]) res = string.format("%s@%s", res, full_vlan_name) end end return res end -- Fallback: just the IP and VLAN return(hostinfo2hostkey(host_info, true)) end -- ############################################## -- Attempt at retrieving an host label from an host_info, using local caches and DNS resolution. -- This can be more expensive if compared to only using information found inside host_info. local function hostinfo2label_resolved(host_info, show_vlan, shorten_len) local ip = host_info["ip"] or host_info["host"] local res -- If local broadcast domain host and DHCP, try to get the label associated -- to the MAC address if host_info["mac"] and (host_info["broadcast_domain_host"] or host_info["dhcpHost"]) then res = getHostAltName(host_info["mac"]) end if isEmptyString(res) then -- Try and get the resolved name res = ntop.getResolvedName(ip) if not isEmptyString(res) then res = string.lower(res) else -- Nothing found, just fallback to the IP address res = ip end end return label2formattedlabel(res, host_info, show_vlan, shorten_len) end -- ############################################## -- Retrieve an host label from an host_info. The minimum fields of -- the host_info are "host" and "vlan", however a full JSON from Host::lua -- is needed to provide an accurate result. -- -- The following order is used to determine the label: -- MAC label (LBD hosts only), IP label, MDNS/DHCP name from C, resolved IP -- -- NOTE: The function attempt at labelling an host only using information found in host_info. -- In case host_info is not enough to label the host, then local caches and DNS resolution kick in -- to find a label (at the expense of extra time). function hostinfo2label(host_info, show_vlan, shorten_len) local alt_name = nil local ip = host_info["ip"] or host_info["host"] -- Take the label as found in the host structure local res = host_info.label if isEmptyString(res) then -- Use any user-configured custom name -- This goes first as a label set by the user MUST take precedance over any other possibly available label res = getHostAltName(ip) if isEmptyString(res) then -- Read what is found inside host `name`, e.g., name as found by dissected traffic such as DHCP res = host_info["name"] if isEmptyString(res) then return hostinfo2label_resolved(host_info, show_vlan, shorten_len) end end end return label2formattedlabel(res, host_info, show_vlan, shorten_len) end -- ############################################## -- Just a convenience function for hostinfo2label with only IP and VLAN function ip2label(ip, vlan) return hostinfo2label({host = ip, vlan = (vlan or 0)}) end -- ############################################## function mac2label(mac) local alt_name = getHostAltName(mac) if not isEmptyString(alt_name) and (alt_name ~= mac) then return(alt_name) end alt_name = ntop.getCache(getDhcpNameKey(interface.getId(), mac)) if not isEmptyString(alt_name) and (alt_name ~= mac) then return(alt_name) end -- Fallback: just the MAC return(mac) end -- ############################################## -- Mac Addresses -- -- A function to give a useful device name function getDeviceName(device_mac, skip_manufacturer) local name = mac2label(device_mac) if name == device_mac then -- Not found, try with first host local info = interface.getHostsInfo(false, nil, 1, 0, nil, nil, nil, tonumber(vlan), nil, nil, device_mac) if (info ~= nil) then for x, host in pairs(info.hosts) do -- Make sure the IP is in the broadcast domain to avoid setting up names to MACs such as the gateway if host.broadcast_domain_host and not isEmptyString(host.name) and host.name ~= host.ip and host.name ~= "NoIP" then name = host.name elseif host.ip ~= "0.0.0.0" then name = ip2label(host.ip) if name == host.ip then name = nil end end break end else name = nil end end if isEmptyString(name) then if (not skip_manufacturer) then name = get_symbolic_mac(device_mac, true) else -- last resort name = device_mac end end if isEmptyString(name) or name == device_mac then return '' end return name end local specialMACs = { "01:00:0C", "01:80:C2", "01:00:5E", "01:0C:CD", "01:1B:19", "FF:FF", "33:33" } function isSpecialMac(mac) for _,key in pairs(specialMACs) do if(string.contains(mac, key)) then return true end end return false end -- ############################################## -- @brief Implements the logic to decide whether to show or not the url for a given `host_info` local function hostdetails_exists(host_info, hostdetails_params) if not hostdetails_params then hostdetails_params = {} end if hostdetails_params["page"] ~= "historical" and not hostdetails_params["ts_schema"] then -- If the requested host_details.lua page is not the "historical" page -- and if no ts_schema has been requested -- then we check for host existance in memory, to make sure the page host_details.lua -- won't bring to an empty page. if not host_info["ipkey"] then -- host_info hasn't been generated with Host::lua so we can try and -- see if the host is active local active_host = interface.getHostInfo(hostinfo2hostkey(host_info)) if not active_host then return false end end else -- If the requested page is the "historical" page, or if a ts_schema has been requested, -- then we assume page host_details.lua -- exists if the timeseries are enabled and if the requested timeseries exists for the host if not hostdetails_params["ts_schema"] then -- Default schema for hosts hostdetails_params["ts_schema"] = "host:traffic" end -- A ts_schema has been requested, let's see if it exists local ts_utils = require("ts_utils_core") local tags = table.merge(host_info, hostdetails_params) if not tags["ifid"] then tags["ifid"] = interface.getId() end -- If nIndex support is enabled, then there's no need to check for existence of the -- schema: nIndex flows must be visible from the historical page even when there's no timeseries -- associated if not interfaceHasClickHouseSupport() and not ts_utils.exists(hostdetails_params["ts_schema"], tags) then -- If here, the requested schema, along with its hostdetails_params doesn't exist return false end end return true end -- ############################################## -- @brief Generates an host_details.lua url (if available) -- @param host_info A lua table containing at least keys `host` and `vlan` or a full lua table generated with Host::lua -- @param href_params A lua table containing params host_details.lua params, e.g., {page = "historical"} -- @param href_check Performs existance checks on the link to avoid generating links to inactive hosts or hosts without timeseries -- @return A string containing the url (if available) or an empty string when the url is not available function hostinfo2detailsurl(host_info, href_params, href_check) local tag_utils = require "tag_utils" local res = '' if not href_check or hostdetails_exists(host_info, href_params) then local auth = require "auth" local url_params = table.tconcat(href_params or {}, "=", "&") -- Alerts pages for the host are in alert_stats.lua (Alerts menu) if href_params and href_params.page == "engaged-alerts" then if auth.has_capability(auth.capabilities.alerts) then res = string.format("%s/lua/alert_stats.lua?page=host&status=engaged&ip=%s%s%s", ntop.getHttpPrefix(), hostinfo2hostkey(host_info), tag_utils.SEPARATOR, "eq") end elseif href_params and href_params.page == "alerts" then if auth.has_capability(auth.capabilities.alerts) then res = string.format("%s/lua/alert_stats.lua?page=host&status=historical&ip=%s%s%s", ntop.getHttpPrefix(), hostinfo2hostkey(host_info), tag_utils.SEPARATOR, "eq") end -- All other pages are in host_details.lua else res = string.format("%s/lua/host_details.lua?%s%s%s", ntop.getHttpPrefix(), hostinfo2url(host_info), isEmptyString(url_params) and '' or '&', url_params, href_value) end end return res end -- ############################################## -- @brief Generates an host_details.lua a href link (if available), starting from an `host_info` structure -- @param host_info A lua table containing at least keys `host` and `vlan` or a full lua table generated with Host::lua -- @param href_params A lua table containing params host_details.lua params, e.g., {page = "historical"} -- @param href_value A string containing the visible value shown between a href tags -- @param href_tooltip A string containing a tooltip shown when hovering the mouse on the link -- @param href_check Performs existance checks on the link to avoid generating links to inactive hosts or hosts without timeseries -- @param href_only_with_ts True means that a HREF is geneated only of there are timeseries for this host -- @return A string containing the a href link or a plain string without a href function hostinfo2detailshref(host_info, href_params, href_value, href_tooltip, href_check, href_only_with_ts, show_value_with_no_ref) local show_href = false local res = "" if(href_only_with_ts == true) then local detailLevel = ntop.getCache("ntopng.prefs.hosts_ts_creation") if(detailLevel == "full") then local l7 = ntop.getCache("ntopng.prefs.host_ndpi_timeseries_creation") if(l7 ~= "none") then show_href = true end end else show_href = true end if(show_href) then local hostdetails_url = hostinfo2detailsurl(host_info, href_params, href_check) if not isEmptyString(hostdetails_url)then res = string.format("%s", hostdetails_url, href_tooltip or '', href_value or '') else if show_value_with_no_ref == nil or show_value_with_no_ref == true then res = href_value or '' end end return res else return(href_value) end end -- ############################################## -- @brief Generates an host_details.lua a href link (if available), starting from an ip and a vlan -- @param ip A string with a valid ip address -- @param vlan A string or a number with a VLAN or nil when VLAN information is not available -- @param href_params A lua table containing params host_details.lua params, e.g., {page = "historical"} -- @param href_value A string containing the visible value shown between a href tags -- @param href_tooltip A string containing a tooltip shown when hovering the mouse on the link -- @param href_check Performs existance checks on the link to avoid generating links to inactive hosts or hosts without timeseries -- @return A string containing the a href link or a plain string without a href function ip2detailshref(ip, vlan, href_params, href_value, href_tooltip, href_check) return hostinfo2detailshref({host = ip, vlan = tonumber(vlan) or 0}, href_params, href_value, href_tooltip, href_check) end -- ############################################## -- Flow Utils -- function flowinfo2hostname(flow_info, host_type, alerts_view, add_hostname) local name local orig_name if (alerts_view and not hasClickHouseSupport()) or (add_hostname ~= nil and add_hostname == false) then -- do not return resolved name as it will hide the IP address return(flow_info[host_type..".ip"]) end if(flow_info == nil) then return("") end if(host_type == "srv") then if flow_info["host_server_name"] ~= nil and flow_info["host_server_name"] ~= "" and flow_info["host_server_name"]:match("%w") and not isIPv4(flow_info["host_server_name"]) and not isIPv6(flow_info["host_server_name"]) then -- remove possible ports from the name return(flow_info["host_server_name"]:gsub(":%d+$", "")) end if(flow_info["protos.tls.certificate"] ~= nil and flow_info["protos.tls.certificate"] ~= "") then return(flow_info["protos.tls.certificate"]) end end local hostinfo = { host = flow_info[host_type..".ip"], label = flow_info[host_type..".host"], mac = flow_info[host_type..".mac"], dhcpHost = flow_info[host_type..".dhcpHost"], broadcast_domain_host = flow_info[host_type..".broadcast_domain_host"], vlan = flow_info["vlan"], } return(hostinfo2label(hostinfo)) end function flowinfo2process(process, host_info_to_url) local fmt, proc_name, proc_user_name = '', '', '' if process then -- TODO: add links back once restored if not isEmptyString(process["name"]) then local full_clean_name = process["name"]:gsub("'",'') local t = split(full_clean_name, "/") clean_name = t[#t] proc_name = string.format(" %s", 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(" %s", -- 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(" %s", 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(" %s", -- ntop.getHttpPrefix(), -- formatted_pod, -- formatted_pod) -- end fmt = string.format("[%s]", table.concat({cont_name, pod_name}, '')) end return fmt end -- ############################################## function getLocalNetworkAliasKey() return "ntopng.network_aliases" end -- ############################################## function getLocalNetworkAliasById(network) local networks_stats = interface.getNetworksStats() local network_id = tonumber(network) -- If network is (u_int8_t)-1 then return an empty value if network == nil or network == network_utils.UNKNOWN_NETWORK then return ' ' end local label = '' for n, ns in pairs(networks_stats) do if ns.network_id == network_id then label = getFullLocalNetworkName(ns.network_key) end end return label end -- ############################################## function getLocalNetworkAlias(network) local alias = ntop.getLocalNetworkAlias(network) or nil if not alias then alias = ntop.getHashCache(getLocalNetworkAliasKey(), network) end if not isEmptyString(alias) then return alias end return network end -- ############################################## function getLocalNetworkLabel(network) local alias = getLocalNetworkAlias(network) if alias ~= network then return string.format("%s · %s", alias, network) end return network end -- ############################################## function getFullLocalNetworkName(network) local alias = getLocalNetworkAlias(network) if alias ~= network then return string.format("%s [%s]", alias, network) end return network end -- ############################################## function setLocalNetworkAlias(network, alias) if((network ~= alias) or isEmptyString(alias)) then ntop.setHashCache(getLocalNetworkAliasKey(), network, alias) else ntop.delHashCache(getLocalNetworkAliasKey(), network) end end -- ############################################## function getVlanAliasKey() return "ntopng.vlan_aliases" end -- ############################################## function getVlanAlias(vlan_id) local alias = ntop.getHashCache(getVlanAliasKey(), vlan_id) if not isEmptyString(alias) then return alias end return tostring(vlan_id) end -- ############################################## function setVlanAlias(vlan_id, alias) if((vlan_id ~= alias) or isEmptyString(alias)) then ntop.setHashCache(getVlanAliasKey(), vlan_id, alias) else ntop.delHashCache(getVlanAliasKey(), vlan_id) end end -- ############################################## function getFullVlanName(vlan_id, compact) local alias = getVlanAlias(vlan_id) -- In case of vlan 0, return empty string as name if tonumber(vlan_id) == 0 then return '' end if not isEmptyString(alias) then if not isEmptyString(alias) and alias ~= tostring(vlan_id) then if compact then alias = shortenString(alias) return string.format("%s", alias) else return string.format("%u [%s]", vlan_id, alias) end end end return vlan_id end -- ############################################## function flow2hostinfo(host_info, host_type) local host_name local res = interface.getHostMinInfo(host_info[host_type .. ".ip"]) if((res == nil) or (res["name"] == nil)) then host_name = host_info[host_type .. ".ip"] else host_name = res["name"] end return({host = host_info[host_type .. ".ip"], vlan = host_info[host_type .. ".vlan"], name = host_name}) end -- ############################################## -- URL Util -- -- -- Split the host key (ip@vlan) creating a new lua table. -- Example: -- info = hostkey2hostinfo(key) -- ip = info["host"] -- vlan = info["vlan"] -- function hostkey2hostinfo(key) local host = {} local info = split(key,"@") if(info[1] ~= nil) then host["host"] = info[1] end if(info[2] ~= nil) then host["vlan"] = tonumber(info[2]) else host["vlan"] = 0 end return host end -- -- Analyze the host_info table and return the host key. -- Example: -- host_info = interface.getHostInfo("127.0.0.1",0) -- key = hostinfo2hostkey(host_info) -- function hostinfo2hostkey(host_info, host_type, show_vlan) local rsp = "" if(host_type == "cli") then local cli_ip = host_info["cli.ip"] or host_info["cli_ip"] if cli_ip then rsp = rsp..cli_ip end elseif(host_type == "srv") then local srv_ip = host_info["srv.ip"] or host_info["srv_ip"] if srv_ip then rsp = rsp..srv_ip end else if(host_info["ip"] ~= nil) then rsp = rsp..host_info["ip"] elseif(host_info["mac"] ~= nil) then rsp = rsp..host_info["mac"] elseif(host_info["host"] ~= nil) then rsp = rsp..host_info["host"] elseif(host_info["name"] ~= nil) then rsp = rsp..host_info["name"] end end local vlan_id = tonumber(host_info["vlan"] or host_info["vlan_id"] or 0) if vlan_id ~= 0 or show_vlan then rsp = rsp..'@'..tostring(vlan_id) end if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2URL => ".. rsp .. "\n") end return rsp end function member2visual(member) local info = hostkey2hostinfo(member) local host = info.host local hlen = string.len(host) if string.ends(host, "/32") and isIPv4(string.sub(host, 1, hlen-3)) then host = string.sub(host, 1, hlen-3) elseif string.ends(host, "/128") and isIPv6(string.sub(host, 1, hlen-4)) then host = string.sub(host, 1, hlen-4) end return hostinfo2hostkey({host=host, vlan=info.vlan}) end -- -- Analyze the get_info and return a new table containing the url information about an host. -- Example: url2host(_GET) -- function url2hostinfo(get_info) local host = {} -- Catch when the host key is using as host url parameter if((get_info["host"] ~= nil) and (string.find(get_info["host"],"@"))) then get_info = hostkey2hostinfo(get_info["host"]) end if(get_info["host"] ~= nil) then host["host"] = get_info["host"] if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"URL2HOST => Host:"..get_info["host"].."\n") end end if(get_info["vlan"] ~= nil) then host["vlan"] = tonumber(get_info["vlan"]) if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"URL2HOST => Vlan:"..get_info["vlan"].."\n") end else host["vlan"] = 0 end return host end -- -- Catch the main information about an host from the host_info table and return the corresponding url. -- Example: -- hostinfo2url(host_key), return an url based on the host_key -- hostinfo2url(host[key]), return an url based on the host value -- hostinfo2url(flow[key],"cli"), return an url based on the client host information in the flow table -- hostinfo2url(flow[key],"srv"), return an url based on the server host information in the flow table -- function hostinfo2url(host_info, host_type, novlan) local rsp = '' -- local version = 0 local version = 1 if(host_type == "cli") then if(host_info["cli.ip"] ~= nil) then rsp = rsp..'host='..hostinfo2hostkey(flow2hostinfo(host_info, "cli")) end elseif(host_type == "srv") then if(host_info["srv.ip"] ~= nil) then rsp = rsp..'host='..hostinfo2hostkey(flow2hostinfo(host_info, "srv")) end else if((type(host_info) ~= "table")) then host_info = hostkey2hostinfo(host_info) end if(host_info["host"] ~= nil) then rsp = rsp..'host='..host_info["host"] elseif(host_info["ip"] ~= nil) then rsp = rsp..'host='..host_info["ip"] elseif(host_info["mac"] ~= nil) then rsp = rsp..'host='..host_info["mac"] --Note: the host'name' is not supported (not accepted by lint) --elseif(host_info["name"] ~= nil) then -- rsp = rsp..'host='..host_info["name"] end end if(novlan == nil) then if((host_info["vlan"] ~= nil) and (tonumber(host_info["vlan"]) ~= 0)) then if(version == 0) then rsp = rsp..'&vlan='..tostring(host_info["vlan"]) elseif(version == 1) then rsp = rsp..'@'..tostring(host_info["vlan"]) end end end if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2URL => ".. rsp .. "\n") end return rsp end -- -- Catch the main information about an host from the host_info table and return the corresponding json. -- Example: -- hostinfo2json(host[key]), return a json string based on the host value -- hostinfo2json(flow[key],"cli"), return a json string based on the client host information in the flow table -- hostinfo2json(flow[key],"srv"), return a json string based on the server host information in the flow table -- function hostinfo2json(host_info,host_type) local rsp = '' if(host_type == "cli") then if(host_info["cli.ip"] ~= nil) then rsp = rsp..'host: "'..host_info["cli.ip"]..'"' end elseif(host_type == "srv") then if(host_info["srv.ip"] ~= nil) then rsp = rsp..'host: "'..host_info["srv.ip"]..'"' end else if((type(host_info) ~= "table") and (string.find(host_info,"@"))) then host_info = hostkey2hostinfo(host_info) end if(host_info["host"] ~= nil) then rsp = rsp..'host: "'..host_info["host"]..'"' elseif(host_info["ip"] ~= nil) then rsp = rsp..'host: "'..host_info["ip"]..'"' elseif(host_info["name"] ~= nil) then rsp = rsp..'host: "'..host_info["name"] ..'"' elseif(host_info["mac"] ~= nil) then rsp = rsp..'host: "'..host_info["mac"] ..'"' end end if((host_info["vlan"] ~= nil) and (host_info["vlan"] ~= 0)) then rsp = rsp..', vlan: "'..tostring(host_info["vlan"]) .. '"' end if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2JSON => ".. rsp .. "\n") end return rsp end -- -- Catch the main information about an host from the host_info table and return the corresponding jqueryid. -- Example: host 192.168.1.254, vlan0 ==> 1921681254_0 function hostinfo2jqueryid(host_info,host_type) local rsp = '' if(host_type == "cli") then if(host_info["cli.ip"] ~= nil) then rsp = rsp..''..host_info["cli.ip"] end elseif(host_type == "srv") then if(host_info["srv.ip"] ~= nil) then rsp = rsp..''..host_info["srv.ip"] end else if((type(host_info) ~= "table") and (string.find(host_info,"@"))) then host_info = hostkey2hostinfo(host_info) end if(host_info["host"] ~= nil) then rsp = rsp..''..host_info["host"] elseif(host_info["ip"] ~= nil) then rsp = rsp..''..host_info["ip"] elseif(host_info["name"] ~= nil) then rsp = rsp..''..host_info["name"] elseif(host_info["mac"] ~= nil) then rsp = rsp..''..host_info["mac"] end end if((host_info["vlan"] ~= nil) and (host_info["vlan"] ~= 0)) then rsp = rsp..'@'..tostring(host_info["vlan"]) end rsp = string.gsub(rsp, "%.", "__") rsp = string.gsub(rsp, "/", "___") rsp = string.gsub(rsp, ":", "____") if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2KEY => ".. rsp .. "\n") end return rsp end -- ############################################ -- Redis Utils -- ############################################ -- Inpur: General prefix (i.e ntopng.pref) -- Output: User based prefix, if it exists -- -- Examples: -- With user: ntopng.pref.user_name -- Without: ntopng.pref function getRedisPrefix(str) if not (isEmptyString(_SESSION["user"] )) then -- Login enabled return (str .. '.' .. _SESSION["user"]) else -- Login disabled return (str) end end ----- End of Redis Utils ------ function isPausedInterface(current_ifname) if(not isEmptyString(_POST["toggle_local"])) then return(_POST["toggle_local"] == "0") end state = ntop.getCache("ntopng.prefs.ifid_"..tostring(interface.name2id(current_ifname)).."_not_idle") if(state == "0") then return true else return false end end function getThroughputType() local throughput_type = ntop.getCache("ntopng.prefs.thpt_content") if throughput_type == "" then throughput_type = "bps" end return throughput_type end function processColor(proc) if(proc == nil) then return("") elseif(proc["average_cpu_load"] < 33) then return(""..proc["name"].."") elseif(proc["average_cpu_load"] < 66) then return(""..proc["name"].."") else return(""..proc["name"].."") end end -- Table preferences function getDefaultTableSort(table_type) local table_key = getRedisPrefix("ntopng.sort.table") local value = nil if(table_type ~= nil) then value = ntop.getHashCache(table_key, "sort_"..table_type) end if((value == nil) or (value == "")) then value = 'column_' end return(value) end function getDefaultTableSortOrder(table_type, force_get) local table_key = getRedisPrefix("ntopng.sort.table") local value = nil if(table_type ~= nil) then value = ntop.getHashCache(table_key, "sort_order_"..table_type) end if((value == nil) or (value == "")) and (force_get ~= true) then value = 'desc' end return(value) end function getDefaultTableSize() table_key = getRedisPrefix("ntopng.sort.table") value = ntop.getHashCache(table_key, "rows_number") if((value == nil) or (value == "")) then value = 10 end return(tonumber(value)) end function tablePreferences(key, value, force_set) table_key = getRedisPrefix("ntopng.sort.table") if((value == nil) or (value == "")) and (force_set ~= true) then -- Get preferences return ntop.getHashCache(table_key, key) else -- Set preferences ntop.setHashCache(table_key, key, value) return(value) end end function getInterfaceSpeed(ifid) local ifname = getInterfaceName(ifid) local ifspeed = ntop.getCache('ntopng.prefs.ifid_'..tostring(ifid)..'.speed') if not isEmptyString(ifspeed) and tonumber(ifspeed) ~= nil then ifspeed = tonumber(ifspeed) else ifspeed = interface.getMaxIfSpeed(ifid) end return ifspeed end function setInterfaceRegreshRate(ifid, refreshrate) local key = "ntopng.prefs.ifid_"..tostring(ifid)..".refresh_rate" if isEmptyString(refreshrate) then ntop.delCache(key) else ntop.setCache(key, tostring(refreshrate)) end end local function getCustomnDPIProtoCategoriesKey() return "ntop.prefs.custom_nDPI_proto_categories" end function getCustomnDPIProtoCategories() local ndpi_protos = interface.getnDPIProtocols() local key = getCustomnDPIProtoCategoriesKey() local res = {} for _, app_id in pairs(ndpi_protos) do local custom_category = ntop.getHashCache(key, tostring(app_id)) if not isEmptyString(custom_category) then res[tonumber(app_id)] = tonumber(custom_category) end end return res end function setCustomnDPIProtoCategory(app_id, new_cat_id) ntop.setnDPIProtoCategory(app_id, new_cat_id) local key = getCustomnDPIProtoCategoriesKey(ifid) -- NOTE: when the ndpi struct changes, the custom associations are -- reloaded by Ntop::loadProtocolsAssociations ntop.setHashCache(key, tostring(app_id), tostring(new_cat_id)); end -- "Some Very Long String" -> "Some Ver...g String" function shortenCollapse(s, max_len) local replacement = "..." local r_len = string.len(replacement) local s_len = string.len(s) if max_len == nil then max_len = ntop.getPref("ntopng.prefs.max_ui_strlen") max_len = tonumber(max_len) if(max_len == nil) then max_len = 24 end end if max_len <= r_len then return replacement end if s_len > max_len then local half = math.floor((max_len-r_len) / 2) return string.sub(s, 1, half) .. replacement .. string.sub(s, s_len-half+1) end return s end -- ############################################## function getHumanReadableInterfaceName(interface_name) local interface_id = nil if(interface_name == "__system__") then return(i18n("system")) elseif tonumber(interface_name) ~= nil then -- convert ID to name interface_id = tonumber(interface_name) interface_name = getInterfaceName(interface_name) else -- Parameter is a string, let's take it's id first interface_id = getInterfaceId(interface_name) -- and then get the name interface_name = getInterfaceName(interface_id) end local key = 'ntopng.prefs.ifid_'..tostring(interface_id)..'.name' local custom_name = ntop.getCache(key) if not isEmptyString(custom_name) then return(shortenCollapse(custom_name)) end return interface_name end -- ############################################## function unescapeHTML(s) local unesc = function (h) local res = string.char(tonumber(h, 16)) return res end -- s = string.gsub(s, "+", " ") s = string.gsub(s, "%%(%x%x)", unesc) return s end -- ############################################## function unescapeHttpHost(host) if isEmptyString(host) then return(host) end return string.gsub(string.gsub(host, "http:__", "http://"), "https:__", "https://") end -- ############################################## function harvestUnusedDir(path, min_epoch) local files = ntop.readdir(path) -- print("Reading "..path.."
\n") for k,v in pairs(files) do if(v ~= nil) then local p = os_utils.fixPath(path .. "/" .. v) if(ntop.isdir(p)) then harvestUnusedDir(p, min_epoch) else local when = ntop.fileLastChange(path) if((when ~= -1) and (when < min_epoch)) then os.remove(p) end end end end end -- ############################################## function harvestJSONTopTalkers(days) local when = os.time() - 86400 * days ifnames = interface.getIfNames() for _,ifname in pairs(ifnames) do interface.select(ifname) local _ifstats = interface.getStats() local dirs = ntop.getDirs() local basedir = os_utils.fixPath(dirs.workingdir .. "/" .. _ifstats.id) harvestUnusedDir(os_utils.fixPath(basedir .. "/top_talkers"), when) harvestUnusedDir(os_utils.fixPath(basedir .. "/flows"), when) end end -- ############################################## function isAdministratorOrPrintErr(isJsonResponse) if (isAdministrator()) then return(true) end local isJson = isJsonResponse or false if (isJson) then local json = require("dkjson") sendHTTPContentTypeHeader('application/json') print(json.encode({})) else local page_utils = require("page_utils") page_utils.print_header() dofile(dirs.installdir .. "/scripts/lua/inc/menu.lua") print("
Access forbidden
") end return(false) end -- ############################################## function getKeysSortedByValue(tbl, sortFunction) local keys = {} for key in pairs(tbl) do table.insert(keys, key) end table.sort(keys, function(a, b) return sortFunction(tbl[a], tbl[b]) end) return keys end function getKeys(t, col) local keys = {} for k,v in pairs(t) do keys[tonumber(v[col])] = k end return keys end -- ############################################## function formatBreed(breed, is_encrypted) local ret = "" if(breed == "Safe") then if(is_encrypted == false) then ret = "" end elseif(breed == "Acceptable") then -- if(is_encrypted == false) then ret = "" end elseif(breed == "Fun") then ret = "" elseif(breed == "Unsafe") then ret = "" elseif(breed == "Dangerous") then ret = "" end if(is_encrypted == true) then ret = ret .. " " end return(ret) end function getFlag(country) if((country == nil) or (country == "")) then return("") else return(" ") end end -- GENERIC UTILS -- split function split(s, delimiter) result = {}; if(s ~= nil) then if delimiter == nil then -- No delimiter, split all characters for match in s:gmatch"." do table.insert(result, match); end else -- Split by delimiter for match in (s..delimiter):gmatch("(.-)"..delimiter) do table.insert(result, match); end end end return result; end -- startswith function startswith(s, char) return string.sub(s, 1, string.len(s)) == char end -- strsplit function strsplit(s, delimiter) result = {}; for match in (s..delimiter):gmatch("(.-)"..delimiter) do if(match ~= "") then result[match] = true end end return result; end -- isempty function isempty(array) local count = 0 for _,__ in pairs(array) do count = count + 1 end return (count == 0) end -- isin function isin(s, array) if (s == nil or s == "" or array == nil or isempty(array)) then return false end for _, v in pairs(array) do if (s == v) then return true end end return false end -- hasKey function hasKey(key, theTable) if((theTable == nil) or (theTable[key] == nil)) then return(false) else return(true) end end function getUsernameInputPattern() -- maximum len must be kept in sync with MAX_PASSWORD_LEN return [[^(?=[a-zA-Z0-9._@!-?]{3,30}$)(?!.*[_.]{2})[^_.].*[^_.]$]] end function getPasswordInputPattern() -- maximum len must be kept in sync with MAX_PASSWORD_LEN return [[^[\w\$\\!\/\(\)= \?\^\*@_\-\u0000-\u0019\u0021-\u00ff]{5,31}$]] end -- NOTE: keep in sync with validateLicense() function getLicensePattern() return [[^[a-zA-Z0-9\+/=]+$]] end function getIPv4Pattern() return "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$" end function getACLPattern() local ipv4 = "(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])" local netmask = "(\\/([0-9]|[1-2][0-9]|3[0-2]))" local cidr = ipv4..netmask local yesorno_cidr = "[\\+\\-]"..cidr return "^"..yesorno_cidr.."(,"..yesorno_cidr..")*$" end function getMacPattern() return "^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F]{2}$" end function getURLPattern() return "^https?://.+$" end -- ############################################## function getRestUrl(script, is_pro, is_enterprise) if is_enterprise then return(ntop.getHttpPrefix() .. "/lua/enterprise/rest/v2/get/" .. script) elseif is_pro then return(ntop.getHttpPrefix() .. "/lua/pro/rest/v2/get/" .. script) else return(ntop.getHttpPrefix() .. "/lua/rest/v2/get/" .. script) end end -- ############################################## -- get_mac_classification function get_mac_classification(m, extended_name) local short_extended = ntop.getMacManufacturer(m) or {} if extended_name then return short_extended.extended or short_extended.short or m else return short_extended.short or m end return m end local magic_macs = { ["00:00:00:00:00:00"] = "", ["FF:FF:FF:FF:FF:FF"] = "Broadcast", ["01:00:0C:CC:CC:CC"] = "CDP", ["01:00:0C:CC:CC:CD"] = "CiscoSTP", ["01:80:C2:00:00:00"] = "STP", ["01:80:C2:00:00:00"] = "LLDP", ["01:80:C2:00:00:03"] = "LLDP", ["01:80:C2:00:00:0E"] = "LLDP", ["01:80:C2:00:00:08"] = "STP", ["01:1B:19:00:00:00"] = "PTP", ["01:80:C2:00:00:0E"] = "PTP" } local magic_short_macs = { ["01:00:5E"] = "IPv4mcast", ["33:33:"] = "IPv6mcast" } function macInfoWithSymbName(mac, name) return(' '..name..' ') end function macInfo(mac) return(' '..mac..' ') end -- get_symbolic_mac function get_symbolic_mac(mac_address, no_href, add_extra_info) if(magic_macs[mac_address] ~= nil) then return(magic_macs[mac_address]) else local m = string.sub(mac_address, 1, 8) local t = string.sub(mac_address, 10, 17) if(magic_short_macs[m] ~= nil) then if(add_extra_info == true) then return(magic_short_macs[m].."_"..t.." ("..macInfo(mac_address)..")") else if no_href then return(magic_short_macs[m].."_"..t) else return(macInfoWithSymbName(mac_address, magic_short_macs[m].."_"..t)) end end else local s = get_mac_classification(m) if(m == s) then if no_href then return get_mac_classification(m) .. ":" .. t else return '' .. get_mac_classification(m) .. ":" .. t .. '' end else local href = "" if not no_href then href = '' end if(add_extra_info == true) then return(href .. get_mac_classification(m).."_"..t.." ("..macInfo(mac_address)..")" .. "") else return(href .. get_mac_classification(m).."_"..t .. "") 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('[ %s ]', 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(""..site.." ") end -- ############################################### -- prints purged information for hosts / flows function purgedErrorString() local info = ntop.getInfo(false) return i18n("purged_error_message",{url=ntop.getHttpPrefix()..'/lua/admin/prefs.lua?tab=in_memory', product=info["product"]}) end -- print TCP flags function formatTCPFlags(flags) local out = '' if(hasbit(flags,0x02)) then out = out .. 'S ' end if(hasbit(flags,0x10)) then out = out .. 'A ' end if(hasbit(flags,0x01)) then out = out .. 'F ' end if(hasbit(flags,0x08)) then out = out .. 'P ' end if(hasbit(flags,0x04)) then out = out .. 'R ' end if(hasbit(flags,0x20)) then out = out .. 'U ' end if(hasbit(flags,0x40)) then out = out .. 'E ' end if(hasbit(flags,0x80)) then out = out .. 'C ' end return out end -- print TCP flags function printTCPFlags(flags) print(formatTCPFlags(flags)) end -- convert the integer carrying TCP flags in a more convenient lua table function TCPFlags2table(flags) local res = { ["FIN"] = 0, ["SYN"] = 0, ["RST"] = 0, ["PSH"] = 0, ["ACK"] = 0, ["URG"] = 0, ["ECE"] = 0, ["CWR"] = 0, } if(hasbit(flags,0x01)) then res["FIN"] = 1 end if(hasbit(flags,0x02)) then res["SYN"] = 1 end if(hasbit(flags,0x04)) then res["RST"] = 1 end if(hasbit(flags,0x08)) then res["PSH"] = 1 end if(hasbit(flags,0x10)) then res["ACK"] = 1 end if(hasbit(flags,0x20)) then res["URG"] = 1 end if(hasbit(flags,0x40)) then res["ECE"] = 1 end if(hasbit(flags,0x80)) then res["CWR"] = 1 end return res end -- ########################################## function historicalProtoHostHref(ifId, host, l4_proto, ndpi_proto_id, info, vlan, no_print) if ntop.isEnterpriseM() then local now = os.time() local ago1h = now - 3600 local prefs = ntop.getPrefs() if prefs.is_dump_flows_to_clickhouse_enabled then local hist_url = ntop.getHttpPrefix().."/lua/pro/db_search.lua?" local params = {epoch_end = now, epoch_begin = ago1h, ifid = ifId} if host then local host_k = hostinfo2hostkey(host) if isEmptyString(host_k) then host_k = host end params["ip"] = host_k..";eq" end if l4_proto then params["l4proto"] = l4_proto..";eq" end if ndpi_proto_id then params["l7proto"] = ndpi_proto_id..";eq" end if vlan and vlan ~= 0 then params["vlan_id"] = vlan..";eq" end if info then params["info"] = info..";in" end local url_params = table.tconcat(params, "=", "&") if not no_print then print(' ') -- print('') print('') -- print('') else return '' end end end end -- ############################################# -- Add here the icons you guess based on the Mac address -- TODO move to discovery stuff local guess_icon_keys = { ["dell inc."] = "fas fa-desktop", ["vmware, inc."] = "fas fa-desktop", ["xensource, inc."] = "fas fa-desktop", ["lanner electronics, inc."] = "fas fa-desktop", ["nexcom international co., ltd."] = "fas fa-desktop", ["apple, inc."] = "fab fa-apple", ["cisco systems, inc"] = "fas fa-arrows-alt", ["juniper networks"] = "fas fa-arrows-alt", ["brocade communications systems, inc."] = "fas fa-arrows-alt", ["force10 networks, inc."] = "fas fa-arrows-alt", ["huawei technologies co.,ltd"] = "fas fa-arrows-alt", ["alcatel-lucent ipd"] = "fas fa-arrows-alt", ["arista networks, inc."] = "fas fa-arrows-alt", ["3com corporation"] = "fas fa-arrows-alt", ["routerboard.com"] = "fas fa-arrows-alt", ["extreme networks"] = "fas fa-arrows-alt", ["xerox corporation"] = "fas fa-print" } function guessHostIcon(key) local m = string.lower(get_manufacturer_mac(key)) local icon = guess_icon_keys[m] if((icon ~= nil) and (icon ~= "")) then return(" ") 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] = [[
]] -- foreach character in format string.gsub(fmt, ".", function(k) local v = fmt_to_data[k] if v ~= nil then local line = {} if((max_val == nil) or (v.value < max_val)) then local input_name = ("opt_resbt_%s_%s"):format(k, ctrl_id) local input = ([[ ]]):format(k, truncate(v.value), v.label, input_name, input_name, ternary((selected == k), 'checked="checked"', "")) local label = ([[ ]]):format(ternary((selected == k), "btn-primary", "btn-secondary"), input_name, v.label) line[#line+1] = input line[#line+1] = label html_lines[#html_lines+1] = table.concat(line, "") end end end) html_lines[#html_lines+1] = [[
]] -- Note: no // comment below, only /* */ local js_init_code = [[ var _resol_inputs = []; function resol_selector_get_input(a_button) { return $("input", $(a_button).closest(".form-group.mb-3")).last(); } function resol_selector_get_buttons(an_input) { return $(".btn-group", $(an_input).closest(".form-group.mb-3")).first().find("input"); } /* This function scales values wrt selected resolution */ function resol_selector_reset_input_range($selected) { let duration = $($selected); let input = resol_selector_get_input(duration); let raw = parseInt(input.attr("data-min")); if (! isNaN(raw)) input.attr("min", Math.sign(raw) * Math.ceil(Math.abs(raw) / duration.val())); raw = parseInt(input.attr("data-max")); if (! isNaN(raw)) input.attr("max", Math.sign(raw) * Math.ceil(Math.abs(raw) / duration.val())); var step = parseInt(input.attr("data-step-" + duration.attr("data-resol"))); if (! isNaN(step)) { input.attr("step", step); /* Align value */ input.val(input.val() - input.val() % step); } else input.attr("step", ""); resol_recheck_input_range(input); } /* * Remove the checked value inside the radio buttons * and add it only to the one selected */ function resol_selector_change_callback(event) { $(this).parent().find('label').removeClass('btn-primary').addClass('btn-secondary'); $(this).parent().find('input[type="radio"]').prop('checked', false); $(this).prop('checked', true).removeClass('btn-secondary').addClass('btn-primary'); $(this).parent().find('label[for="' + $(this).attr('id') + '"]').removeClass('btn-secondary').addClass('btn-primary'); resol_selector_reset_input_range($(this)); } /* Function used to check the value input range */ function resol_recheck_input_range(input) { let value = input.val(); if (input[0].hasAttribute("min") && Number.isNaN(input.attr("min"))) value = Math.max(parseInt(input.val()), !input.attr("min")); if (input[0].hasAttribute("max") && Number.isNaN(input.attr("max"))) value = Math.min(parseInt(input.val()), !input.attr("max")); if ((input.val() != "") && (input.val() != value)) input.val(value); } function resol_selector_on_form_submit(event) { var form = $(this); if (event.isDefaultPrevented() || (form.find(".has-error").length > 0)) return false; resol_selector_finalize(form); return true; } function resol_selector_get_raw(input) { var buttons = resol_selector_get_buttons(input); var selected = buttons.filter(":checked"); return parseInt(selected.val()) * parseInt(input.val()); } function resol_selector_finalize(form) { $.each(_resol_inputs, function(i, elem) { /* Skip elements which are not part of the form */ if (! $(elem).closest("form").is(form)) return; var selected = $(elem).find("input[checked]"); var input = resol_selector_get_input(selected); /* transform in raw units */ var new_input = $(""); new_input.attr("name", input.attr("name")); input.removeAttr("name"); new_input.val(resol_selector_get_raw(input)); new_input.appendTo(form); }); /* remove added input names */ $("input[name^=opt_resbt_]", form).removeAttr("name"); }]] local js_specific_code = [[ $("#]] .. ctrl_id .. [[ input").change(resol_selector_change_callback); $(function() { var elemid = "#]] .. ctrl_id .. [["; _resol_inputs.push(elemid); var selected = $(elemid + " input[checked]"); resol_selector_reset_input_range(selected); /* setup the form submit callback (only once) */ var form = selected.closest("form"); if (! form.attr("data-options-handler")) { form.attr("data-options-handler", 1); form.submit(resol_selector_on_form_submit); } }); ]] -- join strings and strip newlines local html = string.gsub(table.concat(html_lines, " "), "\n", "") js_init_code = string.gsub(js_init_code, "", "") js_specific_code = string.gsub(js_specific_code, "\n", "") if tonumber(value) ~= nil then -- returns the new value with selected resolution return {html=html, init=js_init_code, js=js_specific_code, value=tonumber(value) / fmt_to_data[selected].value} else return {html=html, init=js_init_code, js=js_specific_code, value=nil} end end -- ########################################### -- -- Extracts parameters from a lua table. -- This function performs the inverse conversion of javascript paramsPairsEncode. -- -- Note: plain parameters (not encoded with paramsPairsEncode) remain unchanged only -- when strict mode is *not* enabled -- function paramsPairsDecode(params, strict_mode) local res = {} for k,v in pairs(params) do local sp = split(k, "key_") if #sp == 2 then local keyid = sp[2] local value = "val_"..keyid if params[value] then res[v] = params[value] end end if((not strict_mode) and (res[v] == nil)) then -- this is a plain parameter res[k] = v end end return res end function isBridgeInterface(ifstats) return ifstats.inline end function hasSnmpDevices(ifid) if (not ntop.isEnterpriseM()) or (not isAdministrator()) then return false end return has_snmp_devices(ifid) end function getTopFlowPeers(hostname_vlan, max_hits, detailed, other_options) local detailed = detailed or false local paginator_options = { sortColumn = "column_bytes", a2zSortOrder = false, detailedResults = detailed, maxHits = max_hits, } if other_options ~= nil then paginator_options = table.merge(paginator_options, other_options) end local res = interface.getFlowsInfo(hostname_vlan, paginator_options) if ((res ~= nil) and (res.flows ~= nil)) then return res.flows else return {} end end function stripVlan(name) local key = string.split(name, "@") if((key ~= nil) and (#key == 2)) then -- Verify that the host is actually an IP address and the VLAN actually -- a number to avoid stripping things that are not vlans (e.g. part of an host name) local addr = key[1] if((tonumber(key[2]) ~= nil) and (isIPv6(addr) or isIPv4(addr))) then return(addr) end end return(name) end function getSafeChildIcon() return(" ") end -- ########################################### function getNtopngRelease(ntopng_info, verbose) local release if ntopng_info.oem or ntopng_info["version.nedge_edition"] then release = "" elseif(ntopng_info["version.enterprise_l_edition"]) then release = "Enterprise L" elseif(ntopng_info["version.enterprise_m_edition"]) then release = "Enterprise M" elseif(ntopng_info["version.enterprise_edition"]) or (ntopng_info["version.nedge_enterprise_edition"]) then release = "Enterprise" elseif(ntopng_info["pro.release"]) then release = "Professional" elseif(ntopng_info["version.embedded_edition"]) then release = "/Embedded" else release = "Community" end -- E.g., ntopng edge v.4.3.210112 (Ubuntu 16.04.6 LTS) local res = string.format("%s %s v.%s (%s)", ntopng_info.product, release, ntopng_info.version, ntopng_info.OS) if verbose and ntopng_info.revision then res = string.format("%s %s v.%s rev.%s (%s)", ntopng_info.product, release, ntopng_info.version, ntopng_info.revision, ntopng_info.OS) end if not ntopng_info.oem then local vers = string.split(ntopng_info["version.git"], ":") if vers and vers[2] then local ntopng_git_url = "" res = string.format("%s | %s", res, ntopng_git_url) end end return res end -- ########################################### -- avoids manual HTTP prefix and /lua concatenation function page_url(path) return ntop.getHttpPrefix().."/lua/"..path end -- extracts a page url from the path function path_get_page(path) local prefix = ntop.getHttpPrefix() .. "/lua/" if string.find(path, prefix) == 1 then return string.sub(path, string.len(prefix) + 1) end return path end -- ########################################### function swapKeysValues(tbl) local new_tbl = {} for k, v in pairs(tbl or {}) do new_tbl[v] = k end return new_tbl end -- ########################################### -- A redis hash mac -> first_seen function getFirstSeenDevicesHashKey(ifid) return "ntopng.seen_devices.ifid_" .. ifid end -- ########################################### function getHideFromTopSet(ifid) return "ntopng.prefs.iface_" .. ifid .. ".hide_from_top" end -- ########################################### function getGwMacsSet(ifid) return "ntopng.prefs.iface_" .. ifid .. ".gw_macs" end -- ########################################### function printWarningAlert(message) print[[]] 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[[
]]) if (msg.type == "warning") then print("".. i18n("warning") .. ": ") elseif (msg.type == "danger") then print("".. i18n("error") .. ": ") end print(msg.text) print[[
]] 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 .. ([[ ]]) end return ([[ ]]) end -- ########################################### function getHttpUrlPrefix() if starts(_SERVER["HTTP_HOST"], 'https://') then return "https://" else return "http://" end end -- ########################################### -- Compares IPv4 / IPv6 addresses function ip_address_asc(a, b) return(ntop.ipCmp(a, b) < 0) end function ip_address_rev(a, b) return(ntop.ipCmp(a, b) > 0) end -- ########################################### -- @brief Deletes all the cache/prefs keys matching the pattern function deleteCachePattern(pattern) local keys = ntop.getKeysCache(pattern) for key in pairs(keys or {}) do ntop.delCache(key) end end -- ########################################### -- NOTE: '~= "0"' is used for prefs which are enabled by default function areInterfaceTimeseriesEnabled(ifid) return((ntop.getPref("ntopng.prefs.interface_rrd_creation") ~= "0")) end function areInterfaceL7TimeseriesEnabled(ifid) return(areInterfaceTimeseriesEnabled(ifid) and (ntop.getPref("ntopng.prefs.interface_ndpi_timeseries_creation") ~= "per_category")) end function areInterfaceCategoriesTimeseriesEnabled(ifid) local rv = ntop.getPref("ntopng.prefs.interface_ndpi_timeseries_creation") -- note: categories are disabled by default return(areInterfaceTimeseriesEnabled(ifid) and ((rv == "per_category") or (rv == "both"))) end function areHostTimeseriesEnabled(ifid) local rv = ntop.getPref("ntopng.prefs.hosts_ts_creation") if isEmptyString(rv) then rv = "light" end return((rv == "light") or (rv == "full")) end function areHostL7TimeseriesEnabled(ifid) local rv = ntop.getPref("ntopng.prefs.host_ndpi_timeseries_creation") -- note: host protocols are disabled by default return((ntop.getPref("ntopng.prefs.hosts_ts_creation") == "full") and ((rv == "per_protocol") or (rv == "both"))) end function areHostCategoriesTimeseriesEnabled(ifid) local rv = ntop.getPref("ntopng.prefs.host_ndpi_timeseries_creation") -- note: host protocols are disabled by default return((ntop.getPref("ntopng.prefs.hosts_ts_creation") == "full") and ((rv == "per_category") or (rv == "both"))) end function areSystemTimeseriesEnabled() return(ntop.getPref("ntopng.prefs.system_probes_timeseries") ~= "0") end function areHostPoolsTimeseriesEnabled(ifid) return(ntop.isPro() and (ntop.getPref("ntopng.prefs.host_pools_rrd_creation") == "1")) end function getPoolName(pool_id) if isEmptyString(pool_id) or pool_id == "0" then return "Default" else local key = "ntopng.prefs.host_pools.details."..pool_id return ntop.getHashCache(key, "name") end end function areASTimeseriesEnabled(ifid) return(ntop.getPref("ntopng.prefs.asn_rrd_creation") == "1") end function areInternalTimeseriesEnabled(ifid) -- NOTE: no separate preference so far return(areSystemTimeseriesEnabled()) end function areCountryTimeseriesEnabled(ifid) return((ntop.getPref("ntopng.prefs.country_rrd_creation") == "1")) end function areOSTimeseriesEnabled(ifid) return((ntop.getPref("ntopng.prefs.os_rrd_creation") == "1")) end function areVlanTimeseriesEnabled(ifid) return(ntop.getPref("ntopng.prefs.vlan_rrd_creation") == "1") end function areMacsTimeseriesEnabled(ifid) return(ntop.getPref("ntopng.prefs.l2_device_rrd_creation") == "1") end function areContainersTimeseriesEnabled(ifid) -- NOTE: no separate preference so far return(true) end function areSnmpTimeseriesEnabled(device, port_idx) return(ntop.getPref("ntopng.prefs.snmp_devices_rrd_creation") == "1") end function areFlowdevTimeseriesEnabled(ifid, device) return(ntop.getPref("ntopng.prefs.flow_device_port_rrd_creation") == "1") end -- ########################################### -- version is major.minor.veryminor function version2int(v) if(v == nil) then return(0) end e = string.split(v, "%."); if(e ~= nil) then major = e[1] minor = e[2] veryminor = e[3] if(major == nil or tonumber(major) == nil or type(major) ~= "string") then major = 0 end if(minor == nil or tonumber(minor) == nil or type(minor) ~= "string") then minor = 0 end if(veryminor == nil or tonumber(veryminor) == nil or type(veryminor) ~= "string") then veryminor = 0 end version = tonumber(major)*1000 + tonumber(minor)*100 -- + tonumber(veryminor) return(version) else return(0) end end function get_version_update_msg(info, latest_version) 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() if ntop.isOffline() then return nil end -- get the latest major release local latest_version = ntop.getCache("ntopng.cache.major_release") -- tprint(debug.traceback()) if isEmptyString(latest_version) then local rsp = ntop.httpGet("https://www.ntop.org/ntopng.version", "", "", 10 --[[ seconds ]]) if (not isEmptyString(rsp)) and (not isEmptyString(rsp["CONTENT"])) then latest_version = trimSpace(string.gsub(rsp["CONTENT"], "\n", "")) else -- a value that won't trigger an update message latest_version = "0.0.0" end ntop.setCache("ntopng.cache.major_release", latest_version, 86400 --[[ recheck interval]]) end return get_version_update_msg(info, latest_version) end -- ########################################### -- To be called inside the flows tableCallback function initFlowsRefreshRows() print[[ datatableInitRefreshRows($("#table-flows"), "key_and_hash", 10000, { /* List of rows with trend icons */ "column_thpt": ]] print(ternary(getThroughputType() ~= "bps", "NtopUtils.fpackets", "NtopUtils.bitsToSize")) print[[, "column_bytes": NtopUtils.bytesToSize, }); $("#dt-bottom-details > .float-left > p").first().append('. ]] print(i18n('flows_page.idle_flows_not_listed')) print[[');]] end -- ########################################### function canRestoreHost(ifid, ip, vlan) local ip_to_mac = string.format("ntopng.ip_to_mac.ifid_%u__%s@%d", ifid, ip, vlan) local key_to_check -- Check if there is a MAC address associated local mac = ntop.getCache(ip_to_mac) if not isEmptyString(mac) then key_to_check = string.format("ntopng.serialized_hostsbymac.ifid_%u__%s_%s", ifid, mac, ternary(isIPv4(ip), "v4", "v6")) else key_to_check = string.format("ntopng.serialized_hosts.ifid_%u__%s@%d", ifid, ip, vlan) end return(not table.empty(ntop.getKeysCache(key_to_check))) end -- ########################################### --- Test if each element inside the table t satisfies the predicate function --- @param t table The table containing values to test --- @param predicate function The function that return a boolean value (true|false) --- @return boolean function table.all(t, predicate) if type(t) ~= 'table' then traceError(TRACE_DEBUG, TRACE_CONSOLE, "the first paramater is not a table!") return false end if type(predicate) ~= 'function' then traceError(TRACE_DEBUG, TRACE_CONSOLE, "the passed predicate is not a function!") return false end if t == nil then return false end for _, value in pairs(t) do -- check if the value satisfies the boolean predicate local term = predicate(value) -- if the return value is valid and true then do nothing -- otherwise stop the loop and return false if term == nil then -- inform the client about the nil value traceError(TRACE_DEBUG, TRACE_CONSOLE, "a null term has been returned from the predicate function!") return false elseif not term then return false end end -- each entry satisfies the predicate return true end -- ########################################### --- Perform a linear search to check if an element is inside a table --- @param t table The table to scan --- @param needle any The element to search --- @param comp function The compare function used to compare the searched element with others --- @return boolean True if the element is insie the table, False otherwise function table.contains(t, needle, comp) if (t == nil) then return false end if (type(t) ~= "table") then return false end if (#t == 0) then return false end local default_compare = (function(e) return e == needle end) comp = comp or default_compare for _, element in ipairs(t) do if comp(element) then return true end end return false end -- ########################################### function build_query_url(excluded) local query = "?" for key, value in pairs(_GET) do if not(table.contains(excluded, key)) then query = query .. string.format("%s=%s&", key, value) end end return query end -- ########################################### function build_query_params(params) local query = "?" local t = {} for key, value in pairs(params) do t[#t+1] = string.format("%s=%s", key, value) end return query .. table.concat(t, '&') end -- ########################################### function create_ndpi_proto_name(v) local app = "" if v["proto.ndpi"] then app = getApplicationLabel(v["proto.ndpi"]) else local master_proto = interface.getnDPIProtoName(tonumber(v["l7_master_proto"])) local app_proto = interface.getnDPIProtoName(tonumber(v["l7_proto"])) if master_proto == app_proto then app = app_proto elseif master_proto == "Unknown" then app = app_proto else app = master_proto if app_proto ~= "Unknown" then app = app .. "." .. app_proto end end app = getApplicationLabel(app) end return app end -- ########################################### --- Insert an element inside the table if is not present function table.insertIfNotPresent(t, element, comp) if table.contains(t, element, comp) then return end t[#t+1] = element end -- ########################################### --- Fold right table with a custom function --- @param t table Table to fold --- @param func function Function to execute on table values --- @param val any The returned default value function table.foldr(t, func, val) for i,v in pairs(t) do val = func(val, v) end return val end -- ########################################### function table.has_key(table, key) return table[key] ~= nil end -- ########################################### function buildHostHREF(ip_address, vlan_id, page) local stats if(stats == nil) then stats = interface.getHostInfo(ip_address, vlan_id) else stats = stats.stats end if(stats == nil) then return(ip_address) else local hinfo = hostkey2hostinfo(ip_address) local name = hostinfo2label(hinfo) local res if((name == nil) or (name == "")) then name = ip_address end res = ''..name..'' return(res) end end function builMapHREF(service_peer, map, page) -- Getting minimal stats to know if the host is still present in memory local name local vlan = service_peer.vlan service_peer.vlan = nil local map_url = ntop.getHttpPrefix()..'/lua/pro/enterprise/network_maps.lua?map=' .. map .. '&page=' .. page .. '&' ..hostinfo2url(service_peer) local host_url = '' local host_icon if vlan then map_url = map_url .. '&vlan_id=' .. vlan end -- Getting stats and formatting initial href if (service_peer.ip or service_peer.host) and not service_peer.is_mac then -- Host URL only if the host is active host_url = hostinfo2detailsurl({host = service_peer.ip or service_peer.host, vlan = vlan}, nil, true --[[ check of the host is active --]]) local hinfo = interface.getHostMinInfo(service_peer.ip or service_peer.host, vlan) name = hostinfo2label(hinfo or service_peer) host_icon = "fa-laptop" else local minfo = interface.getMacInfo(service_peer.host) -- The URL only if the MAC is active if minfo and table.len(minfo) > 0 then host_url = ntop.getHttpPrefix()..'/lua/mac_details.lua?'..hostinfo2url(service_peer) end if (service_peer.ip and service_peer.is_mac) or not service_peer.is_mac then local hinfo = interface.getHostMinInfo(service_peer.ip or service_peer.host, vlan) name = hostinfo2label(hinfo or service_peer) else name = mac2label(service_peer.host) end if isMacAddress(name) then name = get_symbolic_mac(name, true) end host_icon = "fa-microchip" end -- Getting the name if present name = name or service_peer.host if vlan and tonumber(vlan) ~= 0 then name = name .. '@' .. getFullVlanName(vlan) end local res if not isEmptyString(host_url) then res = string.format('%s ', map_url, name, host_url, host_icon) else res = string.format('%s', map_url, name) end return res end -- ##################### -- Used by REST v1 function formatAlertAHref(key, value, label) return "" .. label .. "" end -- ############################################## function getObsPointAliasKey() return "ntopng.observation_point_aliases" end -- ############################################## function getObsPointAlias(observation_point_id, add_id, add_href) local alias = ntop.getHashCache(getObsPointAliasKey(), observation_point_id) local ret if not isEmptyString(alias) then if(add_id == true) then ret = observation_point_id .. " [".. alias .."]" else ret = alias end else ret = tostring(observation_point_id) end if(add_href == true) then ret = ""..ret.."" end return ret end -- ############################################## function setObsPointAlias(observation_point_id, alias) if((observation_point_id ~= alias) and not isEmptyString(alias)) then ntop.setHashCache(getObsPointAliasKey(), observation_point_id, alias) else ntop.delHashCache(getObsPointAliasKey(), observation_point_id) end end -- ############################################## function getFullObsPointName(observation_point_id, compact, add_id) local alias = getObsPointAlias(observation_point_id, add_id) if not isEmptyString(observation_point_id) then if not isEmptyString(observation_point_id) and alias ~= tostring(observation_point_id) then if compact then alias = shortenString(alias) return string.format("%s", alias) else return string.format("%u [%s]", observation_point_id, alias) end end end return observation_point_id end -- ############################################## function addScoreToAlertDescr(msg, score) return (msg .. string.format(" [%s: %s]", i18n("score"), format_utils.formatValue(score))) end -- ############################################## function addHTTPInfoToAlertDescr(msg, alert_json) if ((alert_json) and (table.len(alert_json["proto"] or {}) > 0) and (table.len(alert_json["proto"]["http"]) > 0)) then local http_info = format_http_info({ http_info = alert_json["proto"]["http"]["last_method"], last_return_code = alert_json["proto"]["http"]["last_return_code"], last_user_agent = alert_json["proto"]["http"]["last_user_agent"], last_url = alert_json["proto"]["http"]["last_url"] }) if http_info["last_method"] then msg = msg .. string.format(" [ %s: %s ]", i18n("db_explorer.http_method"), http_info["last_method"]) end if http_info["last_return_code"] then msg = msg .. string.format(" [ %s: %s ]", i18n("last_response_status_code"), http_info["last_return_code"]) end if http_info["last_user_agent"] then msg = msg .. string.format(" [ %s: %s ]", i18n("last_user_agent"), http_info["last_user_agent"]) end if http_info["last_url"] then msg = msg .. string.format(" [ %s: %s ]", i18n("last_url"), http_info["last_url"]) end end return msg end -- ############################################## function addDNSInfoToAlertDescr(msg, alert_json) if ((alert_json) and (table.len(alert_json["proto"] or {}) > 0) and (table.len(alert_json["proto"]["dns"] or {}) > 0)) then local dns_info = format_dns_query_info({ last_query_type = alert_json["proto"]["dns"]["last_query_type"], last_return_code = alert_json["proto"]["dns"]["last_return_code"], last_query = alert_json["proto"]["dns"]["last_query"] }) if dns_info["last_query_type"] then msg = msg .. string.format(" [ %s: %s ]", i18n("last_query_type"), dns_info["last_query_type"]) end if dns_info["last_return_code"] then msg = msg .. string.format(" [ %s: %s ]", i18n("last_return_code"), dns_info["last_return_code"]) end if dns_info["last_query"] then msg = msg .. string.format(" [ %s: %s ]", i18n("last_url"), dns_info["last_query"]) end end return msg end -- ############################################## function addTLSInfoToAlertDescr(msg, alert_json) if ((alert_json) and (table.len(alert_json["proto"] or {}) > 0) and (table.len(alert_json["proto"]["tls"] or {}) > 0)) then local tls_info = format_tls_info({ notBefore = alert_json["proto"]["tls"]["notBefore"], notAfter = alert_json["proto"]["tls"]["notAfter"], client_requested_server_name = alert_json["proto"]["tls"]["client_requested_server_name"], ['ja3.server_unsafe_cipher'] = alert_json["proto"]["tls"]["ja3.server_unsafe_cipher"] }) if tls_info["tls_certificate_validity"] then msg = msg .. string.format(" [ %s: %s ]", i18n("tls_certificate_validity"), tls_info["tls_certificate_validity"]) end if tls_info["ja3.server_unsafe_cipher"] then msg = msg .. string.format(" [ %s: %s ]", i18n("ja3.server_unsafe_cipher"), tls_info["ja3.server_unsafe_cipher"]) end if tls_info["client_requested_server_name"] then msg = msg .. string.format(" [ %s: %s ]", i18n("client_requested_server_name"), tls_info["client_requested_server_name"]) end end return msg end -- ############################################## function addICMPInfoToAlertDescr(msg, alert_json) if ((alert_json) and (table.len(alert_json["proto"] or {}) > 0) and (table.len(alert_json["proto"]["icmp"] or {}) > 0)) then local icmp_info = format_icmp_info({ code = alert_json["proto"]["icmp"]["code"], type = alert_json["proto"]["icmp"]["type"] }) -- Already formatted by the function if icmp_info["type"] then msg = msg .. string.format(" [ %s: %s ]", i18n("icmp_type"), icmp_info["type"]) end if icmp_info["code"] then msg = msg .. string.format(" [ %s: %s ]", i18n("icmp_code"), icmp_info["code"]) end end return msg end -- ############################################## function addBytesInfoToAlertDescr(msg, value) msg = string.format("%s [ %s: %s | %s: %s ]", msg, i18n("server_traffic"), bytesToSize(value["srv2cli_bytes"] or 0), i18n("client_traffic"), bytesToSize(value["cli2srv_bytes"] or 0)) return msg end -- ############################################## function addExtraFlowInfo(alert_json, value) local msg = "" msg = addHTTPInfoToAlertDescr(msg, alert_json) msg = addDNSInfoToAlertDescr(msg, alert_json) msg = addTLSInfoToAlertDescr(msg, alert_json) msg = addICMPInfoToAlertDescr(msg, alert_json) msg = addBytesInfoToAlertDescr(msg, value) return msg end -- ############################################## -- ##################### local iec104_typeids = { M_SP_NA_1=0x01, M_SP_TA_1=0x02, M_DP_NA_1=0x03, M_DP_TA_1=0x04, M_ST_NA_1=0x05, M_ST_TA_1=0x06, M_BO_NA_1=0x07, M_BO_TA_1=0x08, M_ME_NA_1=0x09, M_ME_TA_1=0x0A, M_ME_NB_1=0x0B, M_ME_TB_1=0x0C, M_ME_NC_1=0x0D, M_ME_TC_1=0x0E, M_IT_NA_1=0x0F, M_IT_TA_1=0x10, M_EP_TA_1=0x11, M_EP_TB_1=0x12, M_EP_TC_1=0x13, M_PS_NA_1=0x14, M_ME_ND_1=0x15, M_SP_TB_1=30, M_DP_TB_1=31, M_ST_TB_1=32, M_BO_TB_1=33, M_ME_TD_1=34, M_ME_TE_1=35, M_ME_TF_1=36, M_IT_TB_1=37, M_EP_TD_1=38, M_EP_TE_1=39, M_EP_TF_1=40, ASDU_TYPE_41=41, ASDU_TYPE_42=42, ASDU_TYPE_43=43, ASDU_TYPE_44=44, C_SC_NA_1=45, C_DC_NA_1=46, C_RC_NA_1=47, C_SE_NA_1=48, C_SE_NB_1=49, C_SE_NC_1=50, C_BO_NA_1=51, C_SC_TA_1=58, C_DC_TA_1=59, C_RC_TA_1=60, C_SE_TA_1=61, C_SE_TB_1=62, C_SE_TC_1=63, C_BO_TA_1=64, M_EI_NA_1=70, C_IC_NA_1=100, C_CI_NA_1=101, C_RD_NA_1=102, C_CS_NA_1=103, C_TS_NA_1=104, C_RP_NA_1=105, C_CD_NA_1=106, C_TS_TA_1=107, P_ME_NA_1=110, P_ME_NB_1=111, P_ME_NC_1=112, P_AC_NA_1=113, F_FR_NA_1=120, F_SR_NA_1=121, F_SC_NA_1=122, F_LS_NA_1=123, F_FA_NA_1=124, F_SG_NA_1=125, F_DR_TA_1=126, } function iec104_typeids2str(c) if(c == nil) then return end for s,v in pairs(iec104_typeids) do if(v == c) then return(s.." (".. v ..")") end end return(c) end function table.slice(t, start_table, end_table) if t == nil then error("The array to slice cannot be nil!") end if end_table > #t then end_table = #t end if start_table < 1 then error("Invalid bounds!") end local res = {} for i = start_table, end_table, 1 do res[#res + 1] = t[i] end return res end function add_historical_flow_explorer_button_ref(extra_params) if (ntop.getPrefs()["is_dump_flows_to_clickhouse_enabled"]) == false then return '' end local base_url = ntop.getHttpPrefix() .. "/lua/pro/db_search.lua?" for k, v in pairs(extra_params) do base_url = base_url .. k .. "=" .. v["value"] .. ";" .. v["operator"] end local button = '' return button end function add_delete_obs_point_button() local button = '' if isAdministrator() then button = '' end return button end function format_device_name(device_ip, short_version) local device_name = device_ip if ntop.isPro() then device_name = hostinfo2label(hostkey2hostinfo(device_ip)) if device_name ~= device_ip then if short_version then device_name = shortenString(device_name, 32) end device_name = string.format('%s [%s]', device_ip, device_name) end end return device_name end -- ############################################## -- @brief This function format the SNMP interface name. -- @params device_ip: snmp device ip -- portidx: number or string, interface index to format -- short_version: boolean, long formatting version (e.g. flow info) or short version (e.g. dropdown menu) function format_portidx_name(device_ip, portidx, short_version) local idx_name = portidx -- SNMP is available only with Pro version at least if ntop.isPro() then local snmp_cached_dev = require "snmp_cached_dev" local cached_dev = snmp_cached_dev:create(device_ip) if (cached_dev) and (cached_dev["interfaces"]) then local port_info = cached_dev["interfaces"][tostring(portidx)] if port_info then package.path = dirs.installdir .. "/pro/scripts/lua/modules/?.lua;" .. package.path snmp_location = require "snmp_location" if not port_info["id"] then port_info["id"] = port_info["index"] port_info["snmp_device_ip"] = cached_dev["host_ip"] end if short_version then idx_name = string.format('%s [%s]', port_info["index"], port_info["name"]) else idx_name = string.format('%s', i18n("snmp.interface_device_2", {interface=snmp_location.snmp_port_link(port_info, true), device=snmp_location.snmp_device_link(cached_dev["host_ip"])})) end end end end return idx_name end -- ############################################## function print_copy_button(id, data) print('') print("") end -- ############################################## function get_badge(info) local badge = 'success' if info ~= true and info ~= 0 then badge = 'danger' end return badge end -- ############################################## -- @brief Given a table of values, if available, it's going to format the values with the standard -- info and then return the same table formatted function format_dns_query_info(dns_info) if dns_info.last_query_type then dns_info.last_query_type = string.format('%s', dns_utils.getQueryType(dns_info.last_query_type)) end if dns_info.last_return_code then local badge = get_badge(dns_info.last_return_code) dns_info.last_return_code = string.format('%s', badge, dns_utils.getResponseStatusCode(dns_info.last_return_code)) end if dns_info.last_query then local url = dns_info["last_query"] url = string.gsub(url, " ", "") -- Clean the URL from spaces and %20, spaces in html dns_info.last_query = i18n("external_link_url", { proto = 'https', url = url, url_name = dns_info["last_query"] }) end return dns_info end -- ############################################## function format_tls_info(tls_info) if tls_info.notBefore then tls_info.notBefore = formatEpoch(tls_info.notBefore) end if tls_info.notAfter then tls_info.notAfter = formatEpoch(tls_info.notAfter) end if tls_info.notBefore and tls_info.notAfter then tls_info["tls_certificate_validity"] = string.format("%s - %s", tls_info.notBefore, tls_info.notAfter) tls_info.notBefore = nil tls_info.notAfter = nil end if tls_info.tls_version then if tls_info.tls_version > 0 then tls_info["tls_version"] = ntop.getTLSVersionName(tls_info.tls_version) else tls_info["tls_version"] = nil end end if tls_info.client_requested_server_name then local url = tls_info["client_requested_server_name"] url = string.gsub(url, " ", "") -- Clean the URL from spaces and %20, spaces in html tls_info["client_requested_server_name"] = i18n("external_link_url", { proto = 'https', url = url, url_name = url}) end if tls_info["ja3.server_cipher"] then tls_info["ja3.server_cipher"] = nil end if tls_info["ja3.server_unsafe_cipher"] then local badge = get_badge(tls_info["ja3.server_unsafe_cipher"] == "safe") tls_info["ja3.server_unsafe_cipher"] = string.format('%s', badge, tls_info["ja3.server_unsafe_cipher"]) end if tls_info["ja3.server_hash"] then tls_info["ja3.server_hash"] = i18n("copy_button", { full_name = tls_info["ja3.server_hash"], name = tls_info["ja3.server_hash"] }) end if tls_info["ja3.client_hash"] then tls_info["ja3.client_hash"] = i18n("copy_button", { full_name = tls_info["ja3.client_hash"], name = tls_info["ja3.client_hash"] }) end if tls_info["server_names"] then tls_info["server_names"] = i18n("copy_button", { full_name = tls_info["server_names"], name = shortenString(tls_info["server_names"],128) }) end return tls_info end -- ############################################## function format_icmp_info(icmp_info) local icmp_utils = require "icmp_utils" if icmp_info.code then icmp_info.code = icmp_utils.get_icmp_code(icmp_info.type, icmp_info.code) end if icmp_info.type then icmp_info.type = icmp_utils.get_icmp_type(icmp_info.type) end return icmp_info end -- ############################################## function format_http_info(http_info) if http_info["last_return_code"] then local badge = get_badge(http_info.last_return_code == 200) http_info["last_return_code"] = string.format('%s', badge, http_utils.getResponseStatusCode(http_info["last_return_code"])) end if http_info["last_method"] then http_info["last_method"] = string.format('%s', http_info["last_method"]) end if http_info["last_url"] then local url = http_info["last_url"] if string.find(http_info["last_url"], '^/') then url = (http_info["server_name"] or "") .. http_info["last_url"] end url = string.gsub(url, " ", "") -- Clean the URL from spaces and %20, spaces in html http_info["last_url"] = i18n("external_link_url", { proto = 'http', url = url, url_name = url}) end if http_info["server_name"] then http_info["server_name"] = i18n("copy_button", { full_name = http_info["server_name"], name = shortenString(http_info["server_name"], 32)}) end return http_info end -- ############################################## function format_common_info(flow_info, formatted_info) local predominant_bytes = i18n("traffic_srv_to_cli") if (tonumber(flow_info["cli2srv_bytes"] or 0)) > (tonumber(flow_info["srv2cli_bytes"] or 0)) then predominant_bytes = i18n("traffic_cli_to_srv") end formatted_info["predominant_direction"] = predominant_bytes formatted_info["server_traffic"] = bytesToSize(flow_info["srv2cli_bytes"] or 0) formatted_info["client_traffic"] = bytesToSize(flow_info["cli2srv_bytes"] or 0) return formatted_info end -- ############################################## function format_proto_info(proto_info) local proto_details = {} for key, value in pairs(proto_info) do if type(value) ~= "table" then proto_info[key] = nil end end for proto, info in pairs(proto_info) do if proto == "tls" then proto_details[proto] = format_tls_info(info) elseif proto == "dns" then proto_details[proto] = format_dns_query_info(info) elseif proto == "http" then proto_details[proto] = format_http_info(info) elseif proto == "icmp" then proto_details[proto] = format_icmp_info(info) end break end return proto_details end -- ############################################## -- @brief This function, given an IP and a vlan return the concat of host@vlan -- @params host_ip: A string containing the IP -- vlan: A string or a number containing the vlan id -- @return A string IP@vlan function format_ip_vlan(ip, vlan) local host = ip if (vlan) and (tonumber(vlan) ~= 0) then host = host .. '@' .. (tonumber(vlan) or vlan) end return host end -- ############################################## -- @brief This function, given an alert and "cli" or "srv" string is going to return the formatted hostname -- @params alert: A table with the alert infos -- cli_srv: A string "cli" or "srv" used to get the required info -- @return A string hostname@vlan function format_alert_hostname(alert, cli_srv) local host = alert[cli_srv .. "_name"] if(isEmptyString(host)) then host = alert[cli_srv .. "_ip"] end return format_ip_vlan(shortenString(host, 26), alert["vlan"]) end -- ############################################## -- @brief This function format the info field used in tables -- @params info: A string containing the info field -- no_html: A boolean, true if no_html is requested (e.g. Download in CSV format), -- false otherwise -- @return A string containing the info field formatted function format_external_link(url, name, no_html, proto) local external_field = url proto = ternary(((proto) and (proto == 'http')), 'http', 'https') if no_html == false then if not isEmptyString(url) then url = string.gsub(url, " ", "") -- Clean the URL from spaces and %20, spaces in html external_field = i18n("external_link_url", { proto = proto, url = url, url_name = name}) end end return external_field end -- ############################################## -- @brief This function format the info field used in tables -- @params info: A string containing the info field -- no_html: A boolean, true if no_html is requested (e.g. Download in CSV format), -- false otherwise -- @return A string containing the info field formatted function format_query_json_value(alert_or_flow, nested_field) local field_to_search = "ALERT_JSON" if alert_or_flow == 'alert' then field_to_search = "json" end return string.format('JSON_VALUE(%s, \'$.%s\')', field_to_search, nested_field) end -- ############################################## function get_confidence(confidence_id, shorten_string) local tag_utils = require "tag_utils" local confidence_name = confidence_id if confidence_id and tonumber(confidence_id) then confidence_id = tonumber(confidence_id) for _, confidence in pairs(tag_utils.confidence or {}) do if confidence.id == confidence_id then confidence_name = confidence.label break end end end return confidence_name end -- ############################################## function format_confidence_badge(confidence, shorten_string) local badge = "" if confidence == 0 then badge = "" .. get_confidence(confidence, shorten_string) .. "" elseif confidence then badge = "" .. get_confidence(confidence, shorten_string) .. "" end return badge end -- ############################################## function format_query_direction(op, val) local historical_flow_utils = require "historical_flow_utils" local direction_where = "" if val == "0" then direction_where = "(" .. historical_flow_utils.get_flow_column_by_tag("cli_location") .. " " .. op .. " '0' AND " .. historical_flow_utils.get_flow_column_by_tag("srv_location") .. " " .. op .. " '0')" elseif val == "1" then direction_where = "(" .. historical_flow_utils.get_flow_column_by_tag("cli_location") .. " " .. op .. " '1' AND " .. historical_flow_utils.get_flow_column_by_tag("srv_location") .. " " .. op .. " '1')" elseif val == "2" then direction_where = "(" .. historical_flow_utils.get_flow_column_by_tag("cli_location") .. " " .. op .. " '0' AND " .. historical_flow_utils.get_flow_column_by_tag("srv_location") .. " " .. op .. " '1')" elseif val == "3" then direction_where = "(" .. historical_flow_utils.get_flow_column_by_tag("cli_location") .. " " .. op .. " '1' AND " .. historical_flow_utils.get_flow_column_by_tag("srv_location") .. " " .. op .. " '0')" end return direction_where end -- ############################################## function format_confidence_from_json(record) local json = require "dkjson" local alert_json = {} local confidence = nil if record["ALERT_JSON"] then alert_json = json.decode(record["ALERT_JSON"]) elseif record["json"] then alert_json = json.decode(record["json"]) end if (alert_json) and (alert_json.proto) and (alert_json.proto.confidence) and (not isEmptyString(alert_json.proto.confidence)) then confidence = get_confidence(alert_json.proto.confidence) end return confidence end -- -- IMPORTANT -- Leave it at the end so it can use the functions -- defined in this file -- http_lint = require "http_lint"