Wildcard DNS entries for DHCP leases

At work we're building web applications and web sites for different customers. We are not relying on a central server for development; every developer has each application or web site running locally on his own machine.

Until recently this setup had a downside: It was not easy to share a link to your installation with a fellow developer because everyone had local host name entries in his /etc/hosts file. Sharing a link to http://project/ did not work.

What we needed were entries at our central DNS server, one for each project and each developer. That's nothing we wanted to do by hand, so an automated solution had to be found.

DHCP

Computers in our network get their IP address dynamically via DHCP. During the DHCP setup process, they send out their preferred Host name which our gateway server - running IPFire - uses to automatically generate DNS entries.

My machine, dubbed mogiedyne, sends this host name in its DHCP request. The dhcpd on the gateway machine writes down this name in the DHCP client lease database which the DNS server - dnsmasq is configured to read (with the --dhcp-leasefile= option).

This gets us automatic DNS entries for the clients. Since the DNS domain name is set to mogic, everyone can get my IP address by sending a DNS request for mogiedyne.mogic.

What's missing now is DNS entries for project.mogiedyne.mogic.

Wildcard DNS entries

Neither dnsmasq nor dhcpd or IPFire support wildcard DNS entries for DHCP leases out of the box, so I had to cook up that myself.

Thanks to a blog post I found out that dhcpd is able to call shell scripts when a DHCP lease is handed out, given back or expires. dnsmasq on the other hand is able to read configuration files from a given directory.

With this knowledge a plan formed in my head:

  1. Whenever a DHCP lease is given out, a script generates a dnsmasq configuration file that contains a wildcard entry for the client that obtained the lease.
  2. dnsmasq is restarted and reads all the client configuration files.
  3. When a lease is returned or expires, the client config file is deleted.

Configuration

dnsmasq

IPFire does not modify dnsmasq's standard configuration file path, so I could use it to tell it about the configuration file directory:

/etc/dnsmasq.conf
conf-dir=/etc/dnsmasq.d/

dhcpd

Now dhcpd had to execute a script whenever a lease changes:

/var/ipfire/dhcp/dhcpd.conf.local
on commit {
  set noname = concat("dhcp-", binary-to-ascii(10, 8, "-", leased-address));
  set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
  set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
  set ClientName = pick-first-value(option host-name, host-decl-name, config-option host-name, noname);
  # log(concat("Commit: IP: ", ClientIP, " Mac: ", ClientMac, " Name: ", ClientName));
  execute("/usr/local/bin/dnsmasq-dhcp-wildcards", "add", ClientIP, ClientName, ClientMac);
}
on release {
  set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
  set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
  # log(concat("Release: IP: ", ClientIP, " Mac: ", ClientMac));
  # cannot get a ClientName here, for some reason that always fails
  execute("/usr/local/bin/dnsmasq-dhcp-wildcards", "del", ClientIP, "", ClientMac);
}
on expiry {
  set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
  # cannot get a ClientMac here, apparently this only works when actually receiving a packet
  # log(concat("Expired: IP: ", ClientIP));
  # cannot get a ClientName here, for some reason that always fails
  execute("/usr/local/bin/dnsmasq-dhcp-wildcards", "del", ClientIP, "", "0");
}

Configuration update script

The last part was the script that writes and deletes the configuration files.

I had to use the IP address in the file name, since the host name was not available when the lease expires or is given back.

/usr/local/bin/dnsmasq-dhcp-wildcards
#!/bin/sh
tld=.example
 
mode=$1
ip=$2
name=$3$tld
 
filename="/etc/dnsmasq.d/wildcard-dhcp-$ip.conf"
#echo $@ >> /tmp/log
if [ "$mode" == add ]; then
    echo "address=/.$name/$ip" > "$filename"
    /etc/init.d/dnsmasq restart &
elif [ "$mode" == del ]; then
    rm "$filename"
    /etc/init.d/dnsmasq restart &
fi

Unfortunately I have to restart dnsmasq when the configuration changes; it does not pick up the changes automatically. This brings some milliseconds in which DNS requests are not answered.

The restart is done in background to not block dhcpd which stops its operation until the script is finished.

Written by Christian Weiske.

Comments? Please send an e-mail.