interfaces: add prefix range option for Kea dynamic PD

This extends the prefix ID selection to be able to reserve a
range of IDs in order to automatically hand them out via Kea.

The accepted value is between 1 and the end of the PD ID range
and also validates against other IDs and their ranges.

This approach differs from the old ISC DHCPv6 in that we can
make room for delegation to avoid later surprises.  It might
force a user to reshuffle his ID range, but Kea wants a IA-NA
subnet that is within the pool reserved here.
This commit is contained in:
Franco Fichtner 2026-05-19 16:30:48 +02:00
parent 82a340e2be
commit c5f2af18e8

View file

@ -248,17 +248,38 @@ function validate_track6_idassoc6(&$pconfig, $if)
if ($ipv6_delegation_length >= 0) {
$ipv6_num_prefix_ids = pow(2, $ipv6_delegation_length);
$track6_prefix_id = intval($pconfig["{$pconfig['type6']}-prefix-id--hex"], 16);
$track6_prefix_range = $pconfig["{$pconfig['type6']}_prefix_range"];
if ($track6_prefix_id < 0 || $track6_prefix_id >= $ipv6_num_prefix_ids) {
$input_errors[] = gettext("You specified an IPv6 prefix ID that is out of range.");
} elseif (strlen($track6_prefix_range)) {
if (!ctype_digit($track6_prefix_range)) {
$input_errors[] = gettext("You specified an IPv6 prefix range that is not valid.");
} elseif ($track6_prefix_range < 1 || $track6_prefix_id + $track6_prefix_range > $ipv6_num_prefix_ids) {
$input_errors[] = gettext("You specified an IPv6 prefix range that is out of range.");
}
}
$default_id = interface_dhcpv6_id($pconfig["{$pconfig['type6']}-interface"]);
$assoc_pd_ref = !empty($pconfig["{$pconfig['type6']}_assoc_pd"]) && ctype_digit($pconfig["{$pconfig['type6']}_assoc_pd"]) ?
$pconfig["{$pconfig['type6']}_assoc_pd"] : $default_id;
foreach (link_interface_to_track6($pconfig["{$pconfig['type6']}-interface"]) as $trackif => $trackcfg) {
if ($trackif == $if) {
continue;
}
$assoc_pd_link = !empty($trackcfg['track6_assoc_pd']) ? $trackcfg['track6_assoc_pd'] : $default_id;
if ($trackif != $if && $assoc_pd_ref == $assoc_pd_ref && $trackcfg['track6-prefix-id'] == $track6_prefix_id) {
if ($assoc_pd_ref != $assoc_pd_ref) {
continue;
}
/* the end of non-overlapping intervals needs to specify 0 ... n-1 */
$track6_range_end = !empty($track6_prefix_range) ? $track6_prefix_range - 1 : 0;
$track6_range_end += $track6_prefix_id;
$range_end_link = !empty($trackcfg['track6_prefix_range']) ? $trackcfg['track6_prefix_range'] - 1 : 0;
$range_end_link += $trackcfg['track6-prefix-id'];
if ($trackcfg['track6-prefix-id'] == $track6_prefix_id) {
$input_errors[] = gettext('You specified an IPv6 prefix ID that is already in use.');
break;
} elseif ($trackcfg['track6-prefix-id'] <= $track6_range_end && $track6_prefix_id <= $range_end_link) {
$input_errors[] = gettext('You specified an IPv6 prefix range that is already in use.');
break;
}
}
if (isset($config['interfaces'][$pconfig["{$pconfig['type6']}-interface"]]['dhcp6-prefix-id'])) {
@ -308,6 +329,9 @@ function store_track6_idassoc6(&$new_config, &$pconfig)
if (isset($pconfig["{$pconfig['type6']}_ifid--hex"]) && ctype_xdigit($pconfig["{$pconfig['type6']}_ifid--hex"])) {
$new_config['track6_ifid'] = intval($pconfig["{$pconfig['type6']}_ifid--hex"], 16);
}
if (!empty($pconfig["{$pconfig['type6']}_prefix_range"])) {
$new_config['track6_prefix_range'] = $pconfig["{$pconfig['type6']}_prefix_range"];
}
if (!empty($pconfig["{$pconfig['type6']}_assoc_pd"])) {
$new_config['track6_assoc_pd'] = $pconfig["{$pconfig['type6']}_assoc_pd"];
}
@ -504,6 +528,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
'track6-prefix-id',
'track6_assoc_pd',
'track6_ifid',
'track6_prefix_range',
];
foreach ($std_copy_fieldnames as $fieldname) {
$pconfig[$fieldname] = isset($a_interfaces[$if][$fieldname]) ? $a_interfaces[$if][$fieldname] : null;
@ -524,7 +549,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$pconfig['dhcpd6track6allowoverride'] = isset($a_interfaces[$if]['dhcpd6track6allowoverride']);
$pconfig['dhcp6_request_dns'] = empty($pconfig['dhcp6_norequest_dns']);
foreach(['-interface', '-prefix-id', '-prefix-id--hex', '_assoc_pd', '_ifid', '_ifid--hex'] as $fieldname) {
foreach(['-interface', '-prefix-id', '-prefix-id--hex', '_assoc_pd', '_ifid', '_ifid--hex', '_prefix_range'] as $fieldname) {
/* only for form consistency */
$pconfig["idassoc6{$fieldname}"] = $pconfig["track6{$fieldname}"];
}
@ -2641,6 +2666,16 @@ include("head.inc");
</div>
</td>
</tr>
<tr>
<td><a id="help_for_idassoc6_prefix_range" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Reserved prefix range') ?></td>
<td>
<input name="idassoc6_prefix_range" type="text" class="form-control" id="idassoc6_prefix_range" placeholder="1" value="<?= $pconfig['idassoc6_prefix_range'] ?>" />
</div>
<div class="hidden" data-for="help_for_idassoc6_prefix_range">
<?= gettext('The value in this field is the length of the reserved prefix range for downstream prefix delegation. The range starts at the given prefix ID. The default is to only reserve the given prefix ID.') ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_idassoc6_ifid" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?= gettext('Optional interface ID') ?></td>
<td>
@ -2706,6 +2741,16 @@ include("head.inc");
</div>
</td>
</tr>
<tr>
<td><a id="help_for_track6_prefix_range" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Reserved prefix range') ?></td>
<td>
<input name="track6_prefix_range" type="text" class="form-control" id="track6_prefix_range" placeholder="1" value="<?= $pconfig['track6_prefix_range'] ?>" />
</div>
<div class="hidden" data-for="help_for_track6_prefix_range">
<?= gettext('The value in this field is the length of the reserved prefix range for downstream prefix delegation. The range starts at the given prefix ID. The default is to only reserve the given prefix ID.') ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_track6_ifid" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?= gettext('Optional interface ID') ?></td>
<td>