-- -- (C) 2017 - ntop.org -- dirs = ntop.getDirs() package.path = dirs.installdir .. "/scripts/lua/modules/?/init.lua;" .. package.path local ntop_info = ntop.getInfo() local host_pools_utils = {} host_pools_utils.DEFAULT_POOL_ID = "0" host_pools_utils.DEFAULT_ROUTING_POLICY_ID = "0" host_pools_utils.FIRST_AVAILABLE_POOL_ID = "1" host_pools_utils.DEFAULT_POOL_NAME = "Not Assigned" host_pools_utils.MAX_NUM_POOLS = 128 -- Note: keep in sync with C host_pools_utils.LIMITED_NUMBER_POOL_MEMBERS = ntop_info["constants.max_num_pool_members"] -- this takes into account the special pools host_pools_utils.LIMITED_NUMBER_TOTAL_HOST_POOLS = ntop_info["constants.max_num_host_pools"] -- this does not take into account the special pools host_pools_utils.LIMITED_NUMBER_USER_HOST_POOLS = host_pools_utils.LIMITED_NUMBER_TOTAL_HOST_POOLS - 1 local function get_pool_members_key(ifid, pool_id) return "ntopng.prefs." .. ifid .. ".host_pools.members." .. pool_id end local function get_pool_ids_key(ifid) return "ntopng.prefs." .. ifid .. ".host_pools.pool_ids" end local function get_pool_details_key(ifid, pool_id) return "ntopng.prefs." .. ifid .. ".host_pools.details." .. pool_id end local function get_user_pool_dump_key(ifid) return "ntopng.prefs." .. ifid .. ".host_pools.dump" end -- It is safe to call this multiple times local function initInterfacePools(ifid) host_pools_utils.createPool(ifid, host_pools_utils.DEFAULT_POOL_ID, host_pools_utils.DEFAULT_POOL_NAME) end function host_pools_utils.getPoolDetail(ifid, pool_id, detail) local details_key = get_pool_details_key(ifid, pool_id) return ntop.getHashCache(details_key, detail) end function host_pools_utils.setPoolDetail(ifid, pool_id, detail, value) local details_key = get_pool_details_key(ifid, pool_id) return ntop.setHashCache(details_key, detail, tostring(value)) end local function addMemberToRedisPool(members_key, member_key) local n = table.len(ntop.getMembersCache(members_key) or {}) if n >= host_pools_utils.LIMITED_NUMBER_POOL_MEMBERS then return false end ntop.setMembersCache(members_key, member_key) return true end -------------------------------------------------------------------------------- function host_pools_utils.createPool(ifid, pool_id, pool_name, children_safe, enforce_quotas_per_pool_member) local details_key = get_pool_details_key(ifid, pool_id) local ids_key = get_pool_ids_key(ifid) local n = table.len(ntop.getMembersCache(ids_key) or {}) if n >= host_pools_utils.LIMITED_NUMBER_TOTAL_HOST_POOLS then return false end ntop.setMembersCache(ids_key, pool_id) ntop.setHashCache(details_key, "name", pool_name) ntop.setHashCache(details_key, "children_safe", tostring(children_safe or false)) ntop.setHashCache(details_key, "enforce_quotas_per_pool_member", tostring(enforce_quotas_per_pool_member or false)) return true end function host_pools_utils.deletePool(ifid, pool_id) local rrd_base = host_pools_utils.getRRDBase(ifid, pool_id) local ids_key = get_pool_ids_key(ifid) local details_key = get_pool_details_key(ifid, pool_id) local members_key = get_pool_members_key(ifid, pool_id) local dump_key = get_user_pool_dump_key(ifid) ntop.delMembersCache(ids_key, pool_id) ntop.delCache(details_key) ntop.delCache(members_key) ntop.delHashCache(dump_key, pool_id) ntop.rmdir(rrd_base) end function getMembershipInfo(member_and_vlan) -- Check if the member is already in another pool local hostinfo = hostkey2hostinfo(member_and_vlan) local addr, mask = splitNetworkPrefix(hostinfo["host"]) local vlan = hostinfo["vlan"] local is_mac = isMacAddress(addr) if not is_mac then addr = ntop.networkPrefix(addr, mask) end local find_info = interface.findMemberPool(addr, vlan, is_mac) -- This is the normalized key, which should always be used to refer to the member local key if not is_mac then key = host2member(addr, vlan, mask) else key = addr end local info = {key=key} local exists = false if find_info ~= nil then -- The host has been found if is_mac or ((not is_mac) and (find_info.matched_prefix == addr) and (find_info.matched_bitmask == mask)) then info["existing_member_pool"] = find_info.pool_id exists = true end end return exists, info end -- -- Note: -- -- When strict_host_mode is not set, hosts which have a MAC address will have the -- MAC address changed instead of the IP address when their MAC address is already bound to -- a pool. -- function host_pools_utils.changeMemberPool(ifid, member_and_vlan, new_pool, info --[[optional]], strict_host_mode --[[optional]]) if not strict_host_mode then local hostkey, is_network = host_pools_utils.getMemberKey(member_and_vlan) if (not is_network) and (not isMacAddress(member_and_vlan)) then -- this is a single host, try to get the MAC address if info == nil then local hostinfo = hostkey2hostinfo(hostkey) info = interface.getHostInfo(hostinfo["host"], hostinfo["vlan"]) end if not isEmptyString(info["mac"]) then local mac_has_pool, mac_pool_info = getMembershipInfo(info["mac"]) if mac_has_pool and mac_pool_info.existing_member_pool ~= host_pools_utils.DEFAULT_POOL_ID then -- we must change the MAC address in order to change the host pool member_and_vlan = info["mac"] end end end end local members_key = get_pool_members_key(ifid, new_pool) local member_exists, info = getMembershipInfo(member_and_vlan) local prev_pool if member_exists then -- use the normalized key member_and_vlan = info.key prev_pool = info.existing_member_pool else prev_pool = host_pools_utils.DEFAULT_POOL_ID end if prev_pool == new_pool then return false end host_pools_utils.deletePoolMember(ifid, prev_pool, info.key) addMemberToRedisPool(members_key, info.key) return true end function host_pools_utils.addPoolMember(ifid, pool_id, member_and_vlan) local members_key = get_pool_members_key(ifid, pool_id) local member_exists, info = getMembershipInfo(member_and_vlan) if member_exists then return false, info else local rv = addMemberToRedisPool(members_key, info.key) return rv, info end end function host_pools_utils.deletePoolMember(ifid, pool_id, member_and_vlan) local members_key = get_pool_members_key(ifid, pool_id) -- Possible delete volatile member if ntop.isPro() then interface.removeVolatileMemberFromPool(member_and_vlan, tonumber(pool_id)) end -- Possible delete non-volatile member ntop.delMembersCache(members_key, member_and_vlan) end function host_pools_utils.emptyPool(ifid, pool_id) local members_key = get_pool_members_key(ifid, pool_id) if ntop.isPro() then -- Remove volatile members for _,v in pairs(interface.getHostPoolsVolatileMembers()[tonumber(pool_id)] or {}) do interface.removeVolatileMemberFromPool(v.member, tonumber(pool_id)) end end -- Remove non-volatile members ntop.delCache(members_key) end function host_pools_utils.getPoolsList(ifid, without_info) local ids_key = get_pool_ids_key(ifid) local ids = ntop.getMembersCache(ids_key) if not ids then ids = {} end for i, id in pairs(ids) do ids[i] = tonumber(id) end local pools = {} initInterfacePools(ifid) for _, pool_id in pairsByValues(ids, asc) do pool_id = tostring(pool_id) local pool if without_info then pool = {id=pool_id} else pool = { id = pool_id, name = host_pools_utils.getPoolName(ifid, pool_id), children_safe = host_pools_utils.getChildrenSafe(ifid, pool_id), enforce_quotas_per_pool_member = host_pools_utils.getEnforceQuotasPerPoolMember(ifid, pool_id), } end pools[#pools + 1] = pool end return pools end function host_pools_utils.getPoolMembers(ifid, pool_id) local members_key = get_pool_members_key(ifid, pool_id) local members = {} local all_members = ntop.getMembersCache(members_key) or {} local volatile = {} if ntop.isPro() then for _,v in pairs(interface.getHostPoolsVolatileMembers()[tonumber(pool_id)] or {}) do if not v.expired then all_members[#all_members + 1] = v["member"] volatile[v["member"]] = v["residual_lifetime"] end end end for _,v in pairsByValues(all_members, asc) do local hostinfo = hostkey2hostinfo(v) local residual_lifetime = NULL if volatile[v] ~= nil then residual_lifetime = volatile[v]; end members[#members + 1] = {address=hostinfo["host"], vlan=hostinfo["vlan"], key=v, residual=residual_lifetime} end return members end function host_pools_utils.getMemberKey(member) -- handle vlan local is_network local host_key local address = hostkey2hostinfo(member)["host"] if isMacAddress(address) then host_key = address is_network = false else local network, prefix = splitNetworkPrefix(address) if(((isIPv4(network)) and (prefix ~= 32)) or ((isIPv6(network)) and (prefix ~= 128))) then -- this is a network host_key = address is_network = true else -- this is an host host_key = network is_network = false end end return host_key, is_network end function host_pools_utils.getPoolName(ifid, pool_id) return host_pools_utils.getPoolDetail(ifid, pool_id, "name") end function host_pools_utils.getChildrenSafe(ifid, pool_id) return toboolean(host_pools_utils.getPoolDetail(ifid, pool_id, "children_safe")) end function host_pools_utils.getRoutingPolicyId(ifid, pool_id) local routing_policy_id = host_pools_utils.getPoolDetail(ifid, pool_id, "routing_policy_id") if isEmptyString(routing_policy_id) then routing_policy_id = "0" end return routing_policy_id end function host_pools_utils.setRoutingPolicyId(ifid, pool_id, routing_policy_id) return host_pools_utils.setPoolDetail(ifid, pool_id, "routing_policy_id", routing_policy_id) end function host_pools_utils.getEnforceQuotasPerPoolMember(ifid, pool_id) return toboolean(host_pools_utils.getPoolDetail(ifid, pool_id, "enforce_quotas_per_pool_member")) end function host_pools_utils.clearPools() for _, ifname in pairs(interface.getIfNames()) do local ifid = getInterfaceId(ifname) local ifstats = interface.getStats() if not ifstats.isView then local pools_list = host_pools_utils.getPoolsList(ifid) for _, pool in pairs(pools_list) do host_pools_utils.deletePool(ifid, pool["id"]) end end end end function host_pools_utils.initPools() for _, ifname in pairs(interface.getIfNames()) do local ifid = getInterfaceId(ifname) local ifstats = interface.getStats() -- Note: possible shapers are initialized in shaper_utils::initShapers if not ifstats.isView then initInterfacePools(ifid) end end end function host_pools_utils.getMacPool(mac_address) local exists, info = getMembershipInfo(mac_address) if exists then return tostring(info.existing_member_pool) else return host_pools_utils.DEFAULT_POOL_ID end end function host_pools_utils.getUndeletablePools(ifid) local pools = {} for user_key,_ in pairs(ntop.getKeysCache("ntopng.user.*.host_pool_id") or {}) do local pool_id = ntop.getCache(user_key) if tonumber(pool_id) ~= nil then local username = string.split(user_key, "%.")[3] local allowed_ifname = ntop.getCache("ntopng.user."..username..".allowed_ifname") -- verify if the Captive Portal User is actually active for the interface if getInterfaceName(ifid) == allowed_ifname then pools[pool_id] = true end end end return pools end function host_pools_utils.purgeExpiredPoolsMembers() local ifnames = interface.getIfNames() for _, ifname in pairs(ifnames) do interface.select(ifname) if isCaptivePortalActive() then interface.purgeExpiredPoolsMembers() end end end function host_pools_utils.getRRDBase(ifid, pool_id) local dirs = ntop.getDirs() return fixPath(dirs.workingdir .. "/" .. ifid .. "/host_pools/" .. pool_id) end function host_pools_utils.updateRRDs(ifid, dump_ndpi, verbose) -- NOTE: requires graph_utils for pool_id, pool_stats in pairs(interface.getHostPoolsStats()) do local pool_base = host_pools_utils.getRRDBase(ifid, pool_id) if(not(ntop.exists(pool_base))) then ntop.mkdir(pool_base) end -- Traffic stats local rrdpath = fixPath(pool_base .. "/bytes.rrd") createRRDcounter(rrdpath, 300, verbose) ntop.rrd_update(rrdpath, "N:"..tolongint(pool_stats["bytes.sent"]) .. ":" .. tolongint(pool_stats["bytes.rcvd"])) -- nDPI stats if dump_ndpi then for proto,v in pairs(pool_stats["ndpi"] or {}) do local ndpiname = fixPath(pool_base.."/"..proto..".rrd") createRRDcounter(ndpiname, 300, verbose) ntop.rrd_update(ndpiname, "N:"..tolongint(v["bytes.sent"])..":"..tolongint(v["bytes.rcvd"])) end end end end function host_pools_utils.printQuotas(pool_id, host, page_params) local pools_stats = interface.getHostPoolsStats() local pool_stats = pools_stats and pools_stats[tonumber(pool_id)] local ndpi_stats = pool_stats.ndpi local category_stats = pool_stats.ndpi_categories local quota_and_protos = shaper_utils.getPoolProtoShapers(ifId, pool_id) -- Empty check local empty = true for _, proto in pairs(quota_and_protos) do if ((tonumber(proto.traffic_quota) > 0) or (tonumber(proto.time_quota) > 0)) then -- at least a quota is set empty = false break end end if empty then print("
"..i18n("shaping.no_quota_data").. ". Create new quotas here.
") else print[[
]] print(i18n("protocol")) print[[ ]] print(i18n("shaping.daily_traffic")) print[[ ]] print(i18n("shaping.daily_time")) print[[
]] end end function host_pools_utils.getFirstAvailablePoolId(ifid) local ids_key = get_pool_ids_key(ifid) local ids = ntop.getMembersCache(ids_key) or {} for i, id in pairs(ids) do ids[i] = tonumber(id) end local host_pool_id = tonumber(host_pools_utils.FIRST_AVAILABLE_POOL_ID) for _, pool_id in pairsByValues(ids, asc) do if pool_id > host_pool_id then break end host_pool_id = math.max(pool_id + 1, host_pool_id) end return tostring(host_pool_id) end return host_pools_utils