]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
modules/policy: now can reroute/rewrite responses
authorMarek Vavrusa <marek@vavrusa.com>
Thu, 9 Jun 2016 07:38:26 +0000 (00:38 -0700)
committerMarek Vavrusa <marek@vavrusa.com>
Wed, 6 Jul 2016 06:33:38 +0000 (23:33 -0700)
* 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)

modules/policy/README.rst
modules/policy/policy.lua
modules/renumber/renumber.lua

index 96a010dbbbe78159130b1c99f555f978be1d3496..ca2675a4836481d7d462627189a3ba8bd8536634 100644 (file)
@@ -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.
 
index dec443d4a073b04c69fbc8206fcd8d355fec64de..eb911fd7b5f736eb42d2bbce3d1feb845ff15e1f 100644 (file)
@@ -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
index 17fb302d472ea5fb8a27eab49c98d6ecc108404c..c4afb7366eb565f431cbaccd7fa86a88d91b65be 100644 (file)
@@ -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