EVOLUTION-MANAGER
Edit File: flow_utils.lua
-- -- (C) 2013-24 - ntop.org -- dirs = ntop.getDirs() package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path require "template" require "voip_utils" require "lua_utils" local graph_utils = require "graph_utils" local tcp_flow_state_utils = require("tcp_flow_state_utils") local format_utils = require("format_utils") local flow_consts = require "flow_consts" local alert_consts = require "alert_consts" local json = require("dkjson") if ntop.isPro() then package.path = dirs.installdir .. "/scripts/lua/pro/modules/?.lua;" .. package.path shaper_utils = require("shaper_utils") end -- The same base id of the ntop_flow.h file local ntop_base_id = 57472 -- Keep in sync with the defines in ntop_flow.h, -- otherwise it could happen that some fields are not mapped local flow_label_id = { [tostring(ntop_base_id + 524)] = 'COMMUNITY_ID' } -- ####################### local flow_verdict_mapping = {"Unknown", -- 0 "Pass", -- 1 "Drop" -- 2 } local flow_verdict_icon = {'', '<i class="fas fa-check"></i>', '<i class="fas fa-ban"></i>'} -- ####################### function getFlowLabelFromId(id) local label = flow_label_id[tostring(id)] if label == nil then label = id end return label end -- ####################### -- Given a field and a flow, checks if the flow already has a non empty field function fieldAlreadyPresent(field, flow) field = string.lower(field) if (flow[field]) and (not isEmptyString(flow[field])) then return true end return false end -- ####################### function parseFlowVerdict(flow_verdict, minimal) if flow_verdict_mapping[flow_verdict + 1] then if minimal then return flow_verdict_mapping[flow_verdict + 1] .. " " .. flow_verdict_icon[flow_verdict + 1] else return (flow_verdict .. " (" .. flow_verdict_mapping[flow_verdict + 1] .. " " .. flow_verdict_icon[flow_verdict + 1] .. ")") end end return flow_verdict end -- ####################### function addFlowVerdictBadge(flow_verdict, minimal) local flow_verdict_class = "badge bg-secondary" if tonumber(flow_verdict) == 1 then flow_verdict_class = "badge bg-success" elseif tonumber(flow_verdict) == 2 then flow_verdict_class = "badge bg-danger" end local flow_verdict_formatted = parseFlowVerdict(flow["flow_verdict"], minimal) return '<span class="' .. flow_verdict_class .. '">' .. flow_verdict_formatted .. '</span>' end -- ####################### function formatInterfaceId(id, idx, snmpdevice) if (id == 65535) then return ("Unknown") else if (snmpdevice ~= nil) then return ('<A HREF="/lua/flows_stats.lua?deviceIP=' .. snmpdevice .. '&' .. idx .. '=' .. id .. '">' .. id .. '</A>') else return (id) end end end -- ####################### function formatTrafficProfile(profile) local res = "" if not isEmptyString(profile) then res = "<span class='badge bg-primary'>" .. profile .. "</span> " end return res end -- ####################### -- Extracts the information serialized into alert_info from the flow -- checks function flow2alertinfo(flow) local alert_info = flow["alert_info"] if (alert_info and (string.sub(alert_info, 1, 1) == "{")) then local res = json.decode(alert_info) if (res ~= nil) then return (res) end end return (alert_info) end -- ####################### function getFlowsFilter() -- Pagination local sortColumn = _GET["sortColumn"] local sortOrder = _GET["sortOrder"] local currentPage = _GET["currentPage"] local perPage = _GET["perPage"] -- Other Filters local port = _GET["port"] local application = _GET["application"] local category = _GET["category"] local network_id = _GET["network"] local traffic_profile = _GET["traffic_profile"] local traffic_type = _GET["traffic_type"] local flowhosts_type = _GET["flowhosts_type"] local ipversion = _GET["version"] local l4proto = _GET["l4proto"] local vlan = _GET["vlan"] local username = _GET["username"] local host = _GET["host"] local pid_name = _GET["pid_name"] local container = _GET["container"] local pod = _GET["pod"] local icmp_type = _GET["icmp_type"] local icmp_code = _GET["icmp_cod"] local dscp_filter = _GET["dscp"] local host_pool = _GET["host_pool_id"] local flow_status = _GET["flow_status"] local flow_status_severity = _GET["flow_status_severity"] local alert_type = _GET["alert_type"] local alert_type_severity = _GET["alert_type_severity"] local deviceIP = _GET["deviceIP"] local inIfIdx = _GET["inIfIdx"] local outIfIdx = _GET["outIfIdx"] local asn = _GET["asn"] local tcp_state = _GET["tcp_flow_state"] local talking_with = _GET["talking_with"] local client = _GET["client"] local server = _GET["server"] local flow_info = _GET["flow_info"] local iface_index = tonumber(_GET["interface_filter"] or -1) if sortColumn == nil or sortColumn == "column_" or sortColumn == "" then sortColumn = getDefaultTableSort("flows") elseif sortColumn ~= "column_" and sortColumn ~= "" then tablePreferences("sort_flows", sortColumn) else sortColumn = "column_client" end if sortOrder == nil then sortOrder = getDefaultTableSortOrder("flows") elseif sortColumn ~= "column_" and sortColumn ~= "" then tablePreferences("sort_order_flows", sortOrder) end if currentPage == nil then currentPage = 1 else currentPage = tonumber(currentPage) end if perPage == nil then perPage = getDefaultTableSize() else perPage = tonumber(perPage) tablePreferences("rows_number", perPage) end if port ~= nil then port = tonumber(port) end if network_id ~= nil then network_id = tonumber(network_id) end local to_skip = (currentPage - 1) * perPage local a2z = false if sortOrder == "desc" then a2z = false else a2z = true end local pageinfo = { ["perPage"] = perPage, ["currentPage"] = currentPage, ["sortOrder"] = sortOrder or "", ["sortColumn"] = sortColumn or "", ["toSkip"] = to_skip, ["maxHits"] = perPage, ["a2zSortOrder"] = a2z, ["hostFilter"] = host, ["portFilter"] = port, ["LocalNetworkFilter"] = network_id, ["ifaceIndex"] = iface_index } if application ~= nil and application ~= "" then local param = string.split(application, "%.") if param and #param == 2 then -- Example 5.26 pageinfo["l7protoFilter"] = application else if not tonumber(application) then pageinfo["l7protoFilter"] = interface.getnDPIProtoId(application) else pageinfo["l7protoFilter"] = tonumber(application) end end end if category ~= nil and category ~= "" then if tonumber(category) then pageinfo["l7categoryFilter"] = tonumber(category) else pageinfo["l7categoryFilter"] = interface.getnDPICategoryId(category) end end if traffic_profile ~= nil then pageinfo["trafficProfileFilter"] = traffic_profile end if not isEmptyString(flowhosts_type) then if flowhosts_type == "local_origin_remote_target" then pageinfo["clientMode"] = "local" pageinfo["serverMode"] = "remote" elseif flowhosts_type == "local_only" then pageinfo["clientMode"] = "local" pageinfo["serverMode"] = "local" elseif flowhosts_type == "remote_origin_local_target" then pageinfo["clientMode"] = "remote" pageinfo["serverMode"] = "local" elseif flowhosts_type == "remote_only" then pageinfo["clientMode"] = "remote" pageinfo["serverMode"] = "remote" end end if not isEmptyString(traffic_type) then if traffic_type:contains("unicast") then pageinfo["unicast"] = true else pageinfo["unicast"] = false end if traffic_type:contains("one_way") then pageinfo["unidirectional"] = true end end if not isEmptyString(alert_type) then if alert_type == "normal" then pageinfo["alertedFlows"] = false pageinfo["filteredFlows"] = false elseif alert_type == "alerted" then pageinfo["alertedFlows"] = true elseif alert_type == "periodic" then pageinfo["periodicFlows"] = true pageinfo["filteredFlows"] = false elseif alert_type == "filtered" then pageinfo["filteredFlows"] = true else pageinfo["statusFilter"] = tonumber(alert_type) or alert_type end end if not isEmptyString(alert_type_severity) then local s = alert_consts.severity_groups[alert_type_severity] if s then pageinfo["statusSeverityFilter"] = s.severity_group_id end end if not isEmptyString(ipversion) then pageinfo["ipVersion"] = tonumber(ipversion) end if not isEmptyString(l4proto) then pageinfo["L4Protocol"] = tonumber(l4proto) end if not isEmptyString(vlan) then pageinfo["vlanIdFilter"] = tonumber(vlan) end if not isEmptyString(username) then pageinfo["usernameFilter"] = username end if not isEmptyString(pid_name) then pageinfo["pidnameFilter"] = pid_name end if not isEmptyString(container) then pageinfo["container"] = container end if not isEmptyString(pod) then pageinfo["pod"] = pod end if not isEmptyString(deviceIP) then pageinfo["deviceIpFilter"] = deviceIP if not isEmptyString(inIfIdx) then pageinfo["inIndexFilter"] = tonumber(inIfIdx) end if not isEmptyString(outIfIdx) then pageinfo["outIndexFilter"] = tonumber(outIfIdx) end end if not isEmptyString(asn) then pageinfo["asnFilter"] = tonumber(asn) end pageinfo["icmp_type"] = tonumber(icmp_type) pageinfo["icmp_code"] = tonumber(icmp_code) if not isEmptyString(dscp_filter) then pageinfo["dscpFilter"] = tonumber(dscp_filter) end if not isEmptyString(talking_with) then pageinfo["talkingWith"] = talking_with end if not isEmptyString(host_pool) then pageinfo["poolFilter"] = tonumber(host_pool) end if not isEmptyString(tcp_state) then pageinfo["tcpFlowStateFilter"] = tcp_state end if not isEmptyString(client) then pageinfo["client"] = client end if not isEmptyString(server) then pageinfo["server"] = server end if not isEmptyString(flow_info) then pageinfo["flow_info"] = flow_info end return pageinfo end -- ####################### function handleCustomFlowField(key, value, snmpdevice) if key == 'TCP_FLAGS' then return (formatTcpFlags(value)) elseif key == 'INPUT_SNMP' then return (formatInterfaceId(value, "inIfIdx", snmpdevice)) elseif key == 'OUTPUT_SNMP' then return (formatInterfaceId(value, "outIfIdx", snmpdevice)) elseif key == 'TOTAL_FLOWS_EXP' then return (format_utils.formatValue(value)) elseif key == 'EXPORTER_IPV4_ADDRESS' or key == 'NPROBE_IPV4_ADDRESS' then if ntop.isPro() then return ("<A HREF=\"" .. ntop.getHttpPrefix() .. "/lua/pro/enterprise/flowdevice_details.lua?ip=" .. value .. "\">" .. value .. "</A>") else return (value) end elseif key == 'FLOW_USER_NAME' then elems = string.split(value, ';') if ((elems ~= nil) and (#elems == 6)) then r = '<table class="table table-bordered table-striped">' imsi = elems[1] mcc = string.sub(imsi, 1, 3) if (flow_consts.mobile_country_code[mcc] ~= nil) then mcc_name = " [" .. flow_consts.mobile_country_code[mcc] .. "]" else mcc_name = "" end r = r .. "<th>" .. i18n("flow_details.imsi") .. "</th><td>" .. elems[1] .. mcc_name r = r .. " <A HREF='http://www.numberingplans.com/?page=analysis&sub=imsinr'><i class='fas fa-info'></i></A></td></tr>" r = r .. "<th>" .. i18n("flow_details.nsapi") .. "</th><td>" .. elems[2] .. "</td></tr>" r = r .. "<th>" .. i18n("flow_details.gsm_cell_lac") .. "</th><td>" .. elems[3] .. "</td></tr>" r = r .. "<th>" .. i18n("flow_details.gsm_cell_identifier") .. "</th><td>" .. elems[4] .. "</td></tr>" r = r .. "<th>" .. i18n("flow_details.sac_service_area_code") .. "</th><td>" .. elems[5] .. "</td></tr>" r = r .. "<th>" .. i18n("ip_address") .. "</th><td>" .. ntop.inet_ntoa(elems[6]) .. "</td></tr>" r = r .. "</table>" return (r) else return (value) end elseif key == 'SIP_TRYING_TIME' or key == 'SIP_RINGING_TIME' or key == 'SIP_INVITE_TIME' or key == 'SIP_INVITE_OK_TIME' or key == 'SIP_INVITE_FAILURE_TIME' or key == 'SIP_BYE_TIME' or key == 'SIP_BYE_OK_TIME' or key == 'SIP_CANCEL_TIME' or key == 'SIP_CANCEL_OK_TIME' then if (value ~= '0') then return (formatEpoch(value)) else return "0" end elseif key == 'RTP_IN_JITTER' or key == 'RTP_OUT_JITTER' then if (value ~= nil and value ~= '0') then return (value / 1000) else return 0 end elseif key == 'RTP_IN_MAX_DELTA' or key == 'RTP_OUT_MAX_DELTA' or key == 'RTP_MOS' or key == 'RTP_R_FACTOR' or key == 'RTP_IN_MOS' or key == 'RTP_OUT_MOS' or key == 'RTP_IN_R_FACTOR' or key == 'RTP_OUT_R_FACTOR' or key == 'RTP_IN_TRANSIT' or key == 'RTP_OUT_TRANSIT' then if (value ~= nil and value ~= '0') then return (value / 100) else return 0 end end -- Unformatted value if (type(value) == "boolean") then if (value) then value = i18n("yes") else value = i18n("no") end end return value end -- ####################### function formatTcpFlags(flags) if (flags == 0) then return ("") end rsp = "<A HREF=\"http://en.wikipedia.org/wiki/Transmission_Control_Protocol\">" if ((flags & 1) == 2) then rsp = rsp .. " SYN " end if ((flags & 16) == 16) then rsp = rsp .. " ACK " end if ((flags & 1) == 1) then rsp = rsp .. " FIN " end if ((flags & 4) == 4) then rsp = rsp .. " RST " end if ((flags & 8) == 8) then rsp = rsp .. " PUSH " end return (rsp .. "</A>") end -- ####################### local dns_types = { ['A'] = 1, ['NS'] = 2, ['MD'] = 3, ['MF'] = 4, ['CNAME'] = 5, ['SOA'] = 6, ['MB'] = 7, ['MG'] = 8, ['MR'] = 9, ['NULL'] = 10, ['WKS'] = 11, ['PTR'] = 12, ['HINFO'] = 13, ['MINFO'] = 14, ['MX'] = 15, ['TXT'] = 16, ['AAAA'] = 28, ['A6'] = 38, ['SPF'] = 99, ['AXFR'] = 252, ['MAILB'] = 253, ['MAILA'] = 254, ['ANY'] = 255 } -- ####################### function get_dns_type(dns_type_name) if dns_types[dns_type_name] then return dns_types[dns_type_name] else return 0 end end -- ####################### function get_dns_type_label(dns_type) dns_type = tonumber(dns_type) if dns_type then for k, v in pairs(dns_types) do if v == dns_type then return k end end end return string.format("%u", dns_type) end -- ####################### function extractSIPCaller(caller) local i local j -- find string between \" and \" i = string.find(caller, "\\\"") if (i ~= nil) then j = string.find(caller, "\\\"", i + 2) if (j ~= nil) then return string.sub(caller, i + 2, j - 1) end end -- find string between " and " i = string.find(caller, "\"") if (i ~= nil) then j = string.find(caller, "\"", i + 1) if (j ~= nil) then return string.sub(caller, i + 1, j - 1) end end -- find string between : and @ i = string.find(caller, ":") if (i ~= nil) then j = string.find(caller, "@", i + 1) if (j ~= nil) then return string.sub(caller, i + 1, j - 1) end end return caller end -- ####################### function map_failure_resp_code(fail_resp_code_string) if (fail_resp_code_string ~= nil) then if (fail_resp_code_string == "200") then return "OK" end if (fail_resp_code_string == "100") then return "TRYING" end if (fail_resp_code_string == "180") then return "RINGING" end if (tonumber(fail_resp_code_string) > 399) then return "FAILURE" end end return fail_resp_code_string end -- ####################### local function formatFlowHost(flow, cli_or_srv, historical_bounds, hyperlink_suffix) local hyperlink_params if historical_bounds then hyperlink_params = { page = "historical", epoch_begin = historical_bounds[1], epoch_end = historical_bounds[2], detail_view = "top_l7_contacts" } elseif type(hyperlink_suffix) == "table" then hyperlink_params = hyperlink_suffix end local host local tooltip = '' if (cli_or_srv) then host = interface.getHostMinInfo(flow[cli_or_srv .. ".ip"], flow[cli_or_srv .. ".vlan"]) else host = interface.getHostMinInfo(flow[cli_or_srv .. ".ip"], flow[cli_or_srv .. ".vlan"]) end local host_name if (host ~= nil) then host_name = host["name"] tooltip = host["ip"] if isEmptyString(host_name) then host_name = host["ip"] tooltip = '' end end if (host_name == nil) then host_name = "" end host_name = host_name .. format_utils.formatFullAddressCategory(host) local mac if (host == nil) then mac = nil else mac = host["mac"] end return hostinfo2detailshref(flow2hostinfo(flow, cli_or_srv), hyperlink_params, host_name, tooltip, true --[[ perform link existance checks --]] ), mac end function formatFlowPort(flow, cli_or_srv, port, historical_bounds) if not historical_bounds then return "<A HREF=\"" .. ntop.getHttpPrefix() .. "/lua/flows_stats.lua?port=" .. port .. "\">" .. port .. "</A>" end -- TODO port filter return hostinfo2detailshref(flow2hostinfo(flow, cli_or_srv), { page = "historical", epoch_begin = historical_bounds[1], epoch_end = historical_bounds[2], detail_view = "flows", port = port }, port, port, true --[[ check href existance --]] ) end function getFlowLabel(flow, show_macs, add_hyperlinks, historical_bounds, hyperlink_suffix, add_flag, add_hostnames, nohtml) if flow == nil then return "" end local cli_name = flowinfo2hostname(flow, "cli", nil, add_hostnames) local srv_name = flowinfo2hostname(flow, "srv", nil, add_hostnames) local cli_mac = flow["cli.mac"] local srv_mac = flow["srv.mac"] local cli_as = nil local srv_as = nil if ((not isIPv4(cli_name)) and (not isIPv6(cli_name))) then cli_name = shortenString(cli_name) end if ((not isIPv4(srv_name)) and (not isIPv6(srv_name))) then srv_name = shortenString(srv_name) end local cli_port local srv_port if flow["cli.port"] and (flow["cli.port"] > 0 or flow["proto.l4"] == "TCP" or flow["proto.l4"] == "UDP") then cli_port = flow["cli.port"] end if flow["srv.port"] and (flow["srv.port"] > 0 or flow["proto.l4"] == "TCP" or flow["proto.l4"] == "UDP") then srv_port = flow["srv.port"] end if add_hyperlinks then cli_name, cli_mac = formatFlowHost(flow, "cli", historical_bounds, hyperlink_suffix) srv_name, srv_mac = formatFlowHost(flow, "srv", historical_bounds, hyperlink_suffix) if cli_port then cli_port = formatFlowPort(flow, "cli", cli_port, historical_bounds) end if srv_port then srv_port = formatFlowPort(flow, "srv", srv_port, historical_bounds) end if ((flow.cli_as ~= nil) and (flow.cli_as ~= 0)) then cli_as = "<A HREF=\"" .. ntop.getHttpPrefix() .. "/lua/hosts_stats.lua?asn=" .. flow.cli_as .. "\">" .. shortenString(flow.cli_as_name or "", 14) .. "</A>" cli_mac = "" else if cli_mac and (cli_mac ~= "00:00:00:00:00:00") and not interface.isView() then cli_mac = "<A HREF=\"" .. ntop.getHttpPrefix() .. "/lua/hosts_stats.lua?mac=" .. cli_mac .. "\">" .. cli_mac .. "</A>" elseif interface.isView() and (cli_mac ~= "00:00:00:00:00:00") then cli_mac = cli_mac or "" else cli_mac = "" end end if ((flow.dst_as ~= nil) and (flow.dst_as ~= 0)) then dst_as = "<A HREF=\"" .. ntop.getHttpPrefix() .. "/lua/hosts_stats.lua?asn=" .. flow.dst_as .. "\">" .. shortenString(flow.dst_as_name or "", 14) .. "</A>" srv_mac = "" else if srv_mac and (srv_mac ~= "00:00:00:00:00:00") and not interface.isView() then srv_mac = "<A HREF=\"" .. ntop.getHttpPrefix() .. "/lua/hosts_stats.lua?mac=" .. srv_mac .. "\">" .. srv_mac .. "</A>" elseif interface.isView() and (srv_mac ~= "00:00:00:00:00:00") then srv_mac = srv_mac or "" else srv_mac = "" end end end local label = "" if not isEmptyString(cli_name) then label = label .. cli_name end if add_flag then local info = interface.getHostInfo(flow["cli.ip"], flow["cli.vlan"]) if (info ~= nil) then label = label .. getFlag(info["country"]) end end if cli_port then label = label .. ":" .. cli_port end if (cli_as ~= nil) then label = label .. " [ " .. cli_as .. " ]" else if show_macs and not isEmptyString(cli_mac) then label = label .. " [ " .. cli_mac .. " ]" end end if (not nohtml) then label = label .. " <i class=\"fas fa-exchange-alt fa-lg\" aria-hidden=\"true\"></i> " else label = label .. " -> " end if not isEmptyString(srv_name) then label = label .. srv_name end if add_flag then local info = interface.getHostInfo(flow["srv.ip"], flow["srv.vlan"]) if (info ~= nil) then label = label .. getFlag(info["country"]) end end if srv_port then label = label .. ":" .. srv_port end if (dst_as ~= nil) then label = label .. " [ " .. dst_as .. " ]" else if show_macs and not isEmptyString(srv_mac) then label = label .. " [ " .. srv_mac .. " ]" end end local s_info = flow2alertinfo(flow) if (s_info ~= nil) then if (not isEmptyString(s_info.info)) then label = label .. " [" .. s_info.info .. "]" end end return label end -- ####################### function getFlowKey(name) if tonumber(name) then name = getFlowLabelFromId(name) end local s = flow_consts.flow_fields_description[name] if (s == nil) then -- Try to decode the name as <PEN>.<FIELD> -- then try to look up the name or directly the field -- in the rtemplate (pen is ignored). -- TODO: currently rtemplate is flat and PENs are ignored, we should add PEN there local pen, field = name:match("^(%d+)%.(%d+)$") local v = (rtemplate[tonumber(name)] or rtemplate[tonumber(field)]) if (v == nil) then return (name) end s = flow_consts.flow_fields_description[v] end if (s ~= nil) then s = string.gsub(s, "<", "<") s = string.gsub(s, ">", ">") return (s) else return (name) end end -- ####################### function fieldIDToFieldName(id) local id_num local name local pen_id = string.split(id, "%.") if pen_id then id_num = tonumber(pen_id[2]) else id_num = tonumber(id) end if id_num then name = rtemplate[id_num] else name = id end return name end -- ####################### function isFieldProtocol(protocol, field) if not field or not protocol then return false end local key_name = fieldIDToFieldName(field) if not key_name then return false end if starts(key_name, protocol) then return true end return false end -- ####################### function removeProtocolFields(protocol, array) elements_to_remove = {} n = 0 for key, value in pairs(array) do if (isFieldProtocol(protocol, key)) then elements_to_remove[n] = key n = n + 1 end end for key, value in pairs(elements_to_remove) do if (value ~= nil) then array[value] = nil end end return array end -- ####################### function isFlowValueDefined(info, field) if (info[field] ~= nil) then return true else for key, value in pairs(info) do local key_name = fieldIDToFieldName(key) if (key_name == field) then return true end end end return false end -- ####################### function getFlowValue(info, field) local return_value = "0" local value_original = "0" if ((info == nil) or (table.len(info) == 0)) then return ("") end if (info[field] ~= nil) then return_value = handleCustomFlowField(field, info[field]) value_original = info[field] else for key, value in pairs(info) do local key_name = fieldIDToFieldName(key) if (key_name == field) then return_value = handleCustomFlowField(key_name, value) value_original = value end end end return_value = string.gsub(return_value, "<", "<") return_value = string.gsub(return_value, ">", ">") return_value = string.gsub(return_value, "\"", "\\\"") -- io.write(field.." = ["..return_value..","..value_original.."]\n") return return_value, value_original end -- ####################### function mapCallState(call_state) -- return call_state if (call_state == "CALL_STARTED") then return ("<span class=\"badge bg-secondary\">" .. (i18n("flow_details.call_started")) .. "</span>") elseif (call_state == "CALL_IN_PROGRESS") then return ("<span class=\"badge bg-info\"><i class=\"fas fa-phone\"></i> " .. i18n("flow_details.ongoing_call") .. "</span>") elseif (call_state == "CALL_COMPLETED") then return ("<span class=\"badge bg-success\">" .. i18n("flow_details.call_completed") .. "</span>") elseif (call_state == "CALL_ERROR") then return ("<span class=\"badge bg-danger\">" .. i18n("flow_details.call_error") .. "</span>") elseif (call_state == "CALL_CANCELED") then return ("<span class=\"badge bg-warning\">" .. i18n("flow_details.call_canceled") .. "</span>") else return ("<span class=\"badge bg-secondary\">" .. call_state .. "</span>") end end -- ####################### function isThereProtocol(protocol, info) local found = 0 for key, value in pairs(info) do if isFieldProtocol(protocol, key) then found = 1 break end end return found end -- ####################### function isThereSIPCall(info) local retVal = 0 local call_state = getFlowValue(info, "SIP_CALL_STATE") if ((call_state ~= nil) and (call_state ~= "")) then retVal = 1 end return retVal end -- ####################### function getSIPInfo(infoPar) local called_party = "" local calling_party = "" local sip_found_flow local returnString = "" local infoFlow, posFlow, errFlow = json.decode(infoPar["moreinfo.json"], 1, nil) if (infoFlow ~= nil) then sip_found_flow = isThereSIPCall(infoFlow) if (sip_found_flow == 1) then called_party = getFlowValue(infoFlow, "SIP_CALLED_PARTY") calling_party = getFlowValue(infoFlow, "SIP_CALLING_PARTY") called_party = string.gsub(called_party, "\\\"", "\"") calling_party = string.gsub(calling_party, "\\\"", "\"") called_party = extractSIPCaller(called_party) calling_party = extractSIPCaller(calling_party) if (((called_party == nil) or (called_party == "")) and ((calling_party == nil) or (calling_party == ""))) then returnString = "" else returnString = calling_party .. " <i class='fas fa-exchange-alt fa-sm' aria-hidden='true'></i> " .. called_party end end end return returnString end -- ####################### function getRTPInfo(infoPar) local call_id local returnString = "" local infoFlow, posFlow, errFlow = json.decode(infoPar["moreinfo.json"], 1, nil) if infoFlow ~= nil then call_id = getFlowValue(infoFlow, "RTP_SIP_CALL_ID") if tostring(call_id) ~= "" then call_id = "<i class='fas fa-phone fa-sm' aria-hidden='true' title='SIP Call-ID'></i> " .. call_id else call_id = "" end returnString = call_id end if (infoPar.rtp_stream_type ~= nil) then if (infoPar.rtp_stream_type == "screen_share") then returnString = '<i class="fas fa-desktop"></i> <span class="badge bg-secondary">' .. i18n("rtp.screen_share") ..'</span>' elseif (infoPar.rtp_stream_type == "audio") then returnString = '<i class="fas fa-volume-up"></i> <span class="badge bg-secondary">' .. i18n("rtp.audio") .. '</span>' elseif (infoPar.rtp_stream_type == "video") then returnString = '<i class="fas fa-video"></i> <span class="badge bg-secondary">' .. i18n("rtp.video") .. '</span>' elseif (infoPar.rtp_stream_type == "audio_video") then returnString = '<i class="fas fa-video"></i> <span class="badge bg-secondary">' .. i18n("rtp.audio_video") .. '</span>' end end return returnString end -- ####################### function getSIPTableRows(flow, info) local string_table = "" local call_id local call_id_ico = "<i class='fas fa-phone' aria-hidden='true'></i> " local called_party = "" local calling_party = "" local rtp_codecs = "" local sip_rtp_src_addr = 0 local sip_rtp_dst_addr = 0 local print_second = 0 local print_second_2 = 0 -- check if there is a SIP field local sip_found = isThereProtocol("SIP", info) if (sip_found == 1) then sip_found = isThereSIPCall(info) end if (sip_found == 1) then string_table = string_table .. "<tr><th colspan=3 >" .. i18n("flow_details.sip_protocol_information") .. "</th></tr>\n" call_id = flow["protos.sip_call_id"] or getFlowValue(info, "SIP_CALL_ID") if ((call_id == nil) or (call_id == "")) then string_table = string_table .. "<tr id=\"call_id_tr\" style=\"display: none;\"><th width=33%> " .. i18n("flow_details.call_id") .. " " .. call_id_ico .. "</th><td colspan=2><div id=call_id></div></td></tr>\n" else string_table = string_table .. "<tr id=\"call_id_tr\" style=\"display: table-row;\"><th width=33%> " .. i18n("flow_details.call_id") .. " " .. call_id_ico .. "</th><td colspan=2><div id=call_id>" .. call_id .. "</div></td></tr>\n" end if ((getFlowValue(info, "SIP_CALL_STATE") == nil) or (getFlowValue(info, "SIP_CALL_STATE") == "")) then string_table = string_table .. "<tr id=\"sip_call_state_tr\" style=\"display: none;\"><th width=33%> " .. i18n("flow_details.call_state") .. " </th><td colspan=2><div id=call_state></div></td></tr>\n" else string_table = string_table .. "<tr id=\"sip_call_state_tr\" style=\"display: table-row;\"><th width=33%> " .. i18n("flow_details.call_state") .. " </th><td colspan=2><div id=call_state>" .. mapCallState(getFlowValue(info, "SIP_CALL_STATE")) .. "</div></td></tr>\n" end called_party = getFlowValue(info, "SIP_CALLED_PARTY") calling_party = getFlowValue(info, "SIP_CALLING_PARTY") called_party = string.gsub(called_party, "\\\"", "\"") calling_party = string.gsub(calling_party, "\\\"", "\"") called_party = extractSIPCaller(called_party) calling_party = extractSIPCaller(calling_party) if (((called_party == nil) or (called_party == "")) and ((calling_party == nil) or (calling_party == ""))) then string_table = string_table .. "<tr id=\"called_calling_tr\" style=\"display: none;\"><th>" .. i18n("flow_details.call_initiator") .. " <i class=\"fas fa-exchange-alt fa-lg\"></i> " .. i18n("flow_details.called_party") .. "</th><td colspan=2><div id=calling_called_party></div></td></tr>\n" else string_table = string_table .. "<tr id=\"called_calling_tr\" style=\"display: table-row;\"><th>" .. i18n("flow_details.call_initiator") .. " <i class=\"fas fa-exchange-alt fa-lg\"></i> " .. i18n("flow_details.called_party") .. "</th><td colspan=2><div id=calling_called_party>" .. calling_party .. " <i class=\"fas fa-exchange-alt fa-lg\"></i> " .. called_party .. "</div></td></tr>\n" end rtp_codecs = getFlowValue(info, "SIP_RTP_CODECS") if ((rtp_codecs == nil) or (rtp_codecs == "")) then string_table = string_table .. "<tr id=\"rtp_codecs_tr\" style=\"display: none;\"><th width=33%>" .. i18n("flow_details.rtp_codecs") .. "</th><td colspan=2> <div id=rtp_codecs></></td></tr>\n" else string_table = string_table .. "<tr id=\"rtp_codecs_tr\" style=\"display: table-row;\"><th width=33%>" .. i18n("flow_details.rtp_codecs") .. "</th><td colspan=2> <div id=rtp_codecs>" .. rtp_codecs .. "</></td></tr>\n" end local string_table_1 = "" local string_table_2 = "" local string_table_3 = "" local string_table_4 = "" local string_table_5 = "" local show_rtp_stream = 0 local sip_rtp_ipv4_src_addr = getFlowValue(info, "SIP_RTP_IPV4_SRC_ADDR") if ((sip_rtp_ipv4_src_addr ~= nil) and (sip_rtp_ipv4_src_addr ~= "")) then sip_rtp_src_addr = 1 string_table_1 = sip_rtp_ipv4_src_addr if (string_table_1 ~= "0.0.0.0") then sip_rtp_src_address_ip = string_table_1 interface.select(ifname) rtp_host = interface.getHostInfo(string_table_1) if (rtp_host ~= nil) then string_table_1 = hostinfo2detailshref(rtp_host, nil, sip_rtp_src_address_ip) end show_rtp_stream = 1 end end if(show_rtp_stream == 1) then local sip_rtp_l4_src_port = getFlowValue(info, "SIP_RTP_L4_SRC_PORT") if ((sip_rtp_l4_src_port ~= nil) and (sip_rtp_l4_src_port ~= "0") and (sip_rtp_l4_src_port ~= "") and (sip_rtp_src_addr == 1)) then -- string_table = string_table ..":"..sip_rtp_l4_src_port -- string_table_2 = ":"..sip_rtp_l4_src_port sip_rtp_src_port = sip_rtp_l4_src_port string_table_2 = ":<A HREF=\"" .. ntop.getHttpPrefix() .. "/lua/flows_stats.lua?port=" .. sip_rtp_src_port .. "\">" string_table_2 = string_table_2 .. sip_rtp_src_port string_table_2 = string_table_2 .. "</A>" show_rtp_stream = 1 end local sip_rtp_ipv4_dst_addr = getFlowValue(info, "SIP_RTP_IPV4_DST_ADDR") if ((sip_rtp_src_addr == 1) or ((sip_rtp_ipv4_dst_addr ~= nil) and (sip_rtp_ipv4_dst_addr ~= ""))) then -- string_table = string_table.." <i class=\"fas fa-exchange-alt fa-lg\"></i> " string_table_3 = " <i class=\"fas fa-exchange-alt fa-lg\"></i> " show_rtp_stream = 1 end if ((sip_rtp_ipv4_dst_addr ~= nil) and (sip_rtp_ipv4_dst_addr ~= "")) then sip_rtp_dst_addr = 1 string_table_4 = sip_rtp_ipv4_dst_addr if (string_table_4 ~= "0.0.0.0") then sip_rtp_dst_address_ip = string_table_4 interface.select(ifname) rtp_host = interface.getHostInfo(string_table_4) if (rtp_host ~= nil) then string_table_4 = hostinfo2detailshref(rtp_host, nil, sip_rtp_dst_address_ip) end show_rtp_stream = 1 end end local sip_rtp_l4_dst_port = getFlowValue(info, "SIP_RTP_L4_DST_PORT") if ((sip_rtp_l4_dst_port ~= nil) and (sip_rtp_l4_dst_port ~= "") and (sip_rtp_l4_dst_port ~= "0") and (sip_rtp_dst_addr == 1)) then -- string_table = string_table ..":"..sip_rtp_l4_dst_port -- string_table_5 = ":"..sip_rtp_l4_dst_port sip_rtp_dst_port = sip_rtp_l4_dst_port string_table_5 = ":<A HREF=\"" .. ntop.getHttpPrefix() .. "/lua/flows_stats.lua?port=" .. sip_rtp_dst_port .. "\">" string_table_5 = string_table_5 .. sip_rtp_dst_port string_table_5 = string_table_5 .. "</A>" show_rtp_stream = 1 end end if (show_rtp_stream == 1) then string_table = string_table .. "<tr id=\"rtp_stream_tr\" style=\"display: table-row;\"><th width=33%>" .. i18n("flow_details.rtp_stream_peers") .. " (src <i class=\"fas fa-exchange-alt fa-lg\"></i> dst)</th><td colspan=2><div id=rtp_stream>" else string_table = string_table .. "<tr id=\"rtp_stream_tr\" style=\"display: none;\"><th width=33%>" .. i18n("flow_details.rtp_stream_peers") .. " (src <i class=\"fas fa-exchange-alt fa-lg\"></i> dst)</th><td colspan=2><div id=rtp_stream>" end string_table = string_table .. string_table_1 .. string_table_2 .. string_table_3 .. string_table_4 .. string_table_5 local rtp_flow_key = interface.getFlowKey(sip_rtp_src_address_ip or "", tonumber(sip_rtp_src_port) or 0, sip_rtp_dst_address_ip or "", tonumber(sip_rtp_dst_port) or 0, 17 --[[ UDP --]] ) -- TODO: fix if tonumber(rtp_flow_key) ~= nil and interface.findFlowByKeyAndHashId(tonumber(rtp_flow_key), 0) ~= nil then string_table = string_table .. ' ' string_table = string_table .. "<A class='btn btn-sm btn-info' HREF=\"" .. ntop.getHttpPrefix() .. "/lua/flow_details.lua?flow_key=" .. rtp_flow_key string_table = string_table .. "&label=" .. sip_rtp_src_address_ip .. ":" .. sip_rtp_src_port string_table = string_table .. " <-> " string_table = string_table .. sip_rtp_dst_address_ip .. ":" .. sip_rtp_dst_port .. "\">" string_table = string_table .. '<i class="fas fa-search-plus"></i></a>' end string_table = string_table .. "</div></td></tr>\n" val, val_original = getFlowValue(info, "SIP_REASON_CAUSE") if (tostring(val_original) ~= "0") then string_table = string_table .. "<tr id=\"cbf_reason_cause_tr\" style=\"display: table-row;\"><th width=33%> " .. i18n("flow_details.cancel_bye_failure_reason_cause") .. " </th><td colspan=2><div id=reason_cause>" string_table = string_table .. val else string_table = string_table .. "<tr id=\"cbf_reason_cause_tr\" style=\"display: none;\"><th width=33%> " .. i18n("flow_details.cancel_bye_failure_reason_cause") .. " </th><td colspan=2><div id=reason_cause>" end string_table = string_table .. "</div></td></tr>\n" if isFlowValueDefined(info, "SIP_C_IP") then string_table = string_table .. "<tr id=\"sip_c_ip_tr\" style=\"display: table-row;\"><th width=33%> " .. i18n("flow_details.c_ip_addresses") .. " </th><td colspan=2><div id=c_ip>" .. getFlowValue(info, "SIP_C_IP") .. "</div></td></tr>\n" end end return string_table end -- ####################### function getRTPTableRows(info) local string_table = "" -- check if there is a RTP field local rtp_found = isThereProtocol("RTP", info) if (rtp_found == 1) then -- SSRC string_table = string_table .. "<tr><th colspan=3 >" .. i18n("flow_details.rtp_protocol_information") .. "</th></tr>\n" if isFlowValueDefined(info, "RTP_SSRC") then sync_source_var = getFlowValue(info, "RTP_SSRC") if ((sync_source_var == nil) or (sync_source_var == "")) then sync_source_hide = "style=\"display: none;\"" else sync_source_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"sync_source_id_tr\" " .. sync_source_hide .. " ><th> " .. i18n("flow_details.sync_source_id") .. " </th><td colspan=2><div id=sync_source_id>" .. sync_source_var .. "</td></tr>\n" end -- ROUND-TRIP-TIME if isFlowValueDefined(info, "RTP_RTT") then local rtp_rtt_var = getFlowValue(info, "RTP_RTT") if ((rtp_rtt_var == nil) or (rtp_rtt_var == "")) then rtp_rtt_hide = "style=\"display: none;\"" else rtp_rtt_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"rtt_id_tr\" " .. rtp_rtt_hide .. "><th>" .. i18n("flow_details.round_trip_time") .. "</th><td colspan=2><span id=rtp_rtt>" if ((rtp_rtt_var ~= nil) and (rtp_rtt_var ~= "")) then string_table = string_table .. rtp_rtt_var .. " ms " end string_table = string_table .. "</span> <span id=rtp_rtt_trend></span></td></tr>\n" end -- RTP-IN-TRASIT if isFlowValueDefined(info, "RTP_IN_TRANSIT") then local rtp_in_transit = getFlowValue(info, "RTP_IN_TRANSIT") / 100 local rtp_out_transit = getFlowValue(info, "RTP_OUT_TRANSIT") / 100 if (((rtp_in_transit == nil) or (rtp_in_transit == "")) and ((rtp_out_transit == nil) or (rtp_out_transit == ""))) then rtp_transit_hide = "style=\"display: none;\"" else rtp_transit_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"rtp_transit_id_tr\" " .. rtp_transit_hide .. "><th>" .. i18n("flow_details.rtp_transit_in_out") .. "</th><td><div id=rtp_transit_in>" .. getFlowValue(info, "RTP_IN_TRANSIT") .. "</div></td><td><div id=rtp_transit_out>" .. getFlowValue(info, "RTP_OUT_TRANSIT") .. "</div></td></tr>\n" end -- TONES if isFlowValueDefined(info, "RTP_DTMF_TONES") then local rtp_dtmf_var = getFlowValue(info, "RTP_DTMF_TONES") if ((rtp_dtmf_var == nil) or (rtp_dtmf_var == "")) then rtp_dtmf_hide = "style=\"display: none;\"" else rtp_dtmf_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"dtmf_id_tr\" " .. rtp_dtmf_hide .. "><th>" .. i18n("flow_details.dtmf_tones_sent") .. "</th><td colspan=2><span id=dtmf_tones>" .. rtp_dtmf_var .. "</span></td></tr>\n" end -- FIRST REQUEST if isFlowValueDefined(info, "RTP_FIRST_SEQ") then local first_flow_sequence_var = getFlowValue(info, "RTP_FIRST_SEQ") local last_flow_sequence_var = getFlowValue(info, "RTP_FIRST_SEQ") if (((first_flow_sequence_var == nil) or (first_flow_sequence_var == "")) and ((last_flow_sequence_var == nil) or (last_flow_sequence_var == ""))) then first_last_flow_sequence_hide = "style=\"display: none;\"" else first_last_flow_sequence_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"first_last_flow_sequence_id_tr\" " .. first_last_flow_sequence_hide .. "><th>" .. i18n("flow_details.first_last_flow_sequence") .. "</th><td><div id=first_flow_sequence>" .. first_flow_sequence_var .. "</div></td><td><div id=last_flow_sequence>" .. last_flow_sequence_var .. "</div></td></tr>\n" end -- CALL-ID if isFlowValueDefined(info, "RTP_SIP_CALL_ID") then local sip_call_id_var = getFlowValue(info, "RTP_SIP_CALL_ID") if ((sip_call_id_var == nil) or (sip_call_id_var == "")) then sip_call_id_hide = "style=\"display: none;\"" else sip_call_id_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"sip_call_id_tr\" " .. sip_call_id_hide .. "><th> " .. i18n("flow_details.sip_call_id") .. " <i class='fas fa-phone fa-sm' aria-hidden='true' title='SIP Call-ID'></i> </th><td colspan=2><div id=rtp_sip_call_id>" .. sip_call_id_var .. "</div></td></tr>\n" end -- TWO-WAY CALL-QUALITY INDICATORS string_table = string_table .. "<tr><th>" .. i18n("flow_details.call_quality_indicators") .. "</th><th>" .. i18n("flow_details.forward") .. "</th><th>" .. i18n("flow_details.reverse") .. "</th></tr>" -- JITTER if isFlowValueDefined(info, "RTP_IN_JITTER") then local rtp_in_jitter = getFlowValue(info, "RTP_IN_JITTER") / 100 local rtp_out_jitter = getFlowValue(info, "RTP_OUT_JITTER") / 100 if (((rtp_in_jitter == nil) or (rtp_in_jitter == "")) and ((rtp_out_jitter == nil) or (rtp_out_jitter == ""))) then rtp_out_jitter_hide = "style=\"display: none;\"" else rtp_out_jitter_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"jitter_id_tr\" " .. rtp_out_jitter_hide .. "><th>" .. i18n("flow_details.jitter") .. "</th><td><span id=jitter_in>" if ((rtp_in_jitter ~= nil) and (rtp_in_jitter ~= "")) then string_table = string_table .. rtp_in_jitter .. " ms " end string_table = string_table .. "</span> <span id=jitter_in_trend></span></td><td><span id=jitter_out>" if ((rtp_out_jitter ~= nil) and (rtp_out_jitter ~= "")) then string_table = string_table .. rtp_out_jitter .. " ms " end string_table = string_table .. "</span> <span id=jitter_out_trend></span></td></tr>\n" end -- PACKET LOSS if isFlowValueDefined(info, "RTP_IN_PKT_LOST") then local rtp_in_pkt_lost = getFlowValue(info, "RTP_IN_PKT_LOST") local rtp_out_pkt_lost = getFlowValue(info, "RTP_OUT_PKT_LOST") if (((rtp_in_pkt_lost == nil) or (rtp_in_pkt_lost == "")) and ((rtp_out_pkt_lost == nil) or (rtp_out_pkt_lost == ""))) then rtp_packet_loss_hide = "style=\"display: none;\"" else rtp_packet_loss_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"rtp_packet_loss_id_tr\" " .. rtp_packet_loss_hide .. "><th>" .. i18n("flow_details.lost_packets") .. "</th><td><span id=packet_lost_in>" if ((rtp_in_pkt_lost ~= nil) and (rtp_in_pkt_lost ~= "")) then string_table = string_table .. formatPackets(rtp_in_pkt_lost) end string_table = string_table .. "</span> <span id=packet_lost_in_trend></span></td><td><span id=packet_lost_out>" if ((rtp_out_pkt_lost ~= nil) and (rtp_out_pkt_lost ~= "")) then string_table = string_table .. formatPackets(rtp_out_pkt_lost) end string_table = string_table .. " </span> <span id=packet_lost_out_trend></span></td></tr>\n" end -- PACKET DROPS if isFlowValueDefined(info, "RTP_IN_PKT_DROP") then local rtp_in_pkt_drop = getFlowValue(info, "RTP_IN_PKT_DROP") local rtp_out_pkt_drop = getFlowValue(info, "RTP_OUT_PKT_DROP") if (((rtp_in_pkt_drop == nil) or (rtp_in_pkt_drop == "")) and ((rtp_out_pkt_drop == nil) or (rtp_out_pkt_drop == ""))) then rtp_pkt_drop_hide = "style=\"display: none;\"" else rtp_pkt_drop_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"packet_drop_id_tr\" " .. rtp_pkt_drop_hide .. "><th>" .. i18n("flow_details.dropped_packets") .. "</th><td><span id=packet_drop_in>" if ((rtp_in_pkt_drop ~= nil) and (rtp_in_pkt_drop ~= "")) then string_table = string_table .. formatPackets(rtp_in_pkt_drop) end string_table = string_table .. "</span> <span id=packet_drop_in_trend></span></td><td><span id=packet_drop_out>" if ((rtp_out_pkt_drop ~= nil) and (rtp_out_pkt_drop ~= "")) then string_table = string_table .. formatPackets(rtp_out_pkt_drop) end string_table = string_table .. " </span> <span id=packet_drop_out_trend></span></td></tr>\n" end -- MAXIMUM DELTA BETWEEN CONSECUTIVE PACKETS if isFlowValueDefined(info, "RTP_IN_MAX_DELTA") then local rtp_in_max_delta = getFlowValue(info, "RTP_IN_MAX_DELTA") local rtp_out_max_delta = getFlowValue(info, "RTP_OUT_MAX_DELTA") if (((rtp_in_max_delta == nil) or (rtp_in_max_delta == "")) and ((rtp_out_max_delta == nil) or (rtp_out_max_delta == ""))) then rtp_max_delta_hide = "style=\"display: none;\"" else rtp_max_delta_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"delta_time_id_tr\" " .. rtp_max_delta_hide .. "><th>" .. i18n("flow_details.max_packet_interarrival_time") .. "</th><td><span id=max_delta_time_in>" if ((rtp_in_max_delta ~= nil) and (rtp_in_max_delta ~= "")) then string_table = string_table .. rtp_in_max_delta .. " ms " end string_table = string_table .. "</span> <span id=max_delta_time_in_trend></span></td><td><span id=max_delta_time_out>" if ((rtp_out_max_delta ~= nil) and (rtp_out_max_delta ~= "")) then string_table = string_table .. rtp_out_max_delta .. " ms " end string_table = string_table .. "</span> <span id=max_delta_time_out_trend></span></td></tr>\n" end -- PAYLOAD TYPE if isFlowValueDefined(info, "RTP_IN_PAYLOAD_TYPE") then local rtp_payload_in_var = formatRtpPayloadType(getFlowValue(info, "RTP_IN_PAYLOAD_TYPE")) local rtp_payload_out_var = formatRtpPayloadType(getFlowValue(info, "RTP_OUT_PAYLOAD_TYPE")) if (((rtp_payload_in_var == nil) or (rtp_payload_in_var == "")) and ((rtp_payload_out_var == nil) or (rtp_payload_out_var == ""))) then rtp_payload_hide = "style=\"display: none;\"" else rtp_payload_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"payload_id_tr\" " .. rtp_payload_hide .. "><th>" .. i18n("flow_details.payload_type") .. "</th><td><div id=payload_type_in>" .. rtp_payload_in_var .. "</div></td><td><div id=payload_type_out>" .. rtp_payload_out_var .. "</div></td></tr>\n" end -- MOS if isFlowValueDefined(info, "RTP_IN_MOS") then local rtp_in_mos = getFlowValue(info, "RTP_IN_MOS") local rtp_out_mos = getFlowValue(info, "RTP_OUT_MOS") if (rtp_in_mos == nil or rtp_in_mos == "") and (rtp_out_mos == nil or rtp_out_mos == "") then quality_mos_hide = "style=\"display: none;\"" else quality_mos_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"quality_mos_id_tr\" " .. quality_mos_hide .. ">" .. "<th>" .. i18n("flow_details.pseudo_mos") .. "</th>" .. "<td><span id=mos_in_signal></span><span id=mos_in>" if ((rtp_in_mos ~= nil) and (rtp_in_mos ~= "")) then string_table = string_table .. MosPercentageBar(rtp_in_mos) end string_table = string_table .. "</span> <span id=mos_in_trend></span></td>" string_table = string_table .. "<td><span id=mos_out_signal></span><span id=mos_out>" if ((rtp_out_mos ~= nil) and (rtp_out_mos ~= "")) then string_table = string_table .. MosPercentageBar(rtp_out_mos) end string_table = string_table .. "</span> <span id=mos_out_trend></span>" .. "</td></tr>" end -- R_FACTOR if isFlowValueDefined(info, "RTP_IN_R_FACTOR") then local rtp_in_r_factor = getFlowValue(info, "RTP_IN_R_FACTOR") / 100 local rtp_out_r_factor = getFlowValue(info, "RTP_OUT_R_FACTOR") / 100 if (rtp_in_r_factor == nil or rtp_in_r_factor == "" or rtp_in_r_factor == "0") and (rtp_out_r_factor == nil or rtp_out_r_factor == "" or rtp_out_r_factor == "0") then quality_r_factor_hide = "style=\"display: none;\"" else quality_r_factor_hide = "style=\"display: table-row;\"" end string_table = string_table .. "<tr id=\"quality_r_factor_id_tr\" " .. quality_r_factor_hide .. "><th>" .. i18n("flow_details.r_factor") .. "</th><td><span id=r_factor_in_signal></span><span id=r_factor_in>" if ((rtp_in_r_factor ~= nil) and (rtp_in_r_factor ~= "")) then string_table = string_table .. RFactorPercentageBar(rtp_in_r_factor) end string_table = string_table .. "</span> <span id=r_factor_in_trend></span></td>" string_table = string_table .. "<td><span id=r_factor_out_signal></span><span id=r_factor_out>" if ((rtp_out_r_factor ~= nil) and (rtp_out_r_factor ~= "")) then string_table = string_table .. RFactorPercentageBar(rtp_out_r_factor) end string_table = string_table .. "</span> <span id=r_factor_out_trend></span></td></tr>" end end return string_table end -- ####################### function getFlowQuota(ifid, info, as_client) local pool_id, quota_source if as_client then pool_id = info["cli.pool_id"] quota_source = info["cli.quota_source"] else pool_id = info["srv.pool_id"] quota_source = info["srv.quota_source"] end local master_proto, app_proto = splitProtocol(info["proto.ndpi"]) app_proto = app_proto or master_proto local pools_stats = interface.getHostPoolsStats() local pool_stats = pools_stats and pools_stats[tonumber(pool_id)] local quota_and_protos = shaper_utils.getPoolProtoShapers(ifid, pool_id) if pool_stats ~= nil then local key = nil if quota_source == "policy_source_protocol" then proto_stats = pool_stats.ndpi -- determine if the quota is on the app or master proto if (quota_and_protos[master_proto] ~= nil) then key = master_proto else key = app_proto end elseif quota_source == "policy_source_category" then key = flow["proto.ndpi_cat"] proto_stats = nil category_stats = pool_stats.ndpi_categories elseif quota_source == "policy_source_pool" then key = "Default" proto_stats = nil category_stats = { default = pool_stats.cross_application } end if key ~= nil then local proto_info = nil if key ~= "Default" then proto_info = quota_and_protos[key] else proto_info = shaper_utils.getCrossApplicationShaper(ifid, pool_id) end if proto_info ~= nil then return proto_info, proto_stats, category_stats end end end return nil end -- ####################### function printFlowQuota(ifid, info, as_client) local flow_quota, proto_stats, category_stats = getFlowQuota(ifid, info, as_client) if flow_quota ~= nil then print("<table style='width:100%; table-layout: fixed;'><tr>") print(string.gsub(graph_utils.printProtocolQuota(flow_quota, proto_stats, category_stats, { traffic = true, time = true }, true), "\n", "")) print("</tr></table>") else print(i18n("shaping.no_quota_applied")) end end -- ####################### function printFlowSNMPInfo(snmpdevice, input_idx, output_idx) local inputidx_name = format_portidx_name(snmpdevice, tostring(input_idx)) local outputidx_name = format_portidx_name(snmpdevice, tostring(output_idx)) print( "<tr><th rowspan='2'>" .. i18n("details.flow_snmp_localization") .. "</th><th>" .. i18n("flows_page.inIfIdx") .. "</th><td><span class=\"badge bg-info\">" .. (inputidx_name or "") .. "</span></td></tr>") print("<tr><th>" .. i18n("flows_page.outIfIdx") .. "</th><td><span class=\"badge bg-info\">" .. (outputidx_name or "") .. "</span></td></tr>") end -- ####################### function printBlockFlowJs() print [[ var block_flow_csrf = "]] print(ntop.getRandomCSRFValue()) print [["; function block_flow(flow_key, flow_hash_id) { var url = "]] print(ntop.getHttpPrefix()) print [[/lua/pro/nedge/block_flow.lua"; $.ajax({ type: 'GET', url: url, cache: false, data: { csrf: block_flow_csrf, flow_key: flow_key, flow_hash_id: flow_hash_id, }, success: function(content) { var data = jQuery.parseJSON(content); var row_id = flow_key + "_" + flow_hash_id; if (data.status == "BLOCKED") { $('#'+row_id+'_block') .removeClass('bg-secondary') .addClass('bg-danger') .attr('title', ']] print(i18n("flow_details.flow_traffic_is_dropped")) print [['); } }, error: function(content) { console.log("error"); } }); } ]] end -- ####################### function printL4ProtoDropdown(base_url, page_params, l4_proto) local l4proto = _GET["l4proto"] local l4proto_filter if not isEmptyString(l4proto) then l4proto_filter = '<span class="fas fa-filter"></span>' else l4proto_filter = '' end -- table.clone needed to modify some parameters while keeping the original unchanged local l4proto_params = table.clone(page_params) l4proto_params["l4proto"] = nil -- Used to possibly remove tcp state filters when selecting a non-TCP l4 protocol local l4proto_params_non_tcp = table.clone(l4proto_params) if l4proto_params_non_tcp["tcp_flow_state"] then l4proto_params_non_tcp["tcp_flow_state"] = nil end print [[\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.l4_protocol")) print [[]] print(l4proto_filter) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu dropdown-menu-end scrollable-dropdown" role="menu" id="flow_dropdown">]] print('<li><a class="dropdown-item') print(l4proto == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, l4proto_params_non_tcp)) print [[">]] print(i18n("flows_page.all_l4_protocols")) print [[</a></li>]] if l4_proto then for key, value in pairsByKeys(l4_proto, asc) do local num_proto = tonumber(key) print [[<li]] print([[><a class="dropdown-item ]] .. (tonumber(l4proto) == key and 'active' or '') .. [[" href="]]) local l4_table = ternary(key ~= 6, l4proto_params_non_tcp, l4proto_params) l4_table["l4proto"] = key print(getPageUrl(base_url, l4_table)) print [[">]] print(l4_proto_to_string(key)) print [[ (]] print(string.format("%s", format_utils.formatValue(value.count))) print [[)</a></li>]] end end print [[</ul>]] end -- ####################### local function printFlowDevicesFilterDropdown(base_url, page_params) local snmp_cached_dev = require "snmp_cached_dev" local flowdevs = interface.getFlowDevices() local observationPointId = ntop.getUserObservationPointId() or 0 --[[ if observationPointId ~= 0 then local obs_info = interface.getObsPointsInfo()["ObsPoints"] local exporter_list = {} tprint(obs_info) for k, v in ipairs(obs_info) do if (v.obs_point == observationPointId) then exporter_list = v.exporter_list break end end flowdevs = exporter_list end ]] local devips = getProbesName(flowdevs, false, false) local ordering_fun = pairsByKeys local devips_order = ntop.getPref("ntopng.prefs.flow_table_probe_order") == "1" -- Order by Probe Name if devips_order then ordering_fun = pairsByValues end local cur_dev = _GET["deviceIP"] local cur_dev_filter = '' local snmp_community = '' if not isEmptyString(cur_dev) then cur_dev_filter = '<span class="fas fa-filter"></span>' end -- table.clone needed to modify some parameters while keeping the original unchanged local dev_params = table.clone(page_params) for _, p in pairs({"deviceIP", "outIfIdx", "inIfIdx"}) do dev_params[p] = nil end print [[, '<div class="btn-group float-right">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.device_ip")) print [[]] print(cur_dev_filter) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu dropdown-menu-end scrollable-dropdown" role="menu" id="flow_dropdown">\ ]] print('<li><a class="dropdown-item') print(cur_dev == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, dev_params)) print [[">]] print(i18n("flows_page.all_devices")) print [[</a></li>\]] for interface, device_list in pairs(devips) do for dev_ip, dev_resolved_name in pairsByValues(device_list, asc) do local dev_name = dev_ip local dev_name_full = dev_ip dev_params["deviceIP"] = dev_name dev_name = format_name_value(dev_resolved_name, dev_ip, true) dev_name_full = format_name_value(dev_resolved_name, dev_ip, false) print [[ <li>\ <a class="dropdown-item ]] print(dev_ip == cur_dev and 'active' or '') print [[" href="]] print(getPageUrl(base_url, dev_params)) print [[" title="]] print(dev_name_full) print [[">]] print(dev_name) print [[</a></li>\]] end end print [[ </ul>\ </div>']] if cur_dev ~= nil then -- also print dropddowns for input and output interface index local ports_table = interface.getFlowDeviceInfoByIP(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 = '<span class="fas fa-filter"></span>' end -- table.clone needed to modify some parameters while keeping the original unchanged local if_params = table.clone(page_params) if_params[direction] = nil print [[, '<div class="btn-group float-right">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page." .. direction)) print [[]] print(cur_if_filter) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">\ ]] print('<li><a class="dropdown-item') print(cur_if == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, if_params)) print [[">]] print(i18n("flows_page.all_" .. direction)) print [[</a></li>\]] for _, ports in pairs(ports_table) do for portidx, _ in pairsByKeys(ports, asc) do if_params[direction] = portidx local idx_name = format_portidx_name(cur_dev, portidx, true) local label = idx_name if portidx == tonumber(idx_name) then label = i18n("flows_page." .. direction) .. " " .. idx_name end print [[ <li>\ <a class="dropdown-item ]] print(cur_if == tostring(portidx) and 'active' or '') print [[" href="]] print(getPageUrl(base_url, if_params)) print [[">]] print(label) print [[</a></li>\]] end end print [[ </ul>\ </div>']] end end end -- ####################### local function printDropdownEntries(entries, base_url, param_arr, param_filter, curr_filter) for _, htype in ipairs(entries) do if type(htype) == "string" then -- plain html print(htype) goto continue end param_arr[param_filter] = htype[1] print [[<li]] print([[><a class="dropdown-item ]] .. (htype[1] == curr_filter and 'active' or '') .. [[" href="]]) print(getPageUrl(base_url, param_arr)) print [[">]] print(htype[2]) print [[</a></li>]] ::continue:: end end local function getParamFilter(page_params, param_name) if page_params[param_name] then return '<span class="fas fa-filter"></span>' end return '' end function printTabList(base_url, page_params, active) if (ntop.isEnterpriseM() or (ntop.isnEdge() and ntop.isnEdgeEnterprise())) then print [[ <div class="col-md-12 col-lg-12"> <div class="card"> <div class="card-body"> <div id="flows_page_type"> <div class="card-header mb-2"> <ul class="nav nav-tabs card-header-tabs" role="tablist"> <li><a id="live_flows" class="]] if active == "live_flows" then print [[active disabled ]] end print [[nav-item nav-link" href="]] print(getPageUrl(base_url, { host = page_params.host, flows_page_type = "live_flows" })) print [[">]] print(i18n("flows_page.live_flows")) print [[</a></li> <li><a id="aggregated" class=" ]] if active == "aggregated_flows" then print [[active disabled ]] end print [[nav-item nav-link" href="]] print(getPageUrl(base_url, { host = page_params.host, flows_page_type = "aggregated_flows" })) print [[" >]] print(i18n("flows_page.aggregated_live_flows")) print [[</a></li> </ul> </div> </div> </div> ]] end end function printFlowPagesDropdown(base_url, page_params) local flowhosts_type_params = table.clone(page_params) if (ntop.isEnterpriseM()) then print [[ <div class="btn-group"> <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.type")) print [[</button> <ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_pages_dropdown"> ]] printDropdownEntries({{"live_flows", i18n("flows_page.live_flows")}, {"aggregated_flows", i18n("flows_page.aggregated_live_flows")}}, base_url, flowhosts_type_params, "flows_page_type", page_params.flowhosts_type) print [[ </ul> </div> ]] else print [[ <div class="btn-group"> <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.type")) print [[</button> <ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_pages_dropdown"> ]] printDropdownEntries({{"live_flows", i18n("flows_page.live_flows")}}, base_url, flowhosts_type_params, "flows_page_type", page_params.flowhosts_type) print [[ </ul> </div> ]] end end function printActiveFlowsDropdown(base_url, page_params, ifstats, flowstats, is_ebpf_flows) -- Local / Remote hosts selector -- table.clone needed to modify some parameters while keeping the original unchanged local flowhosts_type_params = table.clone(page_params) flowhosts_type_params["flowhosts_type"] = nil print [['\ <div class="btn-group">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.hosts")) print(getParamFilter(page_params, "flowhosts_type")) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">\ ]] print('<li><a class="dropdown-item') print(page_params.flowhosts_type == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, flowhosts_type_params)) print [[">]] print(i18n("flows_page.all_hosts")) print [[</a></li>\]] printDropdownEntries({{"local_only", i18n("flows_page.local_only")}, {"remote_only", i18n("flows_page.remote_only")}, {"local_origin_remote_target", i18n("flows_page.local_cli_remote_srv")}, {"remote_origin_local_target", i18n("flows_page.local_srv_remote_cli")}}, base_url, flowhosts_type_params, "flowhosts_type", page_params.flowhosts_type) print [[\ </ul>\ </div>\ ']] local talking_with_params = table.clone(page_params) talking_with_params["talking_with"] = nil if talking_with_params["host"] then local talking_with_list = {} for host, num_flows in pairs(flowstats["talking_with"] or {}) do if talking_with_params["host"] ~= host then local hinfo = hostkey2hostinfo(host) talking_with_list[#talking_with_list + 1] = {host, hostinfo2label(hinfo) .. " (" .. format_utils.formatValue(num_flows) .. ")"} end end print [[, '\ <div class="btn-group">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.talking_with")) print(getParamFilter(page_params, "talking_with")) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu">\ ]] print('<li><a class="dropdown-item') print(page_params.talking_with == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, talking_with_params)) print [[">]] print(i18n("flows_page.all_hosts")) print [[</a></li>\]] printDropdownEntries(talking_with_list, base_url, talking_with_params, "talking_with", page_params.talking_with) -- Check if talking_with_list is empty to print \ else is going to didn't call get_flows_data and html crash if #talking_with_list > 0 then print [[\]] end print [[ </ul>\ </div>\ ']] end if (not (talking_with_params["server"] and talking_with_params["client"])) then if talking_with_params["client"] then local talking_with_list = {} for host, num_flows in pairs(flowstats["talking_with"] or {}) do if talking_with_params["client"] ~= host then local hinfo = hostkey2hostinfo(host) talking_with_list[#talking_with_list + 1] = {host, hostinfo2label(hinfo) .. " (" .. format_utils.formatValue(num_flows) .. ")"} end end print [[, '\ <div class="btn-group">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.talking_with")) print(getParamFilter(page_params, "talking_with")) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu">\ ]] print('<li><a class="dropdown-item') print(page_params.talking_with == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, talking_with_params)) print [[">]] print(i18n("flows_page.all_hosts")) print [[</a></li>\]] printDropdownEntries(talking_with_list, base_url, talking_with_params, "talking_with", page_params.talking_with) -- Check if talking_with_list is empty to print \ else is going to didn't call get_flows_data and html crash if #talking_with_list > 0 then print [[\]] end print [[ </ul>\ </div>\ ']] end if talking_with_params["server"] then local talking_with_list = {} for host, num_flows in pairs(flowstats["talking_with"] or {}) do if talking_with_params["server"] ~= host then local hinfo = hostkey2hostinfo(host) talking_with_list[#talking_with_list + 1] = {host, hostinfo2label(hinfo) .. " (" .. format_utils.formatValue(num_flows) .. ")"} end end print [[, '\ <div class="btn-group">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.talking_with")) print(getParamFilter(page_params, "talking_with")) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu">\ ]] print('<li><a class="dropdown-item') print(page_params.talking_with == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, talking_with_params)) print [[">]] print(i18n("flows_page.all_hosts")) print [[</a></li>\]] printDropdownEntries(talking_with_list, base_url, talking_with_params, "talking_with", page_params.talking_with) -- Check if talking_with_list is empty to print \ else is going to didn't call get_flows_data and html crash if #talking_with_list > 0 then print [[\]] end print [[ </ul>\ </div>\ ']] end end -- Status selector -- table.clone needed to modify some parameters while keeping the original unchanged local alert_type_params = table.clone(page_params) alert_type_params["alert_type"] = nil print [[, '\ <div class="btn-group">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("status")) print(getParamFilter(page_params, "alert_type")) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu">\ ]] print('<li><a class="dropdown-item') print(page_params.alert_type == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, alert_type_params)) print [[">]] print(i18n("flows_page.all_flows")) print [[</a></li>\]] local entries = {{"normal", i18n("flows_page.normal")}, {"alerted", i18n("flows_page.all_alerted")}, {"periodic", i18n("flows_page.all_periodic")}} local status_stats = flowstats["status"] local first = true -- Add labels to allow alphabetic sort for status_key, status in pairs(status_stats) do if status.count > 0 then status.label = alert_consts.alertTypeLabel(status_key, true --[[ no html --]] ) end end for status_key, status in pairsByField(status_stats, "label", asc) do if status.count > 0 then if first then entries[#entries + 1] = '<li class="dropdown-header" id="alerted-flows-title" style="padding-left: 8px; padding-top: 4px; padding-bottom: 4px">' .. i18n("flow_details.alerted_flows") .. '</li>' first = false end entries[#entries + 1] = {string.format("%u", status_key), (status.label) .. " (" .. format_utils.formatValue(status.count) .. ")"} end end if isBridgeInterface(ifstats) then entries[#entries + 1] = {"filtered", i18n("flows_page.blocked")} end printDropdownEntries(entries, base_url, alert_type_params, "alert_type", page_params.alert_type) print [[\ </ul>\ </div>\ ']] -- Flow Status Severity local alert_type_severity_params = table.clone(page_params) alert_type_severity_params["alert_type_severity"] = nil print [[, '\ <div class="btn-group">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.alert_type_severity")) print(getParamFilter(page_params, "alert_type_severity")) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu">\ ]] print('<li><a class="dropdown-item') print(page_params.alert_type_severity == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, alert_type_severity_params)) print [[">]] print(i18n("flows_page.all_flows")) print [[</a></li>]] local entries entries = {} local severity_stats = flowstats["alert_levels"] for s, severity_details in pairsByField(alert_consts.severity_groups, "severity_group_id", asc) do if severity_stats[s] and severity_stats[s] > 0 then entries[#entries + 1] = {s, (i18n(severity_details.i18n_title) or s) .. " (" .. format_utils.formatValue(severity_stats[s]) .. ")"} end end printDropdownEntries(entries, base_url, alert_type_severity_params, "alert_type_severity", page_params.alert_type_severity) print [[\ </ul>\ </div>\ ']] if not is_ebpf_flows then if page_params["l4proto"] and page_params["l4proto"] == "6" then -- TCP flow state filter -- table.clone needed to modify some parameters while keeping the original unchanged local tcp_state_params = table.clone(page_params) tcp_state_params["tcp_flow_state"] = nil print [[, '\ <div class="btn-group">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.tcp_state")) print(getParamFilter(page_params, "tcp_flow_state")) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu">\ ]] print('<li><a class="dropdown-item') print(page_params.tcp_flow_state == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, tcp_state_params)) print [[">]] print(i18n("flows_page.all_flows")) print [[</a></li>\]] local entries = {} for _, entry in pairs({"established", "connecting", "closed", "reset"}) do entries[#entries + 1] = {entry, tcp_flow_state_utils.state2i18n(entry)} end printDropdownEntries(entries, base_url, tcp_state_params, "tcp_flow_state", page_params.tcp_flow_state) print [[\ </ul>\ </div>\ ']] end -- Unidirectional flows selector -- 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 [[, '\ <div class="btn-group">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("flows_page.direction")) print(getParamFilter(page_params, "traffic_type")) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu">\ ]] print('<li><a class="dropdown-item') print(page_params.traffic_type == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, traffic_type_params)) print [[">]] print(i18n("flows_page.all_flows")) print [[</a></li>\]] printDropdownEntries({{"unicast", i18n("flows_page.non_multicast")}, {"broadcast_multicast", i18n("flows_page.multicast")}, {"one_way_unicast", i18n("flows_page.one_way_non_multicast")}, {"one_way_broadcast_multicast", i18n("flows_page.one_way_multicast")}}, base_url, traffic_type_params, "traffic_type", page_params.traffic_type) print [[\ </ul>\ </div>\ ']] else -- is_ebpf_flows if not page_params.container then -- POD filter local pods = interface.getPodsStats() -- table.clone needed to modify some parameters while keeping the original unchanged local pods_params = table.clone(page_params) pods_params["pod"] = nil if not table.empty(pods) then print [[, '\ <div class="btn-group">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("containers_stats.pod")) print(getParamFilter(page_params, "pod")) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu">\ ]] local entries = {} for pod_id, pod in pairsByKeys(pods) do entries[#entries + 1] = {pod_id, shortenString(pod_id)} end print('<li><a class="dropdown-item') print(page_params.pod == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, pods_params)) print [[">]] print(i18n("containers_stats.all_pods")) print [[</a></li>\]] printDropdownEntries(entries, base_url, pods_params, "pod", page_params.pod) print [[\ </ul>\ </div>\ ']] end end if not page_params.pod then -- Container filter local containers = interface.getContainersStats() -- table.clone needed to modify some parameters while keeping the original unchanged local container_params = table.clone(page_params) container_params["container"] = nil if not table.empty(containers) then print [[, '\ <div class="btn-group">\ <button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">]] print(i18n("containers_stats.container")) print(getParamFilter(page_params, "container")) print [[<span class="caret"></span></button>\ <ul class="dropdown-menu scrollable-dropdown" role="menu">\ ]] local entries = {} for container_id, container in pairsByKeys(containers) do entries[#entries + 1] = {container_id, format_utils.formatContainer(container.info)} end print('<li><a class="dropdown-item') print(page_params.container == nil and ' active' or '') print [[" href="]] print(getPageUrl(base_url, container_params)) print [[">]] print(i18n("containers_stats.all_containers")) print [[</a></li>\]] printDropdownEntries(entries, base_url, container_params, "container", page_params.container) print [[\ </ul>\ </div>\ ']] end end end -- L7 Application print(', \'<div class="btn-group"><button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">' .. i18n("report.applications") .. ' ' .. getParamFilter(page_params, "application") .. '<span class="caret"></span></button> <ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">') print('<li><a class="dropdown-item') print(page_params.application == nil and ' active' or '') print('" href="') -- table.clone needed to modify some parameters while keeping the original unchanged local application_filter_params = table.clone(page_params) application_filter_params["application"] = nil print(getPageUrl(base_url, application_filter_params)) print('">' .. i18n("flows_page.all_proto") .. '</a></li>') if not isEmptyString(page_params["application"]) then -- An application has been explicitly selected from the dropdown -- so only that application is shown as dropdown a dropdown item. -- The application will also include all sub-applications, e.g., DNS -- will include DNS.Google, DNS.Facebook and so on. print('<li><a class="dropdown-item active href="') application_filter_params["application"] = page_params["application"] print(getPageUrl(base_url, application_filter_params)) local application_split = string.split(application_filter_params["application"], "%.") local application_name = application_filter_params["application"] if application_split and #application_split == 2 then application_name = string.format("%s.%s", interface.getnDPIProtoName(tonumber(application_split[1])), interface.getnDPIProtoName(tonumber(application_split[2]))) else local _application = tonumber(application_filter_params["application"]) if (_application) then application_name = interface.getnDPIProtoName(_application) end end print('">' .. application_name .. '</a></li>') else -- No application selected in the dropdown. Show all the available applications -- as reported in flowstats for key, value in pairsByKeys(flowstats["ndpi"], asc) do local class_active = '' if (key == page_params.application) then class_active = 'active' end print('<li><a class="dropdown-item ' .. class_active .. '" href="') application_filter_params["application"] = key print(getPageUrl(base_url, application_filter_params)) print('">' .. key .. '</a></li>') end end print("</ul> </div>'") -- L7 Application Category print(', \'<div class="btn-group"><button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">' .. i18n("users.categories") .. ' ' .. getParamFilter(page_params, "category") .. '<span class="caret"></span></button> <ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">') print('<li><a class="dropdown-item') print(page_params.category == nil and ' active' or '') print('" href="') -- table.clone needed to modify some parameters while keeping the original unchanged local category_filter_params = table.clone(page_params) category_filter_params["category"] = nil print(getPageUrl(base_url, category_filter_params)) print('">' .. i18n("flows_page.all_categories") .. '</a></li>') local ndpicatstats = ifstats["ndpi_categories"] for key, value in pairsByKeys(ndpicatstats, asc) do local class_active = '' if (key == page_params.category) then class_active = 'active' end print('<li><a class="dropdown-item ' .. class_active .. '" href="') category_filter_params["category"] = key print(getPageUrl(base_url, category_filter_params)) print('">' .. getCategoryLabel(key, value.category) .. '</a></li>') end print("</ul> </div>'") -- DSCP selector -- table.clone needed to modify some parameters while keeping the original unchanged local dscp_params = table.clone(page_params) dscp_params["dscp"] = nil print [[, '<div class="btn-group float-right">]] printDSCPDropdown(base_url, dscp_params, flowstats["dscps"] or {}, format_utils) print [[</div>']] -- Host Pool selector -- 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"] = nil print [[, '<div class="btn-group float-right">]] printHostPoolDropdown(base_url, host_pool_params, flowstats["host_pool_id"] or {}, format_utils) print [[</div>']] -- Host Pool selector -- table.clone needed to modify some parameters while keeping the original unchanged local local_network_params = table.clone(page_params) local_network_params["network"] = nil print [[, '<div class="btn-group float-right">]] printLocalNetworksDropdown(base_url, local_network_params) print [[</div>']] -- IP version selector -- table.clone needed to modify some parameters while keeping the original unchanged local ipversion_params = table.clone(page_params) ipversion_params["version"] = nil print [[, '<div class="btn-group float-right">]] printIpVersionDropdown(base_url, ipversion_params) print [[</div>']] -- L4 protocol selector -- table.clone needed to modify some parameters while keeping the original unchanged local l4proto_params = table.clone(page_params) l4proto_params["l4proto"] = nil print [[, '<div class="btn-group float-right">]] printL4ProtoDropdown(base_url, l4proto_params, flowstats["l4_protocols"]) print [[</div>']] -- VLAN selector -- table.clone needed to modify some parameters while keeping the original unchanged local vlan_params = table.clone(page_params) if ifstats.vlan then print [[, '<div class="btn-group float-right">]] printVLANFilterDropdown(base_url, vlan_params) print [[</div>']] end if ntop.isPro() then local hashname = "ntopng.prefs.profiles" local profiles = ntop.getHashKeysCache(hashname) or {} local profiles_defined = false for k, _ in pairsByKeys(profiles) do profiles_defined = true break end if profiles_defined then -- Traffic Profiles print( ', \'<div class="btn-group"><button class="btn btn-link dropdown-toggle" data-bs-toggle="dropdown">' .. i18n("traffic_profiles.traffic_profiles") .. ' ' .. getParamFilter(page_params, "traffic_profile") .. '<span class="caret"></span></button> <ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">') print('<li><a class="dropdown-item') print(page_params.traffic_profile == nil and ' active' or '') print('" href="') -- table.clone needed to modify some parameters while keeping the original unchanged local traffic_profile_filter_params = table.clone(page_params) traffic_profile_filter_params["traffic_profile"] = nil print(getPageUrl(base_url, traffic_profile_filter_params)) print('">' .. i18n("traffic_profiles.all_profiles") .. '</a></li>') for key, _ in pairsByKeys(profiles) do local class_active = '' if (key == _GET["traffic_profile"]) then class_active = 'active' end print('<li><a class="dropdown-item ' .. class_active .. '" href="') traffic_profile_filter_params["traffic_profile"] = key print(getPageUrl(base_url, traffic_profile_filter_params)) print('">' .. key .. '</a></li>') end print("</ul> </div>'") end end if ntop.isPro() and interface.isPacketInterface() == false then printFlowDevicesFilterDropdown(base_url, vlan_params) end end -- ####################### function getFlowsTableTitle(base_url) local active_msg = "" local status_type if _GET["alert_type"] then local alert_type_id = tonumber(_GET["alert_type"]) if (alert_type_id ~= nil) then status_type = alert_consts.alertTypeLabel(tonumber(_GET["alert_type"]), true) else status_type = firstToUpper(_GET["alert_type"]) end end if _GET["alert_type_severity"] then local alert_type_severity = _GET["alert_type_severity"] local s = alert_consts.severity_groups[alert_type_severity] active_msg = active_msg .. " " .. i18n(s.i18n_title) end if _GET["application"] then local application = _GET["application"] local application_split = string.split(application, "%.") if application_split and #application_split == 2 then application = string.format("%s.%s", interface.getnDPIProtoName(tonumber(application_split[1])), interface.getnDPIProtoName(tonumber(application_split[2]))) else local _application = tonumber(application) if (_application) then application = interface.getnDPIProtoName(_application) end end active_msg = active_msg .. " " .. application end if _GET["category"] then active_msg = active_msg .. " " .. _GET["category"] end if _GET["vhost"] then active_msg = active_msg .. " " .. _GET["vhost"] end if status_type then active_msg = active_msg .. " " .. status_type end if (_GET["network_name"] ~= nil) then active_msg = active_msg .. i18n("network", { network = _GET["network_name"] }) end if (_GET["host"] ~= nil) then local host_info = interface.getHostInfo(_GET["host"]) local host_name = "" if (host_info) then host_name = host_info.names.resolved end if isEmptyString(base_url) then base_url = ntop.getHttpPrefix() end active_msg = active_msg .. i18n("flows_page.host", { host = _GET["host"], host_name = ternary(isEmptyString(host_name), _GET["host"], host_name), base_url = base_url }) end if (_GET["client"] ~= nil) then local client_info = interface.getHostInfo(_GET["client"]) local client_name = "" if (client_info) then client_name = client_info.names.resolved end if (_GET["server"] ~= nil) then local server_info = interface.getHostInfo(_GET["server"]) local server_name = "" if (server_info) then server_name = server_info.names.resolved end active_msg = active_msg .. i18n("flows_page.client_to_server", { client = _GET["client"], client_name = ternary(isEmptyString(client_name), _GET["client"], client_name), server = _GET["server"], server_name = ternary(isEmptyString(server_name), _GET["server"], server_name), base_url = base_url }) else active_msg = active_msg .. i18n("flows_page.client", { client = _GET["client"], client_name = ternary(isEmptyString(client_name), _GET["client"], client_name), base_url = base_url }) end else if (_GET["server"] ~= nil) then local server_info = interface.getHostInfo(_GET["server"]) local server_name = "" if (server_info) then server_name = server_info.names.resolved end active_msg = active_msg .. i18n("flows_page.server", { server = _GET["server"], base_url = base_url, server_name = ternary(isEmptyString(server_name), _GET["server"], server_name) }) end end if (_GET["flow_info"] ~= nil) then active_msg = active_msg .. i18n("flows_page.flow_info", { flow_info = _GET["flow_info"] }) end if (_GET["port"] ~= nil) then active_msg = active_msg .. i18n("flows_page.port", { port = _GET["port"], base_url = base_url }) end if (_GET["inIfIdx"] ~= nil) then active_msg = active_msg .. " [" .. i18n("flows_page.inIfIdx") .. " " .. _GET["inIfIdx"] .. "]" end if (_GET["outIfIdx"] ~= nil) then active_msg = active_msg .. " [" .. i18n("flows_page.outIfIdx") .. " " .. _GET["outIfIdx"] .. "]" end if (_GET["deviceIP"] ~= nil) then active_msg = active_msg .. " [" .. i18n("flows_page.device_ip") .. " " .. _GET["deviceIP"] .. "]" end if (_GET["container"] ~= nil) then active_msg = active_msg .. " [" .. i18n("containers_stats.container") .. " " .. format_utils.formatContainerFromId(_GET["container"]) .. "]" end if (_GET["pod"] ~= nil) then active_msg = active_msg .. " [" .. i18n("containers_stats.pod") .. " " .. shortenString(_GET["pod"]) .. "]" end if ((_GET["icmp_type"] ~= nil) and (_GET["icmp_cod"] ~= nil)) then local is_v4 = true if (_GET["version"] ~= nil) then is_v4 = (_GET["version"] == "4") end local icmp_utils = require "icmp_utils" local icmp_label = icmp_utils.get_icmp_label(ternary(is_v4, 4, 6), _GET["icmp_type"], _GET["icmp_cod"]) active_msg = active_msg .. " [" .. icmp_label .. "]" end if (_GET["tcp_flow_state"] ~= nil) then active_msg = active_msg .. " [" .. tcp_flow_state_utils.state2i18n(_GET["tcp_flow_state"]) .. "]" end if not interface.isPacketInterface() then active_msg = i18n("flows_page.recently_active_flows", { filter = active_msg }) elseif interface.isPcapDumpInterface() then active_msg = i18n("flows_page.flows", { filter = active_msg }) else active_msg = i18n("flows_page.active_flows", { filter = active_msg }) end return active_msg end -- ####################### -- A one line flow description -- This uses the information from flow.getInfo() function shortFlowLabel(flow) local info = "" if not isEmptyString(flow["info"]) then info = " [" .. flow["info"] .. "]" end return (string.format("[%s] %s %s:%d -> %s:%s%s", flow["proto.ndpi"], flow["proto.l4"], flow["cli.ip"], flow["cli.port"], flow["srv.ip"], flow["srv.port"], info)) end -- #######################