From 101cd0491fd9c93d332df0a251d23af160579d79 Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Sun, 16 Nov 2025 16:52:56 +0800 Subject: [PATCH 01/15] Kea-DDNS: Implement Model changes to support Kea DHCP DDNS --- src/etc/inc/plugins.inc.d/kea.inc | 16 +- .../app/models/OPNsense/Kea/KeaDhcpDdns.php | 248 ++++++++++++++++++ .../app/models/OPNsense/Kea/KeaDhcpDdns.xml | 135 ++++++++++ .../mvc/app/models/OPNsense/Kea/KeaDhcpv4.php | 66 +++++ .../mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml | 33 ++- .../mvc/app/models/OPNsense/Kea/KeaDhcpv6.php | 66 +++++ .../mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml | 33 ++- .../templates/OPNsense/Kea/keactrl.conf | 2 +- 8 files changed, 594 insertions(+), 5 deletions(-) create mode 100644 src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php create mode 100644 src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml diff --git a/src/etc/inc/plugins.inc.d/kea.inc b/src/etc/inc/plugins.inc.d/kea.inc index f5fa4bc24f3..b98d9cb5213 100644 --- a/src/etc/inc/plugins.inc.d/kea.inc +++ b/src/etc/inc/plugins.inc.d/kea.inc @@ -62,6 +62,14 @@ function kea_services() $services[] = $service; } + if (!(new \OPNsense\Kea\KeaDhcpDdns())->general->enabled->isEmpty()) { + $service = $template; + $service['pidfile'] = '/var/run/kea/kea-dhcp-ddns.kea-dhcp-ddns.pid'; + $service['description'] = gettext('KEA DHCP DDNS server'); + $service['id'] = 'ddns'; + $services[] = $service; + } + return $services; } @@ -146,6 +154,7 @@ function kea_configure_do($verbose = false) { $keaDhcpv4 = new \OPNsense\Kea\KeaDhcpv4(); $keaDhcpv6 = new \OPNsense\Kea\KeaDhcpv6(); + $keaDhcpDdns = new \OPNsense\Kea\KeaDhcpDdns(); killbypid('/var/run/kea_prefix_watcher.pid'); @@ -159,6 +168,9 @@ function kea_configure_do($verbose = false) /* skip kea-dhcp6.conf when configured manually */ $keaDhcpv6->generateConfig(); } + if ($keaDhcpDdns->isEnabled() && $keaDhcpDdns->general->manual_config->isEmpty()) { + $keaDhcpDdns->generateConfig(); + } (new \OPNsense\Kea\KeaCtrlAgent())->generateConfig(); if ($keaDhcpv6->isEnabled()) { mwexecfb( @@ -175,7 +187,7 @@ function kea_configure_do($verbose = false) function kea_syslog() { $logfacilities = []; - $logfacilities['kea'] = ['facility' => ['kea-dhcp4', 'kea-dhcp6', 'kea-ctrl-agent']]; + $logfacilities['kea'] = ['facility' => ['kea-dhcp4', 'kea-dhcp6', 'kea-ctrl-agent', 'kea-dhcp-ddns']]; return $logfacilities; } @@ -289,7 +301,7 @@ function kea_xmlrpc_sync() 'description' => gettext('Kea DHCP'), 'section' => 'OPNsense.Kea', 'id' => 'kea', - 'services' => ["kea-dhcpv4", "kea-dhcpv6"], + 'services' => ["kea-dhcpv4", "kea-dhcpv6", "kea-dhcp-ddns"], ]; return $result; diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php new file mode 100644 index 00000000000..aeefde73fc2 --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php @@ -0,0 +1,248 @@ +general->enabled); + if (!$ddns_enabled) { + $dhcpv4 = new KeaDhcpv4(); + $dhcpv6 = new KeaDhcpv6(); + $v4_ddns = !empty((string)$dhcpv4->general->enable_ddns); + $v6_ddns = !empty((string)$dhcpv6->general->enable_ddns); + if ($v4_ddns || $v6_ddns) { + $messages->appendMessage( + new Message( + gettext('Cannot disable DHCP-DDNS service while DHCPv4 or DHCPv6 DDNS is enabled.'), + 'general.enabled' + ) + ); + } + } + + // Explicitly validate that forward and reverse domain names end with a dot (FQDN) + foreach ($this->forward_ddns->ddns_domains->iterateItems() as $domain) { + if (!$validateFullModel && !$domain->isFieldChanged()) { + continue; + } + $name = trim((string)$domain->name); + if ($name !== '' && substr($name, -1) !== '.') { + $messages->appendMessage( + new Message( + gettext('Domain must be a fully qualified domain name ending with a dot.'), + $domain->__reference . '.name' + ) + ); + } + } + foreach ($this->reverse_ddns->ddns_domains->iterateItems() as $domain) { + if (!$validateFullModel && !$domain->isFieldChanged()) { + continue; + } + $name = trim((string)$domain->name); + if ($name !== '' && substr($name, -1) !== '.') { + $messages->appendMessage( + new Message( + gettext('Domain must be a fully qualified domain name ending with a dot.'), + $domain->__reference . '.name' + ) + ); + } + } + + return $messages; + } + + public function isEnabled() + { + return (string)$this->general->enabled == '1'; + } + + /** + * Build a map of shared DNS servers defined at root level (dns_servers) + * keyed by their UUID for quick lookup. + * @return array + */ + private function getSharedDnsServersMap() + { + $map = []; + $tsigNameMap = $this->getTsigKeyNameMap(); + foreach ($this->dns_servers->iterateItems() as $uuid => $srv) { + $item = []; + if ((string)$srv->ip_address !== '') { + $item['ip-address'] = (string)$srv->ip_address; + } + if ((string)$srv->port !== '') { + $item['port'] = (int)((string)$srv->port); + } + if ((string)$srv->key_name !== '') { + $kn = (string)$srv->key_name; + // key_name is a ModelRelationField (UUID). Resolve to TSIG key name. + if (isset($tsigNameMap[$kn]) && $tsigNameMap[$kn] !== '') { + $item['key-name'] = $tsigNameMap[$kn]; + } + } + if (!empty($item)) { + $map[$uuid] = $item; + } + } + return $map; + } + + /** + * Build a map uuid => tsig key name for quick lookup when resolving relations. + * @return array + */ + private function getTsigKeyNameMap() + { + $map = []; + foreach ($this->tsig_keys->iterateItems() as $uuid => $key) { + $name = (string)$key->name; + if ($name !== '') { + $map[$uuid] = $name; + } + } + return $map; + } + + private function getTsigKeys() { + $tsig_keys = []; + foreach ($this->tsig_keys->iterateItems() as $key) { + $item = []; + if ((string)$key->name !== '') { + $item['name'] = (string)$key->name; + } + if ((string)$key->algorithm !== '') { + $item['algorithm'] = (string)$key->algorithm; + } + if ((string)$key->secret !== '') { + $item['secret'] = (string)$key->secret; + } + if (!empty($item)) { + $tsig_keys[] = $item; + } + } + return $tsig_keys; + } + + private function buildDomains ($domainsNode) { + $domains = []; + $serversMap = $this->getSharedDnsServersMap(); + $tsigNameMap = $this->getTsigKeyNameMap(); + foreach ($domainsNode->iterateItems() as $domain) { + $entry = []; + if ((string)$domain->name !== '') { + // emit stored value as-is; validation ensures FQDN (trailing dot) + $entry['name'] = (string)$domain->name; + } + if ((string)$domain->key_name !== '') { + $kn = (string)$domain->key_name; // UUID from ModelRelationField + if (isset($tsigNameMap[$kn]) && $tsigNameMap[$kn] !== '') { + $entry['key-name'] = $tsigNameMap[$kn]; + } + } + + // dns-servers referenced via ModelRelationField (comma-separated UUIDs) + $servers = []; + $refs = isset($domain->dns_servers) ? (string)$domain->dns_servers : ''; + if (!empty($refs)) { + foreach (array_filter(explode(',', $refs)) as $uuid) { + $uuid = trim($uuid); + if ($uuid === '' || !isset($serversMap[$uuid])) { + continue; + } + $servers[] = $serversMap[$uuid]; + } + } + if (!empty($servers)) { + $entry['dns-servers'] = $servers; + } + + if (!empty($entry)) { + $domains[] = $entry; + } + } + return $domains; + } + + private function getForwardDomains() { + return $this->buildDomains($this->forward_ddns->ddns_domains); + } + + private function getReverseDomains() { + return $this->buildDomains($this->reverse_ddns->ddns_domains); + } + + public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp-ddns.conf') + { + $result = [ + 'DhcpDdns' => [ + 'ip-address' => '127.0.0.1', + 'port' => 53001, + 'control-socket' => [ + 'socket-type' => 'unix', + 'socket-name' => '/var/run/kea/kea-ddns-ctrl-socket' + ], + 'loggers' => [ + [ + 'name' => 'kea-dhcp-ddns', + 'output_options' => [ + [ + 'output' => 'syslog' + ] + ], + 'severity' => 'INFO', + ] + ], + 'tsig-keys' => $this->getTsigKeys(), + 'forward-ddns' => [ + 'ddns-domains' => $this->getForwardDomains() + ], + 'reverse-ddns' => [ + 'ddns-domains' => $this->getReverseDomains() + ] + ] + ]; + + File::file_put_contents($target, json_encode($result, JSON_PRETTY_PRINT), 0600); + } +} diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml new file mode 100644 index 00000000000..68f1ec9e956 --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml @@ -0,0 +1,135 @@ + + //OPNsense/Kea/dhcp_ddns + 1.0.7 + Kea DHCP-DDNS Configuration + + + + 0 + Y + + + + + + + Y + + + N + + Y + + + 1 + 65535 + 53 + Y + + + + + OPNsense.Kea.KeaDhcpDdns + tsig_keys + name + + + + + + + + N + Please specify a valid domain name. + + + Duplicate forward domain exists. + UniqueConstraint + + + + + + + OPNsense.Kea.KeaDhcpDdns + tsig_keys + name + + + + + + + OPNsense.Kea.KeaDhcpDdns + dns_servers + name + + + Y + Y + Please select at least one DNS server. + + + + + + + N + Please specify a valid domain name. + + + Duplicate reverse domain exists. + UniqueConstraint + + + + + + + OPNsense.Kea.KeaDhcpDdns + tsig_keys + name + + + + + + + OPNsense.Kea.KeaDhcpDdns + dns_servers + name + + + Y + Y + Please select at least one DNS server. + + + + + + Y + + + Duplicate TSIG key name exists. + UniqueConstraint + + + + + Y + + HMAC-MD5 + HMAC-SHA1 + HMAC-SHA224 + HMAC-SHA256 + HMAC-SHA384 + HMAC-SHA512 + + + + Y + + + + diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php index c0dff19e6fc..247a8c53b9c 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php @@ -90,6 +90,33 @@ public function performValidation($validateFullModel = false) } } + // Require DDNS service to be enabled when DHCPv4's DDNS is enabled + if (!empty((string)$this->general->enable_ddns)) { + $ddns = new KeaDhcpDdns(); + if (empty((string)$ddns->general->enabled)) { + $messages->appendMessage( + new Message(gettext('Enable the DHCP-DDNS service to use Dynamic DNS updates.'), 'general.enable_ddns') + ); + } + } + + // Enforce that ddns qualifying suffix ends with a dot when set + foreach ($this->subnets->subnet4->iterateItems() as $subnet) { + if (!$validateFullModel && !$subnet->isFieldChanged()) { + continue; + } + $send_updates = !empty((string)$subnet->ddns_options->send_updates); + $suffix = trim((string)$subnet->ddns_options->qualifying_suffix); + if ($send_updates && $suffix !== '' && substr($suffix, -1) !== '.') { + $messages->appendMessage( + new Message( + gettext('DDNS qualifying suffix must end with a dot.'), + $subnet->__reference . '.ddns_options.qualifying_suffix' + ) + ); + } + } + return $messages; } @@ -175,6 +202,40 @@ private function getConfigSubnets() 'pools' => [], 'reservations' => [] ]; + + // Conditionally include DDNS settings only when send-updates is enabled, + // and only include fields that have meaningful values. + $ddns_send_updates = !empty((string)$subnet->ddns_options->send_updates); + if ($ddns_send_updates) { + $record['ddns-send-updates'] = true; + if (!empty((string)$subnet->ddns_options->override_no_update)) { + $record['ddns-override-no-update'] = true; + } + if (!empty((string)$subnet->ddns_options->override_client_update)) { + $record['ddns-override-client-update'] = true; + } + if ((string)$subnet->ddns_options->replace_client_name !== '') { + $record['ddns-replace-client-name'] = (string)$subnet->ddns_options->replace_client_name; + } + if ((string)$subnet->ddns_options->generated_prefix !== '') { + $record['ddns-generated-prefix'] = (string)$subnet->ddns_options->generated_prefix; + } + if ((string)$subnet->ddns_options->qualifying_suffix !== '') { + $record['ddns-qualifying-suffix'] = (string)$subnet->ddns_options->qualifying_suffix; + } + if ((string)$subnet->ddns_options->hostname_char_set !== '') { + $record['hostname-char-set'] = (string)$subnet->ddns_options->hostname_char_set; + } + if ((string)$subnet->ddns_options->hostname_char_replacement !== '') { + $record['hostname-char-replacement'] = (string)$subnet->ddns_options->hostname_char_replacement; + } + if (!empty((string)$subnet->ddns_options->update_on_renew)) { + $record['ddns-update-on-renew'] = true; + } + if ((string)$subnet->ddns_options->conflict_resolution_mode !== '') { + $record['ddns-conflict-resolution-mode'] = (string)$subnet->ddns_options->conflict_resolution_mode; + } + } /* add pools */ foreach (array_filter(explode("\n", $subnet->pools->getValue())) as $pool) { $record['pools'][] = ['pool' => $pool]; @@ -237,6 +298,11 @@ public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp4.conf') 'socket-type' => 'unix', 'socket-name' => '/var/run/kea/kea4-ctrl-socket' ], + 'dhcp-ddns' => [ + 'enable-updates' => !empty((string)$this->general->enable_ddns), + 'server-ip' => '127.0.0.1', + 'server-port' => 53001, + ], 'loggers' => [ [ 'name' => 'kea-dhcp4', diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml index a5139f3012d..25a408660bf 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml @@ -1,6 +1,6 @@ //OPNsense/Kea/dhcp4 - 1.0.4 + 1.0.5 Kea DHCPv4 configuration @@ -28,6 +28,7 @@ raw + @@ -136,6 +137,36 @@ + + + + + + never + Y + + never + always + when-present + when-not-present + + + + + + + + + check-with-dhcid + Y + + check-with-dhcid + no-check-with-dhcid + check-exists-with-dhcid + no-check-without-dhcid + + + diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php index d8d5a746e5d..2668df54232 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php @@ -72,6 +72,33 @@ public function performValidation($validateFullModel = false) } } + // Require DDNS service to be enabled when DHCPv6's DDNS is enabled + if (!empty((string)$this->general->enable_ddns)) { + $ddns = new KeaDhcpDdns(); + if (empty((string)$ddns->general->enabled)) { + $messages->appendMessage( + new Message(gettext('Enable the DHCP-DDNS service to use Dynamic DNS updates.'), 'general.enable_ddns') + ); + } + } + + // Enforce that ddns qualifying suffix ends with a dot when set + foreach ($this->subnets->subnet6->iterateItems() as $subnet) { + if (!$validateFullModel && !$subnet->isFieldChanged()) { + continue; + } + $send_updates = !empty((string)$subnet->ddns_options->send_updates); + $suffix = trim((string)$subnet->ddns_options->qualifying_suffix); + if ($send_updates && $suffix !== '' && substr($suffix, -1) !== '.') { + $messages->appendMessage( + new Message( + gettext('DDNS qualifying suffix must end with a dot.'), + $subnet->__reference . '.ddns_options.qualifying_suffix' + ) + ); + } + } + return $messages; } @@ -129,6 +156,40 @@ private function getConfigSubnets() 'pd-pools' => [], 'reservations' => [] ]; + + // Conditionally include DDNS settings only when send-updates is enabled, + // and only include fields that have meaningful values. + $ddns_send_updates = !empty((string)$subnet->ddns_options->send_updates); + if ($ddns_send_updates) { + $record['ddns-send-updates'] = true; + if (!empty((string)$subnet->ddns_options->override_no_update)) { + $record['ddns-override-no-update'] = true; + } + if (!empty((string)$subnet->ddns_options->override_client_update)) { + $record['ddns-override-client-update'] = true; + } + if ((string)$subnet->ddns_options->replace_client_name !== '') { + $record['ddns-replace-client-name'] = (string)$subnet->ddns_options->replace_client_name; + } + if ((string)$subnet->ddns_options->generated_prefix !== '') { + $record['ddns-generated-prefix'] = (string)$subnet->ddns_options->generated_prefix; + } + if ((string)$subnet->ddns_options->qualifying_suffix !== '') { + $record['ddns-qualifying-suffix'] = (string)$subnet->ddns_options->qualifying_suffix; + } + if ((string)$subnet->ddns_options->hostname_char_set !== '') { + $record['hostname-char-set'] = (string)$subnet->ddns_options->hostname_char_set; + } + if ((string)$subnet->ddns_options->hostname_char_replacement !== '') { + $record['hostname-char-replacement'] = (string)$subnet->ddns_options->hostname_char_replacement; + } + if (!empty((string)$subnet->ddns_options->update_on_renew)) { + $record['ddns-update-on-renew'] = true; + } + if ((string)$subnet->ddns_options->conflict_resolution_mode !== '') { + $record['ddns-conflict-resolution-mode'] = (string)$subnet->ddns_options->conflict_resolution_mode; + } + } $if = $subnet->interface->getValue(); if (isset($cfg->interfaces->$if) && !empty($cfg->interfaces->$if->if)) { $record['interface'] = (string)$cfg->interfaces->$if->if; @@ -223,6 +284,11 @@ public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp6.conf') 'socket-type' => 'unix', 'socket-name' => '/var/run/kea/kea6-ctrl-socket' ], + 'dhcp-ddns' => [ + 'enable-updates' => !empty((string)$this->general->enable_ddns), + 'server-ip' => '127.0.0.1', + 'server-port' => 53001, + ], 'loggers' => [ [ 'name' => 'kea-dhcp6', diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml index 34ffa71e4cf..e8f34272e73 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml @@ -1,6 +1,6 @@ //OPNsense/Kea/dhcp6 - 1.0.0 + 1.0.1 Kea DHCPv6 configuration @@ -20,6 +20,7 @@ Y 1 + @@ -105,6 +106,36 @@ Y + + + + + + never + Y + + never + always + when-present + when-not-present + + + + + + + + + check-with-dhcid + Y + + check-with-dhcid + no-check-with-dhcid + check-exists-with-dhcid + no-check-without-dhcid + + + diff --git a/src/opnsense/service/templates/OPNsense/Kea/keactrl.conf b/src/opnsense/service/templates/OPNsense/Kea/keactrl.conf index bf8bc80986b..cd2f71867cc 100644 --- a/src/opnsense/service/templates/OPNsense/Kea/keactrl.conf +++ b/src/opnsense/service/templates/OPNsense/Kea/keactrl.conf @@ -34,7 +34,7 @@ dhcp4={% if not helpers.empty('OPNsense.Kea.dhcp4.general.enabled') %}yes{% else dhcp6={% if not helpers.empty('OPNsense.Kea.dhcp6.general.enabled') %}yes{% else %}no{% endif %} # Start DHCP DDNS server? -dhcp_ddns=no +dhcp_ddns={% if not helpers.empty('OPNsense.Kea.dhcp_ddns.general.enabled') %}yes{% else %}no{% endif %} # Start Control Agent? ctrl_agent={% if not helpers.empty('OPNsense.Kea.ctrl_agent.general.enabled') %}yes{% else %}no{% endif %} From a33bbb93566aceefde4e63113b997c57aee6ee7c Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Sun, 16 Nov 2025 16:53:37 +0800 Subject: [PATCH 02/15] Kea-DDNS: Implement View & Controller changes to support Kea DHCP DDNS --- .../OPNsense/Kea/Api/DhcpddnsController.php | 151 ++++++++++++++++++ .../OPNsense/Kea/DhcpController.php | 22 +++ .../Kea/forms/dialogDdnsDnsServer.xml | 33 ++++ .../Kea/forms/dialogDdnsForwardDomain.xml | 27 ++++ .../Kea/forms/dialogDdnsReverseDomain.xml | 27 ++++ .../OPNsense/Kea/forms/dialogDdnsTsigKey.xml | 20 +++ .../OPNsense/Kea/forms/dialogSubnet4.xml | 100 ++++++++++++ .../OPNsense/Kea/forms/dialogSubnet6.xml | 100 ++++++++++++ .../OPNsense/Kea/forms/generalSettings4.xml | 6 + .../OPNsense/Kea/forms/generalSettings6.xml | 6 + .../Kea/forms/generalSettingsDdns.xml | 19 +++ .../mvc/app/models/OPNsense/Kea/Menu/Menu.xml | 1 + .../mvc/app/views/OPNsense/Kea/dhcp_ddns.volt | 112 +++++++++++++ 13 files changed, 624 insertions(+) create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Kea/Api/DhcpddnsController.php create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsTsigKey.xml create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettingsDdns.xml create mode 100644 src/opnsense/mvc/app/views/OPNsense/Kea/dhcp_ddns.volt diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/Api/DhcpddnsController.php b/src/opnsense/mvc/app/controllers/OPNsense/Kea/Api/DhcpddnsController.php new file mode 100644 index 00000000000..b5df609e37c --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/Api/DhcpddnsController.php @@ -0,0 +1,151 @@ +searchBase('forward_ddns.ddns_domains', null, 'name'); + } + + public function getForwardDomainAction($uuid = null) + { + return $this->getBase('ddns_domains', 'forward_ddns.ddns_domains', $uuid); + } + + public function addForwardDomainAction() + { + return $this->addBase('ddns_domains', 'forward_ddns.ddns_domains'); + } + + public function setForwardDomainAction($uuid) + { + return $this->setBase('ddns_domains', 'forward_ddns.ddns_domains', $uuid); + } + + public function delForwardDomainAction($uuid) + { + return $this->delBase('forward_ddns.ddns_domains', $uuid); + } + + /* Reverse DDNS domains */ + public function searchReverseDomainAction() + { + return $this->searchBase('reverse_ddns.ddns_domains', null, 'name'); + } + + public function getReverseDomainAction($uuid = null) + { + return $this->getBase('ddns_domains', 'reverse_ddns.ddns_domains', $uuid); + } + + public function addReverseDomainAction() + { + return $this->addBase('ddns_domains', 'reverse_ddns.ddns_domains'); + } + + public function setReverseDomainAction($uuid) + { + return $this->setBase('ddns_domains', 'reverse_ddns.ddns_domains', $uuid); + } + + public function delReverseDomainAction($uuid) + { + return $this->delBase('reverse_ddns.ddns_domains', $uuid); + } + + /* TSIG keys */ + public function searchTsigKeyAction() + { + return $this->searchBase('tsig_keys', null, 'name'); + } + + public function getTsigKeyAction($uuid = null) + { + return $this->getBase('tsig_keys', 'tsig_keys', $uuid); + } + + public function addTsigKeyAction() + { + return $this->addBase('tsig_keys', 'tsig_keys'); + } + + public function setTsigKeyAction($uuid) + { + return $this->setBase('tsig_keys', 'tsig_keys', $uuid); + } + + public function delTsigKeyAction($uuid) + { + return $this->delBase('tsig_keys', $uuid); + } + + /* Shared DNS servers */ + public function searchDnsServerAction() + { + return $this->searchBase('dns_servers', null, 'ip_address'); + } + + public function getDnsServerAction($uuid = null) + { + return $this->getBase('dns_servers', 'dns_servers', $uuid); + } + + public function addDnsServerAction() + { + return $this->addBase('dns_servers', 'dns_servers'); + } + + public function setDnsServerAction($uuid) + { + return $this->setBase('dns_servers', 'dns_servers', $uuid); + } + + public function delDnsServerAction($uuid) + { + return $this->delBase('dns_servers', $uuid); + } +} diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/DhcpController.php b/src/opnsense/mvc/app/controllers/OPNsense/Kea/DhcpController.php index 9e3f115ec9a..950e2ea8ddc 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/DhcpController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/DhcpController.php @@ -88,4 +88,26 @@ public function leases6Action() { $this->view->pick('OPNsense/Kea/leases6'); } + + public function ddnsAction() + { + $this->view->pick('OPNsense/Kea/dhcp_ddns'); + $this->view->formGeneralSettings = $this->getForm("generalSettingsDdns"); + + // Forward zones (domains) + $this->view->formDialogForwardDomain = $this->getForm("dialogDdnsForwardDomain"); + $this->view->formGridForwardDomain = $this->getFormGrid("dialogDdnsForwardDomain"); + + // Reverse zones (domains) + $this->view->formDialogReverseDomain = $this->getForm("dialogDdnsReverseDomain"); + $this->view->formGridReverseDomain = $this->getFormGrid("dialogDdnsReverseDomain"); + + // Shared DNS servers + $this->view->formDialogDnsServer = $this->getForm("dialogDdnsDnsServer"); + $this->view->formGridDnsServer = $this->getFormGrid("dialogDdnsDnsServer"); + + // TSIG keys + $this->view->formDialogTsigKey = $this->getForm("dialogDdnsTsigKey"); + $this->view->formGridTsigKey = $this->getFormGrid("dialogDdnsTsigKey"); + } } diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml new file mode 100644 index 00000000000..fa91efa2f68 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml @@ -0,0 +1,33 @@ +
+ + dns_servers.name + + text + Display name for this DNS server (not used in generated config). + + + true + + + + dns_servers.ip_address + + text + DNS server IP address (IPv4 or IPv6) + + + dns_servers.port + + text + DNS server port, default 53 + + + dns_servers.key_name + + dropdown + Select an optional TSIG key for updates sent to this server. When set, this server-specific key overrides any TSIG key configured on the domain/zone. + + + + +
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml new file mode 100644 index 00000000000..609879308ed --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml @@ -0,0 +1,27 @@ +
+ + ddns_domains.name + + text + Forward DDNS domain (e.g. example.com.) + + + ddns_domains.key_name + + dropdown + Select an optional TSIG key to authenticate updates for this domain. Note: if a DNS server has a TSIG key configured, that server-specific key takes precedence over the domain key. + + + + + + ddns_domains.dns_servers + + select_multiple + Select one or more shared DNS servers to update for this domain. + + + true + + +
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml new file mode 100644 index 00000000000..4f0d12bec45 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml @@ -0,0 +1,27 @@ +
+ + ddns_domains.name + + text + Reverse DDNS domain (e.g. 0.168.192.in-addr.arpa. or ip6.arpa. variant) + + + ddns_domains.key_name + + dropdown + Select an optional TSIG key to authenticate updates for this reverse zone. Note: if a DNS server has a TSIG key configured, that server-specific key takes precedence over the domain key. + + + + + + ddns_domains.dns_servers + + select_multiple + Select one or more shared DNS servers to update for this reverse zone. + + + true + + +
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsTsigKey.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsTsigKey.xml new file mode 100644 index 00000000000..38a2d5a6d72 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsTsigKey.xml @@ -0,0 +1,20 @@ +
+ + tsig_keys.name + + text + TSIG key name. + + + tsig_keys.algorithm + + dropdown + Select the TSIG hash algorithm. + + + tsig_keys.secret + + text + Base64-encoded shared secret. + +
diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml index 4a7aba4922a..cf82bd0a5af 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml @@ -28,6 +28,106 @@ false + + header + + true + + + subnet4.ddns_options.send_updates + + checkbox + Enable Dynamic DNS updates for this subnet. + + false + + + + subnet4.ddns_options.override_no_update + + checkbox + Override client request to disable DNS updates. + true + + false + + + + subnet4.ddns_options.override_client_update + + checkbox + Server performs updates regardless of client preference. + true + + false + + + + subnet4.ddns_options.replace_client_name + + dropdown + Controls when the server replaces the client supplied hostname. + + false + + + + subnet4.ddns_options.generated_prefix + + text + Prefix to use when generating hostnames. + true + + false + + + + subnet4.ddns_options.update_on_renew + + checkbox + Send DNS updates when leases are renewed. + + false + + + + subnet4.ddns_options.qualifying_suffix + + text + Suffix appended to hostnames to form FQDNs. Must end with a dot. + + false + + + + subnet4.ddns_options.hostname_char_set + + text + POSIX Extended Regex Expression that matches invalid characters. Default: [^A-Za-z0-9.-] + true + + false + + + + subnet4.ddns_options.hostname_char_replacement + + text + Replacement for each invalid character. Empty means omit invalid characters. + true + + false + + + + subnet4.ddns_options.conflict_resolution_mode + + dropdown + How to resolve DNS conflicts. + + false + + header diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml index 63d29de2bee..133db34dabe 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml @@ -37,6 +37,106 @@ textbox List of pools, one per line in range or subnet format (e.g. 2001:db8:1::-2001:db8:1::100, 2001:db8:1::/80 + + header + + true + + + subnet6.ddns_options.send_updates + + checkbox + Enable Dynamic DNS updates for this subnet. + + false + + + + subnet6.ddns_options.override_no_update + + checkbox + Override client request to disable DNS updates. + true + + false + + + + subnet6.ddns_options.override_client_update + + checkbox + Server performs updates regardless of client preference. + true + + false + + + + subnet6.ddns_options.replace_client_name + + dropdown + Controls when the server replaces the client supplied hostname. + + false + + + + subnet6.ddns_options.generated_prefix + + text + Prefix to use when generating hostnames. + true + + false + + + + subnet6.ddns_options.update_on_renew + + checkbox + Send DNS updates when leases are renewed. + + false + + + + subnet6.ddns_options.qualifying_suffix + + text + Suffix appended to hostnames to form FQDNs. Must end with a dot. + + false + + + + subnet6.ddns_options.hostname_char_set + + text + POSIX Extended Regex Expression that matches invalid characters. Default: [^A-Za-z0-9.-] + true + + false + + + + subnet6.ddns_options.hostname_char_replacement + + text + Replacement for each invalid character. Empty means omit invalid characters. + true + + false + + + + subnet6.ddns_options.conflict_resolution_mode + + dropdown + How to resolve DNS conflicts. + + false + + header diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings4.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings4.xml index 3ceeddb0a54..a46e427866c 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings4.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings4.xml @@ -44,6 +44,12 @@ dropdown Socket type used for DHCP communication + + dhcpv4.general.enable_ddns + + checkbox + Enable Dynamic DNS Updates (DDNS) using the Kea DHCP-DDNS server. + header diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings6.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings6.xml index 660ad59bc29..ccec6b0ce82 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings6.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings6.xml @@ -38,6 +38,12 @@ checkbox Automatically add a basic set of firewall rules to allow dhcp traffic, more fine grained controls can be offered manually when disabling this option. + + dhcpv6.general.enable_ddns + + checkbox + Enable Dynamic DNS Updates (DDNS) using the Kea DHCP-DDNS server. + header diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettingsDdns.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettingsDdns.xml new file mode 100644 index 00000000000..e6e4c53c9b1 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettingsDdns.xml @@ -0,0 +1,19 @@ +
+ + header + + + + dhcp_ddns.general.enabled + + checkbox + Enable DHCP-DDNS service. + + + dhcp_ddns.general.manual_config + + checkbox + true + Disable configuration file generation and manage the file (/usr/local/etc/kea/kea-dhcp-ddns.conf) manually. + +
diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/Menu/Menu.xml index 3aa4d72fe2e..860c30b9fe5 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/Menu/Menu.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/Menu/Menu.xml @@ -4,6 +4,7 @@ + diff --git a/src/opnsense/mvc/app/views/OPNsense/Kea/dhcp_ddns.volt b/src/opnsense/mvc/app/views/OPNsense/Kea/dhcp_ddns.volt new file mode 100644 index 00000000000..36503655681 --- /dev/null +++ b/src/opnsense/mvc/app/views/OPNsense/Kea/dhcp_ddns.volt @@ -0,0 +1,112 @@ +{# + # 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. + #} + + + + +
+
+ {{ partial("layout_partials/base_form",['fields':formGeneralSettings,'id':'frm_generalsettings'])}} +
+
+ {{ partial('layout_partials/base_bootgrid_table', formGridForwardDomain)}} +
+
+ {{ partial('layout_partials/base_bootgrid_table', formGridReverseDomain)}} +
+
+ {{ partial('layout_partials/base_bootgrid_table', formGridTsigKey)}} +
+
+ {{ partial('layout_partials/base_bootgrid_table', formGridDnsServer)}} +
+
+ +{{ partial('layout_partials/base_apply_button', {'data_endpoint': '/api/kea/service/reconfigure'}) }} +{{ partial("layout_partials/base_dialog",['fields':formDialogForwardDomain,'id':formGridForwardDomain['edit_dialog_id'],'label':lang._('Edit Forward Domain')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogReverseDomain,'id':formGridReverseDomain['edit_dialog_id'],'label':lang._('Edit Reverse Domain')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogTsigKey,'id':formGridTsigKey['edit_dialog_id'],'label':lang._('Edit TSIG Key')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogDnsServer,'id':formGridDnsServer['edit_dialog_id'],'label':lang._('Edit DNS Server')])}} From a05c445b778a96fdc1d913972fa0b3a16ed1738e Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Sun, 16 Nov 2025 16:53:46 +0800 Subject: [PATCH 03/15] Kea-DDNS: Update plist --- plist | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plist b/plist index 15cdc7011d0..4f5b632cb41 100644 --- a/plist +++ b/plist @@ -399,6 +399,7 @@ /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/forms/dialogVlan.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Interfaces/forms/dialogVxlan.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/Api/CtrlAgentController.php +/usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/Api/DhcpddnsController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/Api/Dhcpv4Controller.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/Api/Dhcpv6Controller.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/Api/Leases4Controller.php @@ -407,6 +408,10 @@ /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/Api/ServiceController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/DhcpController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/agentSettings.xml +/usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml +/usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml +/usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml +/usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsTsigKey.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogPDPool6.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogPeer4.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogPeer6.xml @@ -416,6 +421,7 @@ /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings4.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings6.xml +/usr/local/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettingsDdns.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Monit/Api/ServiceController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Monit/Api/SettingsController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Monit/Api/StatusController.php @@ -822,6 +828,8 @@ /usr/local/opnsense/mvc/app/models/OPNsense/Kea/FieldTypes/KeaStaticRoutesField.php /usr/local/opnsense/mvc/app/models/OPNsense/Kea/KeaCtrlAgent.php /usr/local/opnsense/mvc/app/models/OPNsense/Kea/KeaCtrlAgent.xml +/usr/local/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php +/usr/local/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml /usr/local/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php /usr/local/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml /usr/local/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php @@ -973,6 +981,7 @@ /usr/local/opnsense/mvc/app/views/OPNsense/Interface/vlan.volt /usr/local/opnsense/mvc/app/views/OPNsense/Interface/vxlan.volt /usr/local/opnsense/mvc/app/views/OPNsense/Kea/ctrl_agent.volt +/usr/local/opnsense/mvc/app/views/OPNsense/Kea/dhcp_ddns.volt /usr/local/opnsense/mvc/app/views/OPNsense/Kea/dhcpv4.volt /usr/local/opnsense/mvc/app/views/OPNsense/Kea/dhcpv6.volt /usr/local/opnsense/mvc/app/views/OPNsense/Kea/leases4.volt From 720aaadee6c76335e4d1ef53850e96d5cc4c8461 Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Sun, 16 Nov 2025 17:05:17 +0800 Subject: [PATCH 04/15] Kea-DDNS: Skip DDNS enable validation checks when the V4/V6 service is in manual_config mode. --- src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php | 5 ++++- src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php | 3 ++- src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php index aeefde73fc2..20474adea84 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php @@ -49,7 +49,10 @@ public function performValidation($validateFullModel = false) $dhcpv6 = new KeaDhcpv6(); $v4_ddns = !empty((string)$dhcpv4->general->enable_ddns); $v6_ddns = !empty((string)$dhcpv6->general->enable_ddns); - if ($v4_ddns || $v6_ddns) { + // Skip dependency when the respective service uses manual configuration + $v4_blocks_disable = $v4_ddns && $dhcpv4->general->manual_config->isEmpty(); + $v6_blocks_disable = $v6_ddns && $dhcpv6->general->manual_config->isEmpty(); + if ($v4_blocks_disable || $v6_blocks_disable) { $messages->appendMessage( new Message( gettext('Cannot disable DHCP-DDNS service while DHCPv4 or DHCPv6 DDNS is enabled.'), diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php index 247a8c53b9c..e795e4bfa90 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php @@ -91,7 +91,8 @@ public function performValidation($validateFullModel = false) } // Require DDNS service to be enabled when DHCPv4's DDNS is enabled - if (!empty((string)$this->general->enable_ddns)) { + // Skip this check when manual configuration is enabled for DHCPv4 + if (!empty((string)$this->general->enable_ddns) && $this->general->manual_config->isEmpty()) { $ddns = new KeaDhcpDdns(); if (empty((string)$ddns->general->enabled)) { $messages->appendMessage( diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php index 2668df54232..759db6a16d0 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php @@ -73,7 +73,8 @@ public function performValidation($validateFullModel = false) } // Require DDNS service to be enabled when DHCPv6's DDNS is enabled - if (!empty((string)$this->general->enable_ddns)) { + // Skip this check when manual configuration is enabled for DHCPv6 + if (!empty((string)$this->general->enable_ddns) && $this->general->manual_config->isEmpty()) { $ddns = new KeaDhcpDdns(); if (empty((string)$ddns->general->enabled)) { $messages->appendMessage( From 97facce99a05575886bac7f967dfac610ecb8766 Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Thu, 20 Nov 2025 06:51:59 +0800 Subject: [PATCH 05/15] Kea-DDNS: Address some maintainer comments on PR --- .../OPNsense/Kea/Api/DhcpddnsController.php | 15 ++------------- .../mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php | 3 +-- .../mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml | 2 +- .../mvc/app/views/OPNsense/Kea/dhcp_ddns.volt | 3 +-- 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/Api/DhcpddnsController.php b/src/opnsense/mvc/app/controllers/OPNsense/Kea/Api/DhcpddnsController.php index b5df609e37c..4e47963cdf9 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/Api/DhcpddnsController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/Api/DhcpddnsController.php @@ -1,8 +1,7 @@ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,17 +32,7 @@ class DhcpddnsController extends ApiMutableModelControllerBase { protected static $internalModelName = 'dhcp_ddns'; - protected static $internalModelClass = 'OPNsense\\Kea\\KeaDhcpDdns'; - - /** - * Return general model data for DDNS - * @return array - */ - public function getAction() - { - // default implementation from ApiMutableModelControllerBase is sufficient - return parent::getAction(); - } + protected static $internalModelClass = 'OPNsense\Kea\KeaDhcpDdns'; /* Forward DDNS domains */ public function searchForwardDomainAction() diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php index 20474adea84..2f4ed3977bc 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php @@ -1,8 +1,7 @@ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml index 68f1ec9e956..438c6c66653 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml @@ -1,6 +1,6 @@ //OPNsense/Kea/dhcp_ddns - 1.0.7 + 1.0.0 Kea DHCP-DDNS Configuration diff --git a/src/opnsense/mvc/app/views/OPNsense/Kea/dhcp_ddns.volt b/src/opnsense/mvc/app/views/OPNsense/Kea/dhcp_ddns.volt index 36503655681..67b3a04a255 100644 --- a/src/opnsense/mvc/app/views/OPNsense/Kea/dhcp_ddns.volt +++ b/src/opnsense/mvc/app/views/OPNsense/Kea/dhcp_ddns.volt @@ -1,6 +1,5 @@ {# - # Copyright (c) 2023 Deciso B.V. - # All rights reserved. + # Copyright (C) 2025 Yip Rui Fung # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: From 9ecd533b072c72fe9bc44f40f663077d4075b244 Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Fri, 21 Nov 2025 17:49:50 +0800 Subject: [PATCH 06/15] Kea-DDNS: Remove mutual validation between DHCPv4 and DHCPv6 and DDNS services for enable ddns related fields. --- .../app/models/OPNsense/Kea/KeaDhcpDdns.php | 20 ------------------- .../mvc/app/models/OPNsense/Kea/KeaDhcpv4.php | 11 ---------- .../mvc/app/models/OPNsense/Kea/KeaDhcpv6.php | 11 ---------- 3 files changed, 42 deletions(-) diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php index 2f4ed3977bc..47d54b6bb35 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php @@ -41,26 +41,6 @@ public function performValidation($validateFullModel = false) // Run default field-level validators first $messages = parent::performValidation($validateFullModel); - // Prevent disabling the DDNS service while DHCPv4 or DHCPv6 has DDNS enabled - $ddns_enabled = !empty((string)$this->general->enabled); - if (!$ddns_enabled) { - $dhcpv4 = new KeaDhcpv4(); - $dhcpv6 = new KeaDhcpv6(); - $v4_ddns = !empty((string)$dhcpv4->general->enable_ddns); - $v6_ddns = !empty((string)$dhcpv6->general->enable_ddns); - // Skip dependency when the respective service uses manual configuration - $v4_blocks_disable = $v4_ddns && $dhcpv4->general->manual_config->isEmpty(); - $v6_blocks_disable = $v6_ddns && $dhcpv6->general->manual_config->isEmpty(); - if ($v4_blocks_disable || $v6_blocks_disable) { - $messages->appendMessage( - new Message( - gettext('Cannot disable DHCP-DDNS service while DHCPv4 or DHCPv6 DDNS is enabled.'), - 'general.enabled' - ) - ); - } - } - // Explicitly validate that forward and reverse domain names end with a dot (FQDN) foreach ($this->forward_ddns->ddns_domains->iterateItems() as $domain) { if (!$validateFullModel && !$domain->isFieldChanged()) { diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php index e795e4bfa90..eb9b4a01265 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php @@ -90,17 +90,6 @@ public function performValidation($validateFullModel = false) } } - // Require DDNS service to be enabled when DHCPv4's DDNS is enabled - // Skip this check when manual configuration is enabled for DHCPv4 - if (!empty((string)$this->general->enable_ddns) && $this->general->manual_config->isEmpty()) { - $ddns = new KeaDhcpDdns(); - if (empty((string)$ddns->general->enabled)) { - $messages->appendMessage( - new Message(gettext('Enable the DHCP-DDNS service to use Dynamic DNS updates.'), 'general.enable_ddns') - ); - } - } - // Enforce that ddns qualifying suffix ends with a dot when set foreach ($this->subnets->subnet4->iterateItems() as $subnet) { if (!$validateFullModel && !$subnet->isFieldChanged()) { diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php index 759db6a16d0..166ed19b7f1 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php @@ -72,17 +72,6 @@ public function performValidation($validateFullModel = false) } } - // Require DDNS service to be enabled when DHCPv6's DDNS is enabled - // Skip this check when manual configuration is enabled for DHCPv6 - if (!empty((string)$this->general->enable_ddns) && $this->general->manual_config->isEmpty()) { - $ddns = new KeaDhcpDdns(); - if (empty((string)$ddns->general->enabled)) { - $messages->appendMessage( - new Message(gettext('Enable the DHCP-DDNS service to use Dynamic DNS updates.'), 'general.enable_ddns') - ); - } - } - // Enforce that ddns qualifying suffix ends with a dot when set foreach ($this->subnets->subnet6->iterateItems() as $subnet) { if (!$validateFullModel && !$subnet->isFieldChanged()) { From ed5d708d06f21642c41177e7a75cc8cd40685367 Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Fri, 21 Nov 2025 18:00:37 +0800 Subject: [PATCH 07/15] Kea-DDNS: Remove Dhcp4/Dhcp6 enable-ddns option in favor of enabling it when any subnet enables ddns updates --- .../OPNsense/Kea/forms/generalSettings4.xml | 6 ------ .../OPNsense/Kea/forms/generalSettings6.xml | 6 ------ src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php | 10 +++++++++- src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml | 1 - src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php | 10 +++++++++- src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml | 1 - 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings4.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings4.xml index a46e427866c..3ceeddb0a54 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings4.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings4.xml @@ -44,12 +44,6 @@ dropdown Socket type used for DHCP communication
- - dhcpv4.general.enable_ddns - - checkbox - Enable Dynamic DNS Updates (DDNS) using the Kea DHCP-DDNS server. - header diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings6.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings6.xml index ccec6b0ce82..660ad59bc29 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings6.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/generalSettings6.xml @@ -38,12 +38,6 @@ checkbox Automatically add a basic set of firewall rules to allow dhcp traffic, more fine grained controls can be offered manually when disabling this option. - - dhcpv6.general.enable_ddns - - checkbox - Enable Dynamic DNS Updates (DDNS) using the Kea DHCP-DDNS server. - header diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php index eb9b4a01265..4197770df82 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php @@ -289,7 +289,7 @@ public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp4.conf') 'socket-name' => '/var/run/kea/kea4-ctrl-socket' ], 'dhcp-ddns' => [ - 'enable-updates' => !empty((string)$this->general->enable_ddns), + 'enable-updates' => false, 'server-ip' => '127.0.0.1', 'server-port' => 53001, ], @@ -311,6 +311,14 @@ public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp4.conf') if ($expiredLeasesConfig !== null) { $cnf['Dhcp4']['expired-leases-processing'] = $expiredLeasesConfig; } + + foreach ($this->subnets->subnet4->iterateItems() as $subnet) { + if (!empty((string)$subnet->ddns_options->send_updates)) { + $cnf['Dhcp4']['dhcp-ddns']['enable-updates'] = true; + break; + } + } + if (!(new KeaCtrlAgent())->general->enabled->isEmpty()) { $cnf['Dhcp4']['hooks-libraries'] = []; $cnf['Dhcp4']['hooks-libraries'][] = [ diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml index 25a408660bf..ec106141d7f 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml @@ -28,7 +28,6 @@ raw - diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php index 166ed19b7f1..3ebb9d9bb90 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php @@ -275,7 +275,7 @@ public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp6.conf') 'socket-name' => '/var/run/kea/kea6-ctrl-socket' ], 'dhcp-ddns' => [ - 'enable-updates' => !empty((string)$this->general->enable_ddns), + 'enable-updates' => false, 'server-ip' => '127.0.0.1', 'server-port' => 53001, ], @@ -297,6 +297,14 @@ public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp6.conf') if ($expiredLeasesConfig !== null) { $cnf['Dhcp6']['expired-leases-processing'] = $expiredLeasesConfig; } + + foreach ($this->subnets->subnet6->iterateItems() as $subnet) { + if (!empty((string)$subnet->ddns_options->send_updates)) { + $cnf['Dhcp6']['dhcp-ddns']['enable-updates'] = true; + break; + } + } + if (!(new KeaCtrlAgent())->general->enabled->isEmpty()) { $cnf['Dhcp6']['hooks-libraries'] = []; $cnf['Dhcp6']['hooks-libraries'][] = [ diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml index e8f34272e73..4d19d26df45 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml @@ -20,7 +20,6 @@ Y 1 - From 9b7796be3b123c0e1c320e4a2685c60f7b9ff89a Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Fri, 21 Nov 2025 18:14:21 +0800 Subject: [PATCH 08/15] Kea-DDNS: Rename DNS Server 'Name' to 'Description' --- .../OPNsense/Kea/forms/dialogDdnsDnsServer.xml | 8 ++++---- src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml index fa91efa2f68..b1509a801a7 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml @@ -1,11 +1,11 @@
- dns_servers.name - + dns_servers.description + text - Display name for this DNS server (not used in generated config). + Description for this DNS server entry - + true diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml index 438c6c66653..1fb1686d000 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml @@ -12,12 +12,11 @@ - + Y - + N - Y @@ -62,7 +61,7 @@ OPNsense.Kea.KeaDhcpDdns dns_servers - name + description Y @@ -97,7 +96,7 @@ OPNsense.Kea.KeaDhcpDdns dns_servers - name + description Y From 71be959ce4310e813d946df6f7bdf3cb7c1faee6 Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Fri, 21 Nov 2025 18:18:04 +0800 Subject: [PATCH 09/15] Kea-DDNS: remove override_no_update, override_client_update, hostname_char_set, hostname_char_replacement options because they are very unlikely to be needed by almost everyone --- .../OPNsense/Kea/forms/dialogSubnet4.xml | 40 ------------------- .../OPNsense/Kea/forms/dialogSubnet6.xml | 40 ------------------- .../mvc/app/models/OPNsense/Kea/KeaDhcpv4.php | 12 ------ .../mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml | 4 -- .../mvc/app/models/OPNsense/Kea/KeaDhcpv6.php | 12 ------ .../mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml | 4 -- 6 files changed, 112 deletions(-) diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml index cf82bd0a5af..2f8df645c5e 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml @@ -42,26 +42,6 @@ false - - subnet4.ddns_options.override_no_update - - checkbox - Override client request to disable DNS updates. - true - - false - - - - subnet4.ddns_options.override_client_update - - checkbox - Server performs updates regardless of client preference. - true - - false - - subnet4.ddns_options.replace_client_name @@ -99,26 +79,6 @@ false - - subnet4.ddns_options.hostname_char_set - - text - POSIX Extended Regex Expression that matches invalid characters. Default: [^A-Za-z0-9.-] - true - - false - - - - subnet4.ddns_options.hostname_char_replacement - - text - Replacement for each invalid character. Empty means omit invalid characters. - true - - false - - subnet4.ddns_options.conflict_resolution_mode diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml index 133db34dabe..ee15a711f1f 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml @@ -51,26 +51,6 @@ false - - subnet6.ddns_options.override_no_update - - checkbox - Override client request to disable DNS updates. - true - - false - - - - subnet6.ddns_options.override_client_update - - checkbox - Server performs updates regardless of client preference. - true - - false - - subnet6.ddns_options.replace_client_name @@ -108,26 +88,6 @@ false - - subnet6.ddns_options.hostname_char_set - - text - POSIX Extended Regex Expression that matches invalid characters. Default: [^A-Za-z0-9.-] - true - - false - - - - subnet6.ddns_options.hostname_char_replacement - - text - Replacement for each invalid character. Empty means omit invalid characters. - true - - false - - subnet6.ddns_options.conflict_resolution_mode diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php index 4197770df82..7524f4e7ab1 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php @@ -198,12 +198,6 @@ private function getConfigSubnets() $ddns_send_updates = !empty((string)$subnet->ddns_options->send_updates); if ($ddns_send_updates) { $record['ddns-send-updates'] = true; - if (!empty((string)$subnet->ddns_options->override_no_update)) { - $record['ddns-override-no-update'] = true; - } - if (!empty((string)$subnet->ddns_options->override_client_update)) { - $record['ddns-override-client-update'] = true; - } if ((string)$subnet->ddns_options->replace_client_name !== '') { $record['ddns-replace-client-name'] = (string)$subnet->ddns_options->replace_client_name; } @@ -213,12 +207,6 @@ private function getConfigSubnets() if ((string)$subnet->ddns_options->qualifying_suffix !== '') { $record['ddns-qualifying-suffix'] = (string)$subnet->ddns_options->qualifying_suffix; } - if ((string)$subnet->ddns_options->hostname_char_set !== '') { - $record['hostname-char-set'] = (string)$subnet->ddns_options->hostname_char_set; - } - if ((string)$subnet->ddns_options->hostname_char_replacement !== '') { - $record['hostname-char-replacement'] = (string)$subnet->ddns_options->hostname_char_replacement; - } if (!empty((string)$subnet->ddns_options->update_on_renew)) { $record['ddns-update-on-renew'] = true; } diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml index ec106141d7f..6a38bfc8ac6 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml @@ -138,8 +138,6 @@ - - never Y @@ -153,8 +151,6 @@ - - check-with-dhcid Y diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php index 3ebb9d9bb90..d99bbd60e63 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php @@ -152,12 +152,6 @@ private function getConfigSubnets() $ddns_send_updates = !empty((string)$subnet->ddns_options->send_updates); if ($ddns_send_updates) { $record['ddns-send-updates'] = true; - if (!empty((string)$subnet->ddns_options->override_no_update)) { - $record['ddns-override-no-update'] = true; - } - if (!empty((string)$subnet->ddns_options->override_client_update)) { - $record['ddns-override-client-update'] = true; - } if ((string)$subnet->ddns_options->replace_client_name !== '') { $record['ddns-replace-client-name'] = (string)$subnet->ddns_options->replace_client_name; } @@ -167,12 +161,6 @@ private function getConfigSubnets() if ((string)$subnet->ddns_options->qualifying_suffix !== '') { $record['ddns-qualifying-suffix'] = (string)$subnet->ddns_options->qualifying_suffix; } - if ((string)$subnet->ddns_options->hostname_char_set !== '') { - $record['hostname-char-set'] = (string)$subnet->ddns_options->hostname_char_set; - } - if ((string)$subnet->ddns_options->hostname_char_replacement !== '') { - $record['hostname-char-replacement'] = (string)$subnet->ddns_options->hostname_char_replacement; - } if (!empty((string)$subnet->ddns_options->update_on_renew)) { $record['ddns-update-on-renew'] = true; } diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml index 4d19d26df45..b9c3c2b4364 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml @@ -107,8 +107,6 @@ - - never Y @@ -122,8 +120,6 @@ - - check-with-dhcid Y From e124b1f89decd75ed6034c57c1ed7a743f7d2c53 Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Sun, 23 Nov 2025 20:37:09 +0800 Subject: [PATCH 10/15] Kea-DDNS: Address additional maintainer comments --- .../app/models/OPNsense/Kea/KeaDhcpDdns.php | 54 +++++++++---------- .../app/models/OPNsense/Kea/KeaDhcpDdns.xml | 18 +++---- .../mvc/app/models/OPNsense/Kea/KeaDhcpv4.php | 32 +++++------ .../mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml | 4 +- .../mvc/app/models/OPNsense/Kea/KeaDhcpv6.php | 29 +++++----- .../mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml | 4 +- 6 files changed, 67 insertions(+), 74 deletions(-) diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php index 47d54b6bb35..d93a98a710e 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php @@ -46,8 +46,7 @@ public function performValidation($validateFullModel = false) if (!$validateFullModel && !$domain->isFieldChanged()) { continue; } - $name = trim((string)$domain->name); - if ($name !== '' && substr($name, -1) !== '.') { + if (!($domain->name->isEmpty()) && !str_ends_with($domain->name->getValue(), '.')) { $messages->appendMessage( new Message( gettext('Domain must be a fully qualified domain name ending with a dot.'), @@ -60,8 +59,7 @@ public function performValidation($validateFullModel = false) if (!$validateFullModel && !$domain->isFieldChanged()) { continue; } - $name = trim((string)$domain->name); - if ($name !== '' && substr($name, -1) !== '.') { + if (!($domain->name->isEmpty()) && !str_ends_with($domain->name->getValue(), '.')) { $messages->appendMessage( new Message( gettext('Domain must be a fully qualified domain name ending with a dot.'), @@ -76,7 +74,7 @@ public function performValidation($validateFullModel = false) public function isEnabled() { - return (string)$this->general->enabled == '1'; + return $this->general->enabled->isEmpty(); } /** @@ -90,16 +88,16 @@ private function getSharedDnsServersMap() $tsigNameMap = $this->getTsigKeyNameMap(); foreach ($this->dns_servers->iterateItems() as $uuid => $srv) { $item = []; - if ((string)$srv->ip_address !== '') { - $item['ip-address'] = (string)$srv->ip_address; + if (!($srv->ip_address->isEmpty())) { + $item['ip-address'] = $srv->ip_address->getValue(); } - if ((string)$srv->port !== '') { - $item['port'] = (int)((string)$srv->port); + if (!($srv->port->isEmpty())) { + $item['port'] = (int)($srv->port->getValue()); } - if ((string)$srv->key_name !== '') { - $kn = (string)$srv->key_name; + if (!($srv->key_name->isEmpty())) { + $kn = $srv->key_name->getValue(); // key_name is a ModelRelationField (UUID). Resolve to TSIG key name. - if (isset($tsigNameMap[$kn]) && $tsigNameMap[$kn] !== '') { + if (!empty($tsigNameMap[$kn])) { $item['key-name'] = $tsigNameMap[$kn]; } } @@ -118,9 +116,8 @@ private function getTsigKeyNameMap() { $map = []; foreach ($this->tsig_keys->iterateItems() as $uuid => $key) { - $name = (string)$key->name; - if ($name !== '') { - $map[$uuid] = $name; + if (!($key->name->isEmpty())) { + $map[$uuid] = $key->name->getValue(); } } return $map; @@ -130,14 +127,14 @@ private function getTsigKeys() { $tsig_keys = []; foreach ($this->tsig_keys->iterateItems() as $key) { $item = []; - if ((string)$key->name !== '') { - $item['name'] = (string)$key->name; + if (!($key->name->isEmpty())) { + $item['name'] = $key->name->getValue(); } - if ((string)$key->algorithm !== '') { - $item['algorithm'] = (string)$key->algorithm; + if (!($key->algorithm->isEmpty())) { + $item['algorithm'] = $key->algorithm->getValue(); } - if ((string)$key->secret !== '') { - $item['secret'] = (string)$key->secret; + if (!($key->secret->isEmpty())) { + $item['secret'] = $key->secret->getValue(); } if (!empty($item)) { $tsig_keys[] = $item; @@ -152,24 +149,23 @@ private function buildDomains ($domainsNode) { $tsigNameMap = $this->getTsigKeyNameMap(); foreach ($domainsNode->iterateItems() as $domain) { $entry = []; - if ((string)$domain->name !== '') { + if (!($domain->name->isEmpty())) { // emit stored value as-is; validation ensures FQDN (trailing dot) - $entry['name'] = (string)$domain->name; + $entry['name'] = $domain->name->getValue(); } - if ((string)$domain->key_name !== '') { - $kn = (string)$domain->key_name; // UUID from ModelRelationField - if (isset($tsigNameMap[$kn]) && $tsigNameMap[$kn] !== '') { + if (!($domain->key_name->isEmpty())) { + $kn = $domain->key_name->getValue(); // UUID from ModelRelationField + if (!empty($tsigNameMap[$kn])) { $entry['key-name'] = $tsigNameMap[$kn]; } } // dns-servers referenced via ModelRelationField (comma-separated UUIDs) $servers = []; - $refs = isset($domain->dns_servers) ? (string)$domain->dns_servers : ''; + $refs = !($domain->dns_servers->isEmpty()) ? $domain->dns_servers->getValue() : ''; if (!empty($refs)) { foreach (array_filter(explode(',', $refs)) as $uuid) { - $uuid = trim($uuid); - if ($uuid === '' || !isset($serversMap[$uuid])) { + if (empty($uuid) || empty($serversMap[$uuid])) { continue; } $servers[] = $serversMap[$uuid]; diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml index 1fb1686d000..1f74b3e77cc 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.xml @@ -12,16 +12,14 @@ - + Y N Y - - 1 - 65535 + 53 Y @@ -41,10 +39,10 @@ N Please specify a valid domain name. - - Duplicate forward domain exists. + UniqueConstraint - + Duplicate forward domain exists. + @@ -76,10 +74,10 @@ N Please specify a valid domain name. - - Duplicate reverse domain exists. + UniqueConstraint - + Duplicate reverse domain exists. + diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php index 7524f4e7ab1..ba399bcf39a 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php @@ -95,9 +95,10 @@ public function performValidation($validateFullModel = false) if (!$validateFullModel && !$subnet->isFieldChanged()) { continue; } - $send_updates = !empty((string)$subnet->ddns_options->send_updates); - $suffix = trim((string)$subnet->ddns_options->qualifying_suffix); - if ($send_updates && $suffix !== '' && substr($suffix, -1) !== '.') { + + $suffix = $subnet->ddns_options->qualifying_suffix; + if (!($subnet->ddns_options->send_updates->isEmpty()) && + !($suffix->isEmpty()) && !str_ends_with($suffix->getValue(), '.')) { $messages->appendMessage( new Message( gettext('DDNS qualifying suffix must end with a dot.'), @@ -194,24 +195,23 @@ private function getConfigSubnets() ]; // Conditionally include DDNS settings only when send-updates is enabled, - // and only include fields that have meaningful values. - $ddns_send_updates = !empty((string)$subnet->ddns_options->send_updates); - if ($ddns_send_updates) { + // and only include fields that have meaningful values.; + if (!($subnet->ddns_options->send_updates->isEmpty())) { $record['ddns-send-updates'] = true; - if ((string)$subnet->ddns_options->replace_client_name !== '') { - $record['ddns-replace-client-name'] = (string)$subnet->ddns_options->replace_client_name; + if (!($subnet->ddns_options->replace_client_name->isEmpty())) { + $record['ddns-replace-client-name'] = $subnet->ddns_options->replace_client_name->getValue(); } - if ((string)$subnet->ddns_options->generated_prefix !== '') { - $record['ddns-generated-prefix'] = (string)$subnet->ddns_options->generated_prefix; + if (!($subnet->ddns_options->generated_prefix->isEmpty())) { + $record['ddns-generated-prefix'] = $subnet->ddns_options->generated_prefix->getValue(); } - if ((string)$subnet->ddns_options->qualifying_suffix !== '') { - $record['ddns-qualifying-suffix'] = (string)$subnet->ddns_options->qualifying_suffix; + if (!($subnet->ddns_options->qualifying_suffix->isEmpty())) { + $record['ddns-qualifying-suffix'] = $subnet->ddns_options->qualifying_suffix->getValue(); } - if (!empty((string)$subnet->ddns_options->update_on_renew)) { + if (!($subnet->ddns_options->update_on_renew->isEmpty())) { $record['ddns-update-on-renew'] = true; } - if ((string)$subnet->ddns_options->conflict_resolution_mode !== '') { - $record['ddns-conflict-resolution-mode'] = (string)$subnet->ddns_options->conflict_resolution_mode; + if (!($subnet->ddns_options->conflict_resolution_mode->isEmpty())) { + $record['ddns-conflict-resolution-mode'] = $subnet->ddns_options->conflict_resolution_mode->getValue(); } } /* add pools */ @@ -301,7 +301,7 @@ public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp4.conf') } foreach ($this->subnets->subnet4->iterateItems() as $subnet) { - if (!empty((string)$subnet->ddns_options->send_updates)) { + if (!($subnet->ddns_options->send_updates->isEmpty())) { $cnf['Dhcp4']['dhcp-ddns']['enable-updates'] = true; break; } diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml index 6a38bfc8ac6..8b2027a80d1 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml @@ -148,9 +148,9 @@ when-not-present - + - + check-with-dhcid Y diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php index d99bbd60e63..3c616859424 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php @@ -77,9 +77,9 @@ public function performValidation($validateFullModel = false) if (!$validateFullModel && !$subnet->isFieldChanged()) { continue; } - $send_updates = !empty((string)$subnet->ddns_options->send_updates); - $suffix = trim((string)$subnet->ddns_options->qualifying_suffix); - if ($send_updates && $suffix !== '' && substr($suffix, -1) !== '.') { + $suffix = $subnet->ddns_options->qualifying_suffix; + if (!($subnet->ddns_options->send_updates->isEmpty()) && + !($suffix->isEmpty()) && !str_ends_with($suffix->getValue(), '.')) { $messages->appendMessage( new Message( gettext('DDNS qualifying suffix must end with a dot.'), @@ -149,23 +149,22 @@ private function getConfigSubnets() // Conditionally include DDNS settings only when send-updates is enabled, // and only include fields that have meaningful values. - $ddns_send_updates = !empty((string)$subnet->ddns_options->send_updates); - if ($ddns_send_updates) { + if (!($subnet->ddns_options->send_updates->isEmpty())) { $record['ddns-send-updates'] = true; - if ((string)$subnet->ddns_options->replace_client_name !== '') { - $record['ddns-replace-client-name'] = (string)$subnet->ddns_options->replace_client_name; + if (!($subnet->ddns_options->replace_client_name->isEmpty())) { + $record['ddns-replace-client-name'] = $subnet->ddns_options->replace_client_name->getValue(); } - if ((string)$subnet->ddns_options->generated_prefix !== '') { - $record['ddns-generated-prefix'] = (string)$subnet->ddns_options->generated_prefix; + if (!($subnet->ddns_options->generated_prefix->isEmpty())) { + $record['ddns-generated-prefix'] = $subnet->ddns_options->generated_prefix->getValue(); } - if ((string)$subnet->ddns_options->qualifying_suffix !== '') { - $record['ddns-qualifying-suffix'] = (string)$subnet->ddns_options->qualifying_suffix; + if (!($subnet->ddns_options->qualifying_suffix->isEmpty())) { + $record['ddns-qualifying-suffix'] = $subnet->ddns_options->qualifying_suffix->getValue(); } - if (!empty((string)$subnet->ddns_options->update_on_renew)) { + if (!($subnet->ddns_options->update_on_renew->isEmpty())) { $record['ddns-update-on-renew'] = true; } - if ((string)$subnet->ddns_options->conflict_resolution_mode !== '') { - $record['ddns-conflict-resolution-mode'] = (string)$subnet->ddns_options->conflict_resolution_mode; + if (!($subnet->ddns_options->conflict_resolution_mode->isEmpty())) { + $record['ddns-conflict-resolution-mode'] = $subnet->ddns_options->conflict_resolution_mode->getValue(); } } $if = $subnet->interface->getValue(); @@ -287,7 +286,7 @@ public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp6.conf') } foreach ($this->subnets->subnet6->iterateItems() as $subnet) { - if (!empty((string)$subnet->ddns_options->send_updates)) { + if (!($subnet->ddns_options->send_updates->isEmpty())) { $cnf['Dhcp6']['dhcp-ddns']['enable-updates'] = true; break; } diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml index b9c3c2b4364..5d817898ea1 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml @@ -117,9 +117,9 @@ when-not-present - + - + check-with-dhcid Y From b0721f4e3837e7159cb59e0cfb7ae2ed9eda5d2d Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Sun, 23 Nov 2025 20:42:19 +0800 Subject: [PATCH 11/15] Kea-DDNS: Fix table header for Forward/Reverse domains dialogs to be consistent with the tab name. --- .../controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml | 2 +- .../controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml index 609879308ed..c83ce4edb3a 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml @@ -1,7 +1,7 @@ ddns_domains.name - + text Forward DDNS domain (e.g. example.com.) diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml index 4f0d12bec45..955454d7a32 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml @@ -1,7 +1,7 @@ ddns_domains.name - + text Reverse DDNS domain (e.g. 0.168.192.in-addr.arpa. or ip6.arpa. variant) From bec09e39180d1506e2ff1fceed4a9d602ea0ec81 Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Mon, 24 Nov 2025 07:24:49 +0800 Subject: [PATCH 12/15] Kea-DDNS: Remove redundant grid_view keys from ddns dialog form fields --- .../controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml | 7 ------- .../OPNsense/Kea/forms/dialogDdnsForwardDomain.xml | 7 ------- .../OPNsense/Kea/forms/dialogDdnsReverseDomain.xml | 7 ------- 3 files changed, 21 deletions(-) diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml index b1509a801a7..947a6b81aa3 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsDnsServer.xml @@ -4,10 +4,6 @@ text Description for this DNS server entry - - - true - dns_servers.ip_address @@ -26,8 +22,5 @@ dropdown Select an optional TSIG key for updates sent to this server. When set, this server-specific key overrides any TSIG key configured on the domain/zone. - - - diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml index c83ce4edb3a..f9d29901fe2 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsForwardDomain.xml @@ -10,18 +10,11 @@ dropdown Select an optional TSIG key to authenticate updates for this domain. Note: if a DNS server has a TSIG key configured, that server-specific key takes precedence over the domain key. - - -
ddns_domains.dns_servers select_multiple Select one or more shared DNS servers to update for this domain. - - - true - diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml index 955454d7a32..eaa47754b64 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogDdnsReverseDomain.xml @@ -10,18 +10,11 @@ dropdown Select an optional TSIG key to authenticate updates for this reverse zone. Note: if a DNS server has a TSIG key configured, that server-specific key takes precedence over the domain key. - - - ddns_domains.dns_servers select_multiple Select one or more shared DNS servers to update for this reverse zone. - - - true - From 66f4784964ad3ce5089f7954fc270cfb2da607be Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Fri, 28 Nov 2025 21:54:03 +0800 Subject: [PATCH 13/15] Kea-DDNS: Remove the mostly useless generated_prefix and replace_client_name options These options cause kea to generate a DDNS name of prefix-(ip address text) as the hostname. This is of very questionable usefulness, because if you're using that dns record, you already know what the IP address is. --- .../OPNsense/Kea/forms/dialogSubnet4.xml | 19 ------------------- .../OPNsense/Kea/forms/dialogSubnet6.xml | 19 ------------------- .../mvc/app/models/OPNsense/Kea/KeaDhcpv4.php | 6 ------ .../mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml | 11 ----------- .../mvc/app/models/OPNsense/Kea/KeaDhcpv6.php | 6 ------ .../mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml | 11 ----------- 6 files changed, 72 deletions(-) diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml index 2f8df645c5e..2dd2b6b5f59 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet4.xml @@ -42,25 +42,6 @@ false - - subnet4.ddns_options.replace_client_name - - dropdown - Controls when the server replaces the client supplied hostname. - - false - - - - subnet4.ddns_options.generated_prefix - - text - Prefix to use when generating hostnames. - true - - false - - subnet4.ddns_options.update_on_renew diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml index ee15a711f1f..6c95ba19808 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Kea/forms/dialogSubnet6.xml @@ -51,25 +51,6 @@ false - - subnet6.ddns_options.replace_client_name - - dropdown - Controls when the server replaces the client supplied hostname. - - false - - - - subnet6.ddns_options.generated_prefix - - text - Prefix to use when generating hostnames. - true - - false - - subnet6.ddns_options.update_on_renew diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php index ba399bcf39a..2e747d2d4ba 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php @@ -198,12 +198,6 @@ private function getConfigSubnets() // and only include fields that have meaningful values.; if (!($subnet->ddns_options->send_updates->isEmpty())) { $record['ddns-send-updates'] = true; - if (!($subnet->ddns_options->replace_client_name->isEmpty())) { - $record['ddns-replace-client-name'] = $subnet->ddns_options->replace_client_name->getValue(); - } - if (!($subnet->ddns_options->generated_prefix->isEmpty())) { - $record['ddns-generated-prefix'] = $subnet->ddns_options->generated_prefix->getValue(); - } if (!($subnet->ddns_options->qualifying_suffix->isEmpty())) { $record['ddns-qualifying-suffix'] = $subnet->ddns_options->qualifying_suffix->getValue(); } diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml index 8b2027a80d1..cb230369691 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.xml @@ -138,17 +138,6 @@ - - never - Y - - never - always - when-present - when-not-present - - - diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php index 3c616859424..8bd2186fe96 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php @@ -151,12 +151,6 @@ private function getConfigSubnets() // and only include fields that have meaningful values. if (!($subnet->ddns_options->send_updates->isEmpty())) { $record['ddns-send-updates'] = true; - if (!($subnet->ddns_options->replace_client_name->isEmpty())) { - $record['ddns-replace-client-name'] = $subnet->ddns_options->replace_client_name->getValue(); - } - if (!($subnet->ddns_options->generated_prefix->isEmpty())) { - $record['ddns-generated-prefix'] = $subnet->ddns_options->generated_prefix->getValue(); - } if (!($subnet->ddns_options->qualifying_suffix->isEmpty())) { $record['ddns-qualifying-suffix'] = $subnet->ddns_options->qualifying_suffix->getValue(); } diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml index 5d817898ea1..8a14c22c541 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.xml @@ -107,17 +107,6 @@ - - never - Y - - never - always - when-present - when-not-present - - - From c44279366ad6a4ed1496a1bc19091c7e2ccc90d5 Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Fri, 28 Nov 2025 22:07:17 +0800 Subject: [PATCH 14/15] Kea-DDNS: Replace some field access and casting methods to use the newer accessor methods. --- src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php | 4 ++-- src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php | 8 ++++---- src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php index d93a98a710e..3d9960d1b68 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpDdns.php @@ -74,7 +74,7 @@ public function performValidation($validateFullModel = false) public function isEnabled() { - return $this->general->enabled->isEmpty(); + return $this->general->enabled->isEqual('1'); } /** @@ -92,7 +92,7 @@ private function getSharedDnsServersMap() $item['ip-address'] = $srv->ip_address->getValue(); } if (!($srv->port->isEmpty())) { - $item['port'] = (int)($srv->port->getValue()); + $item['port'] = $srv->port->asInt(); } if (!($srv->key_name->isEmpty())) { $kn = $srv->key_name->getValue(); diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php index 2e747d2d4ba..2a7bb03219f 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php @@ -97,7 +97,7 @@ public function performValidation($validateFullModel = false) } $suffix = $subnet->ddns_options->qualifying_suffix; - if (!($subnet->ddns_options->send_updates->isEmpty()) && + if (!($subnet->ddns_options->send_updates->isEqual('1')) && !($suffix->isEmpty()) && !str_ends_with($suffix->getValue(), '.')) { $messages->appendMessage( new Message( @@ -196,12 +196,12 @@ private function getConfigSubnets() // Conditionally include DDNS settings only when send-updates is enabled, // and only include fields that have meaningful values.; - if (!($subnet->ddns_options->send_updates->isEmpty())) { + if (!($subnet->ddns_options->send_updates->isEqual('1'))) { $record['ddns-send-updates'] = true; if (!($subnet->ddns_options->qualifying_suffix->isEmpty())) { $record['ddns-qualifying-suffix'] = $subnet->ddns_options->qualifying_suffix->getValue(); } - if (!($subnet->ddns_options->update_on_renew->isEmpty())) { + if ($subnet->ddns_options->update_on_renew->isEqual('1')) { $record['ddns-update-on-renew'] = true; } if (!($subnet->ddns_options->conflict_resolution_mode->isEmpty())) { @@ -295,7 +295,7 @@ public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp4.conf') } foreach ($this->subnets->subnet4->iterateItems() as $subnet) { - if (!($subnet->ddns_options->send_updates->isEmpty())) { + if ($subnet->ddns_options->send_updates->isEqual('1')) { $cnf['Dhcp4']['dhcp-ddns']['enable-updates'] = true; break; } diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php index 8bd2186fe96..0a4a9a981ff 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv6.php @@ -78,7 +78,7 @@ public function performValidation($validateFullModel = false) continue; } $suffix = $subnet->ddns_options->qualifying_suffix; - if (!($subnet->ddns_options->send_updates->isEmpty()) && + if ($subnet->ddns_options->send_updates->isEqual('1') && !($suffix->isEmpty()) && !str_ends_with($suffix->getValue(), '.')) { $messages->appendMessage( new Message( @@ -149,12 +149,12 @@ private function getConfigSubnets() // Conditionally include DDNS settings only when send-updates is enabled, // and only include fields that have meaningful values. - if (!($subnet->ddns_options->send_updates->isEmpty())) { + if ($subnet->ddns_options->send_updates->isEqual('1')) { $record['ddns-send-updates'] = true; if (!($subnet->ddns_options->qualifying_suffix->isEmpty())) { $record['ddns-qualifying-suffix'] = $subnet->ddns_options->qualifying_suffix->getValue(); } - if (!($subnet->ddns_options->update_on_renew->isEmpty())) { + if ($subnet->ddns_options->update_on_renew->isEqual('1')) { $record['ddns-update-on-renew'] = true; } if (!($subnet->ddns_options->conflict_resolution_mode->isEmpty())) { @@ -280,7 +280,7 @@ public function generateConfig($target = '/usr/local/etc/kea/kea-dhcp6.conf') } foreach ($this->subnets->subnet6->iterateItems() as $subnet) { - if (!($subnet->ddns_options->send_updates->isEmpty())) { + if ($subnet->ddns_options->send_updates->isEqual('1')) { $cnf['Dhcp6']['dhcp-ddns']['enable-updates'] = true; break; } From cf3ecc830649f409e2154609b68e221046284bea Mon Sep 17 00:00:00 2001 From: Yip Rui Fung Date: Sat, 29 Nov 2025 13:32:58 +0800 Subject: [PATCH 15/15] Kea-DDNS: Fix accidental inversion on dhcp4 ddns config generation --- src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php index 2a7bb03219f..5381243913e 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php +++ b/src/opnsense/mvc/app/models/OPNsense/Kea/KeaDhcpv4.php @@ -196,7 +196,7 @@ private function getConfigSubnets() // Conditionally include DDNS settings only when send-updates is enabled, // and only include fields that have meaningful values.; - if (!($subnet->ddns_options->send_updates->isEqual('1'))) { + if ($subnet->ddns_options->send_updates->isEqual('1')) { $record['ddns-send-updates'] = true; if (!($subnet->ddns_options->qualifying_suffix->isEmpty())) { $record['ddns-qualifying-suffix'] = $subnet->ddns_options->qualifying_suffix->getValue();