mvc: Fix idassoc.php converting already decimal stored prefix_id via hexdec(), add unit test for idassoc.php (#10389)

---------

Co-authored-by: Franco Fichtner <franco@opnsense.org>
This commit is contained in:
Monviech 2026-06-08 15:09:23 +02:00 committed by GitHub
parent 9a702a6a6a
commit b0e8dfcc54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 157 additions and 2 deletions

1
plist
View file

@ -1083,6 +1083,7 @@
/usr/local/opnsense/mvc/tests/app/library/OPNsense/Firewall/FilterRuleTest/testDirection.conf
/usr/local/opnsense/mvc/tests/app/library/OPNsense/Firewall/FilterRuleTest/testIcmp.conf
/usr/local/opnsense/mvc/tests/app/library/OPNsense/Firewall/FilterRuleTest/testProtocol.conf
/usr/local/opnsense/mvc/tests/app/library/OPNsense/Interface/IdassocTest.php
/usr/local/opnsense/mvc/tests/app/models/OPNsense/ACL/AclConfig/config.xml
/usr/local/opnsense/mvc/tests/app/models/OPNsense/ACL/AclTest.php
/usr/local/opnsense/mvc/tests/app/models/OPNsense/Base/BaseModel/Migrations/M0_0_1.php

View file

@ -45,7 +45,7 @@ class Idassoc extends Autoconf
$bytes = array_values(unpack('C*', inet_pton($address)));
$source_prefix_len = (int)$source_prefix_len;
$prefix_id = hexdec((string)$prefix_id);
$prefix_id = (int)$prefix_id;
$id_bits = 64 - $source_prefix_len;
for ($i = 0; $i < $id_bits; $i++) {
@ -69,7 +69,7 @@ class Idassoc extends Autoconf
private static function calculateUsablePrefixLength($source_prefix_len, $track6_prefix_id, $track6_prefix_range = ''): int
{
$source_prefix_len = (int)$source_prefix_len;
$track6_prefix_id = hexdec((string)$track6_prefix_id);
$track6_prefix_id = (int)$track6_prefix_id;
$track6_prefix_range = (string)$track6_prefix_range !== '' ? (int)$track6_prefix_range : 1;
$associated_size = 1 << (64 - $source_prefix_len);

View file

@ -0,0 +1,154 @@
<?php
/*
* Copyright (C) 2026 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\Interface;
use OPNsense\Interface\Idassoc;
use PHPUnit\Framework\TestCase;
/*
* Test value mapping:
*
* $track6_interface_prefix:
* Runtime IPv6 prefix associated with the source interface selected by:
* interfaces.<ifname>.track6-interface
* Format: IPv6 CIDR prefix, for example "2001:db8:1234:ab00::/56"
*
* $track6_interface_prefix_len:
* Prefix length extracted from $track6_interface_prefix.
* Format: decimal integer, for example 56
*
* $track6_prefix_id:
* Configuration value:
* interfaces.<ifname>.track6-prefix-id
* Format: decimal string, for example "0", "10", "16", "255"
* The prefix ID is stored as decimal string in the config, even though the GUI overlays it as hex.
* https://github.com/opnsense/core/blob/fb514217bae6525f36834ebd7279a02ad8626f0f/src/www/interfaces.php#L327
*
* $track6_prefix_range:
* Configuration value:
* interfaces.<ifname>.track6_prefix_range
* Format: decimal string, interpreted as number of /64 slots,
* for example "1", "2", "4", "16"
*
* $expected:
* Calculated output value consumed by services:
* prefix_on_link or prefix_allocated
* Format: IPv6 CIDR prefix, for example "2001:db8:1234:ab10::/64"
*/
class IdassocTest extends TestCase
{
private function callPrivateStatic(string $method, array $args = [])
{
$reflection = new \ReflectionClass(Idassoc::class);
$method = $reflection->getMethod($method);
$method->setAccessible(true);
return $method->invokeArgs(null, $args);
}
private function assertCalculatedPrefix(
string $track6_interface_prefix,
string $track6_prefix_id,
string $expected
) {
$this->assertEquals(
$expected,
$this->callPrivateStatic('calculatePrefix', [$track6_interface_prefix, $track6_prefix_id]),
sprintf(
'track6_interface_prefix=%s track6_prefix_id=%s',
$track6_interface_prefix,
$track6_prefix_id
)
);
}
private function assertUsablePrefixLength(
int $track6_interface_prefix_len,
string $track6_prefix_id,
string $track6_prefix_range,
int $expected
) {
$this->assertEquals(
$expected,
$this->callPrivateStatic(
'calculateUsablePrefixLength',
[$track6_interface_prefix_len, $track6_prefix_id, $track6_prefix_range]
),
sprintf(
'track6_interface_prefix_len=%s track6_prefix_id=%s track6_prefix_range=%s',
$track6_interface_prefix_len,
$track6_prefix_id,
$track6_prefix_range
)
);
}
public function testPrefixId()
{
$track6_interface_prefix = '2001:db8:1234:ab00::/56';
$this->assertCalculatedPrefix($track6_interface_prefix, '0', '2001:db8:1234:ab00::/64');
$this->assertCalculatedPrefix($track6_interface_prefix, '1', '2001:db8:1234:ab01::/64');
$this->assertCalculatedPrefix($track6_interface_prefix, '10', '2001:db8:1234:ab0a::/64');
$this->assertCalculatedPrefix($track6_interface_prefix, '15', '2001:db8:1234:ab0f::/64');
// Configuration stores decimal 16; GUI may display this as hexadecimal "10".
$this->assertCalculatedPrefix($track6_interface_prefix, '16', '2001:db8:1234:ab10::/64');
$this->assertCalculatedPrefix($track6_interface_prefix, '255', '2001:db8:1234:abff::/64');
}
public function testDelegationSize()
{
// /52 leaves 12 bits for the subnet ID until /64.
$this->assertCalculatedPrefix('2001:db8:1234:a000::/52', '16', '2001:db8:1234:a010::/64');
// /48 leaves 16 bits for the subnet ID until /64.
$this->assertCalculatedPrefix('2001:db8:1234::/48', '16', '2001:db8:1234:10::/64');
$this->assertCalculatedPrefix('2001:db8:1234::/48', '48879', '2001:db8:1234:beef::/64');
}
public function testPrefixLength()
{
// Empty range or 1 means a single /64 slot.
$this->assertUsablePrefixLength(56, '0', '', 64);
$this->assertUsablePrefixLength(56, '0', '1', 64);
// Larger ranges can be aggregated when starting at an aligned prefix ID.
$this->assertUsablePrefixLength(56, '0', '2', 63);
$this->assertUsablePrefixLength(56, '0', '4', 62);
$this->assertUsablePrefixLength(56, '0', '16', 60);
// Configuration stores decimal 16; GUI displays this as hexadecimal 10.
$this->assertUsablePrefixLength(56, '16', '16', 60);
// Decimal 10 is hexadecimal 0x0a and only aligned to 2 slots.
$this->assertUsablePrefixLength(56, '10', '16', 63);
}
}