interfaces: multi-dhcp6c support and custom PD association #7647

This splits off rtsold and dhcp6c into separate processes
which frees us from the restrictions of faked iterative IDs
for PD associations.  For NA we simply default to 0 now.

I'm not entirely sure why we settled for a single deamon of
dhcp6c back in the day, but there are certianly downsides to
it and I don't see something that wasn't fixed in the meantime
that makes this not work.
This commit is contained in:
Franco Fichtner 2026-01-28 22:52:37 +01:00
parent 91fce10c59
commit c5cb86b6dd
3 changed files with 86 additions and 68 deletions

View file

@ -868,7 +868,7 @@ function interface_reset($interface, $ifacecfg = false, $suspend = false)
case 'slaac':
case 'dhcp6':
interface_dhcpv6_prepare($interface, $ifcfg, true);
killbypid('/var/run/dhcp6c.pid', 'HUP');
killbypid("/var/run/dhcp6c.{$device}.pid", 'HUP');
break;
case 'idassoc6':
case 'track6':
@ -2609,7 +2609,7 @@ function interface_track6_configure($interface, $lancfg, $reload = false)
case 'dhcp6':
if ($reload || !isset($lancfg['enable'])) {
interface_dhcpv6_prepare($lancfg['track6-interface'], $trackcfg);
killbypid('/var/run/dhcp6c.pid', 'HUP');
killbypid("/var/run/dhcp6c.{$trackcfg['if']}.pid", 'HUP');
}
break;
}
@ -2893,15 +2893,16 @@ function interface_dhcpv6_configure($interface, $wancfg)
}
/* always kill rtsold in case of reconfigure */
killbypid('/var/run/rtsold.pid');
killbypid("/var/run/rtsold.{$wancfg['if']}.pid");
@unlink("/tmp/rtsold.{$wancfg['if']}.done");
$rtsold_frmt = ['/usr/sbin/rtsold -%s -aiu -p %s -A %s -R %s'];
$rtsold_frmt = ['/usr/sbin/rtsold -%s -iu -p %s -A %s -R %s %s'];
$rtsold_args = [
$settings->dhcp6_debug->isEmpty() ? 'd' : 'D',
'/var/run/rtsold.pid',
"/var/run/rtsold.{$wancfg['if']}.pid",
'/usr/local/opnsense/scripts/interfaces/rtsold_script.sh',
'/usr/local/opnsense/scripts/interfaces/rtsold_resolvconf.sh',
$wancfg['if'],
];
/* fire up rtsold for IPv6 RAs first */
@ -2917,28 +2918,6 @@ function interface_dhcpv6_configure($interface, $wancfg)
}
}
function interface_dhcpv6_id($interface)
{
global $config;
/* configuration default */
$id = 0;
if (empty($config['interfaces'])) {
return $id;
}
/* detect unique index */
foreach (array_keys($config['interfaces']) as $key) {
if ($key == $interface) {
break;
}
$id += 1;
}
return $id;
}
function interface_dhcpv6_prepare($interface, $wancfg, $cleanup = false)
{
if (!is_array($wancfg)) {
@ -2946,7 +2925,6 @@ function interface_dhcpv6_prepare($interface, $wancfg, $cleanup = false)
}
$settings = new OPNsense\Interfaces\Settings();
$id = interface_dhcpv6_id($interface);
$device = $wancfg['if'];
$dhcp6cconf = "# generated for {$interface} in {$wancfg['ipaddrv6']} mode\n";
@ -2968,9 +2946,9 @@ function interface_dhcpv6_prepare($interface, $wancfg, $cleanup = false)
log_msg("DHCP6 config file override does not exist: '{$dhcp6cfile}'", LOG_ERR);
}
} elseif (!empty($wancfg['adv_dhcp6_config_advanced'])) {
$dhcp6cconf .= DHCP6_Config_File_Advanced($interface, $wancfg, $device, $id);
$dhcp6cconf .= DHCP6_Config_File_Advanced($interface, $wancfg, $device);
} else {
$dhcp6cconf .= DHCP6_Config_File_Basic($interface, $wancfg, $device, $id);
$dhcp6cconf .= DHCP6_Config_File_Basic($interface, $wancfg, $device);
}
if (!$cleanup) {
@ -2978,24 +2956,9 @@ function interface_dhcpv6_prepare($interface, $wancfg, $cleanup = false)
} else {
@unlink("/var/etc/dhcp6c.{$device}.conf");
}
$dhcp6cconf = '';
/* merge configs and prepare single instance of dhcp6c for startup */
foreach (legacy_config_get_interfaces(['enable' => true, 'virtual' => false]) as $_interface => $_wancfg) {
if (empty($_wancfg['ipaddrv6']) || ($_wancfg['ipaddrv6'] != 'dhcp6' && $_wancfg['ipaddrv6'] != 'slaac')) {
continue;
}
if (file_exists("/var/etc/dhcp6c.{$_wancfg['if']}.conf")) {
$dhcp6cconf .= file_get_contents("/var/etc/dhcp6c.{$_wancfg['if']}.conf");
}
}
@file_put_contents('/var/etc/dhcp6c.conf', $dhcp6cconf);
}
function DHCP6_Config_File_Basic($interface, $wancfg, $wanif, $id = 0)
function DHCP6_Config_File_Basic($interface, $wancfg, $wanif)
{
$dhcp6cconf = '';
@ -3020,7 +2983,7 @@ function DHCP6_Config_File_Basic($interface, $wancfg, $wanif, $id = 0)
}
$pd_conf .= " };\n";
/* XXX $id from dhcp6_assoc_pd */
$id = strlen($wancfg['dhcp6_assoc_pd'] ?? '') ? $wancfg['dhcp6_assoc_pd'] : '0';
$assoc_pd[$id] = ($assoc_pd[$id] ?? '') . $pd_conf;
}
@ -3035,25 +2998,26 @@ function DHCP6_Config_File_Basic($interface, $wancfg, $wanif, $id = 0)
}
$pd_conf .= " };\n";
/* XXX $id from track6_assoc_pd */
$id = strlen($lancfg['track6_assoc_pd'] ?? '') ? $lancfg['track6_assoc_pd'] : '0';
$assoc_pd[$id] = ($assoc_pd[$id] ?? '') . $pd_conf;
}
}
if (!count($assoc_pd)) {
/* for backwards compatibility always emit a default PD request */
$assoc_pd[0] = '';
}
}
ksort($assoc_pd, SORT_NUMERIC);
/* for backwards compatibility always emit this PD request */
if (!isset($assoc_pd[$id])) {
$assoc_pd[$id] = '';
}
$dhcp6cconf .= "interface {$wanif} {\n";
if (!isset($wancfg['dhcp6prefixonly'])) {
/* request non-temporary address */
$dhcp6cconf .= " send ia-na {$id};\n";
$dhcp6cconf .= " send ia-na 0;\n";
}
if (is_numeric($wancfg['dhcp6-ia-pd-len'])) {
/* request prefix delegation */
foreach (array_keys($assoc_pd) as $id) {
@ -3074,7 +3038,7 @@ function DHCP6_Config_File_Basic($interface, $wancfg, $wanif, $id = 0)
$dhcp6cconf .= "};\n";
if (!isset($wancfg['dhcp6prefixonly'])) {
$dhcp6cconf .= "id-assoc na {$id} { };\n";
$dhcp6cconf .= "id-assoc na 0 { };\n";
}
foreach ($assoc_pd as $i => $pd_conf) {
@ -3091,7 +3055,7 @@ function DHCP6_Config_File_Basic($interface, $wancfg, $wanif, $id = 0)
return $dhcp6cconf;
}
function DHCP6_Config_File_Advanced($interface, $wancfg, $wanif, $id = 0)
function DHCP6_Config_File_Advanced($interface, $wancfg, $wanif)
{
$send_options = "";
@ -3133,7 +3097,7 @@ function DHCP6_Config_File_Advanced($interface, $wancfg, $wanif, $id = 0)
if (is_numeric($wancfg['adv_dhcp6_id_assoc_statement_address_id'])) {
$id_assoc_statement_address .= "{$wancfg['adv_dhcp6_id_assoc_statement_address_id']}";
} else {
$id_assoc_statement_address .= $id;
$id_assoc_statement_address .= 0; /* only as a fallback */
}
$id_assoc_statement_address .= " {\n";
@ -3163,7 +3127,7 @@ function DHCP6_Config_File_Advanced($interface, $wancfg, $wanif, $id = 0)
if (is_numeric($wancfg['adv_dhcp6_id_assoc_statement_prefix_id'])) {
$id_assoc_statement_prefix .= "{$wancfg['adv_dhcp6_id_assoc_statement_prefix_id']}";
} else {
$id_assoc_statement_prefix .= $id;
$id_assoc_statement_prefix .= 0; /* only as a fallback */
}
$id_assoc_statement_prefix .= " {\n";

View file

@ -33,17 +33,17 @@ if [ -z "${IFNAME}" ]; then
fi
if grep -q "^interface ${IFNAME} " /var/etc/radvd.conf; then
/usr/bin/logger -t dhcp6c "rtsold_script: rejecting dhcp6c"
/usr/bin/logger -t dhcp6c "rtsold_script: rejecting dhcp6c on ${IFNAME}"
echo "Rejecting own configuration."
exit 0
fi
CONFFILE="/var/etc/dhcp6c.${IFNAME}.conf"
PIDFILE="/var/run/dhcp6c.${IFNAME}.pid"
DONEFILE="/tmp/rtsold.${IFNAME}.done"
CONFFILE="/var/etc/dhcp6c.conf"
PIDFILE="/var/run/dhcp6c.pid"
if [ ! -f "${CONFFILE}" ]; then
/usr/bin/logger -t dhcp6c "rtsold_script: skipping dhcp6c"
/usr/bin/logger -t dhcp6c "rtsold_script: skipping dhcp6c on ${IFNAME}"
exit 0
fi
@ -74,10 +74,10 @@ if [ -f "${PIDFILE}" ]; then
fi
if [ -f "${PIDFILE}" ]; then
/usr/bin/logger -t dhcp6c "rtsold_script: reloading dhcp6c"
/usr/bin/logger -t dhcp6c "rtsold_script: reloading dhcp6c on ${IFNAME}"
/bin/pkill -HUP -F "${PIDFILE}"
else
/usr/bin/logger -t dhcp6c "rtsold_script: starting dhcp6c"
/usr/bin/logger -t dhcp6c "rtsold_script: starting dhcp6c on ${IFNAME}"
/usr/local/sbin/dhcp6c $(get_var EXTRAOPTS) -c "${CONFFILE}" -p "${PIDFILE}"
fi

View file

@ -251,20 +251,31 @@ function validate_track6_idassoc6(&$pconfig, $if)
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.");
}
$assoc_pd_ref = '0';
if (!empty($pconfig["{$pconfig['type6']}_assoc_pd"]) && ctype_digit($pconfig["{$pconfig['type6']}_assoc_pd"])) {
$assoc_pd_ref = $pconfig["{$pconfig['type6']}_assoc_pd"];
}
foreach (link_interface_to_track6($pconfig["{$pconfig['type6']}-interface"]) as $trackif => $trackcfg) {
if ($trackif != $if && $trackcfg['track6-prefix-id'] == $track6_prefix_id) {
$assoc_pd_link = !empty($trackcfg['track6_assoc_pd']) ? $trackcfg['track6_assoc_pd'] : '0';
if ($trackif != $if && $assoc_pd_ref == $assoc_pd_ref && $trackcfg['track6-prefix-id'] == $track6_prefix_id) {
$input_errors[] = gettext('You specified an IPv6 prefix ID that is already in use.');
break;
}
}
if (isset($config['interfaces'][$pconfig["{$pconfig['type6']}-interface"]]['dhcp6-prefix-id'])) {
if ($config['interfaces'][$pconfig["{$pconfig['type6']}-interface"]]['dhcp6-prefix-id'] == $track6_prefix_id) {
$assoc_pd_parent = !empty($config['interfaces'][$pconfig["{$pconfig['type6']}-interface"]]['dhcp6_assoc_pd']) ?
$config['interfaces'][$pconfig["{$pconfig['type6']}-interface"]]['dhcp6_assoc_pd'] : '0';
if ($assoc_pd_ref == $assoc_pd_parent && $config['interfaces'][$pconfig["{$pconfig['type6']}-interface"]]['dhcp6-prefix-id'] == $track6_prefix_id) {
$input_errors[] = gettext('You specified an IPv6 prefix ID that is already in use.');
}
}
}
}
if (!empty($pconfig["{$pconfig['type6']}_assoc_pd"]) && !ctype_digit($pconfig["{$pconfig['type6']}_assoc_pd"])) {
$input_errors[] = gettext('You must enter a valid number for the IPv6 prefix association identity.');
}
if (isset($pconfig["{$pconfig['type6']}_ifid--hex"]) && $pconfig["{$pconfig['type6']}_ifid--hex"] != '') {
if (!ctype_xdigit($pconfig["{$pconfig['type6']}_ifid--hex"])) {
$input_errors[] = gettext('You must enter a valid hexadecimal number for the IPv6 interface ID.');
@ -298,6 +309,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']}_assoc_pd"])) {
$new_config['track6_assoc_pd'] = $pconfig["{$pconfig['type6']}_assoc_pd"];
}
if ($pconfig['type6'] == 'track6') {
/* this is not needed in the new world */
$new_config['dhcpd6track6allowoverride'] = !empty($pconfig['dhcpd6track6allowoverride']);
@ -464,6 +478,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
'dhcp6_ifid',
'dhcp6_norequest_dns',
'dhcp6_rapid_commit',
'dhcp6_assoc_pd',
'dhcp6vlanprio',
'dhcphostname',
'dhcprejectfrom',
@ -491,6 +506,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
'subnetv6',
'track6-interface',
'track6-prefix-id',
'track6_assoc_pd',
'track6_ifid',
];
foreach ($std_copy_fieldnames as $fieldname) {
@ -512,7 +528,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', '_ifid', '_ifid--hex'] as $fieldname) {
foreach(['-interface', '-prefix-id', '-prefix-id--hex', '_assoc_pd', '_ifid', '_ifid--hex'] as $fieldname) {
/* only for form consistency */
$pconfig["idassoc6{$fieldname}"] = $pconfig["track6{$fieldname}"];
}
@ -750,14 +766,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$input_errors[] = gettext("You specified an IPv6 prefix ID that is out of range.");
}
}
$assoc_pd_ref = '0';
if (!empty($pconfig['dhcp6_assoc_pd']) && ctype_digit($pconfig['dhcp6_assoc_pd'])) {
$assoc_pd_ref = $pconfig['dhcp6_assoc_pd'];
}
foreach (link_interface_to_track6($pconfig['track6-interface']) as $trackif => $trackcfg) {
if ($trackcfg['track6-prefix-id'] == $dhcp6_prefix_id) {
$assoc_pd_link = !empty($trackcfg['track6_assoc_pd']) ? $trackcfg['track6_assoc_pd'] : '0';
if ($assoc_pd_ref == $assoc_pd_link && $trackcfg['track6-prefix-id'] == $dhcp6_prefix_id) {
$input_errors[] = gettext('You specified an IPv6 prefix ID that is already in use.');
break;
}
}
}
}
if (!empty($pconfig['dhcp6_assoc_pd']) && !ctype_digit($pconfig['dhcp6_assoc_pd'])) {
$input_errors[] = gettext('You must enter a valid number for the IPv6 prefix association identity.');
}
if (isset($pconfig['dhcp6_ifid--hex']) && $pconfig['dhcp6_ifid--hex'] != '') {
if (!ctype_xdigit($pconfig['dhcp6_ifid--hex'])) {
$input_errors[] = gettext('You must enter a valid hexadecimal number for the IPv6 interface ID.');
@ -1123,6 +1147,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if (!empty($pconfig['dhcp6_rapid_commit'])) {
$new_config['dhcp6_rapid_commit'] = true;
}
if (!empty($pconfig['dhcp6_assoc_pd'])) {
$new_config['dhcp6_assoc_pd'] = $pconfig['dhcp6_assoc_pd'];
}
$new_config['adv_dhcp6_interface_statement_send_options'] = $pconfig['adv_dhcp6_interface_statement_send_options'];
$new_config['adv_dhcp6_interface_statement_request_options'] = $pconfig['adv_dhcp6_interface_statement_request_options'];
$new_config['adv_dhcp6_interface_statement_information_only_enable'] = $pconfig['adv_dhcp6_interface_statement_information_only_enable'];
@ -2390,6 +2417,15 @@ include("head.inc");
</div>
</td>
</tr>
<tr class="dhcpv6_basic">
<td><a id="help_for_dhcp6_assoc_pd" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?= gettext('Optional IA-PD ID') ?></td>
<td>
<input name="dhcp6_assoc_pd" type="text" id="dhcp6_assoc_pd" value="<?= html_safe($pconfig['dhcp6_assoc_pd']) ?>" />
<div class="hidden" data-for="help_for_dhcp6_assoc_pd">
<?= gettext('The ID to use for prefix request identity association if required to be non-zero.') ?>
</div>
</td>
</tr>
<tr class="dhcpv6_advanced">
<td><a id="help_for_dhcp6_intf_stmt" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Interface Statement");?></td>
<td>
@ -2619,6 +2655,15 @@ include("head.inc");
</div>
</td>
</tr>
<tr>
<td><a id="help_for_idassoc6_assoc_pd" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?= gettext('Optional IA-PD ID') ?></td>
<td>
<input name="idassoc6_assoc_pd" type="text" id="idassoc6_assoc_pd" value="<?= html_safe($pconfig['idassoc6_assoc_pd']) ?>" />
<div class="hidden" data-for="help_for_idassoc6_assoc_pd">
<?= gettext('The ID to use for prefix request identity association if required to be non-zero.') ?>
</div>
</td>
</tr>
</tbody>
</table>
</div>
@ -2675,6 +2720,15 @@ include("head.inc");
</div>
</td>
</tr>
<tr>
<td><a id="help_for_track6_assoc_pd" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?= gettext('Optional IA-PD ID') ?></td>
<td>
<input name="track6_assoc_pd" type="text" id="track6_assoc_pd" value="<?= html_safe($pconfig['track6_assoc_pd']) ?>" />
<div class="hidden" data-for="help_for_track6_assoc_pd">
<?= gettext('The ID to use for prefix request identity association if required to be non-zero.') ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_dhcpd6_opt" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?= gettext('Manual configuration') ?></td>
<td>