]> git.ipfire.org Git - ipfire-2.x.git/commitdiff
knot-resolver: Create a prototype for a DHCP leases integration
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 16 May 2026 12:56:46 +0000 (13:56 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 21 May 2026 15:28:01 +0000 (15:28 +0000)
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 <michael.tremer@ipfire.org>
config/knot-resolver/kresd.conf
config/knot-resolver/leases.lua [new file with mode: 0644]
config/rootfiles/common/knot-resolver
lfs/knot-resolver

index 5d77e19c257dc2fc73b6e78f3e23736fa9ba9891..0515796f6cfcdc72668090f03580968d6816f408 100644 (file)
@@ -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 (file)
index 0000000..a78a800
--- /dev/null
@@ -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
index a30943786a2867bf6bb3c6394f52e250224bb089..4ac4c4b913dca7a8fdacaf707af9cc75152512e8 100644 (file)
@@ -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
index cd24639d258ea9055e9d3b79c40673a9afd6f862..0404f8e054f45fa59fd0e7068cbd489d9cded80f 100644 (file)
@@ -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