1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Module interface
local ffi = require('ffi')
local prefixes_global = {}
-- get address from config: either subnet prefix or fixed endpoint
local function extract_address(target)
local idx = string.find(target, "!", 1, true)
if idx == nil then
return target, false
end
if idx ~= #target then
error("[renumber] \"!\" symbol in target is only accepted at the end of address")
end
return string.sub(target, 1, idx - 1), true
end
-- Create bitmask from integer mask for single octet: 2 -> 11000000
local function getOctetBitmask(intMask)
return bit.lshift(bit.rshift(255, 8 - intMask), 8 - intMask)
end
-- Merge ipNet with ipHost, using intMask
local function mergeIps(ipNet, ipHost, intMask)
local octetMask
local result = ""
if #ipNet ~= #ipHost then
return nil
end
if intMask == nil then
return ipNet
end
for currentOctetNo = 1, #ipNet do
if intMask >= 8 then
result = result .. ipNet:sub(currentOctetNo,currentOctetNo)
elseif (intMask <= 0) then
result = result .. ipHost:sub(currentOctetNo,currentOctetNo)
else
octetMask = getOctetBitmask(intMask)
result = result .. string.char(bit.bor(
bit.band(string.byte(ipNet:sub(currentOctetNo,currentOctetNo)), octetMask),
bit.band(string.byte(ipHost:sub(currentOctetNo,currentOctetNo)), bit.bnot(octetMask))
))
end
intMask = intMask - 8
end
return result
end
-- Create subnet prefix rule
local function matchprefix(subnet, addr)
local is_exact
addr, is_exact = extract_address(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 bitlen = ffi.C.kr_straddr_subnet(subnet_cd, subnet)
if bitlen < 0 then error('[renumber] invalid subnet: '..subnet) end
return {subnet_cd, bitlen, target, addrtype, is_exact}
end
-- Create name match rule
local function matchname(name, addr)
local is_exact
addr, is_exact = extract_address(addr) -- though matchname() always leads to replacing whole address
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, is_exact}
end
-- Add subnet prefix rewrite rule
local function add_prefix(subnet, addr)
local prefix = matchprefix(subnet, addr)
table.insert(prefixes_global, prefix)
end
-- 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 function renumber_record(tbl, rr)
for i = 1, #tbl do
local prefix = tbl[i]
local subnet = prefix[1]
local bitlen = prefix[2]
local target = prefix[3]
local addrtype = prefix[4]
local is_exact = prefix[5]
-- Match record type to address family and record address to given subnet
-- If provided, compare record owner to prefix name
if match_subnet(subnet, bitlen, addrtype, rr) then
if is_exact then
rr.rdata = target
else
local mergedHost = mergeIps(target, rr.rdata, bitlen)
if mergedHost ~= nil then rr.rdata = mergedHost end
end
return rr
end
end
return nil
end
-- Renumber addresses based on config
local function rule(prefixes)
return function (state, req)
if state == kres.FAIL then return state end
local pkt = req.answer
-- Only successful answers
local records = pkt:section(kres.section.ANSWER)
local ancount = #records
if ancount == 0 then return state end
-- Find renumber candidates
local changed = false
for i = 1, ancount do
local rr = records[i]
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
end
end
end
-- If not rewritten, chain action
if not changed then return state end
-- Replace section if renumbering
local qname = pkt:qname()
local qclass = pkt:qclass()
local qtype = pkt:qtype()
pkt:recycle()
pkt:question(qname, qclass, qtype)
for i = 1, ancount do
local rr = records[i]
-- Strip signatures as rewritten data cannot be validated
if rr.type ~= kres.type.RRSIG then
pkt:put(rr.owner, rr.ttl, rr.class, rr.type, rr.rdata)
end
end
req:set_extended_error(kres.extended_error.FORGED, "DUQR")
return state
end
end
-- Export module interface
local M = {
prefix = matchprefix,
name = matchname,
rule = rule,
match_subnet = match_subnet,
}
-- 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[i][2]) end
end
-- Layers
M.layer = {
finish = rule(prefixes_global),
}
return M
|