diff --git a/modules/policy/policy.lua b/modules/policy/policy.lua index 2279a73451d8b25854d89dedec26c79022e36abb..c65642429bdd3e202acbacd9ae7d0c54232f4d02 100644 --- a/modules/policy/policy.lua +++ b/modules/policy/policy.lua @@ -444,6 +444,69 @@ function policy.rpz(action, path, watch) end end +-- Apply an action when query belongs to a slice (determined by slice_func()) +function policy.slice(slice_func, ...) + local actions = {...} + if #actions <= 0 then + error('[poli] at least one action must be provided to policy.slice()') + end + + return function(_, query) + local index = slice_func(query, #actions) + return actions[index] + end +end + +-- Initializes slicing function that randomly assigns queries to a slice based on their registrable domain +function policy.slice_randomize_psl(seed) + local has_psl, psl_lib = pcall(require, 'psl') + if not has_psl then + error('[poli] lua-psl is required for policy.slice_randomize_psl()') + end + -- load psl + local has_latest, psl = pcall(psl_lib.latest) + if not has_latest then -- compatiblity with lua-psl < 0.15 + psl = psl_lib.builtin() + end + + if seed == nil then + seed = os.time() / (3600 * 24 * 7) + end + seed = math.floor(seed) -- convert to int + + return function(query, length) + assert(length > 0) + + local domain = kres.dname2str(query:name()) + if domain == nil then -- invalid data: auto-select first action + return 1 + end + if domain:len() > 1 then --remove trailing dot + domain = domain:sub(0, -2) + end + + -- do psl lookup for registrable domain + local reg_domain = psl:registrable_domain(domain) + if reg_domain == nil then -- fallback to unreg. domain + reg_domain = psl:unregistrable_domain(domain) + if reg_domain == nil then -- shouldn't happen: safe fallback + return 1 + end + end + + local rand_seed = seed + -- create deterministic seed for pseudo-random slice assignment + for i = 1, #reg_domain do + rand_seed = rand_seed + reg_domain:byte(i) + end + + -- use lineral congruential generator with values from ANSI C + rand_seed = rand_seed % 0x80000000 -- ensure seed is positive 32b int + local rand = (1103515245 * rand_seed + 12345) % 0x10000 + return 1 + rand % length + end +end + function policy.DENY_MSG(msg) if msg and (type(msg) ~= 'string' or #msg >= 255) then error('DENY_MSG: optional msg must be string shorter than 256 characters')