mirror of
https://github.com/opnsense/core.git
synced 2026-02-18 18:18:13 -05:00
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:
parent
91fce10c59
commit
c5cb86b6dd
3 changed files with 86 additions and 68 deletions
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue