Skip to content

Instantly share code, notes, and snippets.

@nhalstead
Last active March 15, 2019 12:47
Show Gist options
  • Save nhalstead/b020f666295669b8b375dc1951e50d8f to your computer and use it in GitHub Desktop.
Save nhalstead/b020f666295669b8b375dc1951e50d8f to your computer and use it in GitHub Desktop.
PFSense DHCP Record JSON API Addon, Goto your Web Interface and Add the following content to add a JSON API to get all of the DHCP Records stored in the DHCP Server Service.

PFSense DHCP Record JSON API

  1. Goto http://[Router IP]/diag_edit.php and Paste /usr/local/www/status_dhcp_leases_api.php in the box of Path to file to be edited
  2. Paste Code
  3. Click Save
  4. Then goto http://[Router IP]/status_dhcp_leases_api.php

Done!

Keep in mind I have not added Authentication in this as it redirects to a Web Interface and does not return a standard 301 redirect, it just returns HTML.

This File has just been moditified to have an output of JSON NOT HTML and does not have any extra code added to it.

<?php
##|+PRIV
##|*IDENT=page-status-dhcpleases-api
##|*NAME=Status: DHCP leases API
##|*DESCR=Allow access to the 'Status: DHCP leases' via a JSON API.
##|*MATCH=status_dhcp_leases_api.php*
##|-PRIV
//require_once("guiconfig.inc");
require_once("config.inc");
$leasesfile = "{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases";
// Load MAC-Manufacturer table
$mac_man = load_mac_manufacturer_table();
function leasecmp($a, $b) {
return strcmp($a[$_REQUEST['order']], $b[$_REQUEST['order']]);
}
function adjust_gmt($dt) {
global $config;
$dhcpd = $config['dhcpd'];
foreach ($dhcpd as $dhcpditem) {
$dhcpleaseinlocaltime = $dhcpditem['dhcpleaseinlocaltime'];
if ($dhcpleaseinlocaltime == "yes") {
break;
}
}
if ($dhcpleaseinlocaltime == "yes") {
$ts = strtotime($dt . " GMT");
if ($ts !== false) {
return strftime("%Y/%m/%d %H:%M:%S", $ts);
}
}
/* If we did not need to convert to local time or the conversion failed, just return the input. */
return $dt;
}
function remove_duplicate($array, $field) {
foreach ($array as $sub) {
$cmp[] = $sub[$field];
}
$unique = array_unique(array_reverse($cmp, true));
foreach ($unique as $k => $rien) {
$new[] = $array[$k];
}
return $new;
}
$awk = "/usr/bin/awk";
/* this pattern sticks comments into a single array item */
$cleanpattern = "'{ gsub(\"#.*\", \"\");} { gsub(\";\", \"\"); print;}'";
/* We then split the leases file by } */
$splitpattern = "'BEGIN { RS=\"}\";} {for (i=1; i<=NF; i++) printf \"%s \", \$i; printf \"}\\n\";}'";
/* stuff the leases file in a proper format into a array by line */
exec("/bin/cat {$leasesfile} | {$awk} {$cleanpattern} | {$awk} {$splitpattern}", $leases_content);
$leases_count = count($leases_content);
exec("/usr/sbin/arp -an", $rawdata);
$arpdata_ip = array();
$arpdata_mac = array();
foreach ($rawdata as $line) {
$elements = explode(' ', $line);
if ($elements[3] != "(incomplete)") {
$arpent = array();
$arpdata_ip[] = trim(str_replace(array('(', ')'), '', $elements[1]));
$arpdata_mac[] = strtolower(trim($elements[3]));
}
}
unset($rawdata);
$pools = array();
$leases = array();
$i = 0;
$l = 0;
$p = 0;
// Translate these once so we don't do it over and over in the loops below.
$online_string = gettext("online");
$offline_string = gettext("offline");
$active_string = gettext("active");
$expired_string = gettext("expired");
$reserved_string = gettext("reserved");
$dynamic_string = gettext("dynamic");
$static_string = gettext("static");
// Put everything together again
foreach ($leases_content as $lease) {
/* split the line by space */
$data = explode(" ", $lease);
/* walk the fields */
$f = 0;
$fcount = count($data);
/* with less than 20 fields there is nothing useful */
if ($fcount < 20) {
$i++;
continue;
}
while ($f < $fcount) {
switch ($data[$f]) {
case "failover":
$pools[$p]['name'] = trim($data[$f+2], '"');
$pools[$p]['name'] = "{$pools[$p]['name']} (" . convert_friendly_interface_to_friendly_descr(substr($pools[$p]['name'], 5)) . ")";
$pools[$p]['mystate'] = $data[$f+7];
$pools[$p]['peerstate'] = $data[$f+14];
$pools[$p]['mydate'] = $data[$f+10];
$pools[$p]['mydate'] .= " " . $data[$f+11];
$pools[$p]['peerdate'] = $data[$f+17];
$pools[$p]['peerdate'] .= " " . $data[$f+18];
$p++;
$i++;
continue 3;
case "lease":
$leases[$l]['ip'] = $data[$f+1];
$leases[$l]['type'] = $dynamic_string;
$f = $f+2;
break;
case "starts":
$leases[$l]['start'] = $data[$f+2];
$leases[$l]['start'] .= " " . $data[$f+3];
$f = $f+3;
break;
case "ends":
if ($data[$f+1] == "never") {
// Quote from dhcpd.leases(5) man page:
// If a lease will never expire, date is never instead of an actual date.
$leases[$l]['end'] = gettext("Never");
$f = $f+1;
} else {
$leases[$l]['end'] = $data[$f+2];
$leases[$l]['end'] .= " " . $data[$f+3];
$f = $f+3;
}
break;
case "tstp":
$f = $f+3;
break;
case "tsfp":
$f = $f+3;
break;
case "atsfp":
$f = $f+3;
break;
case "cltt":
$f = $f+3;
break;
case "binding":
switch ($data[$f+2]) {
case "active":
$leases[$l]['act'] = $active_string;
break;
case "free":
$leases[$l]['act'] = $expired_string;
$leases[$l]['online'] = $offline_string;
break;
case "backup":
$leases[$l]['act'] = $reserved_string;
$leases[$l]['online'] = $offline_string;
break;
}
$f = $f+1;
break;
case "next":
/* skip the next binding statement */
$f = $f+3;
break;
case "rewind":
/* skip the rewind binding statement */
$f = $f+3;
break;
case "hardware":
$leases[$l]['mac'] = $data[$f+2];
/* check if it's online and the lease is active */
if (in_array($leases[$l]['ip'], $arpdata_ip)) {
$leases[$l]['online'] = $online_string;
} else {
$leases[$l]['online'] = $offline_string;
}
$f = $f+2;
break;
case "client-hostname":
if ($data[$f+1] <> "") {
$leases[$l]['hostname'] = preg_replace('/"/', '', $data[$f+1]);
} else {
$hostname = gethostbyaddr($leases[$l]['ip']);
if ($hostname <> "") {
$leases[$l]['hostname'] = $hostname;
}
}
$f = $f+1;
break;
case "uid":
$f = $f+1;
break;
}
$f++;
}
$l++;
$i++;
/* slowly chisel away at the source array */
array_shift($leases_content);
}
/* remove the old array */
unset($lease_content);
/* remove duplicate items by mac address */
if (count($leases) > 0) {
$leases = remove_duplicate($leases, "ip");
}
if (count($pools) > 0) {
$pools = remove_duplicate($pools, "name");
asort($pools);
}
$got_cid = false;
foreach ($config['interfaces'] as $ifname => $ifarr) {
if (is_array($config['dhcpd'][$ifname]) &&
is_array($config['dhcpd'][$ifname]['staticmap'])) {
foreach ($config['dhcpd'][$ifname]['staticmap'] as $idx => $static) {
if (!empty($static['mac']) || !empty($static['cid'])) {
$slease = array();
$slease['ip'] = $static['ipaddr'];
$slease['type'] = $static_string;
if (!empty($static['cid'])) {
$slease['cid'] = $static['cid'];
$got_cid = true;
}
$slease['mac'] = $static['mac'];
$slease['if'] = $ifname;
$slease['start'] = "";
$slease['end'] = "";
$slease['hostname'] = $static['hostname'];
$slease['descr'] = $static['descr'];
$slease['act'] = $static_string;
$slease['online'] = in_array(strtolower($slease['mac']), $arpdata_mac) ? $online_string : $offline_string;
$slease['staticmap_array_index'] = $idx;
$leases[] = $slease;
}
}
}
}
$dhcp_leases_subnet_counter = array(); //array to sum up # of leases / subnet
$iflist = get_configured_interface_with_descr(); //get interface descr for # of leases
$no_leases_displayed = true;
// Start Foreach and Data Output
$out = array();
foreach ($leases as $data){
if ($data['act'] != $active_string && $data['act'] != $static_string && $_REQUEST['all'] != 1) {
continue;
}
$no_leases_displayed = false;
if ($data['act'] == $active_string) {
/* Active DHCP Lease */
$staticLease = false;
} elseif ($data['act'] == $expired_string) {
// Skip Expired Lease
continue;
} else {
/* Static Mapping */
$staticLease = true;
}
if ($data['act'] != $static_string) {
foreach ($config['dhcpd'] as $dhcpif => $dhcpifconf) {
if (!is_array($dhcpifconf['range'])) {
continue;
}
if (is_inrange_v4($data['ip'], $dhcpifconf['range']['from'], $dhcpifconf['range']['to'])) {
$data['if'] = $dhcpif;
$dlskey = $dhcpif . "-" . $dhcpifconf['range']['from'];
$dhcp_leases_subnet_counter[$dlskey]['dhcpif'] = $dhcpif;
$dhcp_leases_subnet_counter[$dlskey]['from'] = $dhcpifconf['range']['from'];
$dhcp_leases_subnet_counter[$dlskey]['to'] = $dhcpifconf['range']['to'];
$dhcp_leases_subnet_counter[$dlskey]['count'] += 1;
break;
}
// Check if the IP is in the range of any DHCP pools
if (is_array($dhcpifconf['pool'])) {
foreach ($dhcpifconf['pool'] as $dhcppool) {
if (is_array($dhcppool['range'])) {
if (is_inrange_v4($data['ip'], $dhcppool['range']['from'], $dhcppool['range']['to'])) {
$data['if'] = $dhcpif;
$dlskey = $dhcpif . "-" . $dhcppool['range']['from'];
$dhcp_leases_subnet_counter[$dlskey]['dhcpif'] = $dhcpif;
$dhcp_leases_subnet_counter[$dlskey]['from'] = $dhcppool['range']['from'];
$dhcp_leases_subnet_counter[$dlskey]['to'] = $dhcppool['range']['to'];
$dhcp_leases_subnet_counter[$dlskey]['count'] += 1;
break 2;
}
}
}
}
}
}
$mac = $data['mac'];
$mac_hi = strtoupper($mac[0] . $mac[1] . $mac[3] . $mac[4] . $mac[6] . $mac[7]);
$out[] = array(
"ip" => $data['ip'],
"is_static" => ($data['type'] == "static")?true:false,
"mac" => $mac,
"hostname" => $data['hostname'],
"desc" => $data['descr'],
"active_since" => adjust_gmt($data['start']),
"expire" => adjust_gmt($data['end']),
"online" => ($data['online'] == "online")?true:false,
"active" => ($data['act'] == "active")?true:false
);
} // Close Foreach ln: 258
if ($no_leases_displayed){
// Respond with Nothing.
header('Content-Type: application/json');
echo json_encode(array());
}
else {
// Respond with the Payload.
header('Content-Type: application/json');
echo json_encode($out);
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment