This commit is contained in:
Monviech 2026-02-02 12:13:29 +01:00 committed by GitHub
commit 6febf1a16d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1028 additions and 0 deletions

8
plist
View file

@ -1088,6 +1088,14 @@
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/VirtualIPFieldTest.php
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Base/FieldTypes/VirtualIPFieldTest/config.xml
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Dnsmasq/FieldTypes/HostnameFieldTest.php
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Kea/Base/JsonSampleTestCase.php
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Kea/KeaCtrlAgentTest.php
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Kea/KeaDhcpv4Test.php
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Kea/KeaDhcpv6Test.php
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Kea/Snapshots/KeaCtrlAgentTest.json
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Kea/Snapshots/KeaDhcpv4Test.json
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Kea/Snapshots/KeaDhcpv6Test.json
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Kea/Snapshots/config.xml
/usr/local/opnsense/mvc/tests/phpunit.xml
/usr/local/opnsense/mvc/tests/setup.php
/usr/local/opnsense/scripts/auth/add_user.php

View file

@ -0,0 +1,107 @@
<?php
/*
* Copyright (C) 2025 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.
*/
namespace tests\OPNsense\Kea;
use OPNsense\Core\Config;
use OPNsense\Core\AppConfig;
use PHPUnit\Framework\TestCase;
abstract class JsonSampleTestCase extends TestCase
{
abstract protected function getSnapshotFile(): string;
abstract protected function getModelInstance();
abstract protected function getJsonRootKey(): string;
private function getSnapshotPath(): string
{
return dirname(__DIR__) . '/Snapshots/' . $this->getSnapshotFile();
}
private function loadSnapshotConfig(): void
{
$snapDir = dirname(__DIR__) . '/Snapshots';
(new AppConfig())->update('application.configDir', $snapDir);
Config::getInstance()->forceReload();
}
private function loadExpectedSnapshot(): array
{
$file = $this->getSnapshotPath();
$json = json_decode(file_get_contents($file), true);
$this->assertNotNull($json, 'Failed to decode expected JSON: ' . $file);
return $json;
}
private function assertJsonEqualWithPath(array $expected, array $actual, string $path = ''): void
{
// expected keys
foreach ($expected as $key => $expValue) {
$current = $path === '' ? $key : "$path.$key";
$this->assertArrayHasKey($key, $actual, "Missing key at: $current");
$actValue = $actual[$key];
if (is_array($expValue)) {
$this->assertIsArray($actValue, "Type mismatch at: $current");
$this->assertJsonEqualWithPath($expValue, $actValue, $current);
} else {
$this->assertSame($expValue, $actValue, "Value mismatch at: $current");
}
}
// unexpected keys
foreach ($actual as $key => $actValue) {
$current = $path === '' ? $key : "$path.$key";
$this->assertArrayHasKey($key, $expected, "Unexpected key at: $current");
}
}
protected function runJsonSampleTest(): void
{
$this->loadSnapshotConfig();
$model = $this->getModelInstance();
$tmp = tempnam('/var/lib/php/tmp/', 'json_sample_');
$model->generateConfig($tmp);
$expected = $this->loadExpectedSnapshot();
$actual = json_decode(file_get_contents($tmp), true);
$this->assertNotNull($actual, 'Generated JSON is invalid.');
$root = $this->getJsonRootKey();
$this->assertArrayHasKey($root, $expected);
$this->assertArrayHasKey($root, $actual);
$this->assertJsonEqualWithPath($expected[$root], $actual[$root], $root);
}
}

View file

@ -0,0 +1,56 @@
<?php
/*
* Copyright (C) 2025 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.
*/
namespace tests\OPNsense\Kea;
require_once __DIR__ . '/Base/JsonSampleTestCase.php';
use OPNsense\Kea\KeaCtrlAgent;
class KeaCtrlAgentTest extends JsonSampleTestCase
{
protected function getSnapshotFile(): string
{
return 'KeaCtrlAgentTest.json';
}
protected function getModelInstance()
{
return new KeaCtrlAgent();
}
protected function getJsonRootKey(): string
{
return 'Control-agent';
}
public function testJsonSample(): void
{
$this->runJsonSampleTest();
}
}

View file

@ -0,0 +1,107 @@
<?php
/*
* Copyright (C) 2025 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.
*/
namespace tests\OPNsense\Kea;
require_once __DIR__ . '/Base/JsonSampleTestCase.php';
use OPNsense\Kea\KeaDhcpv4;
class KeaDhcpv4Test extends JsonSampleTestCase
{
protected function getSnapshotFile(): string
{
return 'KeaDhcpv4Test.json';
}
protected function getModelInstance()
{
return new KeaDhcpv4();
}
protected function getJsonRootKey(): string
{
return 'Dhcp4';
}
public function testJsonSample(): void
{
$this->runJsonSampleTest();
}
public function testIsEnabled(): void
{
$m = $this->getModelInstance();
$m->general->enabled = "0";
$m->general->interfaces = "";
$this->assertFalse($m->isEnabled());
$m->general->enabled = "1";
$this->assertFalse($m->isEnabled());
$m->general->interfaces = "lan";
$this->assertTrue($m->isEnabled());
}
public function testFwRulesEnabled(): void
{
$m = $this->getModelInstance();
$m->general->enabled = "0";
$m->general->fwrules = "0";
$m->general->interfaces = "";
$this->assertFalse($m->fwrulesEnabled());
$m->general->enabled = "1";
$this->assertFalse($m->fwrulesEnabled());
$m->general->fwrules = "1";
$this->assertFalse($m->fwrulesEnabled());
$m->general->interfaces = "lan";
$this->assertTrue($m->fwrulesEnabled());
}
public function testPhysicalInterfacesParsing(): void
{
// config.xml already loaded by JsonSampleTestCase::loadSnapshotConfig()
$m = $this->getModelInstance();
$m->general->interfaces = "lan,opt1";
$ifs = (new \ReflectionClass($m))
->getMethod('getConfigPhysicalInterfaces')
->invoke($m);
$this->assertSame(['igc0','igc2'], $ifs);
}
}

View file

@ -0,0 +1,106 @@
<?php
/*
* Copyright (C) 2025 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.
*/
namespace tests\OPNsense\Kea;
require_once __DIR__ . '/Base/JsonSampleTestCase.php';
use OPNsense\Kea\KeaDhcpv6;
class KeaDhcpv6Test extends JsonSampleTestCase
{
protected function getSnapshotFile(): string
{
return 'KeaDhcpv6Test.json';
}
protected function getModelInstance()
{
return new KeaDhcpv6();
}
protected function getJsonRootKey(): string
{
return 'Dhcp6';
}
public function testJsonSample(): void
{
$this->runJsonSampleTest();
}
public function testIsEnabled(): void
{
$m = $this->getModelInstance();
$m->general->enabled = "0";
$m->general->interfaces = "";
$this->assertFalse($m->isEnabled());
$m->general->enabled = "1";
$this->assertFalse($m->isEnabled());
$m->general->interfaces = "lan";
$this->assertTrue($m->isEnabled());
}
public function testFwRulesEnabled(): void
{
$m = $this->getModelInstance();
$m->general->enabled = "0";
$m->general->fwrules = "0";
$m->general->interfaces = "";
$this->assertFalse($m->fwrulesEnabled());
$m->general->enabled = "1";
$this->assertFalse($m->fwrulesEnabled());
$m->general->fwrules = "1";
$this->assertFalse($m->fwrulesEnabled());
$m->general->interfaces = "lan";
$this->assertTrue($m->fwrulesEnabled());
}
public function testPhysicalInterfacesParsing(): void
{
// config.xml already loaded by JsonSampleTestCase::loadSnapshotConfig()
$m = $this->getModelInstance();
$m->general->interfaces = "lan,opt1";
$ifs = (new \ReflectionClass($m))
->getMethod('getConfigPhysicalInterfaces')
->invoke($m);
$this->assertSame(['igc0','igc2'], $ifs);
}
}

View file

@ -0,0 +1,32 @@
{
"Control-agent": {
"http-host": "127.0.0.1",
"http-port": 8000,
"control-sockets": {
"dhcp4": {
"socket-type": "unix",
"socket-name": "\/var\/run\/kea\/kea4-ctrl-socket"
},
"dhcp6": {
"socket-type": "unix",
"socket-name": "\/var\/run\/kea\/kea6-ctrl-socket"
},
"d2": {
"socket-type": "unix",
"socket-name": "\/var\/run\/kea\/kea-ddns-ctrl-socket"
}
},
"loggers": [
{
"name": "kea-ctrl-agent",
"output_options": [
{
"output": "syslog"
}
],
"severity": "INFO",
"debuglevel": 0
}
]
}
}

View file

@ -0,0 +1,219 @@
{
"Dhcp4": {
"valid-lifetime": 4000,
"interfaces-config": {
"interfaces": [
"igc0",
"igc2"
],
"dhcp-socket-type": "raw"
},
"lease-database": {
"type": "memfile",
"persist": true
},
"control-socket": {
"socket-type": "unix",
"socket-name": "\/var\/run\/kea\/kea4-ctrl-socket"
},
"loggers": [
{
"name": "kea-dhcp4",
"output_options": [
{
"output": "syslog"
}
],
"severity": "INFO"
}
],
"subnet4": [
{
"id": 1,
"subnet": "192.168.1.1\/24",
"next-server": "",
"match-client-id": false,
"option-data": [
{
"name": "domain-name-servers",
"data": "192.168.1.1"
},
{
"name": "routers",
"data": "192.168.1.1"
},
{
"name": "domain-name",
"data": "unit.test"
},
{
"name": "ntp-servers",
"data": "192.168.1.1"
}
],
"pools": [
{
"pool": "192.168.1.100-192.168.1.110"
}
],
"reservations": [
{
"ip-address": "192.168.1.102",
"hostname": "deb02",
"hw-address": "00:15:5d:00:ad:0b"
},
{
"ip-address": "192.168.1.103",
"hostname": "deb03",
"hw-address": "00:15:5d:00:ad:0c",
"option-data": [
{
"name": "domain-name-servers",
"data": "192.168.130.1"
},
{
"name": "domain-search",
"data": "test2.local,test3.local"
},
{
"name": "routers",
"data": "192.168.130.1"
},
{
"name": "static-routes",
"data": "192.168.1.1,192.168.100.1"
},
{
"name": "classless-static-route",
"data": "192.168.1.0\/24 - 192.168.100.1,192.168.2.0\/24 - 192.168.100.1"
},
{
"name": "domain-name",
"data": "test.local"
},
{
"name": "ntp-servers",
"data": "192.168.130.1"
},
{
"name": "time-servers",
"data": "192.168.1.1"
},
{
"name": "tftp-server-name",
"data": "192.168.1.3"
},
{
"name": "boot-file-name",
"data": "boot.conf"
}
]
}
]
},
{
"id": 2,
"subnet": "192.168.130.0\/24",
"next-server": "192.168.1.1",
"match-client-id": true,
"option-data": [
{
"name": "domain-name-servers",
"data": "192.168.130.1"
},
{
"name": "domain-search",
"data": "test2.local,test3.local"
},
{
"name": "routers",
"data": "192.168.130.1"
},
{
"name": "static-routes",
"data": "192.168.1.1,192.168.100.1"
},
{
"name": "classless-static-route",
"data": "192.168.1.0\/24 - 192.168.100.1,192.168.2.0\/24 - 192.168.100.1"
},
{
"name": "domain-name",
"data": "test.local"
},
{
"name": "ntp-servers",
"data": "192.168.130.1"
},
{
"name": "time-servers",
"data": "192.168.1.1"
},
{
"name": "tftp-server-name",
"data": "192.168.1.3"
},
{
"name": "boot-file-name",
"data": "boot.conf"
},
{
"name": "v6-only-preferred",
"data": "300"
},
{
"name": "v4-dnr",
"data": "1, example.com"
}
],
"pools": [
{
"pool": "192.168.130.2-192.168.130.12"
}
],
"reservations": []
}
],
"expired-leases-processing": {
"hold-reclaimed-time": 20,
"reclaim-timer-wait-time": 10,
"flush-reclaimed-timer-wait-time": 25,
"max-reclaim-time": 10,
"max-reclaim-leases": 100,
"unwarned-reclaim-cycles": 5
},
"hooks-libraries": [
{
"library": "\/usr\/local\/lib\/kea\/hooks\/libdhcp_lease_cmds.so"
},
{
"library": "\/usr\/local\/lib\/kea\/hooks\/libdhcp_ha.so",
"parameters": {
"high-availability": [
{
"this-server-name": "opn-dev-02",
"mode": "hot-standby",
"heartbeat-delay": 10000,
"max-response-delay": 60000,
"max-ack-delay": 5000,
"max-unacked-clients": 10,
"sync-timeout": 60000,
"peers": [
{
"name": "opn-dev-02",
"role": "primary",
"url": "http:\/\/172.16.1.127:8002\/"
},
{
"name": "opn-dev-03",
"role": "standby",
"url": "http:\/\/172.16.1.128:8002\/"
}
]
}
]
}
}
]
}
}

View file

@ -0,0 +1,146 @@
{
"Dhcp6": {
"valid-lifetime": 4000,
"interfaces-config": {
"interfaces": [
"igc0",
"igc2"
]
},
"lease-database": {
"type": "memfile",
"persist": true
},
"control-socket": {
"socket-type": "unix",
"socket-name": "\/var\/run\/kea\/kea6-ctrl-socket"
},
"loggers": [
{
"name": "kea-dhcp6",
"output_options": [
{
"output": "syslog"
}
],
"severity": "INFO"
}
],
"subnet6": [
{
"id": 1,
"subnet": "fd80::\/48",
"option-data": [],
"pools": [
{
"pool": "fd80::100-fd80::199"
}
],
"pd-pools": [
{
"prefix": "fd80:0:0:1000::",
"prefix-len": 52,
"delegated-len": 56
},
{
"prefix": "fd80:0:0:2000::",
"prefix-len": 52,
"delegated-len": 56
}
],
"reservations": [
{
"option-data": [],
"duid": "fe:fe:fe",
"ip-addresses": [
"fd80::1"
]
},
{
"option-data": [
{
"name": "domain-search",
"data": "test1.local,test2.local"
}
],
"duid": "fe:fe:fb",
"hostname": "testhost",
"ip-addresses": [
"fd80::2"
]
}
],
"interface": "igc0"
},
{
"id": 2,
"subnet": "fd81::\/48",
"option-data": [
{
"name": "dns-servers",
"data": "fe80::1,fe80::2"
},
{
"name": "domain-search",
"data": "test2.local,test3.local"
},
{
"name": "v6-dnr",
"data": "1 ,fe80::3"
}
],
"pools": [
{
"pool": "fd81::100-fd81::199"
}
],
"pd-pools": [],
"reservations": [],
"interface": "igc2",
"pd-allocator": "random",
"allocator": "iterative"
}
],
"expired-leases-processing": {
"hold-reclaimed-time": 20,
"reclaim-timer-wait-time": 10,
"flush-reclaimed-timer-wait-time": 25,
"max-reclaim-time": 10,
"max-reclaim-leases": 100,
"unwarned-reclaim-cycles": 5
},
"hooks-libraries": [
{
"library": "\/usr\/local\/lib\/kea\/hooks\/libdhcp_lease_cmds.so"
},
{
"library": "\/usr\/local\/lib\/kea\/hooks\/libdhcp_ha.so",
"parameters": {
"high-availability": [
{
"this-server-name": "opn-dev-02",
"mode": "hot-standby",
"heartbeat-delay": 10000,
"max-response-delay": 60000,
"max-ack-delay": 5000,
"max-unacked-clients": 10,
"sync-timeout": 60000,
"peers": [
{
"name": "opn-dev-02",
"role": "primary",
"url": "http:\/\/172.16.1.127:8001\/"
},
{
"name": "opn04",
"role": "standby",
"url": "http:\/\/172.16.1.130:8001\/"
}
]
}
]
}
}
]
}
}

View file

@ -0,0 +1,247 @@
<?xml version="1.0"?>
<opnsense>
<system>
<hostname>kea-test</hostname>
<domain>unit.test</domain>
</system>
<interfaces>
<lan>
<if>igc0</if>
<descr>LAN</descr>
<enable>1</enable>
</lan>
<opt1>
<if>igc2</if>
<descr>OPT1</descr>
<enable>1</enable>
</opt1>
</interfaces>
<OPNsense>
<Kea>
<ctrl_agent version="0.0.1" persisted_at="1763978116.85">
<general>
<enabled>1</enabled>
<http_host>127.0.0.1</http_host>
<http_port>8000</http_port>
</general>
</ctrl_agent>
<dhcp4 version="1.0.4" persisted_at="1764067735.34">
<general>
<enabled>1</enabled>
<manual_config>0</manual_config>
<interfaces>lan,opt1</interfaces>
<valid_lifetime>4000</valid_lifetime>
<fwrules>1</fwrules>
<dhcp_socket_type>raw</dhcp_socket_type>
</general>
<lexpire>
<hold_reclaimed_time>20</hold_reclaimed_time>
<reclaim_timer_wait_time>10</reclaim_timer_wait_time>
<flush_reclaimed_timer_wait_time>25</flush_reclaimed_timer_wait_time>
<max_reclaim_time>10</max_reclaim_time>
<max_reclaim_leases>100</max_reclaim_leases>
<unwarned_reclaim_cycles>5</unwarned_reclaim_cycles>
</lexpire>
<ha>
<enabled>1</enabled>
<this_server_name>opn-dev-02</this_server_name>
<max_unacked_clients>10</max_unacked_clients>
</ha>
<subnets>
<subnet4 uuid="9b24f372-4d56-419e-9e40-1646438e2936">
<subnet>192.168.1.1/24</subnet>
<next_server/>
<option_data_autocollect>1</option_data_autocollect>
<option_data>
<domain_name_servers>192.168.1.1</domain_name_servers>
<domain_search/>
<routers>192.168.1.1</routers>
<static_routes/>
<classless_static_route/>
<domain_name/>
<ntp_servers>192.168.1.1</ntp_servers>
<time_servers/>
<tftp_server_name/>
<boot_file_name/>
<v6_only_preferred/>
<v4_dnr/>
</option_data>
<match-client-id>0</match-client-id>
<pools>192.168.1.100-192.168.1.110</pools>
<description/>
</subnet4>
<subnet4 uuid="f822c4ca-ab2f-451a-aa07-0d4bbf638a7f">
<subnet>192.168.130.0/24</subnet>
<next_server>192.168.1.1</next_server>
<option_data_autocollect>0</option_data_autocollect>
<option_data>
<domain_name_servers>192.168.130.1</domain_name_servers>
<domain_search>test2.local,test3.local</domain_search>
<routers>192.168.130.1</routers>
<static_routes>192.168.1.1,192.168.100.1</static_routes>
<classless_static_route>192.168.1.0/24 - 192.168.100.1,192.168.2.0/24 - 192.168.100.1</classless_static_route>
<domain_name>test.local</domain_name>
<ntp_servers>192.168.130.1</ntp_servers>
<time_servers>192.168.1.1</time_servers>
<tftp_server_name>192.168.1.3</tftp_server_name>
<boot_file_name>boot.conf</boot_file_name>
<v6_only_preferred>300</v6_only_preferred>
<v4_dnr>1, example.com</v4_dnr>
</option_data>
<match-client-id>1</match-client-id>
<pools>192.168.130.2-192.168.130.12</pools>
<description>DescriptionTest</description>
</subnet4>
</subnets>
<reservations>
<reservation uuid="4d5cff9b-1825-4691-9a40-0f795e0b601b">
<subnet>9b24f372-4d56-419e-9e40-1646438e2936</subnet>
<ip_address>192.168.1.102</ip_address>
<hw_address>00:15:5d:00:ad:0b</hw_address>
<hostname>deb02</hostname>
<description/>
<option_data>
<domain_name_servers/>
<domain_search/>
<routers/>
<static_routes/>
<classless_static_route/>
<domain_name/>
<ntp_servers/>
<time_servers/>
<tftp_server_name/>
<boot_file_name/>
</option_data>
</reservation>
<reservation uuid="9b734746-2f2a-4550-bc9c-818786af67c8">
<subnet>9b24f372-4d56-419e-9e40-1646438e2936</subnet>
<ip_address>192.168.1.103</ip_address>
<hw_address>00:15:5d:00:ad:0c</hw_address>
<hostname>deb03</hostname>
<description>DescriptionTest</description>
<option_data>
<domain_name_servers>192.168.130.1</domain_name_servers>
<domain_search>test2.local,test3.local</domain_search>
<routers>192.168.130.1</routers>
<static_routes>192.168.1.1,192.168.100.1</static_routes>
<classless_static_route>192.168.1.0/24 - 192.168.100.1,192.168.2.0/24 - 192.168.100.1</classless_static_route>
<domain_name>test.local</domain_name>
<ntp_servers>192.168.130.1</ntp_servers>
<time_servers>192.168.1.1</time_servers>
<tftp_server_name>192.168.1.3</tftp_server_name>
<boot_file_name>boot.conf</boot_file_name>
</option_data>
</reservation>
</reservations>
<ha_peers>
<peer uuid="75da3fff-6aac-41a0-9599-90dae4b119c3">
<name>opn-dev-02</name>
<role>primary</role>
<url>http://172.16.1.127:8002/</url>
</peer>
<peer uuid="38efe03a-b5eb-4233-8871-2d112d30eaed">
<name>opn-dev-03</name>
<role>standby</role>
<url>http://172.16.1.128:8002/</url>
</peer>
</ha_peers>
</dhcp4>
<dhcp6 version="1.0.0" persisted_at="1764085415.29" description="Kea DHCPv6 configuration">
<general>
<enabled>1</enabled>
<manual_config>0</manual_config>
<interfaces>lan,opt1</interfaces>
<valid_lifetime>4000</valid_lifetime>
<fwrules>1</fwrules>
</general>
<lexpire>
<hold_reclaimed_time>20</hold_reclaimed_time>
<reclaim_timer_wait_time>10</reclaim_timer_wait_time>
<flush_reclaimed_timer_wait_time>25</flush_reclaimed_timer_wait_time>
<max_reclaim_time>10</max_reclaim_time>
<max_reclaim_leases>100</max_reclaim_leases>
<unwarned_reclaim_cycles>5</unwarned_reclaim_cycles>
</lexpire>
<ha>
<enabled>1</enabled>
<this_server_name>opn-dev-02</this_server_name>
<max_unacked_clients>10</max_unacked_clients>
</ha>
<subnets>
<subnet6 uuid="0a182eb4-14f6-4731-b62f-f8b380828531">
<subnet>fd80::/48</subnet>
<allocator/>
<pd-allocator/>
<option_data>
<dns_servers/>
<domain_search/>
<v6_dnr/>
</option_data>
<pools>fd80::100-fd80::199</pools>
<interface>lan</interface>
<description/>
</subnet6>
<subnet6 uuid="7b3e7e4d-c148-4409-9a5f-5e2348baa23f">
<subnet>fd81::/48</subnet>
<allocator>iterative</allocator>
<pd-allocator>random</pd-allocator>
<option_data>
<dns_servers>fe80::1,fe80::2</dns_servers>
<domain_search>test2.local,test3.local</domain_search>
<v6_dnr>1 ,fe80::3</v6_dnr>
</option_data>
<pools>fd81::100-fd81::199</pools>
<interface>opt1</interface>
<description>TestDescription</description>
</subnet6>
</subnets>
<reservations>
<reservation uuid="ec45c7ca-5596-49f7-be04-9ff3bd0a045c">
<subnet>0a182eb4-14f6-4731-b62f-f8b380828531</subnet>
<ip_address>fd80::1</ip_address>
<duid>fe:fe:fe</duid>
<hostname/>
<domain_search/>
<description/>
</reservation>
<reservation uuid="5503c690-0af4-4efd-8158-244ca69f7ed7">
<subnet>0a182eb4-14f6-4731-b62f-f8b380828531</subnet>
<ip_address>fd80::2</ip_address>
<duid>fe:fe:fb</duid>
<hostname>testhost</hostname>
<domain_search>test1.local,test2.local</domain_search>
<description>TestDescription</description>
</reservation>
</reservations>
<pd_pools>
<pd_pool uuid="1f3b0a63-e7d2-4567-bdd0-7cd980a119d9">
<subnet>0a182eb4-14f6-4731-b62f-f8b380828531</subnet>
<prefix>fd80:0:0:1000::</prefix>
<prefix_len>52</prefix_len>
<delegated_len>56</delegated_len>
<description/>
</pd_pool>
<pd_pool uuid="fb7861cb-c345-466c-ab02-75662bd856ce">
<subnet>0a182eb4-14f6-4731-b62f-f8b380828531</subnet>
<prefix>fd80:0:0:2000::</prefix>
<prefix_len>52</prefix_len>
<delegated_len>56</delegated_len>
<description>TestDescription</description>
</pd_pool>
</pd_pools>
<ha_peers>
<peer uuid="96c44f24-f3a7-41b9-a9d8-b5d98493e691">
<name>opn-dev-02</name>
<role>primary</role>
<url>http://172.16.1.127:8001/</url>
</peer>
<peer uuid="ac2c19df-42c8-44b7-b040-761e58d264b7">
<name>opn04</name>
<role>standby</role>
<url>http://172.16.1.130:8001/</url>
</peer>
</ha_peers>
</dhcp6>
</Kea>
</OPNsense>
</opnsense>