net/upnp: service improvements - additions (#5005)

This commit is contained in:
Self-Hosting-Group 2025-11-17 16:40:05 +01:00 committed by GitHub
parent c1b5dfe2a3
commit 458997d163
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 91 additions and 21 deletions

View File

@ -185,7 +185,7 @@ function miniupnpd_configure_do($verbose = false)
/* configure STUN server if needed */
if (!empty($upnp_config['stun_host'])) {
$config_text .= "ext_perform_stun=yes\n";
$config_text .= "ext_perform_stun=allow-filtered\n";
$config_text .= "ext_stun_host=" . ($upnp_config['stun_host']) . "\n";
$config_text .= "ext_stun_port=" . ($upnp_config['stun_port'] ?? "3478") . "\n";
}
@ -206,6 +206,10 @@ function miniupnpd_configure_do($verbose = false)
$config_text .= "pcp_allow_thirdparty=no\n";
}
if (!empty($upnp_config['ipv6_disable'])) {
$config_text .= "ipv6_disable=yes\n";
}
/* enable logging of packets handled by miniupnpd rules */
if (!empty($upnp_config['logpackets'])) {
$config_text .= "packet_log=yes\n";
@ -225,6 +229,13 @@ function miniupnpd_configure_do($verbose = false)
$config_text .= "/\n";
}
if (!empty($upnp_config['friendly_name'])) {
// Encode required XML entities of text UPnP IGD config options until the daemon does so
$config_text .= "friendly_name=" . htmlspecialchars($upnp_config['friendly_name'], ENT_NOQUOTES | ENT_XML1) . "\n";
} else {
$config_text .= "friendly_name=OPNsense UPnP IGD & PCP\n";
}
/* set uuid and serial */
$config_text .= "uuid=" . miniupnpd_uuid() . "\n";
$config_text .= "serial=" . strtoupper(substr(miniupnpd_uuid(), 0, 8)) . "\n";
@ -247,9 +258,14 @@ function miniupnpd_configure_do($verbose = false)
$config_text .= "enable_upnp=" . ( $upnp_config['enable_upnp'] ? "yes\n" : "no\n" );
$config_text .= "enable_pcp_pmp=" . ( $upnp_config['enable_natpmp'] ? "yes\n" : "no\n" );
# When building with IGDv2, infinite (IGDv1 only) lease time port maps are reduced to 7d
# following the IGDv2 standard. Disabling it at runtime allows IGDv2 incompatible clients
$config_text .= "force_igd_desc_v1=yes\n";
// When building the daemon with UPnP IGDv2, infinite (IGDv1 only) lease duration port maps are reduced
// to 7d, following the IGDv2 standard. Disabling it at runtime allows IGDv2-incompatible clients
if ($upnp_config['upnp_igd_compat'] == 'igdv1') {
$config_text .= "force_igd_desc_v1=yes\n";
}
$config_text .= "lease_file=/var/run/miniupnpd.leases\n";
$config_text .= "lease_file6=/var/run/miniupnpd.leases-ipv6\n";
/* write out the configuration */
file_put_contents('/var/etc/miniupnpd.conf', $config_text);

View File

@ -6,7 +6,7 @@
</patterns>
</page-service-upnp>
<page-status-upnpstatus>
<name>Services: UPnP IGD &amp; PCP: Port Maps</name>
<name>Services: UPnP IGD &amp; PCP: Active Maps</name>
<patterns>
<pattern>status_upnp.php*</pattern>
</patterns>

View File

@ -4,7 +4,7 @@
<Settings order="10" url="/services_upnp.php">
<Edit url="/services_upnp.php?*" visibility="hidden"/>
</Settings>
<PortMaps VisibleName="Port Maps" order="20" url="/status_upnp.php"/>
<ActiveMaps VisibleName="Active Maps" order="20" url="/status_upnp.php"/>
</UPnP>
</Services>
</menu>

View File

@ -75,7 +75,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
'enable_natpmp',
'enable_upnp',
'ext_iface',
'friendly_name',
'iface_array',
'ipv6_disable',
'logpackets',
'overridesubnet',
'overridewanip',
@ -85,6 +87,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
'num_permuser',
'sysuptime',
'upload',
'upnp_igd_compat',
];
foreach (miniupnpd_permuser_list() as $permuser) {
@ -175,7 +178,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// save form data
$upnp = [];
// boolean types
foreach (['enable', 'enable_upnp', 'enable_natpmp', 'logpackets', 'sysuptime', 'permdefault', 'allow_third_party_mapping'] as $fieldname) {
foreach (['enable', 'enable_upnp', 'enable_natpmp', 'logpackets', 'sysuptime', 'permdefault', 'allow_third_party_mapping', 'ipv6_disable'] as $fieldname) {
$upnp[$fieldname] = !empty($pconfig[$fieldname]);
}
// numeric types
@ -183,7 +186,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$upnp['num_permuser'] = $pconfig['num_permuser'];
}
// text field types
foreach (['ext_iface', 'download', 'upload', 'overridewanip', 'overridesubnet', 'stun_host', 'stun_port'] as $fieldname) {
foreach (['ext_iface', 'download', 'upload', 'overridewanip', 'overridesubnet', 'stun_host', 'stun_port', 'friendly_name', 'upnp_igd_compat'] as $fieldname) {
$upnp[$fieldname] = $pconfig[$fieldname];
}
foreach (miniupnpd_permuser_list() as $fieldname) {
@ -257,7 +260,7 @@ include("head.inc");
</td>
</tr>
<tr>
<td><a id="help_for_ext_iface" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("External Interface");?></td>
<td><a id="help_for_ext_iface" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("External interface");?></td>
<td>
<select class="selectpicker" name="ext_iface">
<?php
@ -353,6 +356,12 @@ include("head.inc");
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?= gettext('Disable IPv6 mapping') ?></td>
<td>
<input name="ipv6_disable" type="checkbox" value="yes" <?= !empty($pconfig['ipv6_disable']) ? "checked=\"checked\"" : ""; ?> />
</td>
</tr>
<!-- <tr>
<td><a id="help_for_sysuptime" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Report system uptime");?></td>
<td>
@ -371,6 +380,31 @@ include("head.inc");
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="col-xs-12">
<div class="content-box">
<div class="table-responsive">
<table class="table table-striped opnsense_standard_table_form">
<thead>
<tr>
<th style="width:22%"><?= gettext("UPnP IGD Adjustments") ?></th>
<th style="width:78%"></th>
</tr>
</thead>
<tbody>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?= gettext('UPnP IGD compatibility mode') ?></td>
<td>
<select name="upnp_igd_compat">
<option value="igdv1" <?= $pconfig['upnp_igd_compat'] == 'igdv1' ? "selected=\"selected\"" : ""; ?> ><?= gettext("IGDv1 (IPv4 only)"); ?></option>
<option value="igdv2" <?= $pconfig['upnp_igd_compat'] == 'igdv2' ? "selected=\"selected\"" : ""; ?> ><?= gettext("IGDv2 (with workarounds)"); ?></option>
</select>
</td>
</tr>
<tr>
<td><a id="help_for_download" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Download speed");?></td>
<td>
@ -389,6 +423,12 @@ include("head.inc");
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?= gettext('Router/friendly name') ?></td>
<td>
<input name="friendly_name" type="text" placeholder="OPNsense UPnP IGD &amp; PCP" value="<?= !empty($pconfig['friendly_name']) ? htmlspecialchars($pconfig['friendly_name']) : '' ?>" />
</td>
</tr>
</tbody>
</table>
</div>
@ -400,7 +440,7 @@ include("head.inc");
<table class="table table-striped opnsense_standard_table_form">
<thead>
<tr>
<th colspan="2"><?=gettext("Access Control List");?></th>
<th colspan="2"><?=gettext("Custom Access Control List");?></th>
</tr>
</thead>
<tbody>
@ -416,7 +456,7 @@ include("head.inc");
<tr>
<td><a id="help_for_num_permuser" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Number of entries");?></td>
<td>
<input name="num_permuser" type="text" value="<?= html_safe($pconfig['num_permuser']) ?>" />
<input name="num_permuser" type="text" placeholder="8" value="<?= html_safe($pconfig['num_permuser']) ?>" />
<div class="hidden" data-for="help_for_num_permuser">
<?=gettext("Number of ACL entries to configure.");?>
</div>
@ -433,14 +473,14 @@ include("head.inc");
<input name="<?= html_safe($permuser) ?>" type="text" value="<?= isset($pconfig[$permuser]) ? $pconfig[$permuser] : '' ?>" />
<?php if ($i == 1): ?>
<div class="hidden" data-for="help_for_permuser">
<?=gettext("The ACL specifies which IP addresses and ports can be mapped. IPv6 is always accepted.");?><br/>
<?=gettext("Format: (allow or deny) (ext port or range) (int IP or IP/netmask) (int port or range)");?><br/>
<?=gettext("Syntax: (allow or deny) (ext port or range) (int IP or IP/netmask) (int port or range)");?><br/>
<?=gettext("Example: allow 1024-65535 192.168.1.0/24 1024-65535");?>
</div>
<?php endif ?>
</td>
</tr>
<?php endforeach ?>
<tr><td colspan="2"><?=gettext("The access control list (ACL) specifies which IP addresses and ports can be mapped. IPv6 is currently always accepted unless disabled.");?></td></tr>
</tbody>
</table>
</div>

View File

@ -34,6 +34,8 @@ require_once("plugins.inc.d/miniupnpd.inc");
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($_POST['clear'])) {
miniupnpd_stop();
unlink('/var/run/miniupnpd.leases');
unlink('/var/run/miniupnpd.leases-ipv6');
miniupnpd_start();
header(url_safe('Location: /status_upnp.php'));
exit;
@ -41,7 +43,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
$rdr_entries = array();
exec("/sbin/pfctl -a miniupnpd -s nat -P", $rdr_entries, $pf_ret);
exec("/sbin/pfctl -P -a miniupnpd -s nat; /sbin/pfctl -P -a miniupnpd -s rules", $rdr_entries, $pf_ret);
$service_hook = 'miniupnpd';
include("head.inc");
@ -65,30 +67,42 @@ include("head.inc");
<table class="table table-striped table-hover">
<thead>
<tr>
<th><?=gettext("IP Address")?></th>
<th><?=gettext("IP address")?></th>
<th><?=gettext("Port")?></th>
<th><?=gettext("External Port")?></th>
<th><?=gettext("External port")?></th>
<th><?=gettext("Protocol")?></th>
<th><?=gettext("Source IP")?></th>
<th><?=gettext("Source Port")?></th>
<th><?=gettext("Description")?></th>
<th><?=gettext("Source port")?></th>
<th><?=gettext("Added via / description")?></th>
</tr>
</thead>
<tbody>
<?php
foreach ($rdr_entries as $rdr_entry):
if (!preg_match("/on (?P<iface>.*) inet proto (?P<proto>.*) from (?P<srcaddr>.*) (port (?P<srcport>.*) )?to (?P<extaddr>.*) port = (?P<extport>.*) keep state (label \"(?P<descr>.*)\" )?rtable [0-9] -> (?P<intaddr>.*) port (?P<intport>.*)/", $rdr_entry, $matches)) {
if (!preg_match('/on (?P<iface>.+) inet proto (?P<proto>.+) from (?P<srcaddr>[^ ]+) (port (?P<srcport>.+) )?to (?P<extaddr>.+) port = (?P<extport>.+) keep state (label "(?P<descr>.+)" )?rtable [0-9] -> (?P<intaddr>.+) port (?P<intport>.+)/', $rdr_entry, $matches) &&
!preg_match('/on (?P<iface>.+) inet6 proto (?P<proto>.+) from (?P<srcaddr>[^ ]+) (port = (?P<srcport>.+) )?to (?P<intaddr>.+) port = (?P<intport>\d+) (flags [^ ]+ )?keep state (label "(?P<descr>.+)" )?rtable [0-9]/', $rdr_entry, $matches)) {
continue;
}
if (preg_match('/PCP .*([0-9a-f]{24})$/', $matches['descr'], $descrmatch) === 1) {
$descr = "PCP (nonce {$descrmatch[1]})";
} elseif (preg_match('/^NAT-PMP \d+ \w+$/', $matches['descr'], $descrmatch) === 1) {
$descr = 'NAT-PMP';
} elseif (preg_match('/^pinhole-(\d+).*IGD2 pinhole$/', $matches['descr'], $descrmatch) === 1) {
$descr = "UPnP IGD IPv6 (UID {$descrmatch[1]})";
} elseif (preg_match('/^UPnP IGD/', $matches['descr'], $descrmatch) === 1) {
$descr = $matches['descr'];
} else {
$descr = "UPnP IGD / {$matches['descr']}";
}
?>
<tr>
<td><?= html_safe($matches['intaddr']) ?></td>
<td><?= html_safe($matches['intport']) ?></td>
<td><?= html_safe($matches['extport']) ?></td>
<td><?= ($matches['extport'] != '') ? html_safe($matches['extport']) : html_safe($matches['intport']) ?></td>
<td><?= html_safe(strtoupper($matches['proto'])) ?></td>
<td><?= html_safe($matches['srcaddr']) ?></td>
<td><?= html_safe($matches['srcport'] ?: "any") ?></td>
<td><?= html_safe($matches['descr']) ?></td>
<td><?= html_safe($descr) ?></td>
</tr>
<?php
endforeach;?>