Skip to content

Instantly share code, notes, and snippets.

@stintel
Last active October 12, 2023 14:41
Show Gist options
  • Save stintel/0ea70c262649b8b122961e9b23ea951d to your computer and use it in GitHub Desktop.
Save stintel/0ea70c262649b8b122961e9b23ea951d to your computer and use it in GitHub Desktop.
PowerDNS DNSdist: forward/reverse DNS lookup from ISC Kea DHCP leases
package.path = package.path .. ";" .. "/path/to/dir/containing/this/script/" .. "?.lua"
require "kea"
local dhcpDomains = newSuffixMatchNode()
dhcpDomains:add("8.b.d.0.1.0.0.2.ip6.arpa")
dhcpDomains:add("0.2.0.192.in-addr.arpa.")
dhcpDomains:add("example.net")
local nmg = newNMG()
nmg:addMask("192.0.2.0/24")
nmg:addMask("2001:0bd8::/32")
addAction(AndRule({NetmaskGroupRule(nmg), SuffixMatchNodeRule(dhcpDomains)}), LuaAction(dhcpLookup))
-- without this dnsdist cannot find some modules
package.path = package.path .. ";" .. "/usr/lib/lua/" .. "?.lua"
local cjson = require("cjson")
local http = require("socket.http")
local ltn12 = require("ltn12")
local keaCtrlAgentUrl = "http://localhost:8000"
local dhcpPostLease4GetAll = {
command = "lease4-get-all",
service = {"dhcp4"}
}
local dhcpPostLease6GetAll = {
command = "lease6-get-all",
service = {"dhcp6"}
}
local function keaCtrlAgentPost(data)
local jsonData = cjson.encode(data)
local response = {}
local res, code, headers, status = http.request {
headers = {
["Content-Type"] = "application/json",
-- Kea control agent does not support chunked transfer encoding
-- set the Content-Length header to disable it
["Content-Length"] = string.len(jsonData),
},
method = "POST",
sink = ltn12.sink.table(response),
source = ltn12.source.string(jsonData),
url = keaCtrlAgentUrl,
}
-- print("keaCtrlAgentPost: " .. jsonData)
if code == 200 then
response = table.concat(response)
return cjson.decode(response)
else
return {}
end
end
local function compressip6(addr)
local result = table.concat(addr, ":")
local max_start, max_zeros, zero_count, zero_start = 0, 0, 0, 0
for i, group in ipairs(addr) do
if group == "0" then
if zero_start == 0 then
zero_start = i
end
zero_count = zero_count + 1
else
if zero_count > max_zeros then
max_zeros = zero_count
max_start = zero_start
end
zero_start = 0
zero_count = 0
end
end
if zero_count > max_zeros then
max_zeros = zero_count
max_start = zero_start
end
if max_zeros > 1 then
local after, before = "", ""
if max_start + max_zeros <= #addr then
after = table.concat(addr, ":", max_start + max_zeros)
end
if max_start > 1 then
before = table.concat(addr, ":", 1, max_start - 1)
end
result = before .. "::" .. after
end
return result
end
local function reverse4(record)
local result = {}
for octet in record:gmatch("%d+") do
table.insert(result, octet)
end
return result[4] .. "." .. result[3] .. "." .. result[2] .. "." .. result[1]
end
local function reverse6(record)
local result = {}
record = string.gsub(record, "%.ip6.arpa%.*$", "")
record = string.gsub(record, "%.", "")
record = string.reverse(record)
for word in record:gmatch("%x%x%x%x") do
table.insert(result, string.format("%0x", tonumber(word, 16)))
end
result = compressip6(result)
return result
end
local function lookupForward(leases, hostname)
for _, entry in pairs(leases) do
for _, lease in pairs(entry.arguments.leases) do
-- print(lease.hostname)
if lease.hostname == hostname or lease.hostname .. "." == hostname then
return lease["ip-address"]
end
end
end
return nil
end
local function lookupReverse(leases, ip)
-- print("lookupReverse(): ip: " .. ip)
for _, entry in pairs(leases) do
for _, lease in pairs(entry.arguments.leases) do
-- print(lease.hostname)
if lease["ip-address"] == ip then
return lease["hostname"]
end
end
end
return nil
end
function dhcpLookup(dq)
if dq == nil then
-- print("dhcpLookup() called with nil argument")
return
end
local qname = dq.qname:toString()
local response = nil
-- print("dhcpLookup(): " .. qname)
if dq.qtype == DNSQType.A then
-- print("dhcpLookup() A " .. qname)
local dhcpLeases4 = keaCtrlAgentPost(dhcpPostLease4GetAll)
response = lookupForward(dhcpLeases4, qname)
elseif dq.qtype == DNSQType.AAAA then
-- print("dhcpLookup() AAAA " .. qname)
local dhcpLeases6 = keaCtrlAgentPost(dhcpPostLease6GetAll)
response = lookupForward(dhcpLeases6, qname)
elseif dq.qtype == DNSQType.PTR then
-- print("dhcpLookup() PTR " .. qname)
if string.match(qname, "in-addr.arpa.") ~= nil then
-- print("dhcpLook() PTR IPv4")
local dhcpLeases4 = keaCtrlAgentPost(dhcpPostLease4GetAll)
response = lookupReverse(dhcpLeases4, reverse4(qname))
elseif qname:gmatch("ip6.arpa.") then
-- print("dhcpLook() PTR IPv6")
local dhcpLeases6 = keaCtrlAgentPost(dhcpPostLease6GetAll)
response = lookupReverse(dhcpLeases6, reverse6(qname))
else
-- print("dhcpLook() PTR invalid")
end
else
-- print("not A nor AAAA but " .. dq.qtype)
return DNSAction.None
end
if response == nil then
return DNSAction.Nxdomain
else
return DNSAction.Spoof, response
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment