Mercurial > prosody-modules
view mod_firewall/definitions.lib.lua @ 2522:72cbec103709
mod_firewall: Improve HTTP polling logic
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Mon, 20 Feb 2017 09:26:16 +0000 |
parents | c6fd8975704b |
children | a3a18d09ae8a |
line wrap: on
line source
-- Name arguments are unused here -- luacheck: ignore 212 local definition_handlers = {}; local http = require "net.http"; local timer = require "util.timer"; local set = require"util.set"; local new_throttle = require "util.throttle".create; local multirate_cache_size = module:get_option_number("firewall_multirate_cache_limit", 1000); function definition_handlers.ZONE(zone_name, zone_members) local zone_member_list = {}; for member in zone_members:gmatch("[^, ]+") do zone_member_list[#zone_member_list+1] = member; end return set.new(zone_member_list)._items; end -- Helper function used by RATE handler local function evict_only_unthrottled(name, throttle) throttle:update(); -- Check whether the throttle is at max balance (i.e. totally safe to forget about it) if throttle.balance < throttle.max then -- Not safe to forget return false; end end function definition_handlers.RATE(name, line) local rate = assert(tonumber(line:match("([%d.]+)")), "Unable to parse rate"); local burst = tonumber(line:match("%(%s*burst%s+([%d.]+)%s*%)")) or 1; local max_throttles = tonumber(line:match("%(%s*entries%s+([%d]+)%s*%)")) or multirate_cache_size; local deny_when_full = not line:match("%(allow overflow%)"); return { single = function () return new_throttle(rate*burst, burst); end; multi = function () local cache = require "util.cache".new(max_throttles, deny_when_full and evict_only_unthrottled or nil); return { poll_on = function (_, key, amount) assert(key, "no key"); local throttle = cache:get(key); if not throttle then throttle = new_throttle(rate*burst, burst); if not cache:set(key, throttle) then module:log("warn", "Multirate '%s' has hit its maximum number of active throttles (%d), denying new events", name, max_throttles); return false; end end return throttle:poll(amount); end; } end; }; end local list_backends = { memory = { init = function (self, type, opts) if opts.limit then local have_cache_lib, cache_lib = pcall(require, "util.cache"); if not have_cache_lib then error("In-memory lists with a size limit require Prosody 0.10"); end self.cache = cache_lib.new((assert(tonumber(opts.limit), "Invalid list limit"))); if not self.cache.table then error("In-memory lists with a size limit require a newer version of Prosody 0.10"); end self.items = self.cache:table(); else self.items = {}; end end; add = function (self, item) self.items[item] = true; end; remove = function (self, item) self.items[item] = nil; end; contains = function (self, item) return self.items[item] == true; end; }; http = { init = function (self, url, opts) local poll_interval = assert(tonumber(opts.ttl or "3600"), "invalid ttl for <"..url.."> (expected number of seconds)"); local pattern = opts.pattern or "([^\r\n]+)\r?\n"; assert(pcall(string.match, "", pattern), "invalid pattern for <"..url..">"); if opts.hash then assert(opts.hash:match("^%w+$") and type(hashes[opts.hash]) == "function", "invalid hash function: "..opts.hash); self.hash_function = hashes[opts.hash]; end local etag; local failure_count = 0; local retry_intervals = { 60, 120, 300 }; local function update_list() http.request(url, { headers = { ["If-None-Match"] = etag; }; }, function (body, code, response) local next_poll = poll_interval; if code == 200 and body then etag = response.headers.etag; local items = {}; for entry in body:gmatch(pattern) do items[entry] = true; end self.items = items; module:log("debug", "Fetched updated list from <%s>", url); elseif code == 304 then module:log("debug", "List at <%s> is unchanged", url); elseif code == 0 or (code >= 400 and code <=599) then module:log("warn", "Failed to fetch list from <%s>: %d %s", url, code, tostring(body)); next_poll = 300; failure_count = failure_count + 1; next_poll = retry_intervals[failure_count] or retry_intervals[#retry_intervals]; end if next_poll > 0 then timer.add_task(next_poll+math.random(0, 60), update_list); end end); end update_list(); end; contains = function (self, item) if self.hash_function then item = self.hash_function(item); end return self.items and self.items[item] == true; end; }; }; local function create_list(list_backend, list_def, opts) if not list_backends[list_backend] then error("Unknown list type '"..list_backend.."'", 0); end local list = setmetatable({}, { __index = list_backends[list_backend] }); if list.init then list:init(list_def, opts); end return list; end --[[ %LIST spammers: memory (source: /etc/spammers.txt) %LIST spammers: memory (source: /etc/spammers.txt) %LIST spammers: http://example.com/blacklist.txt ]] function definition_handlers.LIST(list_name, list_definition) local list_backend = list_definition:match("^%w+"); local opts = {}; local opt_string = list_definition:match("^%S+%s+%((.+)%)"); if opt_string then for opt_k, opt_v in opt_string:gmatch("(%w+): ?([^,]+)") do opts[opt_k] = opt_v; end end return create_list(list_backend, list_definition:match("^%S+"), opts); end return definition_handlers;