Commit 4ece8e0e authored by Marek Vavrusa's avatar Marek Vavrusa

modules/policy: now can reroute/rewrite responses

* REROUTE action rewrites all addresses in
  final answers matching given subnet to
  addresses in target subnet (or single address)
* REWRITE action rewrites rdata in final answers
  matching given owner and type (only works on
  A/AAAA now)
parent 7b771974
......@@ -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.
......
......@@ -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
......@@ -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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment