Knot Resolver Conditional DNS Override
·
234 Wörter ·
2 Minuten Lesezeit
Overview
This configuration implements a conditional split-DNS policy in Knot Resolver (kresd):
- Localhost queries (
127.0.0.0/8,::1,::ffff:127.x.x.x, or internal): Resolved normally via upstream (policy.PASS) with an informational log entry. - External/Remote client queries: Intercepted and answered directly with a statically configured IP address (
policy.ANSWER).
Technical Context & Data Structures
Knot Resolver's Lua engine exposes C data structures via LuaJIT FFI:
struct kr_request: Represents the complete client request lifecycle.- The originating client
sockaddris stored atreq.qsource.addr.
- The originating client
struct kr_query: Represents an individual query within the resolution plan.query.sname: Wire-format domain name (converted to text viakres.dname2str()).query.request: Pointer back to the parentkr_request.
kr_straddr(addr): Convertsconst struct sockaddr *to a presentation string (IPv4/IPv6 text).log_info(grp, msg): Emits log entries requiring a valid log group enum (ffi.C.LOG_GRP_POLICY) as its first argument.
Configuration
add include_config the resolver config like so:
config resolver 'kresd'
[..]
option include_config '/etc/kresd/custom.conf'
Then add an /etc/kresd/custom.config :
local ffi = require('ffi')
pcall(ffi.cdef, 'char *kr_straddr(const struct sockaddr *addr);')
log_level('info')
-- Direkte Zuordnung nach Record-Typ (kres.type.A / kres.type.AAAA)
local records = {
['lomotron.de'] = {
[kres.type.A] = { 'LOCAL IPV4 ADRESS' },
[kres.type.AAAA] = {
'LOCAL IPV6 ADDRESS' --ula
}
}
}
policy.add(function (_, query)
if not query or not query.sname then return nil end
local domain = kres.dname2str(query.sname):lower():gsub('%.$', '')
local entry = records[domain]
if entry and entry[query.stype] then
local addr = query.request and query.request.qsource.addr
local ip = addr ~= nil and ffi.string(ffi.C.kr_straddr(addr))
local client_ip = ip and ip:match('^(.-)#') or ip
if not client_ip or client_ip:find('^127%.') or client_ip == '::1' or client_ip:find('^::ffff:127%.') then
log_info(ffi.C.LOG_GRP_POLICY, string.format('%s from localhost (%s) -> PASS', domain, ip or 'internal'))
return policy.PASS
end
local rdata = {}
for _, address in ipairs(entry[query.stype]) do
table.insert(rdata, kres.str2ip(address))
log_info(ffi.C.LOG_GRP_POLICY, string.format('%s from %s -> ANSWER record: %s', domain, ip, address))
end
return policy.ANSWER({
[query.stype] = { rdata = rdata, ttl = 300 }
})
end
end)
Key Features
- Extensibility: Add additional domain redirects simply by adding key-value entries to the
fixed_ipstable:
local records = {
['lomotron.de'] = {
[kres.type.A] = { 'LOCAL IPV4 ADRESS' },
[kres.type.AAAA] = {
'LOCAL IPV6 ADDRESS' --ula
}
}
}
- Type Safety: Directly uses
query.request.qsource.addrand safe FFI bindings for address conversion. - Minimal Overhead: Evaluates queries directly via table lookup and returns built-in policy actions.