diff --git a/modules/policy/README.rst b/modules/policy/README.rst index 96a010dbbbe78159130b1c99f555f978be1d3496..ca2675a4836481d7d462627189a3ba8bd8536634 100644 --- a/modules/policy/README.rst +++ b/modules/policy/README.rst @@ -25,6 +25,7 @@ There are several defined actions: * ``DROP`` - terminate query resolution, returns SERVFAIL to requestor * ``TC`` - set TC=1 if the request came through UDP, forcing client to retry with TCP * ``FORWARD(ip)`` - forward query to given IP and proxy back response (stub mode) +* ``REROUTE({{subnet,target}, ...})`` - reroute addresses in response matching given subnet to given target, e.g. ``{'192.0.2.0/24', '127.0.0.0'}`` will rewrite '192.0.2.55' to '127.0.0.55', see :ref:`renumber module <mod-renumber>` for more information. .. note:: The module (and ``kres``) expects domain names in wire format, not textual representation. So each label in name is prefixed with its length, e.g. "example.com" equals to ``"\7example\3com"``. You can use convenience function ``todname('example.com')`` for automatic conversion. diff --git a/modules/policy/policy.lua b/modules/policy/policy.lua index dec443d4a073b04c69fbc8206fcd8d355fec64de..eb911fd7b5f736eb42d2bbce3d1feb845ff15e1f 100644 --- a/modules/policy/policy.lua +++ b/modules/policy/policy.lua @@ -13,9 +13,22 @@ local function forward(target) end end +-- Rewrite records in packet +local function reroute(tbl, names) + -- Import renumbering rules + local ren = require('renumber') + local prefixes = {} + for from, to in pairs(tbl) do + table.insert(prefixes, names and ren.name(from, to) or ren.prefix(from, to)) + end + -- Return rule closure + tbl = nil + return ren.rule(prefixes) +end + local policy = { -- Policies - PASS = 1, DENY = 2, DROP = 3, TC = 4, FORWARD = forward, + PASS = 1, DENY = 2, DROP = 3, TC = 4, FORWARD = forward, REROUTE = reroute, -- Special values ANY = 0, } @@ -120,9 +133,9 @@ function policy.rpz(action, path, format) end -- Evaluate packet in given rules to determine policy action -function policy.evaluate(policy, req, query) - for i = 1, #policy.rules do - local action = policy.rules[i](req, query) +function policy.evaluate(rules, req, query) + for i = 1, #rules do + local action = rules[i](req, query) if action ~= nil then return action end @@ -158,7 +171,12 @@ end policy.layer = { begin = function(state, req) req = kres.request_t(req) - local action = policy:evaluate(req, req:current()) + local action = policy.evaluate(policy.rules, req, req:current()) + return policy.enforce(state, req, action) + end, + finish = function(state, req) + req = kres.request_t(req) + local action = policy.evaluate(policy.postrules, req, req:current()) return policy.enforce(state, req, action) end } @@ -218,5 +236,6 @@ policy.todnames(private_zones) -- @var Default rules policy.rules = { policy.suffix_common(policy.DENY, private_zones, '\4arpa\0') } +policy.postrules = {} return policy diff --git a/modules/renumber/renumber.lua b/modules/renumber/renumber.lua index 17fb302d472ea5fb8a27eab49c98d6ecc108404c..c4afb7366eb565f431cbaccd7fa86a88d91b65be 100644 --- a/modules/renumber/renumber.lua +++ b/modules/renumber/renumber.lua @@ -2,33 +2,59 @@ local policy = require('policy') local ffi = require('ffi') local bit = require('bit') -local mod = {} local prefixes = {} --- Add subnet prefix rewrite rule -local function add_prefix(subnet, addr) + +-- Create subnet prefix rule +local function matchprefix(subnet, addr) local target = kres.str2ip(addr) if target == nil then error('[renumber] invalid address: '..addr) end + local addrtype = string.find(addr, ':', 1, true) and kres.type.AAAA or kres.type.A local subnet_cd = ffi.new('char[16]') - local family = ffi.C.kr_straddr_family(subnet) local bitlen = ffi.C.kr_straddr_subnet(subnet_cd, subnet) - table.insert(prefixes, {family, subnet_cd, bitlen, target}) + -- Mask unspecified, renumber whole IP + if bitlen == 0 then + bitlen = #target * 8 + end + return {subnet_cd, bitlen, target, addrtype} +end + +-- Create name match rule +local function matchname(name, addr) + local target = kres.str2ip(addr) + if target == nil then error('[renumber] invalid address: '..addr) end + local owner = todname(name) + if not name then error('[renumber] invalid name: '..name) end + local addrtype = string.find(addr, ':', 1, true) and kres.type.AAAA or kres.type.A + return {owner, nil, target, addrtype} +end + +-- Add subnet prefix rewrite rule +local function add_prefix(subnet, addr) + table.insert(prefixes, matchprefix(subnet, addr)) end --- Match IP against given subnet -local function match_subnet(family, subnet, bitlen, addr) - return (#addr >= bitlen / 8) and (ffi.C.kr_bitcmp(subnet, addr, bitlen) == 0) + +-- Match IP against given subnet or record owner +local function match_subnet(subnet, bitlen, addrtype, rr) + local addr = rr.rdata + return addrtype == rr.type and + ((bitlen and (#addr >= bitlen / 8) and (ffi.C.kr_bitcmp(subnet, addr, bitlen) == 0)) or subnet == rr.owner) end + -- Renumber address record local addr_buf = ffi.new('char[16]') -local function renumber(tbl, rr) +local function renumber_record(tbl, rr) for i = 1, #tbl do local prefix = tbl[i] - if match_subnet(prefix[1], prefix[2], prefix[3], rr.rdata) then - local to_copy = prefix[3] + -- Match record type to address family and record address to given subnet + -- If provided, compare record owner to prefix name + if match_subnet(prefix[1], prefix[2], prefix[4], rr) then + -- Replace part or whole address + local to_copy = prefix[2] or (#prefix[3] * 8) local chunks = to_copy / 8 local rdlen = #rr.rdata if rdlen < chunks then return rr end -- Address length mismatch ffi.copy(addr_buf, rr.rdata, rdlen) - ffi.copy(addr_buf, prefix[4], chunks) + ffi.copy(addr_buf, prefix[3], chunks) -- @todo: CIDR not supported to_copy = to_copy - chunks * 8 rr.rdata = ffi.string(addr_buf, rdlen) @@ -37,17 +63,10 @@ local function renumber(tbl, rr) end return nil end --- Config -function mod.config (conf) - if conf == nil then return end - if type(conf) ~= 'table' or type(conf[1]) ~= 'table' then - error('[renumber] expected { {prefix, target}, ... }') - end - for i = 1, #conf do add_prefix(conf[i][1], conf[1][2]) end -end --- Layers -mod.layer = { - finish = function (state, req) + +-- Renumber addresses based on config +local function rule(prefixes) + return function (state, req) if state == kres.FAIL then return state end req = kres.request_t(req) pkt = kres.pkt_t(req.answer) @@ -59,8 +78,8 @@ mod.layer = { local changed = false for i = 1, ancount do local rr = records[i] - if rr.type == kres.type.A then - local new_rr = renumber(prefixes, rr) + if rr.type == kres.type.A or rr.type == kres.type.AAAA then + local new_rr = renumber_record(prefixes, rr) if new_rr ~= nil then records[i] = new_rr changed = true @@ -83,5 +102,27 @@ mod.layer = { end return state end +end + +-- Export module interface +local M = { + prefix = matchprefix, + name = matchname, + rule = rule, +} + +-- Config +function M.config (conf) + if conf == nil then return end + if type(conf) ~= 'table' or type(conf[1]) ~= 'table' then + error('[renumber] expected { {prefix, target}, ... }') + end + for i = 1, #conf do add_prefix(conf[i][1], conf[1][2]) end +end + +-- Layers +M.layer = { + finish = rule(prefixes), } -return mod + +return M