-- -- (C) 2014-18 - ntop.org -- dirs = ntop.getDirs() package.path = dirs.installdir .. "/scripts/lua/modules/i18n/?.lua;" .. package.path package.path = dirs.installdir .. "/scripts/lua/modules/timeseries/?.lua;" .. package.path package.path = dirs.installdir .. "/scripts/lua/modules/flow_dbms/?.lua;" .. package.path require "lua_trace" require "ntop_utils" locales_utils = require "locales_utils" local os_utils = require "os_utils" local format_utils = require "format_utils" local alert_consts = require "alert_consts" local flow_consts = require "flow_consts" local page_utils = require("page_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(String,Start,plain) if type(String) ~= 'string' or type(Start) ~= 'string' then return false end local i,j = string.find(String, Start, 1, plain) return(i ~= nil) end -- ############################################## function shortenString(name, max_len) if(name == nil) then return("") end if max_len == nil then max_len = ntop.getPref("ntopng.prefs.max_ui_strlen") max_len = tonumber(max_len) if(max_len == nil) then max_len = 24 end end if(string.len(name) < max_len) then return(name) else return(string.sub(name, 1, max_len).."...") end end -- ############################################## 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 -- ############################################## -- 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 l4_keys = { { "TCP", "tcp", 6 }, { "UDP", "udp", 17 }, { "ICMP", "icmp", 1 }, { "ICMPv6", "icmpv6", 58 }, { "IGMP", "igmp", 2 }, { "Other IP", "other_ip", -1 } } L4_PROTO_KEYS = {tcp=6, udp=17, icmp=1, other_ip=-1} function __FILE__() return debug.getinfo(2,'S').source end function __LINE__() return debug.getinfo(2, 'l').currentline end -- ############################################## function sendHTTPHeaderIfName(mime, ifname, maxage, content_disposition, extra_headers) info = ntop.getInfo(false) local cookie_attr = ntop.getCookieAttributes() print('HTTP/1.1 200 OK\r\n') print('Cache-Control: max-age=0, no-cache, no-store\r\n') print('Server: ntopng '..info["version"]..' ['.. info["platform"]..']\r\n') print('Pragma: no-cache\r\n') print('X-Frame-Options: DENY\r\n') print('X-Content-Type-Options: nosniff\r\n') if(_SESSION ~= nil) then print('Set-Cookie: session='.._SESSION["session"]..'; max-age=' .. maxage .. '; path=/; ' .. cookie_attr .. '\r\n') end if(ifname ~= nil) then print('Set-Cookie: ifname=' .. ifname .. '; path=/' .. cookie_attr .. '\r\n') end print('Content-Type: '.. mime ..'\r\n') if(content_disposition ~= nil) then print('Content-Disposition: '..content_disposition..'\r\n') end if type(extra_headers) == "table" then for hname, hval in pairs(extra_headers) do print(hname..': '..hval..'\r\n') end end print('Last-Modified: '..os.date("!%a, %m %B %Y %X %Z").."\r\n") print('\r\n') end -- ############################################## function sendHTTPHeaderLogout(mime, content_disposition) sendHTTPHeaderIfName(mime, nil, 0, content_disposition) end -- ############################################## function sendHTTPHeader(mime, content_disposition, extra_headers) sendHTTPHeaderIfName(mime, nil, 3600, content_disposition, extra_headers) end -- ############################################## function sendHTTPContentTypeHeader(content_type, content_disposition, charset) local charset = charset or "utf-8" local mime = content_type.."; charset="..charset sendHTTPHeader(mime, content_disposition) end -- ############################################## function printGETParameters(get) for key, value in pairs(get) do io.write(key.."="..value.."\n") end end -- ############################################## -- Simplified checker function isIPv6Address(ip) if(string.find(ip, ":") ~= nil) then return true end return false 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 local ipversion_params = table.clone(page_params) ipversion_params["version"] = nil print[[\ \ ]] end -- ############################################## function printL4ProtoDropdown(base_url, page_params, l4_protocols) local l4proto = _GET["l4proto"] local l4proto_filter if not isEmptyString(l4proto) then l4proto_filter = '' else l4proto_filter = '' end local l4proto_params = table.clone(page_params) l4proto_params["l4proto"] = 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 local vlan_id_params = table.clone(page_params) vlan_id_params["vlan"] = 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 local traffic_type_params = table.clone(page_params) traffic_type_params["traffic_type"] = nil print[[\ \ ]] end -- ############################################## function printFlowDevicesFilterDropdown(base_url, page_params) if (ntop.isPro()) then package.path = dirs.installdir .. "/pro/scripts/lua/modules/?.lua;" .. package.path require "snmp_utils" end local flowdevs = interface.getFlowDevices() local vlans = interface.getVLANsList() if flowdevs == nil then flowdevs = {} end local devips = {} for dip, _ in pairsByValues(flowdevs, asc) do devips[#devips + 1] = dip end local cur_dev = _GET["deviceIP"] local cur_dev_filter = '' local snmp_community = '' if not isEmptyString(cur_dev) then cur_dev_filter = '' end local dev_params = table.clone(page_params) for _, p in pairs({"deviceIP", "outIfIdx", "inIfIdx"}) do dev_params[p] = nil end print[[, '
\ \ \
']] if cur_dev ~= nil then -- also print dropddowns for input and output interface index local ports = interface.getFlowDeviceInfo(cur_dev) for _, direction in pairs({"outIfIdx", "inIfIdx"}) do local cur_if = _GET[direction] local cur_if_filter = '' if not isEmptyString(cur_if) then cur_if_filter = '' end local if_params = table.clone(page_params) if_params[direction] = nil print[[, '
\ \ \
']] end 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[2] == proto_name then return(proto[3]) end end end function l4_proto_to_string(proto_id) proto_id = tonumber(proto_id) for _, proto in pairs(l4_keys) do if proto[3] == proto_id then return proto[1], proto[2] end end return string.format("%d", proto_id) end -- ############################################## function table.len(T) local count = 0 for _ in pairs(T) do count = count + 1 end return count 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("%s*<[iI].->(.-)","%1") :gsub("<.->(.-)","%1") -- note: this does not handle nested tags :gsub("^%s*(.-)%s*$", "%1") return unescape(cleaned) end function alertLevelToSyslogLevel(v) return alert_consts.alert_severities[v] end function areAlertsEnabled() return (ntop.getPref("ntopng.prefs.disable_alerts_generation") ~= "1") end function mustScanAlerts(ifstats) return areAlertsEnabled() end function hasAlertsDisabled() _POST = _POST or {} return ((_POST["disable_alerts_generation"] ~= nil) and (_POST["disable_alerts_generation"] == "1")) or ((_POST["disable_alerts_generation"] == nil) and (ntop.getPref("ntopng.prefs.disable_alerts_generation") == "1")) end function hasNagiosSupport() if prefs == nil then prefs = ntop.getPrefs() end return prefs.nagios_nsca_host ~= nil end function hasNindexSupport() if not ntop.isEnterprise() or ntop.isWindows() then return false end -- TODO optimize if prefs == nil then prefs = ntop.getPrefs() end if prefs.is_nindex_enabled then return true end return false end -- NOTE: global nindex support may be enabled but some disable on some interfaces function interfaceHasNindexSupport() return(hasNindexSupport() and interface.nIndexEnabled()) end --for _key, _value in pairsByKeys(vals, rev) do -- print(_key .. "=" .. _value .. "\n") --end function truncate(x) return x<0 and math.ceil(x) or math.floor(x) end -- Note that the function below returns a string as returning a number -- would not help as a new float would be returned function toint(num) return string.format("%u", truncate(num)) end function capitalize(str) return (str:gsub("^%l", string.upper)) end local function starstring(len) local s = "" while(len > 0) do s = s .."*" len = len -1 end return(s) end function obfuscate(str) local len = string.len(str) local in_clear = 2 if(len <= in_clear) then return(starstring(len)) else return(string.sub(str, 0, in_clear)..starstring(len-in_clear)) end end function isnumber(str) if((str ~= nil) and (string.len(str) > 0) and (tonumber(str) ~= nil)) then return(true) else return(false) end end function split(pString, pPattern) local Table = {} -- NOTE: use {n = 0} in Lua-5.0 local fpat = "(.-)" .. pPattern local last_end = 1 local s, e, cap = pString:find(fpat, 1) while s do if s ~= 1 or cap ~= "" then table.insert(Table,cap) end last_end = e+1 s, e, cap = pString:find(fpat, last_end) end if last_end <= #pString then cap = pString:sub(last_end) table.insert(Table, cap) end return Table end -- returns the MAXIMUM value found in a table t, together with the corresponding -- index argmax. a pair argmax, max is returned. function tmax(t) local argmx, mx = nil, nil if (type(t) ~= "table") then return nil, nil end for k, v in pairs(t) do -- first iteration if mx == nil and argmx == nil then mx = v argmx = k elseif (v == mx and k > argmx) or v > mx then -- if there is a tie, prefer the greatest argument -- otherwise grab the maximum argmx = k mx = v end end return argmx, mx end -- returns the MINIMUM value found in a table t, together with the corresponding -- index argmin. a pair argmin, min is returned. function tmin(t) local argmn, mn = nil, nil if (type(t) ~= "table") then return nil, nil end for k, v in pairs(t) do -- first iteration if mn == nil and argmn == nil then mn = v argmn = k elseif (v == mn and k > argmn) or v < mn then -- if there is a tie, prefer the greatest argument -- otherwise grab the minimum argmn = k mn = v end end return argmn, mn end function formatEpoch(epoch) return(format_utils.formatEpoch(epoch)) end function starts(String,Start) if((String == nil) or (Start == nil)) then return(false) end return string.sub(String,1,string.len(Start))==Start end function ends(String,End) return End=='' or string.sub(String,-string.len(End))==End end -- ################################################################# function bit(p) return 2 ^ (p - 1) -- 1-based indexing end -- Typical call: if hasbit(x, bit(3)) then ... function hasbit(x, p) return x % (p + p) >= p end function setbit(x, p) return hasbit(x, p) and x or x + p end function clearbit(x, p) return hasbit(x, p) and x - p or x end function isBroadMulticast(ip) if(ip == "0.0.0.0") then return true end -- print(ip) t = string.split(ip, "%.") -- print(table.concat(t, "\n")) if(t == nil) then return false -- Might be an IPv6 address else if(tonumber(t[1]) >= 224) then return true end end return false end function isBroadcastMulticast(ip) -- check NoIP if(ip == "0.0.0.0") then return true end -- check IPv6 t = string.split(ip, "%.") if(t ~= nil) then -- check Multicast / Broadcast if(tonumber(t[1]) >= 224) then return true end end return false end function isIPv4(address) local chunks = {address:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")} if #chunks == 4 then for _, v in pairs(chunks) do if (tonumber(v) < 0) or (tonumber(v) > 255) then return false end end return true 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 addLogoSvg() print [[ ]] end function addGauge(name, url, maxValue, width, height) if(url ~= nil) then print('') end print [[
]] if(url ~= nil) then print('
\n') end end -- Compute the difference in seconds between local time and UTC. function get_timezone() local now = os.time() return math.floor(os.difftime(now, os.time(os.date("!*t", now)))) 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 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 function http_escape(s) s = string.gsub(s, "([&=+%c])", function (c) return string.format("%%%02X", string.byte(c)) end) s = string.gsub(s, " ", "+") return s end -- Windows fixes for interfaces with "uncommon chars" function purifyInterfaceName(interface_name) -- io.write(debug.traceback().."\n") interface_name = string.gsub(interface_name, "@", "_") interface_name = string.gsub(interface_name, ":", "_") interface_name = string.gsub(interface_name, "/", "_") return(interface_name) end -- See datatype AggregationType in ntop_typedefs.h function aggregation2String(value) if(value == 0) then return("Client Name") elseif(value == 1) then return("Server Name") elseif(value == 2) then return("Domain Name") elseif(value == 3) then return("Operating System") elseif(value == 4) then return("Registrar Name") else return(value) end end -- ################################# -- Aggregates items below some edge -- edge: minimum percentage value to create collision -- min_col: minimum collision groups to aggregate function aggregatePie(values, values_sum, edge, min_col) local edge = edge or 0.09 min_col = min_col or 2 local aggr = {} local other = i18n("other") local below_edge = {} -- Initial lookup for k,v in pairs(values) do if v / values_sum <= edge then -- too small below_edge[#below_edge + 1] = k else aggr[k] = v end end -- Decide if to aggregate for _,k in pairs(below_edge) do if #below_edge >= min_col then -- aggregate aggr[other] = aggr[other] or 0 aggr[other] = aggr[other] + values[k] else -- do not aggregate aggr[k] = values[k] end end return aggr end -- ################################# function hostVisualization(ip, name) if (ip ~= name) and isIPv6(ip) then return name.." [IPv6]" end return name end -- ################################# -- NOTE: prefer the getResolvedAddress on this function function resolveAddress(hostinfo, allow_empty) 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 getResolvedAddress(hostinfo) end end return hostVisualization(hostinfo["host"], hostname) end -- ################################# -- NOTE: use host2name when possible function getResolvedAddress(hostinfo) local hostname = ntop.getResolvedName(hostinfo["host"]) return hostVisualization(hostinfo["host"], hostname) 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) local icon = getApplicationIcon(name) name = name:gsub("^%l", string.upper) return(icon.." "..name) end -- ################################# function getCategoryLabel(cat_name) cat_name = cat_name:gsub("^%l", string.upper) return(cat_name) end function getItemsNumber(n) tot = 0 for k,v in pairs(n) do --io.write(k.."\n") tot = tot + 1 end --io.write(tot.."\n") return(tot) end function getHostCommaSeparatedList(p_hosts) hosts = {} hosts_size = 0 for i,host in pairs(split(p_hosts, ",")) do hosts[i] = host hosts_size = hosts_size + 1 end return hosts,hosts_size end -- ############################################## function splitNetworkPrefix(net) local prefix = tonumber(net:match("/(.+)")) local address = net:gsub("/.+","") return address, prefix end -- ############################################## function 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() return "ntopng.host_labels" end -- ############################################## function getDhcpNamesKey(ifid) return "ntopng.dhcp."..ifid..".cache" end -- ############################################## -- Used to avoid resolving host names too many times resolved_host_labels_cache = {} -- host_ip can be a mac. host_mac can be null. function getHostAltName(host_ip, host_mac) local alt_name = nil if not isEmptyString(host_ip) then alt_name = resolved_host_labels_cache[host_ip] end -- cache hit if(alt_name ~= nil) then return(alt_name) end alt_name = ntop.getHashCache(getHostAltNamesKey(), host_ip) if (isEmptyString(alt_name) and (host_mac ~= nil)) then alt_name = ntop.getHashCache(getHostAltNamesKey(), host_mac) end if isEmptyString(alt_name) and ifname ~= nil then local key = getDhcpNamesKey(getInterfaceId(ifname)) if host_mac ~= nil then alt_name = ntop.getHashCache(key, host_mac) elseif isMacAddress(host_ip) then alt_name = ntop.getHashCache(key, host_ip) end end if isEmptyString(alt_name) then alt_name = host_ip end if not isEmptyString(alt_name) then resolved_host_labels_cache[host_ip] = alt_name end return(alt_name) end function setHostAltName(host_ip, alt_name) ntop.setHashCache(getHostAltNamesKey(), host_ip, alt_name) end -- Mac Addresses -- -- A function to give a useful device name function getDeviceName(device_mac, skip_manufacturer) local name = getHostAltName(device_mac) if name == device_mac then -- Not found, try with first host local info = interface.getHostsInfo(false, nil, 1, 0, nil, nil, nil, tonumber(vlan), nil, nil, device_mac) if (info ~= nil) then for x, host in pairs(info.hosts) do if not isEmptyString(host.name) and host.name ~= host.ip and host.name ~= "NoIP" then name = host.name elseif host.ip ~= "0.0.0.0" then name = getHostAltName(host.ip) if name == host.ip then name = nil end end break end else name = nil end end if isEmptyString(name) then if (not skip_manufacturer) then name = get_symbolic_mac(device_mac, true) else -- last resort name = device_mac end end return name end local specialMACs = { "01:00:0C", "01:80:C2", "01:00:5E", "01:0C:CD", "01:1B:19", "FF:FF", "33:33" } function isSpecialMac(mac) for _,key in pairs(specialMACs) do if(string.contains(mac, key)) then return true end end return false end -- Flow Utils -- function host2name(name, vlan) local orig_name = name vlan = tonumber(vlan or "0") name = getHostAltName(name) if(name == orig_name) then rname = getResolvedAddress({host=name, vlan=vlan}) if((rname ~= nil) and (rname ~= "")) then name = rname end end if(vlan > 0) then name = name .. '@' .. vlan end return name end function flowinfo2hostname(flow_info, host_type, alerts_view) local name local orig_name if alerts_view and not hasNindexSupport() then -- do not return resolved name as it will hide the IP address return(flow_info[host_type..".ip"]) end if(host_type == "srv") then if flow_info["host_server_name"] ~= nil and flow_info["host_server_name"] ~= "" and flow_info["host_server_name"]:match("%w") then -- remove possible ports from the name return(flow_info["host_server_name"]:gsub(":%d+$", "")) end if(flow_info["protos.ssl.certificate"] ~= nil and flow_info["protos.ssl.certificate"] ~= "") then return(flow_info["protos.ssl.certificate"]) end end name = flow_info[host_type..".host"] if((name == "") or (name == nil)) then name = flow_info[host_type..".ip"] end return(host2name(name, flow_info["vlan"])) 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 getLocalNetworkAlias(network) local alias = ntop.getHashCache(getLocalNetworkAliasKey(), network) if not isEmptyString(alias) then return alias end return network end -- ############################################## function getFullLocalNetworkName(network) local alias = getLocalNetworkAlias(network) if alias ~= network then return string.format("%s [%s]", alias, network) end return network end -- ############################################## function setLocalNetworkAlias(network, alias) if((network ~= alias) or isEmptyString(alias)) then ntop.setHashCache(getLocalNetworkAliasKey(), network, alias) else ntop.delHashCache(getLocalNetworkAliasKey(), network) end end -- ############################################## -- URL Util -- -- -- Split the host key (ip@vlan) creating a new lua table. -- Example: -- info = hostkey2hostinfo(key) -- ip = info["host"] -- vlan = info["vlan"] -- function hostkey2hostinfo(key) local host = {} local info = split(key,"@") if(info[1] ~= nil) then host["host"] = info[1] end if(info[2] ~= nil) then host["vlan"] = tonumber(info[2]) else host["vlan"] = 0 end return host end -- -- Analyze the host_info table and return the host key. -- Example: -- host_info = interface.getHostInfo("127.0.0.1",0) -- key = hostinfo2hostkey(host_info) -- function hostinfo2hostkey(host_info,host_type,show_vlan) local rsp = "" if(host_type == "cli") then if(host_info["cli.ip"] ~= nil) then rsp = rsp..host_info["cli.ip"] end elseif(host_type == "srv") then if(host_info["srv.ip"] ~= nil) then rsp = rsp..host_info["srv.ip"] end else if(host_info["host"] ~= nil) then rsp = rsp..host_info["host"] elseif(host_info["name"] ~= nil) then rsp = rsp..host_info["name"] elseif(host_info["ip"] ~= nil) then rsp = rsp..host_info["ip"] elseif(host_info["mac"] ~= nil) then rsp = rsp..host_info["mac"] end end if((host_info["vlan"] ~= nil and host_info["vlan"] ~= 0) or show_vlan) then rsp = rsp..'@'..tostring(host_info["vlan"] or 0) end if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2URL => ".. rsp .. "\n") end return rsp end function member2visual(member) local info = hostkey2hostinfo(member) local host = info.host local hlen = string.len(host) if string.ends(host, "/32") and isIPv4(string.sub(host, 1, hlen-3)) then host = string.sub(host, 1, hlen-3) elseif string.ends(host, "/128") and isIPv6(string.sub(host, 1, hlen-4)) then host = string.sub(host, 1, hlen-4) end return hostinfo2hostkey({host=host, vlan=info.vlan}) end -- -- Analyze the get_info and return a new table containing the url information about an host. -- Example: url2host(_GET) -- function url2hostinfo(get_info) local host = {} -- Catch when the host key is using as host url parameter if((get_info["host"] ~= nil) and (string.find(get_info["host"],"@"))) then get_info = hostkey2hostinfo(get_info["host"]) end if(get_info["host"] ~= nil) then host["host"] = get_info["host"] if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"URL2HOST => Host:"..get_info["host"].."\n") end end if(get_info["vlan"] ~= nil) then host["vlan"] = tonumber(get_info["vlan"]) if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"URL2HOST => Vlan:"..get_info["vlan"].."\n") end else host["vlan"] = 0 end return host end -- -- Catch the main information about an host from the host_info table and return the corresponding url. -- Example: -- hostinfo2url(host_key), return an url based on the host_key -- hostinfo2url(host[key]), return an url based on the host value -- hostinfo2url(flow[key],"cli"), return an url based on the client host information in the flow table -- hostinfo2url(flow[key],"srv"), return an url based on the server host information in the flow table -- function hostinfo2url(host_info, host_type, novlan) local rsp = '' -- local version = 0 local version = 1 if(host_type == "cli") then if(host_info["cli.ip"] ~= nil) then rsp = rsp..'host='..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")) 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 -- 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) version_elems = split(info["version"], " ") new_version = version2int(latest_version) this_version = version2int(version_elems[1]) if(new_version > this_version) then return [[
A new ]]..info["product"]..[[ (v.]]..(latest_version)..[[) is available for download: please upgrade.
]] else return "" end end function table.len(table) local count = 0 if(table == nil) then return(0) end for k,v in pairs(table) do count = count + 1 end return count end function table.slice(tbl, first, last, step) local sliced = {} for i = first or 1, last or #tbl, step or 1 do sliced[#sliced+1] = tbl[i] end return sliced end -- ############################################ -- Redis Utils -- ############################################ -- Inpur: General prefix (i.e ntopng.pref) -- Output: User based prefix, if it exists -- -- Examples: -- With user: ntopng.pref.user_name -- Without: ntopng.pref function getRedisPrefix(str) if not (isEmptyString(_SESSION["user"] )) then -- Login enabled return (str .. '.' .. _SESSION["user"]) else -- Login disabled return (str) end end function getRedisIfacePrefix(ifid) return "ntopng.prefs.ifid_"..tostring(ifid) end ----- End of Redis Utils ------ function isPausedInterface(current_ifname) state = ntop.getCache("ntopng.prefs."..current_ifname.."_not_idle") if(state == "0") then return true else return false end end function getThroughputType() throughput_type = ntop.getCache("ntopng.prefs.thpt_content") if(throughput_type == "") then throughput_type = "bps" end return throughput_type end function isLoopback(name) if((name == "lo") or (name == "lo0")) then return(true) else return(false) end 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.'..ifname..'.speed') if not isEmptyString(ifspeed) and tonumber(ifspeed) ~= nil then ifspeed = tonumber(ifspeed) else ifspeed = interface.getMaxIfSpeed(ifid) end return ifspeed end function getInterfaceRefreshRate(ifid) local key = getRedisIfacePrefix(ifid)..".refresh_rate" local refreshrate = ntop.getCache(key) if isEmptyString(refreshrate) or tonumber(refreshrate) == nil then refreshrate = 3 else refreshrate = tonumber(refreshrate) end return refreshrate end function setInterfaceRegreshRate(ifid, refreshrate) local key = getRedisIfacePrefix(ifid)..".refresh_rate" if isEmptyString(refreshrate) then ntop.delCache(key) else ntop.setCache(key, tostring(refreshrate)) end end local function getCustomnDPIProtoCategoriesKey(ifid) return getRedisIfacePrefix(ifid)..".custom_nDPI_proto_categories" end function getCustomnDPIProtoCategories(if_name) local ifid = getInterfaceId(if_name) local ndpi_protos = interface.getnDPIProtocols() local key = getCustomnDPIProtoCategoriesKey(ifid) 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 initCustomnDPIProtoCategories() for _, ifname in pairs(interface.getIfNames()) do interface.select(ifname) local custom = getCustomnDPIProtoCategories(ifname) for app_id, cat_id in pairs(custom) do interface.setnDPIProtoCategory(app_id, cat_id) end end end function setCustomnDPIProtoCategory(if_name, app_id, new_cat_id) interface.select(if_name) interface.setnDPIProtoCategory(app_id, new_cat_id) local ifid = getInterfaceId(if_name) local key = getCustomnDPIProtoCategoriesKey(ifid) 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 key = 'ntopng.prefs.'..interface_name..'.name' local custom_name = ntop.getCache(key) if not isEmptyString(custom_name) then return(shortenCollapse(custom_name)) else interface.select(interface_name) local _ifstats = interface.getStats() local nm = _ifstats.name if(string.contains(nm, "{")) then -- Windows nm = _ifstats.description end -- print(interface_name.."=".._ifstats.name) return(shortenCollapse(nm or '')) end end -- ############################################## function escapeHTML(s) s = string.gsub(s, "([&=+%c])", function (c) return string.format("%%%02X", string.byte(c)) end) s = string.gsub(s, " ", "+") return s end -- ############################################## function unescapeHTML(s) local unesc = function (h) local res = string.char(tonumber(h, 16)) return res end -- s = string.gsub(s, "+", " ") s = string.gsub(s, "%%(%x%x)", unesc) return s end -- ############################################## function 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 haveAdminPrivileges() if(isAdministrator()) then return(true) else page_utils.print_header() dofile(dirs.installdir .. "/scripts/lua/inc/menu.lua") print("
Access forbidden
") return(false) end end -- ############################################## function getKeysSortedByValue(tbl, sortFunction) local keys = {} for key in pairs(tbl) do table.insert(keys, key) end table.sort(keys, function(a, b) return sortFunction(tbl[a], tbl[b]) end) return keys end function getKeys(t, col) local keys = {} for k,v in pairs(t) do keys[tonumber(v[col])] = k end return keys end -- ############################################## function formatBreed(breed) if(breed == "Safe") then return("") elseif(breed == "Acceptable") then return("") elseif(breed == "Fun") then return("") elseif(breed == "Unsafe") then return("") elseif(breed == "Dangerous") then return("") else return("") end 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 for match in (s..delimiter):gmatch("(.-)"..delimiter) do table.insert(result, match); end end return result; end -- startswith function startswith(s, char) return string.sub(s, 1, string.len(s)) == char end -- strsplit function strsplit(s, delimiter) result = {}; for match in (s..delimiter):gmatch("(.-)"..delimiter) do if(match ~= "") then result[match] = true end end return result; end -- isempty function isempty(array) local count = 0 for _,__ in pairs(array) do count = count + 1 end return (count == 0) end -- isin function isin(s, array) if (s == nil or s == "" or array == nil or isempty(array)) then return false end for _, v in pairs(array) do if (s == v) then return true end end return false end -- hasKey function hasKey(key, theTable) if((theTable == nil) or (theTable[key] == nil)) then return(false) else return(true) end end function getPasswordInputPattern() -- maximum len must be kept in sync with MAX_PASSWORD_LEN return [[^[\w\$\\!\/\(\)= \?\^\*@_\-\u0000-\u0019\u0021-\u00ff]{5,31}$]] end function getIPv4Pattern() return "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$" end function getACLPattern() local ipv4 = "(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])" local netmask = "(\\/([0-9]|[1-2][0-9]|3[0-2]))" local cidr = ipv4..netmask local yesorno_cidr = "[\\+\\-]"..cidr return "^"..yesorno_cidr.."(,"..yesorno_cidr..")*$" end function getMacPattern() return "^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F]{2}$" end function getURLPattern() return "^https?://.+$" end -- get_mac_classification function get_mac_classification(m, extended_name) local short_extended = ntop.getMacManufacturer(m) or {} if extended_name then return short_extended.extended or short_extended.short or m else return short_extended.short or m end return m end local magic_macs = { ["FF:FF:FF:FF:FF:FF"] = "Broadcast", ["01:00:0C:CC:CC:CC"] = "CDP", ["01:00:0C:CC:CC:CD"] = "CiscoSTP", ["01:80:C2:00:00:00"] = "STP", ["01:80:C2:00:00:00"] = "LLDP", ["01:80:C2:00:00:03"] = "LLDP", ["01:80:C2:00:00:0E"] = "LLDP", ["01:80:C2:00:00:08"] = "STP", ["01:1B:19:00:00:00"] = "PTP", ["01:80:C2:00:00:0E"] = "PTP" } local magic_short_macs = { ["01:00:5E"] = "IPv4mcast", ["33:33:"] = "IPv6mcast" } function macInfo(mac) return(' '..mac..' ') end -- get_symbolic_mac function get_symbolic_mac(mac_address, only_symbolic) if(magic_macs[mac_address] ~= nil) then return(magic_macs[mac_address]) else local m = string.sub(mac_address, 1, 8) local t = string.sub(mac_address, 10, 17) if(magic_short_macs[m] ~= nil) then if(only_symbolic == true) then return(magic_short_macs[m].."_"..t) else return(magic_short_macs[m].."_"..t.." ("..macInfo(mac_address)..")") end else local s = get_mac_classification(m) if(m == s) then return '' .. get_mac_classification(m) .. ":" .. t .. '' else if(only_symbolic == true) then return(get_mac_classification(m).."_"..t) else return(get_mac_classification(m).."_"..t.." ("..macInfo(mac_address)..")") end end end end end function get_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 -- ############################################### -- NOTE: "flowstatus_info" is a lua table in a common format used -- to dump accurate flow alert information. See flow2statusinfo and alert2statusinfo -- below. -- Uses a flow returned by interface.getFlowsInfo() to create a flowstatus_info. -- NOTE: Keep consistent with alert2statusinfo function flow2statusinfo(flow) if flow["status_info"] then local json = require("dkjson") local res = json.decode(flow["status_info"]) return res end return nil end -- Uses an alert json to create a flowstatus_info. -- NOTE: Keep consistent with flow2statusinfo function alert2statusinfo(flow_json) local res = table.clone(flow_json.status_info) return res end -- ############################################### function formatSuspiciousDeviceProtocolAlert(flowstatus_info) local msg, devtype if not flowstatus_info then return i18n("alerts_dashboard.suspicious_device_protocol") end local discover = require("discover_utils") local forbidden_proto = flowstatus_info["devproto_forbidden_id"] or 0 if (flowstatus_info["devproto_forbidden_peer"] == "cli") then msg = "flow_details.suspicious_client_device_protocol" devtype = flowstatus_info["cli.devtype"] else msg = "flow_details.suspicious_server_device_protocol" devtype = flowstatus_info["srv.devtype"] end local label = discover.devtype2string(devtype) return i18n(msg, {proto=interface.getnDPIProtoName(forbidden_proto), devtype=label, url=getDeviceProtocolPoliciesUrl("device_type=".. devtype.."&l7proto="..forbidden_proto)}) end -- ############################################### function formatIDSFlowAlert(flowstatus_info) if not flowstatus_info then return i18n("alerts_dashboard.ids_alert") end local signature = (flowstatus_info.ids_alert and flowstatus_info.ids_alert.signature) local category = (flowstatus_info.ids_alert and flowstatus_info.ids_alert.category) local severity = (flowstatus_info.ids_alert and flowstatus_info.ids_alert.severity) local signature_info = (signature and signature:split(" ")); local maker = (signature_info and table.remove(signature_info, 1)) local scope = (signature_info and table.remove(signature_info, 1)) local msg = (signature_info and table.concat(signature_info, " ")) if maker and alert_consts.ids_rule_maker[maker] then maker = alert_consts.ids_rule_maker[maker] end local res = i18n("flow_details.ids_alert", { scope=scope, msg=msg, severity=severity, maker=maker } ) return res end -- ############################################### function formatElephantFlowAlert(flowstatus_info, local2remote) local threshold = "" local res = "" if not flowstatus_info then return i18n("flow_details.elephant_flow") end if local2remote then res = i18n("flow_details.elephant_flow_l2r") if flowstatus_info["elephant.l2r_threshold"] then threshold = flowstatus_info["elephant.l2r_threshold"] end else res = i18n("flow_details.elephant_flow_r2l") if flowstatus_info["elephant.r2l_threshold"] then threshold = flowstatus_info["elephant.r2l_threshold"] end end res = string.format("%s", res) if threshold ~= "" then res = string.format("%s [%s]", res, i18n("flow_details.elephant_exceeded", {vol = bytesToSize(threshold)})) end return res end -- ############################################### function formatLongLivedFlowAlert(flowstatus_info) local threshold = "" local res = i18n("flow_details.longlived_flow") if not flowstatus_info then return res end if flowstatus_info["longlived.threshold"] then threshold = flowstatus_info["longlived.threshold"] end res = string.format("%s", res) if threshold ~= "" then res = string.format("%s [%s]", res, i18n("flow_details.longlived_exceeded", {amount = secondsToTime(threshold)})) end return res end -- ############################################### function formatMaliciousSignature(flowstatus_info) local res = i18n("alerts_dashboard.malicious_signature_detected") if not flowstatus_info then return res end if(flowstatus_info.ja3_signature ~= nil) then res = i18n("flow_details.malicious_ja3_signature", { signature = flowstatus_info.ja3_signature, url = "https://sslbl.abuse.ch/ja3-fingerprints/" .. flowstatus_info.ja3_signature, icon = " ", }) end return res end -- ############################################### function formatBlacklistedFlow(status, flowstatus_info, alert) local who = {} if not flowstatus_info then return i18n("flow_details.blacklisted_flow") end if flowstatus_info["blacklisted.cli"] --[[ old format --]] or flowstatus_info["cli.blacklisted"] --[[ new format --]] then who[#who + 1] = i18n("client") end if flowstatus_info["blacklisted.srv"] --[[ old format --]] or flowstatus_info["srv.blacklisted"] --[[ new format --]] then who[#who + 1] = i18n("server") end -- if either the client or the server is blacklisted -- then also the category is blacklisted so there's no need -- to check it. -- Domain is basically the union of DNS names, SSL CNs and HTTP hosts. if #who == 0 and flowstatus_info["blacklisted.cat"] then who[#who + 1] = i18n("domain") end if #who == 0 then return i18n("flow_details.blacklisted_flow") end local res = i18n("flow_details.blacklisted_flow_detailed", {who = table.concat(who, ", ")}) return res end -- ############################################### function formatSSLCertificateMismatch(status, flowstatus_info, alert) if not flowstatus_info then return i18n("flow_details.ssl_certificate_mismatch") end local crts = {} if not isEmptyString(flowstatus_info["ssl_crt.cli"]) then crts[#crts + 1] = string.format("[%s: %s]", i18n("flow_details.ssl_client_certificate"), flowstatus_info["ssl_crt.cli"]) end if not isEmptyString(flowstatus_info["ssl_crt.srv"]) then crts[#crts + 1] = string.format("[%s: %s]", i18n("flow_details.ssl_server_certificate"), flowstatus_info["ssl_crt.srv"]) end return string.format("%s %s", i18n("flow_details.ssl_certificate_mismatch"), table.concat(crts, " ")) end -- ############################################### -- TODO put description formatter into flow_consts.flow_status_types function getFlowStatus(status, flowstatus_info, alert, no_icon) local warn_sign = ternary(no_icon, "", " ") local res = warn_sign..i18n("flow_details.unknown_status",{status=status}) -- NOTE: flowstatus_info can be nil if(status == flow_consts.status_ssl_certificate_mismatch) then res = warn_sign..formatSSLCertificateMismatch(status, flowstatus_info, alert) elseif(status == flow_consts.status_blacklisted) then res = warn_sign..formatBlacklistedFlow(status, flowstatus_info, alert) elseif(status == flow_consts.status_device_protocol_not_allowed) then res = formatSuspiciousDeviceProtocolAlert(flowstatus_info) elseif(status == flow_consts.status_elephant_local_to_remote) then res = warn_sign..formatElephantFlowAlert(flowstatus_info, true --[[ local 2 remote --]]) elseif(status == flow_consts.status_elephant_remote_to_local) then res = warn_sign..formatElephantFlowAlert(flowstatus_info, false --[[ remote 2 local --]]) elseif(status == flow_consts.status_longlived) then res = warn_sign..formatLongLivedFlowAlert(flowstatus_info) elseif(status == flow_consts.status_ids_alert) then res = warn_sign..formatIDSFlowAlert(flowstatus_info) elseif(status == flow_consts.status_tcp_severe_connection_issues) then res = warn_sign..i18n("flow_details.tcp_severe_connection_issues") elseif(status == flow_consts.status_malicious_signature) then res = warn_sign..formatMaliciousSignature(flowstatus_info) elseif(status == flow_consts.status_normal) then res = i18n(flow_consts.flow_status_types[flow_consts.status_normal].i18n_title) elseif(flow_consts.flow_status_types[status] ~= nil) then res = warn_sign..i18n(flow_consts.flow_status_types[status].i18n_title) end return res end -- prints purged information for hosts / flows function purgedErrorString() local info = ntop.getInfo(false) return i18n("purged_error_message",{url=ntop.getHttpPrefix()..'/lua/admin/prefs.lua?tab=in_memory', product=info["product"]}) end -- print TCP flags function printTCPFlags(flags) if(hasbit(flags,0x01)) then print('FIN ') end if(hasbit(flags,0x02)) then print('SYN ') end if(hasbit(flags,0x04)) then print('RST ') end if(hasbit(flags,0x08)) then print('PUSH ') end if(hasbit(flags,0x10)) then print('ACK ') end if(hasbit(flags,0x20)) then print('URG ') end end -- convert the integer carrying TCP flags in a more convenient lua table function TCPFlags2table(flags) local res = {["FIN"] = 0, ["SYN"] = 0, ["RST"] = 0, ["PSH"] = 0, ["ACK"] = 0, ["URG"] = 0} 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 return res end -- ########################################## function historicalProtoHostHref(ifId, host, l4_proto, ndpi_proto_id, info) if ntop.isPro() and ntop.getPrefs().is_dump_flows_to_mysql_enabled == true then local hist_url = ntop.getHttpPrefix().."/lua/pro/db_explorer.lua?search=true&ifid="..ifId local now = os.time() local ago1h = now - 3600 hist_url = hist_url.."&epoch_end="..tostring(now) if((host ~= nil) and (host ~= "")) then hist_url = hist_url.."&"..hostinfo2url(host) end if((l4_proto ~= nil) and (l4_proto ~= "")) then hist_url = hist_url.."&l4proto="..l4_proto end if((ndpi_proto_id ~= nil) and (ndpi_proto_id ~= "")) then hist_url = hist_url.."&protocol="..ndpi_proto_id end if((info ~= nil) and (info ~= "")) then hist_url = hist_url.."&info="..info end print(' ') -- print('') print('') -- print('') end end -- ########################################## local _icmp_types = { { 0, 0, "icmp_v4_types.type_0_0_echo_reply" }, { 3, 0, "icmp_v4_types.type_3_0_network_unreachable" }, { 3, 1, "icmp_v4_types.type_3_1_host_unreachable" }, { 3, 2, "icmp_v4_types.type_3_2_protocol_unreachable" }, { 3, 3, "icmp_v4_types.type_3_3_port_unreachable" }, { 3, 4, "icmp_v4_types.type_3_4_fragmentation_needed_but_no_fragment_bit_set" }, { 3, 5, "icmp_v4_types.type_3_5_source_routing_failed" }, { 3, 6, "icmp_v4_types.type_3_6_destination_network_unknown" }, { 3, 7, "icmp_v4_types.type_3_7_destination_host_unknown" }, { 3, 8, "icmp_v4_types.type_3_8_source_host_isolated" }, { 3, 9, "icmp_v4_types.type_3_9_destination_network_administratively_prohibited" }, { 3, 10, "icmp_v4_types.type_3_10_destination_host_administratively_prohibited" }, { 3, 11, "icmp_v4_types.type_3_11_network_unreachable_for_tos" }, { 3, 12, "icmp_v4_types.type_3_12_host_unreachable_for_tos" }, { 3, 13, "icmp_v4_types.type_3_13_communication_administratively_prohibited_by_filtering" }, { 3, 14, "icmp_v4_types.type_3_14_host_precedence_violation" }, { 3, 15, "icmp_v4_types.type_3_15_precedence_cutoff_in_effect" }, { 4, 0, "icmp_v4_types.type_4_0_source_quench" }, { 5, 0, "icmp_v4_types.type_5_0_redirect_for_network" }, { 5, 1, "icmp_v4_types.type_5_1_redirect_for_host" }, { 5, 2, "icmp_v4_types.type_5_2_redirect_for_tos_and_network" }, { 5, 3, "icmp_v4_types.type_5_3_redirect_for_tos_and_host" }, { 8, 0, "icmp_v4_types.type_8_0_echo_request" }, { 9, 0, "icmp_v4_types.type_9_0_router_advertisement" }, { 10, 0, "icmp_v4_types.type_10_0_route_solicitation" }, { 11, 0, "icmp_v4_types.type_11_0_ttl_equals_0_during_transit" }, { 11, 1, "icmp_v4_types.type_11_1_ttl_equals_0_during_reassembly" }, { 12, 0, "icmp_v4_types.type_12_0_ip_header_bad" }, { 12, 1, "icmp_v4_types.type_12_1_required_options_missing" }, { 13, 0, "icmp_v4_types.type_13_0_timestamp_request" }, { 14, 0, "icmp_v4_types.type_14_0_timestamp_reply" }, { 15, 0, "icmp_v4_types.type_15_0_information_request" }, { 16, 0, "icmp_v4_types.type_16_0_information_reply" }, { 17, 0, "icmp_v4_types.type_17_0_address_mask_request" }, { 18, 0, "icmp_v4_types.type_18_0_address_mask_reply" } } -- Code is currently ignored on IVMPv6 local _icmpv6_types = { { 0, i18n("icmp_v6_types.type_0_reserved") }, { 1, i18n("icmp_v6_types.type_1_destination_unreachable") }, { 2, i18n("icmp_v6_types.type_2_packet_too_big") }, { 3, i18n("icmp_v6_types.type_3_time_exceeded") }, { 4, i18n("icmp_v6_types.type_4_parameter_problem") }, { 100, i18n("icmp_v6_types.type_100_private_experimentation") }, { 101, i18n("icmp_v6_types.type_101_private_experimentation") }, --{ 102-126, i18n("icmp_v6_types.type_102-126_unassigned") }, { 127, i18n("icmp_v6_types.type_127_reserved_for_expansion_of_icmpv6_error_messages") }, { 128, i18n("icmp_v6_types.type_128_echo_request") }, { 129, i18n("icmp_v6_types.type_129_echo_reply") }, { 130, i18n("icmp_v6_types.type_130_multicast_listener_query") }, { 131, i18n("icmp_v6_types.type_131_multicast_listener_report") }, { 132, i18n("icmp_v6_types.type_132_multicast_listener_done") }, { 133, i18n("icmp_v6_types.type_133_router_solicitation") }, { 134, i18n("icmp_v6_types.type_134_router_advertisement") }, { 135, i18n("icmp_v6_types.type_135_neighbor_solicitation") }, { 136, i18n("icmp_v6_types.type_136_neighbor_advertisement") }, { 137, i18n("icmp_v6_types.type_137_redirect_message") }, { 138, i18n("icmp_v6_types.type_138_router_renumbering") }, { 139, i18n("icmp_v6_types.type_139_icmp_node_information_query") }, { 140, i18n("icmp_v6_types.type_140_icmp_node_information_response") }, { 141, i18n("icmp_v6_types.type_141_inverse_neighbor_discovery_solicitation_message") }, { 142, i18n("icmp_v6_types.type_142_inverse_neighbor_discovery_advertisement_message") }, { 143, i18n("icmp_v6_types.type_143_version_2_multicast_listener_report") }, { 144, i18n("icmp_v6_types.type_144_home_agent_address_discovery_request_message") }, { 145, i18n("icmp_v6_types.type_145_home_agent_address_discovery_reply_message") }, { 146, i18n("icmp_v6_types.type_146_mobile_prefix_solicitation") }, { 147, i18n("icmp_v6_types.type_147_mobile_prefix_advertisement") }, { 148, i18n("icmp_v6_types.type_148_certification_path_solicitation_message") }, { 149, i18n("icmp_v6_types.type_149_certification_path_advertisement_message") }, { 150, i18n("icmp_v6_types.type_150_icmp_messages_utilized_by_experimental_mobility_protocols") }, { 151, i18n("icmp_v6_types.type_151_multicast_router_advertisement") }, { 152, i18n("icmp_v6_types.type_152_multicast_router_solicitation") }, { 153, i18n("icmp_v6_types.type_153_multicast_router_termination") }, { 154, i18n("icmp_v6_types.type_154_fmipv6_messages") }, { 155, i18n("icmp_v6_types.type_155_rpl_control_message") }, { 156, i18n("icmp_v6_types.type_156_ilnpv6_locator_update_message") }, { 157, i18n("icmp_v6_types.type_157_duplicate_address_request") }, { 158, i18n("icmp_v6_types.type_158_duplicate_address_confirmation") }, { 159, i18n("icmp_v6_types.type_159_mpl_control_message") }, --{ 160-199, i18n("icmp_v6_types.type_160-199_unassigned") }, { 200, i18n("icmp_v6_types.type_200_private_experimentation") }, { 201, i18n("icmp_v6_types.type_201_private_experimentation") }, { 255, i18n("icmp_v6_types.type_255_reserved_for_expansion_of_icmpv6_informational_messages") } } -- ############################################# function getICMPV6TypeCode(icmp) local t = icmp.type local c = icmp.code for _, _e in ipairs(_icmpv6_types) do if(_e[1] == t) then return(_e[2]) end end if t and c then return (t or '').."/"..(c or '') else return '' end end -- ############################################# function getICMPTypeCode(icmp) local t = icmp.type local c = icmp.code local extra = {src_port = '', dst_port = '', protocol = ''} if icmp["unreach"] then extra["src_port"] = icmp["unreach"]["src_port"]..'' extra["dst_port"] = icmp["unreach"]["dst_port"]..'' extra["protocol"] = getL4ProtoName(icmp["unreach"]["protocol"]) end for _, _e in ipairs(_icmp_types) do if((_e[1] == t) and (_e[2] == c)) then local res = _e[3] return i18n(res, extra) end end return(getICMPV6TypeCode(icmp)) end -- ############################################# -- Add here the icons you guess based on the Mac address -- TODO move to discovery stuff local guess_icon_keys = { ["dell inc."] = "fa-desktop", ["vmware, inc."] = "fa-desktop", ["xensource, inc."] = "fa-desktop", ["lanner electronics, inc."] = "fa-desktop", ["nexcom international co., ltd."] = "fa-desktop", ["apple, inc."] = "fa-apple", ["cisco systems, inc"] = "fa-arrows", ["juniper networks"] = "fa-arrows", ["brocade communications systems, inc."] = "fa-arrows", ["force10 networks, inc."] = "fa-arrows", ["huawei technologies co.,ltd"] = "fa-arrows", ["alcatel-lucent ipd"] = "fa-arrows", ["arista networks, inc."] = "fa-arrows", ["3com corporation"] = "fa-arrows", ["routerboard.com"] = "fa-arrows", ["extreme networks"] = "fa-arrows", ["xerox corporation"] = "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 -- #################################################### function getTzOffsetSeconds() local now = os.time() local local_t = os.date("*t", now) local utc_t = os.date("!*t", now) local delta = os.time(local_t) - os.time(utc_t) if utc_t.isdst then -- DST is the practice of advancing clocks during summer months -- so that evening daylight lasts longer, while sacrificing normal sunrise times. -- utc_t is increased by one hour when the time is DST. -- For example, an UTC time of 2pm would be reported by lua as 3pm with -- the isdst flag set. -- For this reason, we need to add back the hour to the computed delta. delta = delta + 3600 end -- tprint(string.format("local_t %u [%s][isdst: %s]", os.time(local_t), formatEpoch(os.time(local_t)), local_t.isdst)) -- tprint(string.format("utc_t %u [%s][isdst: %s]", os.time(utc_t), formatEpoch(os.time(utc_t)), utc_t.isdst)) return delta end -- #################################################### function makeTimeStamp(d, tzoffset) -- tzoffset is the timezone difference between UTC and Local Time in the browser local pattern = "(%d+)%/(%d+)%/(%d+) (%d+):(%d+):(%d+)" local day,month, year, hour, minute, seconds = string.match(d, pattern); local timestamp = os.time({year=year, month=month, day=day, hour=hour, min=minute, sec=seconds}); -- tprint(string.format("pre-timestamp is %u [%s]", timestamp, formatEpoch(timestamp))) if tzoffset then -- from browser local time to UTC timestamp = timestamp - (tzoffset or 0) -- from UTC to machine local time local delta = getTzOffsetSeconds() timestamp = math.floor(timestamp + (delta or 0)) -- tprint("delta: "..delta.." tzoffset is: "..tzoffset) -- tprint(string.format("post-timestamp is %u [%s]", timestamp, formatEpoch(timestamp))) end return string.format("%u", timestamp) 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" then return true elseif 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 line[#line+1] = [[]] 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")).last(); } function resol_selector_get_buttons(an_input) { return $(".btn-group", $(an_input).closest(".form-group")).first().find("input"); } /* This function scales values wrt selected resolution */ function resol_selector_reset_input_range(selected) { var selected = $(selected); var input = resol_selector_get_input(selected); var raw = parseInt(input.attr("data-min")); if (! isNaN(raw)) input.attr("min", Math.sign(raw) * Math.ceil(Math.abs(raw) / selected.val())); raw = parseInt(input.attr("data-max")); if (! isNaN(raw)) input.attr("max", Math.sign(raw) * Math.ceil(Math.abs(raw) / selected.val())); var step = parseInt(input.attr("data-step-" + selected.attr("data-resol"))); if (! isNaN(step)) { input.attr("step", step); /* Align value */ input.val(input.val() - input.val() % step); } else input.attr("step", ""); resol_recheck_input_range(input); } function resol_selector_change_selection(selected) { selected.attr('checked', 'checked') .closest("label").removeClass('btn-default').addClass('btn-primary') .siblings().removeClass('active').removeClass('btn-primary').addClass('btn-default').find("input").removeAttr('checked'); resol_selector_reset_input_range(selected); } function resol_recheck_input_range(input) { var value = input.val(); if (input[0].hasAttribute("min")) value = Math.max(value, input.attr("min")); if (input[0].hasAttribute("max")) value = Math.min(value, input.attr("max")); var old_val = input.val(); if ((old_val != "") && (old_val != value)) input.val(value); } function resol_selector_change_callback(event) { resol_selector_change_selection($(this)); } function resol_selector_on_form_submit(event) { var form = $(this); if (event.isDefaultPrevented() || (form.find(".has-error").length > 0)) return false; resol_selector_finalize(form); return true; } /* Helper function to set a selector value by raw value */ function resol_selector_set_value(input_id, its_value) { var input = $(input_id); var buttons = resol_selector_get_buttons($(input_id)); var values = []; buttons.each(function() { values.push(parseInt($(this).val())); }); var new_value; var new_i; if (its_value > 0) { /* highest divisor */ var highest_i = 0; for (var i=1; i values[highest_i]) && (its_value % values[i] == 0))) highest_i = i; } new_value = its_value / values[highest_i]; new_i = highest_i; } else { /* smallest value */ new_value = Math.max(its_value, -1); new_i = values.indexOf(Math.min.apply(Math, values)); } /* Set */ input.val(new_value); resol_selector_change_selection($(buttons[new_i])); /* This must be set manually on initialization */ $(buttons[new_i]).closest("label").addClass("active"); } function resol_selector_get_raw(input) { var buttons = resol_selector_get_buttons(input); var selected = buttons.filter(":checked"); return parseInt(selected.val()) * parseInt(input.val()); } function resol_selector_finalize(form) { $.each(_resol_inputs, function(i, elem) { /* Skip elements which are not part of the form */ if (! $(elem).closest("form").is(form)) return; var selected = $(elem).find("input[checked]"); var input = resol_selector_get_input(selected); resol_recheck_input_range(input); /* transform in raw units */ var new_input = $(""); new_input.attr("name", input.attr("name")); input.removeAttr("name"); new_input.val(resol_selector_get_raw(input)); new_input.appendTo(form); }); /* remove added input names */ $("input[name^=opt_resbt_]", form).removeAttr("name"); }]] local js_specific_code = [[ $("#]] .. ctrl_id .. [[ input").change(resol_selector_change_callback); $(function() { var elemid = "#]] .. ctrl_id .. [["; _resol_inputs.push(elemid); var selected = $(elemid + " input[checked]"); resol_selector_reset_input_range(selected); /* setup the form submit callback (only once) */ var form = selected.closest("form"); if (! form.attr("data-options-handler")) { form.attr("data-options-handler", 1); form.submit(resol_selector_on_form_submit); } }); ]] -- join strings and strip newlines local html = string.gsub(table.concat(html_lines, ""), "\n", "") js_init_code = string.gsub(js_init_code, "\n", "") js_specific_code = string.gsub(js_specific_code, "\n", "") if tonumber(value) ~= nil then -- returns the new value with selected resolution return {html=html, init=js_init_code, js=js_specific_code, value=tonumber(value) / fmt_to_data[selected].value} else return {html=html, init=js_init_code, js=js_specific_code, value=nil} end end -- ########################################### -- -- Extracts parameters from a lua table. -- This function performs the inverse conversion of javascript paramsPairsEncode. -- -- Note: plain parameters (not encoded with paramsPairsEncode) remain unchanged only -- when strict mode is *not* enabled -- function paramsPairsDecode(params, strict_mode) local res = {} for k,v in pairs(params) do local sp = split(k, "key_") if #sp == 2 then local keyid = sp[2] local value = "val_"..keyid if params[value] then res[v] = params[value] end end if((not strict_mode) and (res[v] == nil)) then -- this is a plain parameter res[k] = v end end return res end function isBridgeInterface(ifstats) return ifstats.inline end function hasSnmpDevices(ifid) if (not ntop.isEnterprise()) 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) then return(key[1]) else return(name) end end function getSafeChildIcon() return(" ") end -- ########################################### function printntopngRelease(info) if info.oem then return "" end if(info["version.enterprise_edition"]) or (info["version.nedge_enterprise_edition"]) then print(" Enterprise") elseif(info["version.nedge_edition"]) then print(" ") elseif(info["pro.release"]) then print(" Professional") else print(" Community") end if(info["version.embedded_edition"] == true) then print("/Embedded") end print(" Edition\n") end -- ########################################### -- avoids manual HTTP prefix and /lua concatenation function page_url(path) return ntop.getHttpPrefix().."/lua/"..path end -- extracts a page url from the path function path_get_page(path) local prefix = ntop.getHttpPrefix() .. "/lua/" if string.find(path, prefix) == 1 then return string.sub(path, string.len(prefix) + 1) end return path end -- ########################################### function swapKeysValues(tbl) local new_tbl = {} for k, v in pairs(tbl or {}) do new_tbl[v] = k end return new_tbl end -- ########################################### -- A redis hash mac -> first_seen function getFirstSeenDevicesHashKey(ifid) return "ntopng.seen_devices.ifid_" .. ifid end -- ########################################### function getHideFromTopSet(ifid) return "ntopng.prefs.iface_" .. ifid .. ".hide_from_top" end -- ########################################### function printWarningAlert(message) print[[]] 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) function getFolderSize(path) local size = 0 if ntop.isWindows() then -- TODO else if ntop.isdir(path) then local line = os_utils.execWithOutput("du -s "..path.." 2>/dev/null") local values = split(line, '\t') if #values >= 1 then local used = tonumber(values[1]) if used ~= nil then size = used*1024 end end end end return size 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 -- ########################################### -- -- IMPORTANT -- Leave it at the end so it can use the functions -- defined in this file -- http_lint = require "http_lint"