diff --git a/plist b/plist
index d3bfc00f9..7c15b258b 100644
--- a/plist
+++ b/plist
@@ -281,6 +281,9 @@
/usr/local/opnsense/mvc/app/controllers/OPNsense/Cron/IndexController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Cron/ItemController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Cron/forms/dialogEdit.xml
+/usr/local/opnsense/mvc/app/controllers/OPNsense/DHCP/Api/Leases4Controller.php
+/usr/local/opnsense/mvc/app/controllers/OPNsense/DHCP/Api/ServiceController.php
+/usr/local/opnsense/mvc/app/controllers/OPNsense/DHCP/Leases4Controller.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Diagnostics/ActivityController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/ActivityController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/DnsController.php
@@ -711,6 +714,7 @@
/usr/local/opnsense/mvc/app/views/OPNsense/Core/reboot.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Core/service.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Cron/index.volt
+/usr/local/opnsense/mvc/app/views/OPNsense/DHCP/leases4.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/arp.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/dns_diagnostics.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/fw_log.volt
@@ -2007,7 +2011,6 @@
/usr/local/www/services_ntpd_pps.php
/usr/local/www/services_opendns.php
/usr/local/www/services_router_advertisements.php
-/usr/local/www/status_dhcp_leases.php
/usr/local/www/status_dhcpv6_leases.php
/usr/local/www/status_habackup.php
/usr/local/www/status_interfaces.php
diff --git a/src/etc/inc/plugins.inc.d/dhcpd.inc b/src/etc/inc/plugins.inc.d/dhcpd.inc
index 27fd8d7ba..7eaa475d0 100644
--- a/src/etc/inc/plugins.inc.d/dhcpd.inc
+++ b/src/etc/inc/plugins.inc.d/dhcpd.inc
@@ -1856,7 +1856,7 @@ function dhcpd_dhcrelay6_configure($verbose = false)
service_log("done.\n", $verbose);
}
-function dhcpd_staticmap($domain_fallback = 'not.found', $ifconfig_details = null, $valid_addresses = true, $proto = null)
+function dhcpd_staticmap($proto = null, $domain_fallback = 'not.found', $ifconfig_details = null, $valid_addresses = true)
{
$staticmap = [];
foreach (empty($proto) ? [4, 6] : [$proto] as $inet) {
diff --git a/src/etc/inc/plugins.inc.d/dnsmasq.inc b/src/etc/inc/plugins.inc.d/dnsmasq.inc
index edfdb3cc1..d9935f096 100644
--- a/src/etc/inc/plugins.inc.d/dnsmasq.inc
+++ b/src/etc/inc/plugins.inc.d/dnsmasq.inc
@@ -258,7 +258,7 @@ function _dnsmasq_add_host_entries()
$domain = $config['dnsmasq']['regdhcpdomain'];
}
- foreach (plugins_run('static_mapping', [$domain, legacy_interfaces_details()]) as $map) {
+ foreach (plugins_run('static_mapping', [null, $domain, legacy_interfaces_details()]) as $map) {
foreach ($map as $host) {
if (empty($host['hostname'])) {
/* cannot register without a hostname */
diff --git a/src/etc/inc/plugins.inc.d/unbound.inc b/src/etc/inc/plugins.inc.d/unbound.inc
index 5c2b2cc45..f6f932c15 100644
--- a/src/etc/inc/plugins.inc.d/unbound.inc
+++ b/src/etc/inc/plugins.inc.d/unbound.inc
@@ -544,7 +544,7 @@ function unbound_add_host_entries($ifconfig_details)
}
if (!empty($general['regdhcpstatic'])) {
- foreach (plugins_run('static_mapping', [$config['system']['domain'], $ifconfig_details]) as $map) {
+ foreach (plugins_run('static_mapping', [null, $config['system']['domain'], $ifconfig_details]) as $map) {
foreach ($map as $host) {
if (empty($host['hostname'])) {
/* cannot register without a hostname */
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/DHCP/Api/Leases4Controller.php b/src/opnsense/mvc/app/controllers/OPNsense/DHCP/Api/Leases4Controller.php
new file mode 100644
index 000000000..1f7fac2cc
--- /dev/null
+++ b/src/opnsense/mvc/app/controllers/OPNsense/DHCP/Api/Leases4Controller.php
@@ -0,0 +1,167 @@
+sessionClose();
+ $inactive = $this->request->get('inactive');
+ $selected_interfaces = $this->request->get('selected_interfaces');
+ $backend = new Backend();
+ $config = Config::getInstance()->object();
+ $online = [];
+
+ /* get ARP data to match on */
+ $arp_data = json_decode($backend->configdRun('interface list arp json'), true);
+
+ foreach ($arp_data as $arp_entry) {
+ if (!$arp_entry['expired']) {
+ array_push($online, $arp_entry['mac'], $arp_entry['ip']);
+ }
+ }
+
+ /* get configured static leases */
+ $sleases = json_decode($backend->configdRun('dhcpd list static 4'), true);
+
+ /* include inactive leases if requested */
+ $leases = json_decode($backend->configdpRun('dhcpd list leases', [$inactive]), true);
+ foreach ($leases as $idx => $lease) {
+ /* set defaults */
+ $leases[$idx]['type'] = 'dynamic';
+ $leases[$idx]['status'] = 'offline';
+ $leases[$idx]['descr'] = '';
+ $leases[$idx]['mac'] = '';
+ $leases[$idx]['starts'] = '';
+ $leases[$idx]['ends'] = '';
+ $leases[$idx]['hostname'] = '';
+ $leases[$idx]['state'] = $lease['binding'] == 'free' ? 'expired' : $lease['binding'];
+
+ if (array_key_exists('hardware', $lease)) {
+ $mac = $lease['hardware']['mac-address'];
+ $leases[$idx]['mac'] = $mac;
+ $leases[$idx]['status'] = in_array(strtolower($lease['address']), $online) ? 'online' : 'offline';
+ unset($leases[$idx]['hardware']);
+ }
+
+ if (array_key_exists('starts', $lease)) {
+ $leases[$idx]['starts'] = date('Y/m/d H:i:s', $lease['starts']);
+ }
+
+ if (array_key_exists('ends', $lease)) {
+ $leases[$idx]['ends'] = date('Y/m/d H:i:s', $lease['ends']);
+ }
+
+ if (array_key_exists('client-hostname', $lease)) {
+ $leases[$idx]['hostname'] = $lease['client-hostname'];
+ }
+ }
+
+ /* handle static leases */
+ $statics = [];
+ foreach ($sleases["dhcpd"] as $slease) {
+ $static = [];
+ $static['address'] = $slease['ipaddr'];
+ $static['type'] = 'static';
+ $static['mac'] = $slease['mac'];
+ $static['starts'] = '';
+ $static['ends'] = '';
+ $static['hostname'] = $slease['hostname'];
+ $static['descr'] = $slease['descr'];
+ $static['state'] = 'active';
+ $static['status'] = in_array(strtolower($static['mac']), $online) ? 'online' : 'offline';
+ $statics[] = $static;
+ }
+
+ $leases = array_merge($leases, $statics);
+
+ $mac_man = json_decode($backend->configdRun('interface list macdb json'), true);
+ $interfaces = [];
+ foreach ($leases as $idx => $lease) {
+ /* include manufacturer info */
+ $leases[$idx]['man'] = '';
+ if ($lease['mac'] != '') {
+ $mac_hi = strtoupper(substr(str_replace(':', '', $lease['mac']),0, 6));
+ $leases[$idx]['man'] = $mac_man[$mac_hi];
+ }
+
+ /* include interface */
+ $leases[$idx]['if_descr'] = '';
+ $leases[$idx]['if'] = '';
+ foreach ($config->dhcpd->children() as $dhcpif => $dhcpifconf) {
+ $if = $config->interfaces->$dhcpif;
+ if (!empty((string)$if->ipaddr) && Util::isIpAddress((string)$if->ipaddr)) {
+ if (Util::isIPInCIDR($lease['address'], (string)$if->ipaddr . '/' . (string)$if->subnet)) {
+ $intf = (string)$if->descr;
+ $leases[$idx]['if_descr'] = $intf;
+ $leases[$idx]['if'] = $dhcpif;
+
+ if (!array_key_exists($dhcpif, $interfaces)) {
+ $interfaces[$dhcpif] = $intf;
+ }
+ }
+ }
+ }
+ }
+
+ $response = $this->searchRecordsetBase($leases, null, 'address', function ($key) use ($selected_interfaces) {
+ if (empty($selected_interfaces) || in_array($key['if'], $selected_interfaces)) {
+ return true;
+ }
+
+ return false;
+ });
+
+ /* present relevant interfaces to the view so they can be sorted on */
+ $response['interfaces'] = $interfaces;
+ return $response;
+ }
+
+ public function delLeaseAction($ip)
+ {
+ $result = ["result" => "failed"];
+
+ if ($this->request->isPost()) {
+ $this->sessionClose();
+ $response = json_decode((new Backend())->configdpRun("dhcpd remove lease", [$ip]), true);
+ if ($response["removed_leases"] != "0") {
+ $result["result"] = "deleted";
+ }
+ }
+
+
+ return $result;
+ }
+}
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/DHCP/Api/ServiceController.php b/src/opnsense/mvc/app/controllers/OPNsense/DHCP/Api/ServiceController.php
new file mode 100644
index 000000000..92785bd8c
--- /dev/null
+++ b/src/opnsense/mvc/app/controllers/OPNsense/DHCP/Api/ServiceController.php
@@ -0,0 +1,101 @@
+configdRun('service status dhcpd'));
+
+ if (strpos($response, 'is running') > 0) {
+ $status = 'running';
+ } elseif (strpos($response, 'not running') > 0) {
+ $status = 'stopped';
+ } else {
+ $status = 'disabled';
+ }
+
+ return [
+ 'status' => $status,
+ 'widget' => [
+ 'caption_stop' => gettext("stop service"),
+ 'caption_start' => gettext("start service"),
+ 'caption_restart' => gettext("restart service")
+ ]
+ ];
+ }
+
+ public function startAction()
+ {
+ $result = ['status' => 'failed'];
+
+ if ($this->request->isPost()) {
+ $this->sessionClose();
+ $response = trim((new Backend())->configdRun('service start dhcpd'));
+ return ['status' => $response];
+ }
+
+ return $result;
+ }
+
+ public function stopAction()
+ {
+ $result = ['status' => 'failed'];
+
+ if ($this->request->isPost()) {
+ $this->sessionClose();
+ $response = trim((new Backend())->configdRun('service stop dhcpd'));
+ return ['status' => $response];
+ }
+
+ return $result;
+ }
+
+ public function restartAction()
+ {
+ $result = ['status' => 'failed'];
+
+ if ($this->request->isPost()) {
+ $this->sessionClose();
+ $response = trim((new Backend())->configdRun('service restart dhcpd'));
+ return ['status' => $response];
+ }
+
+ return $result;
+ }
+}
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/DHCP/Leases4Controller.php b/src/opnsense/mvc/app/controllers/OPNsense/DHCP/Leases4Controller.php
new file mode 100644
index 000000000..13d6ca02c
--- /dev/null
+++ b/src/opnsense/mvc/app/controllers/OPNsense/DHCP/Leases4Controller.php
@@ -0,0 +1,39 @@
+view->pick('OPNsense/DHCP/leases4');
+ }
+}
diff --git a/src/opnsense/mvc/app/library/OPNsense/Firewall/Util.php b/src/opnsense/mvc/app/library/OPNsense/Firewall/Util.php
index d629a0c84..6d6ba016a 100644
--- a/src/opnsense/mvc/app/library/OPNsense/Firewall/Util.php
+++ b/src/opnsense/mvc/app/library/OPNsense/Firewall/Util.php
@@ -339,6 +339,59 @@ class Util
return md5(json_encode($rule));
}
+ private static function isIPv4InCIDR($ip, $cidr)
+ {
+ list ($subnet, $bits) = explode('/', $cidr);
+ if ($bits === null) {
+ $bits = 32;
+ }
+ $ip = ip2long($ip);
+ $subnet = ip2long($subnet);
+ $mask = -1 << (32 - $bits);
+ $subnet &= $mask;
+ return ($ip & $mask) == $subnet;
+ }
+
+ private static function isIPv6InCIDR($ip, $cidr)
+ {
+ $inet_to_bits = function($ip) {
+ $split = str_split($ip);
+ $bin_ip = '';
+ foreach ($split as $char) {
+ $bin_ip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
+ }
+ return $bin_ip;
+ };
+
+ $in_addr = inet_pton($ip);
+ $bin_ip = $inet_to_bits($in_addr);
+
+ list ($net, $maskbits) = explode('/', $cidr);
+ $net = inet_pton($net);
+ $bin_net = $inet_to_bits($net);
+
+ $ip_net_bits = substr($bin_ip, 0, $maskbits);
+ $net_bits = substr($bin_net, 0, $maskbits);
+
+ return $ip_net_bits === $net_bits;
+ }
+
+ /**
+ * returns whether a given IP (v4 or v6) is in a CIDR block
+ */
+ public static function isIPInCIDR($ip, $cidr)
+ {
+ if (!self::isIpAddress($ip)) {
+ return false;
+ }
+
+ if (str_contains($ip, ':')) {
+ return self::isIPv6InCIDR($ip, $cidr);
+ }
+
+ return self::isIPv4InCIDR($ip, $cidr);
+ }
+
/**
* convert ipv4 cidr to netmask e.g. 24 --> 255.255.255.0
* @param int $bits ipv4 bits
diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml b/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml
index 26e353ba9..0276d0ee5 100644
--- a/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml
+++ b/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml
@@ -455,6 +455,13 @@
services_dhcp.php*
+
+ Services: DHCP Server: Leases
+
+ ui/dhcp/leases4
+ api/dhcp/leases4/*
+
+ Services: DHCPv6 Relay
diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml
index b7bcfd6fb..0840d62fb 100644
--- a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml
+++ b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml
@@ -186,9 +186,7 @@
-
-
-
+
diff --git a/src/opnsense/mvc/app/views/OPNsense/DHCP/leases4.volt b/src/opnsense/mvc/app/views/OPNsense/DHCP/leases4.volt
new file mode 100644
index 000000000..6574edcc7
--- /dev/null
+++ b/src/opnsense/mvc/app/views/OPNsense/DHCP/leases4.volt
@@ -0,0 +1,173 @@
+{#
+ # Copyright (c) 2023 Deciso B.V.
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without modification,
+ # are permitted provided that the following conditions are met:
+ #
+ # 1. Redistributions of source code must retain the above copyright notice,
+ # this list of conditions and the following disclaimer.
+ #
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
+ # this list of conditions and the following disclaimer in the documentation
+ # and/or other materials provided with the distribution.
+ #
+ # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ # POSSIBILITY OF SUCH DAMAGE.
+ #}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ lang._('Interface') }}
+
{{ lang._('IP Address') }}
+
{{ lang._('MAC Address') }}
+
{{ lang._('Hostname') }}
+
{{ lang._('Description') }}
+
{{ lang._('Start') }}
+
{{ lang._('End') }}
+
{{ lang._('Status') }}
+
{{ lang._('State') }}
+
{{ lang._('Lease Type') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/opnsense/scripts/dhcp/get_leases.py b/src/opnsense/scripts/dhcp/get_leases.py
index efe020b25..612829fca 100755
--- a/src/opnsense/scripts/dhcp/get_leases.py
+++ b/src/opnsense/scripts/dhcp/get_leases.py
@@ -38,10 +38,15 @@ import ujson
app_params = {'inactive': '0'}
params.update_params(app_params)
+last_leases = dict()
result = list()
dhcpdleases = watchers.dhcpd.DHCPDLease()
for lease in dhcpdleases.watch():
- if 'ends' not in lease or lease['ends'] is None or lease['ends'] > time.time() or app_params['inactive'] != '0':
+ # only the last entries for a given IP are relevant
+ last_leases[lease['address']] = lease
+
+for lease in last_leases.values():
+ if ('ends' in lease and lease['ends'] is not None and lease['ends'] > time.time()) or app_params['inactive'] != '0':
result.append(lease)
print (ujson.dumps(result))
diff --git a/src/opnsense/service/conf/actions.d/actions_dhcpd.conf b/src/opnsense/service/conf/actions.d/actions_dhcpd.conf
index de5563350..218c10b1a 100644
--- a/src/opnsense/service/conf/actions.d/actions_dhcpd.conf
+++ b/src/opnsense/service/conf/actions.d/actions_dhcpd.conf
@@ -1,9 +1,15 @@
[list.leases]
-command:/usr/local/opnsense/scripts/dhcp/get_leases.py /inactive %s
-parameters:%s
+command:/usr/local/opnsense/scripts/dhcp/get_leases.py
+parameters:/inactive %s
type:script_output
message:list dhcp leases %s
+[list.static]
+command:/usr/local/sbin/pluginctl -r static_mapping
+parameters:%s
+type:script_output
+message: list dhcp static mappings %s
+
[update.prefixes]
command:/usr/local/opnsense/scripts/dhcp/prefixes.php
parameters:
diff --git a/src/opnsense/site-python/watchers/dhcpd.py b/src/opnsense/site-python/watchers/dhcpd.py
index d58f15dee..41f832186 100644
--- a/src/opnsense/site-python/watchers/dhcpd.py
+++ b/src/opnsense/site-python/watchers/dhcpd.py
@@ -76,6 +76,8 @@ class DHCPDLease(object):
field_value = parts[1].split('"')[1]
elif field_name == 'set' and len(parts) >= 4 and parts[1] == 'hostname-override' and parts[3].find('"') > -1:
hostname_override = parts[3].split('"')[1]
+ elif field_name == 'binding' and len(parts) >= 3 and parts[1] == 'state':
+ field_value = parts[2].strip(';')
if field_value is not None:
lease[field_name] = field_value
diff --git a/src/www/services_dhcp.php b/src/www/services_dhcp.php
index 4c430102a..4af036f29 100644
--- a/src/www/services_dhcp.php
+++ b/src/www/services_dhcp.php
@@ -55,7 +55,7 @@ function reconfigure_dhcpd()
clear_subsystem_dirty('staticmaps');
}
-$config_copy_fieldsnames = array('enable', 'staticarp', 'failover_peerip', 'failover_split', 'dhcpleaseinlocaltime','descr',
+$config_copy_fieldsnames = array('enable', 'staticarp', 'failover_peerip', 'failover_split', 'descr',
'defaultleasetime', 'maxleasetime', 'gateway', 'domain', 'domainsearchlist', 'denyunknown','ignoreuids', 'ddnsdomain',
'ddnsdomainprimary', 'ddnsdomainkeyname', 'ddnsdomainkey', 'ddnsdomainalgorithm', 'ddnsupdate', 'mac_allow',
'mac_deny', 'tftp', 'bootfilename', 'ldap', 'netboot', 'nextserver', 'filename', 'filename32', 'filename64',
@@ -869,19 +869,6 @@ include("head.inc");
-
-
=gettext("Time format change"); ?>
-
- />
- =gettext("Change DHCP display lease time from UTC to local time."); ?>
-
-
- =gettext("Warning: By default DHCP leases are displayed in UTC time. By checking this " .
- "box DHCP lease time will be displayed in local time and set to time zone selected. This " .
- "will be used for all DHCP interfaces lease time."); ?>
-
-
-
diff --git a/src/www/status_dhcp_leases.php b/src/www/status_dhcp_leases.php
deleted file mode 100644
index 80089ea12..000000000
--- a/src/www/status_dhcp_leases.php
+++ /dev/null
@@ -1,445 +0,0 @@
-
- * Copyright (C) 2003-2004 Manuel Kasper
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
- * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-require_once("guiconfig.inc");
-require_once("config.inc");
-require_once("interfaces.inc");
-require_once("plugins.inc.d/dhcpd.inc");
-
-function adjust_utc($dt)
-{
- foreach (config_read_array('dhcpd') as $dhcpd) {
- if (!empty($dhcpd['dhcpleaseinlocaltime'])) {
- /* we want local time, so specify this is actually UTC */
- return strftime('%Y/%m/%d %H:%M:%S', strtotime("{$dt} UTC"));
- }
- }
-
- /* lease time is in UTC, here just pretend it's the correct time */
- return strftime('%Y/%m/%d %H:%M:%S UTC', strtotime($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;
-}
-
-$interfaces = legacy_config_get_interfaces(array('virtual' => false));
-
-if ($_SERVER['REQUEST_METHOD'] === 'GET') {
- $leases_content = dhcpd_leases(4);
- $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;
-
- // 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 then 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";
- $f = $f+2;
- break;
- case "starts":
- $leases[$l]['start'] = $data[$f+2];
- $leases[$l]['start'] .= " " . $data[$f+3];
- $f = $f+3;
- break;
- case "ends":
- $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";
- break;
- case "free":
- $leases[$l]['act'] = "expired";
- $leases[$l]['online'] = "offline";
- break;
- case "backup":
- $leases[$l]['act'] = "reserved";
- $leases[$l]['online'] = "offline";
- 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';
- } else {
- $leases[$l]['online'] = 'offline';
- }
- $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);
-
- if (count($leases) > 0) {
- $leases = remove_duplicate($leases,"ip");
- }
-
- if (count($pools) > 0) {
- $pools = remove_duplicate($pools,"name");
- asort($pools);
- }
-
- $macs = [];
- foreach ($leases as $i => $this_lease) {
- if (!empty($this_lease['mac'])) {
- if (!isset($macs[$this_lease['mac']])) {
- $macs[$this_lease['mac']] = [];
- }
- $macs[$this_lease['mac']][] = $i;
- }
- }
-
- foreach (dhcpd_staticmap("not.found", legacy_interfaces_details(), false, 4) as $static) {
- $slease = [];
- $slease['ip'] = $static['ipaddr'];
- $slease['type'] = 'static';
- $slease['mac'] = $static['mac'];
- $slease['start'] = '';
- $slease['end'] = '';
- $slease['hostname'] = $static['hostname'];
- $slease['descr'] = $static['descr'];
- $slease['act'] = 'static';
- $slease['online'] = in_array(strtolower($slease['mac']), $arpdata_mac) ? 'online' : 'offline';
-
- if (isset($macs[$slease['mac']])) {
- /* update lease with static data */
- foreach ($slease as $key => $value) {
- if (!empty($value)) {
- foreach ($macs[$slease['mac']] as $idx) {
- $leases[$idx][$key] = $value;
- }
- }
- }
- } else {
- $leases[] = $slease;
- }
- }
-
- if (isset($_GET['order']) && in_array($_GET['order'], ['int', 'ip', 'mac', 'hostname', 'descr', 'start', 'end', 'online', 'act'])) {
- $order = $_GET['order'];
- } else {
- $order = 'ip';
- }
-
- usort($leases,
- function ($a, $b) use ($order) {
- $cmp = ($order === 'ip') ? 0 : strnatcasecmp($a[$order], $b[$order]);
- if ($cmp === 0) {
- $cmp = ipcmp($a['ip'], $b['ip']);
- }
- return $cmp;
- }
- );
-} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!empty($_POST['deleteip']) && is_ipaddr($_POST['deleteip'])) {
- configdp_run('dhcpd remove lease', [$_POST['deleteip']]);
- }
- exit;
-}
-
-$service_hook = 'dhcpd';
-
-include("head.inc");
-
-$leases_count = 0;
-
-foreach ($leases as $data) {
- if (!($data['act'] == 'active' || $data['act'] == 'static' || $_GET['all'] == 1)) {
- continue;
- }
- $leases_count++;
-}
-
-$gentitle_suffix = " ($leases_count)";
-legacy_html_escape_form_data($leases);
-
-?>
-
-
-
-
-
-