Three years ago I set up SmartOS as a Home Router which required creating a zone specifically for handling DHCP requests on my network. As part of wanting to have more visibility into my network, I wrote a program to notify me whenever a new DHCP lease was given out by the server.

https://github.com/bahamas10/node-dhcpd-notifier

dhcpd-pushover

Usage

The program works by watching the dhcpd.leases file on a regular interval and looking for leases that haven’t been seen before. When a new lease is seen, a configurable external program is executed with environmental variables set that contain information about the new lease seen. From there, I have it execute a script I wrote to send a pushover notification to my phone.

In order to use this program you need to have node.js on a machine that has read access to the dhcpd.leases file. Then, install dhcpd-notifier from npm.

npm install -g dhcpd-notifier

Create a config file (json) with some basic information for the daemon.

{
  "leases": "/var/db/isc-dhcp/dhcpd.leases",
  "interval": 10,
  "loglevel": "debug",
  "exec": {
    "file": "./new-lease",
    "timeout": 30
  }
}

This tells dhcpd-notifier to read /var/db/isc-dhcp/dhcpd.leases every 10 seconds, log with debug log output, and to execute ./new-lease for every new lease seen, killing the script if it takes more than 30 seconds to execute.

For this example my new-lease script looks something like this, to see a more detailed example see example-script in the repo.

#!/usr/bin/env bash

ip=$DHCPD_NOTIFIER_IP
hostname=${DHCPD_NOTIFIER_HOSTNAME:-unknown}
mac=${DHCPD_NOTIFIER_MAC:-unknown}

title="dhcp lease for \"$hostname\""
msg="$ip - $mac"

PUSHOVER_USER='foo' \
    PUSHOVER_TOKEN='bar' \
    pushover "$title" "$msg"

The pushover command source is at the bottom of this blog post.

Note: bunyan is required to parse the log output in a meaningful way.

$ dhcpd-notifier config.json | bunyan
[2018-04-13T03:31:35.482Z] DEBUG: dhcpd-notifier/53675 on dhcp.rapture.com: loaded config
config: {
  "leases": "/var/db/isc-dhcp/dhcpd.leases",
  "interval": 10,
  "loglevel": "debug",
  "exec": {
    "file": "./new-lease",
    "timeout": 30
  },
  "aliases": {},
  "ignore": []
}
[2018-04-13T03:31:35.486Z]  INFO: dhcpd-notifier/53675 on dhcp.rapture.com: watching file /var/db/isc-dhcp/dhcpd.leases for changes every 10 seconds

And an example lease will look something like this:

[2018-04-13T01:12:24.471Z]  INFO: dhcpd-notifier/37209 on dhcp.rapture.com: new lease: "meka" 10.0.1.225 40:8d:5c:XX:XX:XX (Meka Windows Desktop Ethernet)
[2018-04-13T01:12:24.472Z] DEBUG: dhcpd-notifier/37209 on dhcp.rapture.com: executing "./new-lease"
opts: {
  "env": {
    "DHCPD_NOTIFIER_CONFIG": "/opt/custom/etc/dhcpd-notifier/config.json",
    "LANG": "C",
    "PATH": "/opt/local/sbin:/opt/local/bin:/opt/custom/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    "PWD": "/var/empty",
    "TZ": "UTC",
    "DHCPD_NOTIFIER_LEASE": "{\"ip\":\"10.0.1.225\",\"starts\":\"2018-04-13T01:12:15.000Z\",\"ends\":\"2018-04-13T03:12:15.000Z\",\"cltt\":\"2018-04-13T01:12:15.000Z\",\"binding state\":\"active\",\"next binding state\":\"free\",\"rewind binding state\":\"free\",\"hardware ethernet\":\"40:8d:5c:XX:XX:XX\",\"uid\":\"\\\\001@\\\\215\\\\\\\\\\\\274f>\",\"set vendor-class-identifier\":\"= \\\"MSFT 5.0\\\"\",\"client-hostname\":\"meka\",\"alias\":\"Meka Windows Desktop Ethernet\"}",
    "DHCPD_NOTIFIER_ALIAS": "Meka Windows Desktop Ethernet",
    "DHCPD_NOTIFIER_HOSTNAME": "meka",
    "DHCPD_NOTIFIER_MAC": "40:8d:5c:XX:XX:XX",
    "DHCPD_NOTIFIER_IP": "10.0.1.225"
  },
  "timeout": 30000,
  "encoding": "utf8"
}
[2018-04-13T01:12:25.248Z] DEBUG: dhcpd-notifier/37209 on dhcp.rapture.com: execution succeeded (stderr="")
stdout: {"status":1,"request":"4790324e-8c64-4aad-849f-836317ccf648"}

Extras

pushover

#!/usr/bin/env node
/*
 * Simple pushover CLI
 *
 * Author: Dave Eddy <[email protected]>
 * Date: March 21, 2017
 * License: MIT
 */

var po = new (require('pushover-notifications'))({
  user: process.env.PUSHOVER_USER,
  token: process.env.PUSHOVER_TOKEN,
});

process.argv.shift();
process.argv.shift();

var title = process.argv.shift();
var msg = process.argv.join(' ');

if (!title || !msg) {
  console.error('usage: pushover <title> <msg>');
  process.exit(1);
}

var msg = {
  title: title,
  message: msg
};
po.send(msg, function (err, result) {
  if (err)
    throw err;

  console.log(result);
});