ISC-DHCPD events triggering native hooks from within a chroot

15 May 2021 - tsp
Last update 15 May 2021
Reading time 5 mins

What this blog post is about

Basically it’s most of a note for myself to document the dhcpd.conf options that I’m using …

ISC-DHCP - one of the most used DHCP servers and the DHCP server of choice on FreeBSD in my opinion - supports calling external hooks while committing, expiring and releasing leases. This might be interesting to run external accounting or - as in my case - triggering some automation events. I’m personally using this for some different scenarios:

To make this process more modular I’m injecting the join and leave events onto the central message broker. I’m currently using RabbitMQ as my central message broker since I’m used to this solution and it has proven to work in a solid way - later on I plan to move on to an distributed (i.e. not running on a single server or cluster to remove the single point of failure) message broker solution. This is done using a small ANSI C native binary - currently using an external lightweight MQTT library though I also plan to remove this external dependency. I’m currently not using AMQP for this purpose since I wanted to have a small utility that has no external dependencies except the libc. This has been chosen since the utility I’m running is executed in the chroot of dhcpd and most AMQP client libraries do have somewhat more complex dependency chains.

ANSI C mqttpublish utility

Currently this depends on the small MQTT-C library that’s licensed under MIT license. I’ve somewhat modified the code and implemented a mini utility based on their simple publish example that allows me to publish an DHCP event including IP, MAC address and hostname to an local MQTT broker.

After the utility has been built using gmakethe following utilities have been symlinked into the chroot of dhcpd:

Configuration

Now I’ve added the on commit, on release and on expiry handlers to the /usr/local/etc/dhcpd.conf. These handlers convert the lease addresses from binary into a string format, output the MAC address in the way I’d expect it to be written and choose the best fit for the hostname for my type of usage. This information is passed to the mqttpublish utility together with the IP address of the broker, the port to be used, the topic to be published to as well as the username and password for the MQTT broker. This user has only been granted rights to publish into network/dhcp/+ topics and has not been assigned any read permissions.

on commit {
        set clientip = binary-to-ascii(10,8,".",leased-address);
        set clientmac = concat (
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,1,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,2,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,3,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,4,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,5,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,6,1))),2)
        );
        set clientname = pick-first-value(host-decl-name, option fqdn.hostname, option host-name, "UNKNOWN");
        execute("/bin/mqttpublish", "192.0.2.1", "1883", "network/dhcp/commit", "dhcpevents", "PASSWORD", clientip, clientmac, clientname);
}
on release {
        set clientip = binary-to-ascii(10,8,".",leased-address);
        set clientmac = concat (
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,1,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,2,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,3,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,4,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,5,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,6,1))),2)
        );
        set clientname = pick-first-value(host-decl-name, option fqdn.hostname, option host-name, "UNKNOWN");
        execute("/bin/mqttpublish", "192.0.2.1", "1883", "network/dhcp/release", "dhcpevents", "PASSWORD", clientip, clientmac, clientname);
}
on expiry {
        set clientip = binary-to-ascii(10,8,".",leased-address);
        set clientmac = concat (
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,1,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,2,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,3,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,4,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,5,1))),2), ":",
                suffix (concat ("0", binary-to-ascii (16, 8, "", substring(hardware,6,1))),2)
        );
        set clientname = pick-first-value(host-decl-name, option fqdn.hostname, option host-name, "UNKNOWN");
        execute("/bin/mqttpublish", "192.0.2.1", "1883", "network/dhcp/expire", "dhcpevents", "PASSWORD", clientip, clientmac, clientname);
}

During debugging it has been really useful to launch dhcpd in foreground. This can easily be achieved by setting the -d and -f flags for dhcpd_flags in your /etc/rc.conf and launching the daemon. One could of course also start the daemon manually.

dhcpd_flags="-d -f"

This article is tagged:


Data protection policy

Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)

This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/

Valid HTML 4.01 Strict Powered by FreeBSD IPv6 support