From: Michael Tremer Date: Sat, 16 May 2026 12:56:46 +0000 (+0100) Subject: knot-resolver: Create a prototype for a DHCP leases integration X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4795f2c3d277bc56d288c1f8f04414fad8ede501;p=ipfire-2.x.git knot-resolver: Create a prototype for a DHCP leases integration This module will implement a policy handler which can be used to fetch any current DHCP leases from a SQLite3 database. Signed-off-by: Michael Tremer --- diff --git a/config/knot-resolver/kresd.conf b/config/knot-resolver/kresd.conf index 5d77e19c2..0515796f6 100644 --- a/config/knot-resolver/kresd.conf +++ b/config/knot-resolver/kresd.conf @@ -93,6 +93,7 @@ local CA_FILE = "/etc/ssl/cert.pem" -- Load useful modules modules = { 'hints > iterate', -- Allow loading /etc/hosts or custom root hints + 'leases', 'stats', -- Track internal statistics 'predict', -- Prefetch expiring/frequent records 'ta_sentinel', diff --git a/config/knot-resolver/leases.lua b/config/knot-resolver/leases.lua new file mode 100644 index 000000000..a78a800c5 --- /dev/null +++ b/config/knot-resolver/leases.lua @@ -0,0 +1,170 @@ +-- Load modules +local sqlite3 = require("lsqlite3") + +local DB_PATH = "/var/lib/knot-resolver/dhcp-leases.db" +local TTL = 60 + +local M = {} +local db +local sql_fwd +local sql_rev + +local function log_error(s) + print(s) +end + +local function log_debug(s) + print(s) +end + +-- Initializes the module +function M.init() + -- Open the database + db = sqlite3.open(DB_PATH, sqlite3.OPEN_READONLY) + + -- Fail if we cannot open the database + if not db then + log_error("leases: Failed to open " .. DB_PATH) + return -1 + end + + -- Don't ever block + db:exec("PRAGMA query_only = 1") + + -- Prepare the forward lookup query + sql_fwd = db:prepare( + "SELECT address FROM leases WHERE hostname = ?1 COLLATE NOCASE LIMIT 1" + ) + + -- Prepare the reverse lookup query + sql_rev = db:prepare( + "SELECT hostname FROM leases WHERE address = ?1 LIMIT 1" + ) +end + +-- Cleans up the module +function M.deinit() + -- Cleanup the statements + if sql_fwd then + sql_fwd:finalize() + end + if sql_rev then + sql_rev:finalize() + end + + -- Close the database + if db then + db:close() + end +end + +-- Parses an IPv4 address from the reverse pointer query name +local function address_from_reverse_pointer(qname) + local d, c, b, a = qname:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)%.in%-addr%.arpa%.$") + + -- Return nil if we could not parse the name + if not a or not b or not c or not d then + return + end + + -- Concatenate the address + return string.format("%s.%s.%s.%s", a, b, c, d) +end + +local function lookup_fwd(hostname) + -- Reset the statement + sql_fwd:reset() + + -- Bind the query name + sql_fwd:bind_values(hostname) + + -- Execute the statement + if sql_fwd:step() == sqlite3.ROW then + local address = sql_fwd:get_value(0) + + -- Convert the address to wire format + if address then + return kres.str2ip(address) + end + end +end + +local function lookup_rev(qname) + -- Parse the address from the query name + local address = address_from_reverse_pointer(qname) + + -- Fail if we could not parse the address + if not address then + return + end + + -- Reset the statement + sql_rev:reset() + + -- Bind the address + sql_rev:bind_values(address) + + -- Execute the statement + if sql_rev:step() == sqlite3.ROW then + local hostname = sql_rev:get_value(0) + + -- Convert the hostname to wire format + if hostname then + return todname(hostname) + end + end +end + +-- Function that will try to answer the query +function M.answer() + return function(state, req) + -- Fetch the current query + local query = req:current() + + -- Fetch the query name + local qname = kres.dname2str(query.sname) + + -- Fetch the query type + local qtype = query.stype + + -- Log action + log_debug( + string.format("Called for %s (%d)", qname, qtype) + ) + + local answer = {} + + -- Is this a forward lookup? + if qtype == kres.type.A then + -- Perform a forward lookup + local address = lookup_fwd(qname) + + if address then + answer[qtype] = { rdata = address, ttl = TTL } + end + + -- Or is this a reverse lookup? + elseif qtype == kres.type.PTR then + -- Perform a reverse lookup + local hostname = lookup_rev(qname) + + if hostname then + answer[qtype] = { rdata = hostname, ttl = TTL } + end + end + + -- If we have an answer, use the policy module to send it + if answer then + answer = policy.ANSWER(answer) + + -- Otherwise we send NXDOMAIN + else + answer = policy.DENY + end + + -- Pass the state and request + return answer(state, req) + end +end + +return M diff --git a/config/rootfiles/common/knot-resolver b/config/rootfiles/common/knot-resolver index a30943786..4ac4c4b91 100644 --- a/config/rootfiles/common/knot-resolver +++ b/config/rootfiles/common/knot-resolver @@ -76,6 +76,7 @@ usr/lib/knot-resolver/kres_modules/http/topojson.js usr/lib/knot-resolver/kres_modules/http_doh.lua usr/lib/knot-resolver/kres_modules/http_tls_cert.lua usr/lib/knot-resolver/kres_modules/http_trace.lua +usr/lib/knot-resolver/kres_modules/leases.lua usr/lib/knot-resolver/kres_modules/nsid.so usr/lib/knot-resolver/kres_modules/policy.lua usr/lib/knot-resolver/kres_modules/predict.lua diff --git a/lfs/knot-resolver b/lfs/knot-resolver index cd24639d2..0404f8e05 100644 --- a/lfs/knot-resolver +++ b/lfs/knot-resolver @@ -82,6 +82,10 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) cd $(DIR_APP) && ninja -C builddir/ $(MAKETUNING) cd $(DIR_APP) && ninja -C builddir/ install + # Install the leases module + install -v -m 644 $(DIR_SRC)/config/knot-resolver/leases.lua \ + /usr/lib/knot-resolver/kres_modules/leases.lua + # Create cache directory -mkdir -pv /var/cache/knot-resolver chown kresd:kresd /var/cache/knot-resolver