diff --git a/scripts/lua/modules/find_utils.lua b/scripts/lua/modules/find_utils.lua new file mode 100644 index 0000000000..f634a8e989 --- /dev/null +++ b/scripts/lua/modules/find_utils.lua @@ -0,0 +1,660 @@ +-- +-- (C) 2017-24 - ntop.org +-- +local json = require "dkjson" +local tag_utils = require "tag_utils" +local snmp_utils +local snmp_location + +if ntop.isPro() then + package.path = dirs.installdir .. "/scripts/lua/pro/modules/?.lua;" .. package.path + snmp_utils = require "snmp_utils" + snmp_location = require "snmp_location" +end + +local find_utils = {} + +-- Limits +local max_group_items = 5 +local max_total_items = 20 + +-- ----------------------------------------------- + +local function build_flow_alerts_url(key, value, ifid) + local url = ntop.getHttpPrefix() .. '/lua/alert_stats.lua?ifid=' .. ifid .. "&status=historical&page=flow" + + local host_info = hostkey2hostinfo(value) + if host_info['host'] then + url = url .. '&' .. tag_utils.build_request_filter(key, 'eq', host_info['host']) + end + if host_info['vlan'] and host_info['vlan'] ~= 0 then + url = url .. '&' .. tag_utils.build_request_filter('vlan_id', 'eq', host_info['vlan']) + end + + return url +end + +-- ----------------------------------------------- + +local function build_historical_flows_url(key, value, ifid) + local url = ntop.getHttpPrefix() .. '/lua/pro/db_search.lua?ifid=' .. ifid + + local host_info = hostkey2hostinfo(value) + if host_info['host'] then + url = url .. '&' .. tag_utils.build_request_filter(key, 'eq', host_info['host']) + end + if host_info['vlan'] and host_info['vlan'] ~= 0 then + url = url .. '&' .. tag_utils.build_request_filter('vlan_id', 'eq', host_info['vlan']) + end + + return url +end + +-- ----------------------------------------------- + +local function add_historical_flows_link(links, key --[[ ip, mac, name --]], value --[[ actual ip or mac or name (including vlan) --]], ifid) + -- Alerts + local flow_alerts_icon = 'exclamation-triangle' + local flow_alerts_url = build_flow_alerts_url(key, value, ifid) + links[#links + 1] = { + icon = flow_alerts_icon, + title = i18n('alerts_dashboard.alerts'), + url = flow_alerts_url, + } + + if hasClickHouseSupport() then + -- Historical flows + local historical_flows_icon = 'stream' + local historical_flows_url = build_historical_flows_url(key, value, ifid) + links[#links + 1] = { + icon = historical_flows_icon, + title = i18n('db_explorer.historical_data_explorer'), + url = historical_flows_url, + } + end +end + +-- ----------------------------------------------- + +local function add_as_info_link(links, asn, ifid) + -- AS Info + local as_info_icon = 'circle-info' + local as_info_url = ntop.getHttpPrefix() .. '/lua/as_stats.lua?ifid=' .. ifid .. '&asn=' .. asn + links[#links + 1] = { + icon = as_info_icon, + title = i18n('as_info'), + url = as_info_url, + } +end + +-- ----------------------------------------------- + +local function add_icon_link(links, icon, title, url) + -- table.insert(links, 1, link) + links[#links + 1] = { + icon = icon, + title = title, + url = url, + } +end + +-- ----------------------------------------------- + +local function add_asn_link(links) + add_icon_link(links, 'cloud', i18n('as_details.as')) +end + +-- ----------------------------------------------- + +local function add_network_link(links) + add_icon_link(links, 'network-wired', i18n('network')) +end + +-- ----------------------------------------------- + +local function add_device_link(links) + add_icon_link(links, 'plug', i18n('device')) +end + +-- ----------------------------------------------- + +local function add_host_link(links) + add_icon_link(links, 'desktop', i18n('host_details.host')) +end + +-- ----------------------------------------------- + +local function add_snmp_device_link(links, ip) + add_icon_link(links, 'server', i18n('snmp.snmp_device')) +end + +-- ----------------------------------------------- + +local function add_snmp_interface_link(links, ip, index) + add_icon_link(links, 'ethernet', i18n('snmp.snmp_interface')) +end + +-- ----------------------------------------------- + +--- Badges +local function add_badge(badges, label, icon, title) + badges[#badges + 1] = { + label = label, + icon = icon, + title = title, + } +end + +-- ----------------------------------------------- + +local function add_inactive_badge(badges) + add_badge(badges, nil, 'moon', i18n('inactive')) +end + +-- ----------------------------------------------- + +-- Look by network +local function find_network(query, tot_results) + local results = {} + + local network_stats = interface.getNetworksStats() + + for network, stats in pairs(network_stats) do + if #results >= max_group_items or (#results + tot_results) >= max_total_items then + break + end + + local name = getLocalNetworkLabel(network) + + if string.containsIgnoreCase(name, query) then + local network_id = stats.network_id + local links = {} + add_network_link(links) + + results[#results + 1] = { + name = name, + type = "network", + network = network_id, + links = links, + } + end + end + + return results +end + +-- ----------------------------------------------- + +-- Look by AS +local function find_as(query, tot_results, ifid) + local results = {} + + local as_info = interface.getASesInfo() or {} + + for _, as in pairs(as_info.ASes or {}) do + if #results >= max_group_items or (#results + tot_results) >= max_total_items then + break + end + + local asn = "AS" .. as.asn + local as_name = as.asname + local links = {} + local badges = {} + add_asn_link(links) + add_as_info_link(links, as.asn, ifid) + + if string.containsIgnoreCase(as_name, query) then + add_badge(badges, asn) + results[#results + 1] = { + name = as_name, + type="asn", + asn = as.asn, + links = links, + badges = badges, + } + elseif string.containsIgnoreCase(asn, query) then + results[#results + 1] = { + name = asn, + type = "asn", + asn = as.asn, + links = links, + badges = badges, + } + end + end + + return results +end + +-- ----------------------------------------------- + +-- Look by MAC - SNMP +local function find_snmp_mac(query, tot_results) + local results = {} + + -- Check also in the mac addresses of snmp devices + -- The query can be partial so we can't use functions to + -- test if it'a an IPv4, an IPv6, or a mac as they would yield + -- wrong results. We can just check for a dot in the string as if + -- there's a dot then we're sure it can't be a mac + + if ntop.isEnterpriseM() and snmp_location and not query:find("%.") then + local mac = string.upper(query) + local matches = snmp_location.find_mac_snmp_ports(mac, true) + + for _, snmp_port in ipairs(matches) do + if #results >= max_group_items or (#results + tot_results) >= max_total_items then + break + end + + local snmp_device_ip = snmp_port["snmp_device_ip"] + local matching_mac = snmp_port["mac"] + local snmp_port_idx = snmp_port["id"] + local snmp_port_name = snmp_port["name"] + + local title = i18n("snmp.snmp_interface_x", { interface = shortenString(snmp_utils.get_snmp_interface_label({index = snmp_port_idx, name = snmp_port_name}))}) + + title = title .. " · " .. snmp_utils.get_snmp_device_label(snmp_device_ip) + + local links = {} + add_snmp_interface_link(links, snmp_device_ip, snmp_port_idx) + + results[#results + 1] = { + name = matching_mac .. ' '..title, + type = "snmp", + ip = snmp_device_ip, + snmp = snmp_device_ip, + snmp_port_idx = snmp_port_idx, + links = links, + } + end + end + + return results +end + +-- ----------------------------------------------- + +-- Look by interface name - SNMP +local function find_snmp_interface(query, tot_results) + local results = {} + + if ntop.isEnterpriseM() then + local name = string.upper(query) + local matches = snmp_utils.find_snmp_ports_by_name(name, true) + + for _, snmp_port in ipairs(matches) do + if #results >= max_group_items or (#results + tot_results) >= max_total_items then + break + end + + local snmp_device_ip = snmp_port["snmp_device_ip"] + local snmp_port_idx = snmp_port["id"] + local snmp_port_name = snmp_port["name"] + local snmp_port_index_match = snmp_port["index_match"] + + local title = i18n("snmp.snmp_interface_x", { interface = shortenString(snmp_utils.get_snmp_interface_label({index = snmp_port_idx, name = snmp_port_name}))}) + + title = title .. " · " .. snmp_utils.get_snmp_device_label(snmp_device_ip) + + local links = {} + add_snmp_interface_link(links, snmp_device_ip, snmp_port_idx) + + results[#results + 1] = { + name = title, + type = "snmp", + ip = snmp_device_ip, + snmp = snmp_device_ip, + snmp_port_idx = snmp_port_idx, + links = links, + } + end + end + + return results +end + +-- ----------------------------------------------- + +-- Look by SNMP device +local function find_snmp_device(query, tot_results) + local results = {} + + if ntop.isEnterpriseM() then + local name = string.upper(query) + local matches = snmp_utils.find_snmp_devices(name, true) + + for _, snmp_device in ipairs(matches) do + if #results >= max_group_items or (#results + tot_results) >= max_total_items then + break + end + + local title = snmp_utils.get_snmp_device_label(snmp_device["ip"]) + + local links = {} + add_snmp_device_link(links, snmp_device["ip"]) + + results[#results + 1] = { + name = title, + type = "snmp_device", + ip = snmp_device["ip"], + snmp_device = snmp_device["ip"], + links = links, + } + end + end + + return results +end + +-- ----------------------------------------------- + +-- Hosts +local function find_host(query, tot_results, ifid) + local results = {} + + local query_info = hostkey2hostinfo(query) + local is_full_ip = isIPv4(query_info['host']) or isIPv6(query_info['host']) + + local already_printed = {} + + -- Report if a perfect host match was found (full ip) + local exact_ip_match = false + local partial_ip_match = false + + local hosts = {} + + -- 1st look at custom names (aliases) + + -- Note: inefficient, so a limit on the maximum number must be enforced. + local name_prefix = getHostAltNamesKey("") + local ip_to_name = {} + local max_num_names = 500 -- Limit the number of keys in the lookup + local name_keys = ntop.getKeysCache(getHostAltNamesKey("*")) or {} + for k, _ in pairs(name_keys) do + local name = ntop.getCache(k) + + if isEmptyString(name) then + -- key cleanup (unused) + ntop.delCache(k) + else + local ip = k:gsub(name_prefix, "") + ip_to_name[ip] = name + + max_num_names = max_num_names - 1 + if max_num_names == 0 then + break + end + end + end + + for ip, name in pairs(ip_to_name) do + if not hosts[ip] then + if string.containsIgnoreCase(name, query) then + local links = {} + + if name == ip then -- IP + add_host_link(links) + add_historical_flows_link(links, 'ip', ip, ifid) + else -- Name + add_host_link(links) + add_historical_flows_link(links, 'name', name, ifid) + end + + hosts[ip] = { + label = hostinfo2label({host = ip, name = name}), + ip = ip, + name = name, + links = links, + } + end + end + end + + -- Active Hosts + local res = interface.findHost(query) + + for k, host_key in pairs(res) do + if not hosts[k] then + local badges = {} + local links = {} + + local ip = nil + local mac = nil + + local host_info = hostkey2hostinfo(host_key) + local label = hostinfo2label(host_info, true) + if host_key ~= k then + label = label .. " · " .. k + end + + if isMacAddress(host_key) then -- MAC + mac = host_key + add_device_link(links) + add_historical_flows_link(links, 'mac', host_key, ifid) + elseif isIPv6(host_key) then -- IP + ip = host_key + add_host_link(links) + add_historical_flows_link(links, 'ip', host_key, ifid) + add_badge(badges, 'IPv6') + elseif k == host_key then -- IP + ip = host_key + add_host_link(links) + add_historical_flows_link(links, 'ip', host_key, ifid) + else -- Name + ip = k + add_host_link(links) + add_historical_flows_link(links, 'name', host_key, ifid) + end + + partial_ip_match = true + exact_ip_match = is_full_ip and host_info['host'] == query_info['host'] + + hosts[k] = { + label = label, + name = host_key, + ip = ip, + mac = mac, + links = links, + badges = badges, + } + end + end + + -- Inactive hosts - by MAC + local key_to_ip_offset = string.len(string.format("ntopng.ip_to_mac.ifid_%u__", ifid)) + 1 + + for k in pairs(ntop.getKeysCache(string.format("ntopng.ip_to_mac.ifid_%u__%s*", ifid, query)) or {}) do + -- Serialization by MAC address found + local h = hostkey2hostinfo(string.sub(k, key_to_ip_offset)) + + if not hosts[h.host] then + -- Do not override active hosts + local links = {} + add_host_link(links) + add_historical_flows_link(links, 'ip', h.host, ifid) + + local badges = {} + if isIPv6(h.host) then -- IP + add_badge(badges, 'IPv6') + end + add_inactive_badge(badges) + + hosts[h.host] = { + label = hostinfo2label({host=h.host, vlan=h.vlan}, true), + ip = h.host, + name = h.host, + links = links, + badges = badges, + } + end + end + + -- Inactive hosts - by IP + --[[ Note: host serialization is disabled + local key_to_ip_offset = string.len(string.format("ntopng.serialized_hosts.ifid_%u__", ifid)) + 1 + + for k in pairs(ntop.getKeysCache(string.format("ntopng.serialized_hosts.ifid_%u__%s*", ifid, query)) or {}) do + local host_key = string.sub(k, key_to_ip_offset) + + if not hosts[host_key] then + local h = hostkey2hostinfo(host_key) + + -- Do not override active hosts / hosts by MAC + local links = {} + add_host_link(links) + add_historical_flows_link(links, 'ip', host_key, ifid) + + local badges = {} + if isIPv6(h.host) then -- IP + add_badge(badges, 'IPv6') + end + add_inactive_badge(badges) + + hosts[host_key] = { + label = hostinfo2hostkey({host=h.host, vlan=h.vlan}), + name = host_key, + ip = host_key, + links = links, + badges = badges, + } + end + end + --]] + + + -- Also look at the DHCP cache + local key_prefix_offset = string.len(getDhcpNameKey(getInterfaceId(ifname), "")) + 1 + local mac_to_name = ntop.getKeysCache(getDhcpNameKey(getInterfaceId(ifname), "*")) or {} + for k in pairs(mac_to_name) do + + local mac = string.sub(k, key_prefix_offset) + if hosts[mac] then + + local name = ntop.getCache(k) + if not isEmptyString(name) and string.containsIgnoreCase(name, query) then + + local links = {} + add_device_link(links) + add_historical_flows_link(links, 'mac', mac, ifid) + + hosts[mac] = { + label = hostinfo2label({host = mac, mac = mac, name = name}) .. " · " .. mac, + mac = mac, + name = name, + links = links, + } + end + end + end + + -- Build final array with results + + local function build_result(label, value, value_type, links, badges, context) + local r = { + name = label, + type = value_type, + links = links, + badges = badges, + context = context, + } + + r[value_type] = value + + return r + end + + if is_full_ip and partial_ip_match and not exact_ip_match then + what = "ip" + label = i18n("db_search.no_exact_match", {what=what, query=query}) + query = query .. tag_utils.SEPARATOR .. "eq" + results[#results + 1] = build_result(label, query, what, nil, nil, "historical") + end + + for k, v in pairsByField(hosts, 'name', asc) do + if #results >= max_group_items or (#results + tot_results) >= max_total_items then + break + end + + if((v.label ~= "") and (already_printed[v.label] == nil)) then + already_printed[v] = true + + if v.mac then + results[#results + 1] = build_result(v.label, v.mac, "mac", v.links, v.badges) + elseif v.ip then + + -- Add badge for services + local info = interface.getHostMinInfo(v.ip) + if info and info.services then + if not v.badges then v.badges = {} end + for s,_ in pairs(info.services) do + add_badge(v.badges, s:upper()) + end + end + + results[#results + 1] = build_result(v.label, v.ip, "ip", v.links, v.badges) + end + end -- if + end + + if #results == 0 and not isEmptyString(query) then + -- No results - add shortcut to search in historical data + if hasClickHouseSupport() then + local label = "" + local what = "" + if isHostKey(query) then + what = "ip" + label = i18n("db_search.find_in_historical", {what=what, query=query}) + query = query .. tag_utils.SEPARATOR .. "eq" + elseif isMacAddress(query) then + what = "mac" + label = i18n("db_search.find_in_historical", {what=what, query=query}) + query = query .. tag_utils.SEPARATOR .. "eq" + elseif isCommunityId(query) then + what = "community_id" + label = i18n("db_search.find_in_historical", {what=what, query=query}) + query = query .. tag_utils.SEPARATOR .. "eq" + else + what = "hostname" + label = i18n("db_search.find_in_historical", {what=what, query=query}) + query = query .. tag_utils.SEPARATOR .. "in" + end + + results[#results + 1] = build_result(label, query, what, nil, nil, "historical") + end + end + + return results +end + +-- ----------------------------------------------- + +-- Look by network +function find_utils.find(query, hosts_only, ifid) + local results = {} + + local is_system_interface = false + if interface.getId() == tonumber(getSystemInterfaceId()) then + is_system_interface = true + end + + if not hosts_only then + if not is_system_interface then + results = table.merge(results, find_network(query, #results, ifid)) + results = table.merge(results, find_as(query, #results, ifid)) + end + results = table.merge(results, find_snmp_mac(query, #results, ifid)) + results = table.merge(results, find_snmp_interface(query, #results, ifid)) + results = table.merge(results, find_snmp_device(query, #results, ifid)) + end + + if not is_system_interface then + results = table.merge(results, find_host(query, #results, ifid)) + end + + return results +end + +-- ----------------------------------------------- + +return find_utils diff --git a/scripts/lua/rest/v2/get/host/find.lua b/scripts/lua/rest/v2/get/host/find.lua index 75229233ca..fd49a5cbfb 100644 --- a/scripts/lua/rest/v2/get/host/find.lua +++ b/scripts/lua/rest/v2/get/host/find.lua @@ -7,15 +7,7 @@ package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path require "lua_utils" local json = require "dkjson" -local tag_utils = require "tag_utils" -local snmp_utils -local snmp_location - -if ntop.isPro() then - package.path = dirs.installdir .. "/scripts/lua/pro/modules/?.lua;" .. package.path - snmp_utils = require "snmp_utils" - snmp_location = require "snmp_location" -end +local find_utils = require "find_utils" local rest_utils = require("rest_utils") @@ -28,15 +20,6 @@ local rest_utils = require("rest_utils") local rc = rest_utils.consts.success.ok --- Limits -local max_group_items = 5 -local max_total_items = 20 - -local res_count -local already_printed = {} - -local results = {} - if not isEmptyString(_GET["ifid"]) then interface.select(_GET["ifid"]) else @@ -46,15 +29,11 @@ end local query = _GET["query"] local hosts_only = _GET["hosts_only"] -local is_system_interface = false -if interface.getId() == tonumber(getSystemInterfaceId()) then - is_system_interface = true -end +local ifid = interface.getId() if (isEmptyString(query)) then query = "" else - -- clean trailing spaces query = trimString(query) -- remove any decorator from string end @@ -63,7 +42,6 @@ else -- example: 'Consglio nazionale della Sicurezza' doesn't contain -- the substring 'Consiglio Nazionale dei Ministri [xxxx]' query = query:gsub("% %[.*%]*", "") - end -- Empty query @@ -77,581 +55,8 @@ if(isEmptyString(query)) then return end -local ifid = interface.getId() - -local query_info = hostkey2hostinfo(query) -local is_full_ip = isIPv4(query_info['host']) or isIPv6(query_info['host']) - --- Report if a perfect host match was found (full ip) -local exact_ip_match = false -local partial_ip_match = false - ---- Links - -local function build_flow_alerts_url(key, value) - local url = ntop.getHttpPrefix() .. '/lua/alert_stats.lua?ifid=' .. ifid .. "&status=historical&page=flow" - - local host_info = hostkey2hostinfo(value) - if host_info['host'] then - url = url .. '&' .. tag_utils.build_request_filter(key, 'eq', host_info['host']) - end - if host_info['vlan'] and host_info['vlan'] ~= 0 then - url = url .. '&' .. tag_utils.build_request_filter('vlan_id', 'eq', host_info['vlan']) - end - - return url -end - -local function build_historical_flows_url(key, value) - local url = ntop.getHttpPrefix() .. '/lua/pro/db_search.lua?ifid=' .. ifid - - local host_info = hostkey2hostinfo(value) - if host_info['host'] then - url = url .. '&' .. tag_utils.build_request_filter(key, 'eq', host_info['host']) - end - if host_info['vlan'] and host_info['vlan'] ~= 0 then - url = url .. '&' .. tag_utils.build_request_filter('vlan_id', 'eq', host_info['vlan']) - end - - return url -end - -local function add_historical_flows_link(links, key --[[ ip, mac, name --]], value --[[ actual ip or mac or name (including vlan) --]]) - -- Alerts - local flow_alerts_icon = 'exclamation-triangle' - local flow_alerts_url = build_flow_alerts_url(key, value) - links[#links + 1] = { - icon = flow_alerts_icon, - title = i18n('alerts_dashboard.alerts'), - url = flow_alerts_url, - } - - if hasClickHouseSupport() then - -- Historical flows - local historical_flows_icon = 'stream' - local historical_flows_url = build_historical_flows_url(key, value) - links[#links + 1] = { - icon = historical_flows_icon, - title = i18n('db_explorer.historical_data_explorer'), - url = historical_flows_url, - } - end -end - -local function add_as_info_link(links, asn) - -- AS Info - local as_info_icon = 'circle-info' - local as_info_url = ntop.getHttpPrefix() .. '/lua/as_stats.lua?ifid=' .. ifid .. '&asn=' .. asn - links[#links + 1] = { - icon = as_info_icon, - title = i18n('as_info'), - url = as_info_url, - } -end - -local function add_icon_link(links, icon, title, url) - -- table.insert(links, 1, link) - links[#links + 1] = { - icon = icon, - title = title, - url = url, - } -end - -local function add_asn_link(links) - add_icon_link(links, 'cloud', i18n('as_details.as')) -end - -local function add_network_link(links) - add_icon_link(links, 'network-wired', i18n('network')) -end - -local function add_device_link(links) - add_icon_link(links, 'plug', i18n('device')) -end - -local function add_host_link(links) - add_icon_link(links, 'desktop', i18n('host_details.host')) -end - -local function add_snmp_device_link(links, ip) - add_icon_link(links, 'server', i18n('snmp.snmp_device')) -end - -local function add_snmp_interface_link(links, ip, index) - add_icon_link(links, 'ethernet', i18n('snmp.snmp_interface')) -end - ---- Badges - -local function add_badge(badges, label, icon, title) - badges[#badges + 1] = { - label = label, - icon = icon, - title = title, - } -end - -local function add_inactive_badge(badges) - add_badge(badges, nil, 'moon', i18n('inactive')) -end - ---- - --- Look by network -if not is_system_interface and not hosts_only then - local network_stats = interface.getNetworksStats() - res_count = 0 - - for network, stats in pairs(network_stats) do - if((res_count >= max_group_items) or (#results >= max_total_items)) then - break - end - - local name = getLocalNetworkLabel(network) - - if string.containsIgnoreCase(name, query) then - local network_id = stats.network_id - local links = {} - add_network_link(links) - - results[#results + 1] = { - name = name, - type = "network", - network = network_id, - links = links, - } - res_count = res_count + 1 - end - end -end - --- Look by AS -if not is_system_interface and not hosts_only then - local as_info = interface.getASesInfo() or {} - res_count = 0 - - for _, as in pairs(as_info.ASes or {}) do - if((res_count >= max_group_items) or (#results >= max_total_items)) then - break - end - - local asn = "AS" .. as.asn - local as_name = as.asname - local found = false - local links = {} - local badges = {} - add_asn_link(links) - add_as_info_link(links, as.asn) - - if string.containsIgnoreCase(as_name, query) then - add_badge(badges, asn) - results[#results + 1] = { - name = as_name, - type="asn", - asn = as.asn, - links = links, - badges = badges, - } - found = true - elseif string.containsIgnoreCase(asn, query) then - results[#results + 1] = { - name = asn, - type = "asn", - asn = as.asn, - links = links, - badges = badges, - } - found = true - end - - if found then - res_count = res_count + 1 - end - end -end - --- Look by MAC - SNMP -if not hosts_only then - -- Check also in the mac addresses of snmp devices - -- The query can be partial so we can't use functions to - -- test if it'a an IPv4, an IPv6, or a mac as they would yield - -- wrong results. We can just check for a dot in the string as if - -- there's a dot then we're sure it can't be a mac - - if ntop.isEnterpriseM() and snmp_location and not query:find("%.") then - local mac = string.upper(query) - local matches = snmp_location.find_mac_snmp_ports(mac, true) - res_count = 0 - - for _, snmp_port in ipairs(matches) do - if((res_count >= max_group_items) or (#results >= max_total_items)) then - break - end - - local snmp_device_ip = snmp_port["snmp_device_ip"] - local matching_mac = snmp_port["mac"] - local snmp_port_idx = snmp_port["id"] - local snmp_port_name = snmp_port["name"] - - local title = i18n("snmp.snmp_interface_x", { interface = shortenString(snmp_utils.get_snmp_interface_label({index = snmp_port_idx, name = snmp_port_name}))}) - - title = title .. " · " .. snmp_utils.get_snmp_device_label(snmp_device_ip) - - local links = {} - add_snmp_interface_link(links, snmp_device_ip, snmp_port_idx) - - results[#results + 1] = { - name = matching_mac .. ' '..title, - type = "snmp", - ip = snmp_device_ip, - snmp = snmp_device_ip, - snmp_port_idx = snmp_port_idx, - links = links, - } - res_count = res_count + 1 - end - end -end - --- Look by interface name - SNMP -if not hosts_only then - if ntop.isEnterpriseM() then - local name = string.upper(query) - local matches = snmp_utils.find_snmp_ports_by_name(name, true) - res_count = 0 - - for _, snmp_port in ipairs(matches) do - if res_count >= max_group_items or #results >= max_total_items then - break - end - - local snmp_device_ip = snmp_port["snmp_device_ip"] - local snmp_port_idx = snmp_port["id"] - local snmp_port_name = snmp_port["name"] - local snmp_port_index_match = snmp_port["index_match"] - - local title = i18n("snmp.snmp_interface_x", { interface = shortenString(snmp_utils.get_snmp_interface_label({index = snmp_port_idx, name = snmp_port_name}))}) - - title = title .. " · " .. snmp_utils.get_snmp_device_label(snmp_device_ip) - - local links = {} - add_snmp_interface_link(links, snmp_device_ip, snmp_port_idx) - - results[#results + 1] = { - name = title, - type = "snmp", - ip = snmp_device_ip, - snmp = snmp_device_ip, - snmp_port_idx = snmp_port_idx, - links = links, - } - res_count = res_count + 1 - end - end -end - --- Look by SNMP device -if not hosts_only then - if ntop.isEnterpriseM() then - local name = string.upper(query) - local matches = snmp_utils.find_snmp_devices(name, true) - res_count = 0 - - for _, snmp_device in ipairs(matches) do - if res_count >= max_group_items or #results >= max_total_items then - break - end - - local title = snmp_utils.get_snmp_device_label(snmp_device["ip"]) - - local links = {} - add_snmp_device_link(links, snmp_device["ip"]) - - results[#results + 1] = { - name = title, - type = "snmp_device", - ip = snmp_device["ip"], - snmp_device = snmp_device["ip"], - links = links, - } - res_count = res_count + 1 - end - end - -end -- not hosts only - --- Hosts - -local hosts = {} - -if not is_system_interface then - - -- 1st look at custom names (aliases) - - -- Note: inefficient, so a limit on the maximum number must be enforced. - local name_prefix = getHostAltNamesKey("") - local ip_to_name = {} - local max_num_names = 500 -- Limit the number of keys in the lookup - local name_keys = ntop.getKeysCache(getHostAltNamesKey("*")) or {} - for k, _ in pairs(name_keys) do - local name = ntop.getCache(k) - - if isEmptyString(name) then - -- key cleanup (unused) - ntop.delCache(k) - else - local ip = k:gsub(name_prefix, "") - ip_to_name[ip] = name - - max_num_names = max_num_names - 1 - if max_num_names == 0 then - break - end - end - end - - for ip, name in pairs(ip_to_name) do - if not hosts[ip] then - if string.containsIgnoreCase(name, query) then - local links = {} - - if name == ip then -- IP - add_host_link(links) - add_historical_flows_link(links, 'ip', ip) - else -- Name - add_host_link(links) - add_historical_flows_link(links, 'name', name) - end - - hosts[ip] = { - label = hostinfo2label({host = ip, name = name}), - ip = ip, - name = name, - links = links, - } - end - end - end - - -- Active Hosts - local res = interface.findHost(query) - - for k, host_key in pairs(res) do - if not hosts[k] then - local badges = {} - local links = {} - - local ip = nil - local mac = nil - - local host_info = hostkey2hostinfo(host_key) - local label = hostinfo2label(host_info, true) - if host_key ~= k then - label = label .. " · " .. k - end - - if isMacAddress(host_key) then -- MAC - mac = host_key - add_device_link(links) - add_historical_flows_link(links, 'mac', host_key) - elseif isIPv6(host_key) then -- IP - ip = host_key - add_host_link(links) - add_historical_flows_link(links, 'ip', host_key) - add_badge(badges, 'IPv6') - elseif k == host_key then -- IP - ip = host_key - add_host_link(links) - add_historical_flows_link(links, 'ip', host_key) - else -- Name - ip = k - add_host_link(links) - add_historical_flows_link(links, 'name', host_key) - end - - partial_ip_match = true - exact_ip_match = is_full_ip and host_info['host'] == query_info['host'] - - hosts[k] = { - label = label, - name = host_key, - ip = ip, - mac = mac, - links = links, - badges = badges, - } - end - end - - -- Inactive hosts - by MAC - local key_to_ip_offset = string.len(string.format("ntopng.ip_to_mac.ifid_%u__", ifid)) + 1 - - for k in pairs(ntop.getKeysCache(string.format("ntopng.ip_to_mac.ifid_%u__%s*", ifid, query)) or {}) do - -- Serialization by MAC address found - local h = hostkey2hostinfo(string.sub(k, key_to_ip_offset)) - - if not hosts[h.host] then - -- Do not override active hosts - local links = {} - add_host_link(links) - add_historical_flows_link(links, 'ip', h.host) - - local badges = {} - if isIPv6(h.host) then -- IP - add_badge(badges, 'IPv6') - end - add_inactive_badge(badges) - - hosts[h.host] = { - label = hostinfo2label({host=h.host, vlan=h.vlan}, true), - ip = h.host, - name = h.host, - links = links, - badges = badges, - } - end - end - - -- Inactive hosts - by IP - --[[ Note: host serialization is disabled - local key_to_ip_offset = string.len(string.format("ntopng.serialized_hosts.ifid_%u__", ifid)) + 1 - - for k in pairs(ntop.getKeysCache(string.format("ntopng.serialized_hosts.ifid_%u__%s*", ifid, query)) or {}) do - local host_key = string.sub(k, key_to_ip_offset) - - if not hosts[host_key] then - local h = hostkey2hostinfo(host_key) - - -- Do not override active hosts / hosts by MAC - local links = {} - add_host_link(links) - add_historical_flows_link(links, 'ip', host_key) - - local badges = {} - if isIPv6(h.host) then -- IP - add_badge(badges, 'IPv6') - end - add_inactive_badge(badges) - - hosts[host_key] = { - label = hostinfo2hostkey({host=h.host, vlan=h.vlan}), - name = host_key, - ip = host_key, - links = links, - badges = badges, - } - end - end - --]] - - - -- Also look at the DHCP cache - local key_prefix_offset = string.len(getDhcpNameKey(getInterfaceId(ifname), "")) + 1 - local mac_to_name = ntop.getKeysCache(getDhcpNameKey(getInterfaceId(ifname), "*")) or {} - for k in pairs(mac_to_name) do - - local mac = string.sub(k, key_prefix_offset) - if hosts[mac] then - - local name = ntop.getCache(k) - if not isEmptyString(name) and string.containsIgnoreCase(name, query) then - - local links = {} - add_device_link(links) - add_historical_flows_link(links, 'mac', mac) - - hosts[mac] = { - label = hostinfo2label({host = mac, mac = mac, name = name}) .. " · " .. mac, - mac = mac, - name = name, - links = links, - } - end - end - end - - -- Build final array with results - - res_count = 0 - - local function build_result(label, value, value_type, links, badges, context) - local r = { - name = label, - type = value_type, - links = links, - badges = badges, - context = context, - } - - r[value_type] = value - - return r - end - - if is_full_ip and partial_ip_match and not exact_ip_match then - what = "ip" - label = i18n("db_search.no_exact_match", {what=what, query=query}) - query = query .. tag_utils.SEPARATOR .. "eq" - results[#results + 1] = build_result(label, query, what, nil, nil, "historical") - end - - for k, v in pairsByField(hosts, 'name', asc) do - if((res_count >= max_group_items) or (#results >= max_total_items)) then - break - end - - if((v.label ~= "") and (already_printed[v.label] == nil)) then - already_printed[v] = true - - if v.mac then - results[#results + 1] = build_result(v.label, v.mac, "mac", v.links, v.badges) - elseif v.ip then - - -- Add badge for services - local info = interface.getHostMinInfo(v.ip) - if info and info.services then - if not v.badges then v.badges = {} end - for s,_ in pairs(info.services) do - add_badge(v.badges, s:upper()) - end - end - - results[#results + 1] = build_result(v.label, v.ip, "ip", v.links, v.badges) - end - - res_count = res_count + 1 - end -- if - end - - if #results == 0 and not isEmptyString(query) then - -- No results - add shortcut to search in historical data - if hasClickHouseSupport() then - local label = "" - local what = "" - if isHostKey(query) then - what = "ip" - label = i18n("db_search.find_in_historical", {what=what, query=query}) - query = query .. tag_utils.SEPARATOR .. "eq" - elseif isMacAddress(query) then - what = "mac" - label = i18n("db_search.find_in_historical", {what=what, query=query}) - query = query .. tag_utils.SEPARATOR .. "eq" - elseif isCommunityId(query) then - what = "community_id" - label = i18n("db_search.find_in_historical", {what=what, query=query}) - query = query .. tag_utils.SEPARATOR .. "eq" - else - what = "hostname" - label = i18n("db_search.find_in_historical", {what=what, query=query}) - query = query .. tag_utils.SEPARATOR .. "in" - end - - results[#results + 1] = build_result(label, query, what, nil, nil, "historical") - end - end - -end -- hosts - not is_system_interface +-- Lookup +local results = find_utils.find(query, hosts_only, ifid) local data = { interface = ifname,