dnsmasq: store leases in sqlite database

To avoid dnsmasq writing out the whole leasefile on each request
before replying. This gets slow on high‐latency storage.

Also tweak DNS updates a bit.
This commit is contained in:
Timotej Lazar 2025-04-14 16:09:34 +02:00
parent b6b4a16fd4
commit 8e3772e475
4 changed files with 89 additions and 42 deletions

View file

@ -2,9 +2,10 @@
package:
name:
- dnsmasq
- bind-tools
- krb5
- py3-pexpect
- bind-tools # for DNS updates
- krb5 # for DNS updates
- py3-pexpect # for creating kerberos keytab
- sqlite # for lease DB
- name: Configure kerberos
template:
@ -23,11 +24,11 @@
args:
creates: /etc/krb5.keytab
- name: Copy DNS updater script
- name: Copy DHCP lease script
template:
dest: "/usr/local/bin/dns-update"
src: "dns-update.j2"
mode: 0700
dest: "/usr/local/bin/dnsmasq-script"
src: "dnsmasq-script.j2"
mode: 0755
- name: Configure dnsmasq
template:

View file

@ -18,4 +18,8 @@ interface = {{ interfaces | map(attribute='name') | join(',') }}
dhcp-option = option:dns-server,{{ dns | join(',') }}
dhcp-option = option:ntp-server,{{ ntp | join(',') }}
dhcp-script = /usr/local/bin/dns-update
dhcp-script = /usr/local/bin/dnsmasq-script
dhcp-scriptuser = dnsmasq
# track leases with dnsmasq-script instead
leasefile-ro

View file

@ -1,34 +0,0 @@
#!/bin/sh
# don’t spam the DNS server when starting dnsmasq
if [ -n "${DNSMASQ_INTERFACE}" ] ; then
exit
fi
domain={{ domain }}
ldap_user={{ password.ldap_user }}
ttl=3600
address="${3}"
case "${1}" in
add)
host="${4}"
kinit -k "${ldap_user}"
nsupdate -g <<EOF
update add ${host}.${domain} ${ttl} A ${address}
send
EOF
;;
old)
if [ -n "${DNSMASQ_OLD_HOSTNAME}" -a -n "${DNSMASQ_SUPPLIED_HOSTNAME}" ] ; then
kinit -k "${ldap_user}"
nsupdate -g <<EOF
update del ${DNSMASQ_OLD_HOSTNAME}.${domain}
update add ${DNSMASQ_SUPPLIED_HOSTNAME}.${domain} ${ttl} A ${address}
send
EOF
fi
;;
# TODO del, probably
esac

View file

@ -0,0 +1,76 @@
#!/bin/sh
# This script replaces the dnsmasq leasefile with an sqlite database
# for performance on slow storage, and updates DNS records for
# assigned addresses.
cmd="${1}"
# dnsmasq calls this script on startup for each known lease, ignore it
[ "${cmd}" != "init" ] && [ -z "${DNSMASQ_INTERFACE}" ] && exit 0
# parameters
db=/var/lib/misc/dnsmasq.leases.db
domain={{ domain }}
ldap_user={{ password.ldap_user }}
ttl=3600
# sanitize input
mac="${2//[![:xdigit:]:]/}"
ipv4="${3//[![:digit:].]/}"
hostname="${4//[![:alnum:]_-]/}"
client_id="${DNSMASQ_CLIENT_ID//[![:xdigit:]:]/}"
# construct SQL to query or update leases, and nsupdate script to update DNS records
dns=""
sql=""
case "${cmd}" in
"init")
# init runs as root, so ensure the dnsmasq user can write to database later
touch "${db}"
chown dnsmasq:dnsmasq "${db}"
setfacl -m u:dnsmasq:rwx "$(dirname ${db})"
setfacl -m u:dnsmasq:r "/etc/krb5.keytab" # needed for DNS updates
# ensure the leases table exists, and get all leases from it
sql="CREATE TABLE IF NOT EXISTS leases (
ipv4 TEXT PRIMARY KEY NOT NULL,
mac TEXT NOT NULL,
hostname TEXT NOT NULL,
client_id TEXT NOT NULL,
renewed INTEGER NOT NULL);
SELECT renewed, mac, ipv4, hostname, client_id FROM leases;"
;;
"add" | "old")
# add or update the lease
sql="INSERT INTO leases
VALUES ('${ipv4}', '${mac}', '${hostname:-*}', '${client_id:-*}', unixepoch())
ON CONFLICT(ipv4) DO UPDATE SET renewed = unixepoch();"
# add or update the DNS record
if [ -n "${hostname}" ] ; then
dns="update add ${hostname}.${domain} ${ttl} A ${ipv4}\n"
fi
if [ -n "${DNSMASQ_OLD_HOSTNAME}" ] ; then
dns="${dns}update del ${DNSMASQ_OLD_HOSTNAME}.${domain}\n"
fi
;;
"del")
# delete the lease
sql="DELETE FROM leases WHERE ipv4 = '${ipv4}';"
# TODO probably delete the DNS record
;;
esac
# update lease database
if [ -n "${sql}" ]; then
sqlite3 -separator " " "${db}" "${sql}"
fi
# update DNS records
if [ -n "${dns}" ]; then
kinit -k "${ldap_user}"
echo -e "${dns}send" | nsupdate -g
fi