Files
op-packages/.github/diy/patches/upnp.patch
T
2026-08-09 22:16:47 +08:00

5103 lines
212 KiB
Diff

--- a/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
+++ b/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
@@ -7,14 +7,14 @@
const callUpnpGetStatus = rpc.declare({
object: 'luci.upnp',
method: 'get_status',
- expect: { }
+ expect: {}
});
const callUpnpDeleteRule = rpc.declare({
object: 'luci.upnp',
method: 'delete_rule',
- params: [ 'token' ],
- expect: { result : "OK" },
+ params: ['token'],
+ expect: { result: 'OK' },
});
function handleDelRule(num, ev) {
@@ -35,23 +35,23 @@ return baseclass.extend({
},
render: function(data) {
- var table = E('table', { 'class': 'table', 'id': 'upnp_status_table' }, [
+ const table = E('table', { 'class': 'table', 'id': 'upnp_status_table' }, [
E('tr', { 'class': 'tr table-titles' }, [
- E('th', { 'class': 'th' }, _('Client Name')),
- E('th', { 'class': 'th' }, _('Client Address')),
- E('th', { 'class': 'th' }, _('Client Port')),
- E('th', { 'class': 'th' }, _('External Port')),
+ E('th', { 'class': 'th' }, _('Hostname')),
+ E('th', { 'class': 'th' }, _('IP address')),
+ E('th', { 'class': 'th' }, _('Port')),
+ E('th', { 'class': 'th' }, _('External port')),
E('th', { 'class': 'th' }, _('Protocol')),
E('th', { 'class': 'th right' }, _('Expires')),
- E('th', { 'class': 'th' }, _('Description')),
+ E('th', { 'class': 'th' }, _('Added via / description')),
E('th', { 'class': 'th cbi-section-actions' }, '')
])
]);
- var rules = Array.isArray(data[0].rules) ? data[0].rules : [];
+ const rules = Array.isArray(data[0].rules) ? data[0].rules : [];
- var rows = rules.map(function(rule) {
- const padnum = (num, length) => num.toString().padStart(length, "0");
+ const rows = rules.map(function(rule) {
+ const padnum = (num, length) => num.toString().padStart(length, '0');
const expires_sec = rule?.expires || 0;
const hour = Math.floor(expires_sec / 3600);
const minute = Math.floor((expires_sec % 3600) / 60);
@@ -72,8 +72,9 @@ return baseclass.extend({
'%h'.format(rule.descr),
E('button', {
'class': 'btn cbi-button-remove',
- 'click': L.bind(handleDelRule, this, rule.num)
- }, [ _('Delete') ])
+ 'click': L.bind(handleDelRule, this, rule.num),
+ 'title': _('Delete')
+ }, [_('Delete')])
];
});
diff --git a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
index 40218589d691..07ca8b0a9c62 100644
--- a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
+++ b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
@@ -3,27 +3,28 @@
'require dom';
'require poll';
'require uci';
+'require ui';
'require rpc';
'require form';
const callInitAction = rpc.declare({
object: 'luci',
method: 'setInitAction',
- params: [ 'name', 'action' ],
+ params: ['name', 'action'],
expect: { result: false }
});
const callUpnpGetStatus = rpc.declare({
object: 'luci.upnp',
method: 'get_status',
- expect: { }
+ expect: {}
});
const callUpnpDeleteRule = rpc.declare({
object: 'luci.upnp',
method: 'delete_rule',
- params: [ 'token' ],
- expect: { result : "OK" },
+ params: ['token'],
+ expect: { result: 'OK' },
});
function handleDelRule(num, ev) {
@@ -44,10 +45,10 @@ return view.extend({
poll_status: function(nodes, data) {
- var rules = Array.isArray(data[0].rules) ? data[0].rules : [];
+ const rules = Array.isArray(data[0].rules) ? data[0].rules : [];
- var rows = rules.map(function(rule) {
- const padnum = (num, length) => num.toString().padStart(length, "0");
+ const rows = rules.map(function(rule) {
+ const padnum = (num, length) => num.toString().padStart(length, '0');
const expires_sec = rule?.expires || 0;
const hour = Math.floor(expires_sec / 3600);
const minute = Math.floor((expires_sec % 3600) / 60);
@@ -68,8 +69,9 @@ return view.extend({
'%h'.format(rule.descr),
E('button', {
'class': 'btn cbi-button-remove',
- 'click': L.bind(handleDelRule, this, rule.num)
- }, [ _('Delete') ])
+ 'click': L.bind(handleDelRule, this, rule.num),
+ 'title': _('Delete')
+ }, [_('Delete')])
];
});
@@ -80,53 +82,63 @@ return view.extend({
let m, s, o;
- var protocols = '%s & %s/%s'.format(
+ const protocols = _('%s & %s/%s', '%s & %s/%s (%s = UPnP IGD, %s = PCP, %s = NAT-PMP)').format(
'<a href="https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol" target="_blank" rel="noreferrer"><abbr title="UPnP Internet Gateway Device (Control Protocol)">UPnP IGD</abbr></a>',
'<a href="https://en.wikipedia.org/wiki/Port_Control_Protocol" target="_blank" rel="noreferrer"><abbr title="Port Control Protocol">PCP</abbr></a>',
'<a href="https://en.wikipedia.org/wiki/NAT_Port_Mapping_Protocol" target="_blank" rel="noreferrer"><abbr title="NAT Port Mapping Protocol">NAT-PMP</abbr></a>');
- m = new form.Map('upnpd', [_('UPnP IGD & PCP/NAT-PMP Service')],
+ m = new form.Map('upnpd', _('UPnP IGD & PCP/NAT-PMP Service'),
_('The %s protocols allow clients on the local network to configure port maps/forwards on the router autonomously.',
'The %s (%s = UPnP IGD & PCP/NAT-PMP) protocols allow clients on the local network to configure port maps/forwards on the router autonomously.')
- .format(protocols)
+ .format(protocols)
);
+ if (!uci.get('upnpd', 'config')) {
+ ui.addNotification(null, E('div', '<h4>' + _('No suitable configuration was found!') + '</h4><p>' +
+ _('No suitable config (LuCI app %s) found in %s. A related package update (daemon or LuCI app) may be missing.').format('v1.0', '<code>/etc/config/upnpd</code>') + '<br />' +
+ _('Use the software package manager, update lists, and install the related update. Config is migrated with the daemon package update.') + '</p>' +
+ '<a class="btn" href="/cgi-bin/luci/admin/system/package-manager?query=UPnP%20IGD%20&amp;%20PCP/NAT-PMP">' + _('Go to package manager…') + '</a>'), 'warning');
+ m.readonly = true;
+ }
s = m.section(form.GridSection, '_active_rules');
s.render = L.bind(function(view, section_id) {
- var table = E('table', { 'class': 'table cbi-section-table', 'id': 'upnp_status_table' }, [
+ const table = E('table', { 'class': 'table cbi-section-table', 'id': 'upnp_status_table' }, [
E('tr', { 'class': 'tr table-titles' }, [
- E('th', { 'class': 'th' }, _('Client Name')),
- E('th', { 'class': 'th' }, _('Client Address')),
- E('th', { 'class': 'th' }, _('Client Port')),
- E('th', { 'class': 'th' }, _('External Port')),
+ E('th', { 'class': 'th' }, _('Hostname')),
+ E('th', { 'class': 'th' }, _('IP address')),
+ E('th', { 'class': 'th' }, _('Port')),
+ E('th', { 'class': 'th' }, _('External port')),
E('th', { 'class': 'th' }, _('Protocol')),
E('th', { 'class': 'th right' }, _('Expires')),
- E('th', { 'class': 'th' }, _('Description')),
+ E('th', { 'class': 'th' }, _('Added via / description')),
E('th', { 'class': 'th cbi-section-actions' }, '')
])
]);
- var rules = Array.isArray(data[0].rules) ? data[0].rules : [];
+ const rules = Array.isArray(data[0].rules) ? data[0].rules : [];
- var rows = rules.map(function(rule) {
+ const rows = rules.map(function(rule) {
return [
rule.host_hint || _('Unknown'),
rule.intaddr,
rule.intport,
rule.extport,
rule.proto,
- rule.descr,
+ '', // expires
+ '%h'.format(rule.descr),
E('button', {
'class': 'btn cbi-button-remove',
- 'click': L.bind(handleDelRule, this, rule.num)
- }, [ _('Delete') ])
+ 'click': L.bind(handleDelRule, this, rule.num),
+ 'title': _('Delete')
+ }, [_('Delete')])
];
});
cbi_update_table(table, rows, E('em', _('There are no active port maps.')));
return E('div', { 'class': 'cbi-section cbi-tblsection' }, [
- E('h3', _('Active Service Port Maps')), table ]);
+ E('h3', _('Active Port Maps')), table
+ ]);
}, o, this);
s = m.section(form.NamedSection, 'config', 'upnpd', _('Service Settings'));
@@ -134,8 +146,8 @@ return view.extend({
s.tab('setup', _('Service Setup'));
s.tab('advanced', _('Advanced Settings'));
- o = s.taboption('setup', form.Flag, 'enabled', _('Start service'),
- _('Start autonomous port mapping service'));
+ o = s.taboption('setup', form.Flag, 'enabled', _('Enable service'),
+ _('Enable the autonomous port mapping service'));
o.rmempty = false;
o = s.taboption('setup', form.Flag, 'enable_upnp', _('Enable UPnP IGD protocol'));
@@ -152,86 +164,104 @@ return view.extend({
o.default = '1';
o.rmempty = false;
o.depends('enable_upnp', '1');
+ o.retain = true;
o = s.taboption('setup', form.Value, 'download', _('Download speed'),
_('Report maximum download speed in kByte/s'));
o.depends('enable_upnp', '1');
+ o.retain = true;
o = s.taboption('setup', form.Value, 'upload', _('Upload speed'),
_('Report maximum upload speed in kByte/s'));
o.depends('enable_upnp', '1');
+ o.retain = true;
s.taboption('advanced', form.Flag, 'use_stun', _('Use %s', 'Use %s (%s = STUN)')
- .format('<a href="https://en.wikipedia.org/wiki/STUN" target="_blank" rel="noreferrer"><abbr title="Session Traversal Utilities for NAT">STUN</abbr></a>'),
+ .format('<a href="https://en.wikipedia.org/wiki/STUN" target="_blank" rel="noreferrer"><abbr title="Session Traversal Utilities for NAT">STUN</abbr></a>'),
_('To detect the public IPv4 address for unrestricted full-cone/one-to-one NATs'));
o = s.taboption('advanced', form.Value, 'stun_host', _('STUN host'));
- o.depends('use_stun', '1');
o.datatype = 'host';
+ o.depends('use_stun', '1');
+ o.retain = true;
o = s.taboption('advanced', form.Value, 'stun_port', _('STUN port'));
- o.depends('use_stun', '1');
o.datatype = 'port';
o.placeholder = '3478';
+ o.depends('use_stun', '1');
+ o.retain = true;
o = s.taboption('advanced', form.Flag, 'secure_mode', _('Enable secure mode'),
_('Allow adding port maps for requesting IP addresses only'));
o.default = '1';
o.depends('enable_upnp', '1');
+ o.retain = true;
+
+ s.taboption('advanced', form.Flag, 'ipv6_disable', _('Disable IPv6 mapping'));
o = s.taboption('advanced', form.Value, 'notify_interval', _('Notify interval'),
- _('A 900s interval will result in %s notifications with the minimum max-age of 1800s', 'A 900s interval will result in %s (%s = SSDP) notifications with the minimum max-age of 1800s')
- .format('<abbr title="Simple Service Discovery Protocol">SSDP</abbr>'));
- o.datatype = 'uinteger';
+ _('A 900 s interval sends %s announcements with the minimum %s header',
+ 'A 900 s interval sends %s (%s = SSDP) announcements with the minimum %s (%s = Cache-Control: max-age=1800) header')
+ .format('<abbr title="Simple Service Discovery Protocol">SSDP</abbr>', '<code>Cache-Control: max-age=1800</code>'));
+ o.datatype = 'min(900)';
o.placeholder = '900';
o.depends('enable_upnp', '1');
+ o.retain = true;
o = s.taboption('advanced', form.Value, 'port', _('SOAP/HTTP port'));
o.datatype = 'port';
o.placeholder = '5000';
o.depends('enable_upnp', '1');
+ o.retain = true;
- o = s.taboption('advanced', form.Value, 'presentation_url', _('Presentation URL'),
- _('Report custom router web interface (presentation) URL'));
+ o = s.taboption('advanced', form.Value, 'presentation_url', _('Router/presentation URL'),
+ _('Report custom router web interface URL'));
o.placeholder = 'http://192.168.1.1/';
o.depends('enable_upnp', '1');
+ o.retain = true;
o = s.taboption('advanced', form.Value, 'uuid', _('Device UUID'));
- o.depends('enable_upnp', '1');
+ // o.depends('enable_upnp', '1');
+ o.depends('keep-translation', 'to-disable-as-rare-use');
+ o.retain = true;
o = s.taboption('advanced', form.Value, 'model_number', _('Announced model number'));
o.depends('enable_upnp', '1');
+ o.retain = true;
o = s.taboption('advanced', form.Value, 'serial_number', _('Announced serial number'));
o.depends('enable_upnp', '1');
+ o.retain = true;
o = s.taboption('advanced', form.Flag, 'system_uptime', _('Report system instead of service uptime'));
o.default = '1';
- o.depends('enable_upnp', '1');
+ o.depends('keep-translation', 'to-disable-as-rare-use');
+ o.retain = true;
s.taboption('advanced', form.Flag, 'log_output', _('Enable additional logging'),
_('Puts extra debugging information into the system log'));
o = s.taboption('advanced', form.Value, 'upnp_lease_file', _('Service lease file'));
- o.placeholder = '/var/run/miniupnpd.leases';
+ o.depends('keep-translation', 'to-disable-as-rare-use');
+ o.retain = true;
s = m.section(form.GridSection, 'perm_rule', _('Service Access Control List'),
_('ACL specify which client addresses and ports can be mapped, IPv6 always allowed.'));
- s.sortable = true;
s.anonymous = true;
s.addremove = true;
+ s.sortable = true;
s.option(form.Value, 'comment', _('Comment'));
- o = s.option(form.Value, 'int_addr', _('Client Address'));
+ o = s.option(form.Value, 'int_addr', _('IP address'));
o.datatype = 'ip4addr';
o.placeholder = '0.0.0.0/0';
- o = s.option(form.Value, 'int_ports', _('Client Port'));
+ o = s.option(form.Value, 'int_ports', _('Port'));
o.datatype = 'portrange';
o.placeholder = '1-65535';
- o = s.option(form.Value, 'ext_ports', _('External Port'));
+ o = s.option(form.Value, 'ext_ports', _('External port'));
o.datatype = 'portrange';
o.placeholder = '1-65535';
diff --git a/luci-app-upnp/root/usr/share/rpcd/acl.d/luci-app-upnp.json b/luci-app-upnp/root/usr/share/rpcd/acl.d/luci-app-upnp.json
index 4611f501692a..6e1fa8fcd52c 100644
--- a/luci-app-upnp/root/usr/share/rpcd/acl.d/luci-app-upnp.json
+++ b/luci-app-upnp/root/usr/share/rpcd/acl.d/luci-app-upnp.json
@@ -1,6 +1,6 @@
{
"luci-app-upnp": {
- "description": "Grant access to UPnP IGD & PCP/NAT-PMP",
+ "description": "Grant access to UPnP IGD & PCP/NAT-PMP service configuration",
"read": {
"ubus": {
"luci.upnp": [ "get_status" ],
diff --git a/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp b/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
index f72b57b7ba84..56ae951ceb03 100644
--- a/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
+++ b/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
@@ -1,3 +1,5 @@
+#!/usr/bin/env ucode
+
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
// Licensed to the public under the Apache License 2.0.
@@ -7,82 +9,73 @@ import { access, open, popen } from 'fs';
import { connect } from 'ubus';
import { cursor } from 'uci';
-// Establish ubus connection persistently outside of the call handler scope to
-// prevent premature GC'ing. Can be moved into `get_status` callback once
-// https://github.com/jow-/ucode/commit/a58fe4709f661b5f28e26701ea8638efccf5aeb6
-// is merged.
-const ubus = connect();
-
const methods = {
get_status: {
call: function(req) {
const uci = cursor();
-
+ const ubus = connect();
const rules = [];
const leases = [];
-
- const leasefile = open(uci.get('upnpd', 'config', 'upnp_lease_file'), 'r');
-
+ const leasefile = open(uci.get('upnpd', 'config', 'upnp_lease_file') || '/var/run/miniupnpd.leases', 'r');
if (leasefile) {
for (let line = leasefile.read('line'); length(line); line = leasefile.read('line')) {
const record = split(line, ':', 6);
-
if (length(record) == 6) {
+ let descr = trim(record[5]);
+ let m = match(descr, /^PCP [A-Z]+ ([0-9a-f]{24})$/);
+ if (m) descr = 'PCP (nonce ' + m[1] + ')';
+ else if (match(descr, /^NAT-PMP \d+ \w+$/)) descr = 'NAT-PMP';
+ else if (match(descr, /^IGD2 pinhole$/)) descr = 'UPnP IGDv2 IPv6';
+ else if (!match(descr, /^UPnP IGD/)) descr = 'UPnP IGD / ' + descr;
push(leases, {
proto: uc(record[0]),
extport: +record[1],
intaddr: arrtoip(iptoarr(record[2])),
intport: +record[3],
expires: record[4] - timelocal(localtime()),
- description: trim(record[5])
+ descr: descr
});
}
}
-
leasefile.close();
}
- const ipt = popen('iptables --line-numbers -t nat -xnvL MINIUPNPD 2>/dev/null');
-
+ // Workaround daemon bug with iptables >= 1.8.8 by parsing MINIUPNPD-POSTROUTING
+ // See https://github.com/miniupnp/miniupnp/issues/837
+ //const ipt = popen('iptables --line-numbers -t nat -xnvL MINIUPNPD 2>/dev/null');
+ const ipt = popen('iptables --line-numbers -t nat -xnvL MINIUPNPD-POSTROUTING 2>/dev/null');
if (ipt) {
for (let line = ipt.read('line'); length(line); line = ipt.read('line')) {
- let m = match(line, /^([0-9]+)\s+([a-z]+).+dpt:([0-9]+) to:(\S+):([0-9]+)/);
-
+ //let m = match(line, /^([0-9]+) .+ ([ut].p) dpt:([0-9]+) to:(\S+):([0-9]+)/);
+ let m = match(line, /^([0-9]+) .+ \* +([0-9.]+) .+ ([ut].p) spt:([0-9]+) .+ ports: ([0-9]+)/);
if (m) {
push(rules, {
num: m[1],
- proto: uc(m[2]),
- extport: +m[3],
- intaddr: arrtoip(iptoarr(m[4])),
- intport: +m[5],
- descr: ''
+ intaddr: arrtoip(iptoarr(m[2])),
+ intport: +m[4],
+ extport: +m[5],
+ proto: uc(m[3])
});
}
}
-
ipt.close();
}
const nft = popen('nft --handle list chain inet fw4 upnp_prerouting 2>/dev/null');
-
if (nft) {
for (let line = nft.read('line'), num = 1; length(line); line = nft.read('line')) {
- let m = match(line, /^\t\tiif ".+" @nh,72,8 (0x6|0x11) th dport ([0-9]+) dnat ip to ([0-9.]+):([0-9]+)/);
-
+ let m = match(line, /^\t\tiif ".+" @nh,72,8 (0x6|0x11) th dport ([0-9]+).* dnat ip to ([0-9.]+):([0-9]+)/);
if (m) {
push(rules, {
num: `${num}`,
- proto: (m[1] == '0x6') ? 'TCP' : 'UDP',
- extport: +m[2],
intaddr: arrtoip(iptoarr(m[3])),
intport: +m[4],
- descr: ''
+ extport: +m[2],
+ proto: (m[1] == '0x6') ? 'TCP' : 'UDP'
});
-
num++;
}
}
-
nft.close();
}
@@ -90,16 +83,14 @@ const methods = {
for (let rule in rules) {
for (let lease in leases) {
if (lease.proto == rule.proto &&
- lease.intaddr == rule.intaddr &&
- lease.intport == rule.intport &&
- lease.extport == rule.extport)
- {
- rule.descr = lease.description;
+ lease.intaddr == rule.intaddr &&
+ lease.intport == rule.intport &&
+ lease.extport == rule.extport) {
rule.expires = lease.expires;
+ rule.descr = lease.descr;
break;
}
}
-
for (let mac, hint in host_hints) {
if (rule.intaddr in hint.ipaddrs) {
rule.host_hint = hint.name;
@@ -107,7 +98,6 @@ const methods = {
}
}
}
-
req.reply({ rules });
});
}
@@ -117,19 +107,15 @@ const methods = {
args: { token: 'token' },
call: function(req) {
const idx = +req.args?.token;
-
if (idx > 0) {
const uci = cursor();
- const leasefile = uci.get('upnpd', 'config', 'upnp_lease_file');
-
+ const leasefile = uci.get('upnpd', 'config', 'upnp_lease_file') || '/var/run/miniupnpd.leases';
if (access(leasefile)) {
system(['sed', '-i', '-e', `${idx}d`, leasefile]);
system(['/etc/init.d/miniupnpd', 'restart']);
}
-
return { result: 'OK' };
}
-
return { result: 'Bad request' };
}
}
From e7100b61eaf00ae0e11e973ade457edc433c8daa Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 2/7] luci-app-upnp: add `UPnP IGD Adjustments` tab
And rearrange as many options
(to merge with prior)
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
.../luci-static/resources/view/upnp/upnp.js | 71 ++++++++++---------
1 file changed, 36 insertions(+), 35 deletions(-)
diff --git a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
index 07ca8b0a9c62..0f8a4e84ea18 100644
--- a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
+++ b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
@@ -145,6 +145,7 @@ return view.extend({
s.addremove = false;
s.tab('setup', _('Service Setup'));
s.tab('advanced', _('Advanced Settings'));
+ s.tab('igd', _('UPnP IGD Adjustments'));
o = s.taboption('setup', form.Flag, 'enabled', _('Enable service'),
_('Enable the autonomous port mapping service'));
@@ -156,7 +157,7 @@ return view.extend({
o = s.taboption('setup', form.Flag, 'enable_natpmp', _('Enable PCP/NAT-PMP protocols'));
o.default = '1';
- s.taboption('setup', form.Flag, 'ext_allow_private_ipv4', _('Allow private IPv4'),
+ s.taboption('advanced', form.Flag, 'ext_allow_private_ipv4', _('Allow private IPv4'),
_('Enable forwarding for private/reserved IPv4 address'));
o = s.taboption('setup', form.Flag, 'igdv1', _('UPnP IGDv1 compatibility mode'),
@@ -166,16 +167,6 @@ return view.extend({
o.depends('enable_upnp', '1');
o.retain = true;
- o = s.taboption('setup', form.Value, 'download', _('Download speed'),
- _('Report maximum download speed in kByte/s'));
- o.depends('enable_upnp', '1');
- o.retain = true;
-
- o = s.taboption('setup', form.Value, 'upload', _('Upload speed'),
- _('Report maximum upload speed in kByte/s'));
- o.depends('enable_upnp', '1');
- o.retain = true;
-
s.taboption('advanced', form.Flag, 'use_stun', _('Use %s', 'Use %s (%s = STUN)')
.format('<a href="https://en.wikipedia.org/wiki/STUN" target="_blank" rel="noreferrer"><abbr title="Session Traversal Utilities for NAT">STUN</abbr></a>'),
_('To detect the public IPv4 address for unrestricted full-cone/one-to-one NATs'));
@@ -199,50 +190,60 @@ return view.extend({
s.taboption('advanced', form.Flag, 'ipv6_disable', _('Disable IPv6 mapping'));
- o = s.taboption('advanced', form.Value, 'notify_interval', _('Notify interval'),
- _('A 900 s interval sends %s announcements with the minimum %s header',
- 'A 900 s interval sends %s (%s = SSDP) announcements with the minimum %s (%s = Cache-Control: max-age=1800) header')
- .format('<abbr title="Simple Service Discovery Protocol">SSDP</abbr>', '<code>Cache-Control: max-age=1800</code>'));
- o.datatype = 'min(900)';
- o.placeholder = '900';
- o.depends('enable_upnp', '1');
+ o = s.taboption('advanced', form.Flag, 'system_uptime', _('Report system instead of service uptime'));
+ o.default = '1';
+ o.depends('keep-translation', 'to-disable-as-rare-use');
o.retain = true;
- o = s.taboption('advanced', form.Value, 'port', _('SOAP/HTTP port'));
- o.datatype = 'port';
- o.placeholder = '5000';
+ s.taboption('advanced', form.Flag, 'log_output', _('Enable additional logging'),
+ _('Puts extra debugging information into the system log'));
+
+ o = s.taboption('advanced', form.Value, 'upnp_lease_file', _('Service lease file'));
+ o.depends('keep-translation', 'to-disable-as-rare-use');
+ o.retain = true;
+
+ o = s.taboption('igd', form.Value, 'download', _('Download speed'),
+ _('Report maximum download speed in kByte/s'));
o.depends('enable_upnp', '1');
o.retain = true;
- o = s.taboption('advanced', form.Value, 'presentation_url', _('Router/presentation URL'),
- _('Report custom router web interface URL'));
- o.placeholder = 'http://192.168.1.1/';
+ o = s.taboption('igd', form.Value, 'upload', _('Upload speed'),
+ _('Report maximum upload speed in kByte/s'));
o.depends('enable_upnp', '1');
o.retain = true;
- o = s.taboption('advanced', form.Value, 'uuid', _('Device UUID'));
- // o.depends('enable_upnp', '1');
- o.depends('keep-translation', 'to-disable-as-rare-use');
+ o = s.taboption('igd', form.Value, 'model_number', _('Announced model number'));
+ o.depends('enable_upnp', '1');
o.retain = true;
- o = s.taboption('advanced', form.Value, 'model_number', _('Announced model number'));
+ o = s.taboption('igd', form.Value, 'serial_number', _('Announced serial number'));
o.depends('enable_upnp', '1');
o.retain = true;
- o = s.taboption('advanced', form.Value, 'serial_number', _('Announced serial number'));
+ o = s.taboption('igd', form.Value, 'presentation_url', _('Router/presentation URL'),
+ _('Report custom router web interface URL'));
+ o.placeholder = 'http://192.168.1.1/';
o.depends('enable_upnp', '1');
o.retain = true;
- o = s.taboption('advanced', form.Flag, 'system_uptime', _('Report system instead of service uptime'));
- o.default = '1';
+ o = s.taboption('igd', form.Value, 'uuid', _('Device UUID'));
+ // o.depends('enable_upnp', '1');
o.depends('keep-translation', 'to-disable-as-rare-use');
o.retain = true;
- s.taboption('advanced', form.Flag, 'log_output', _('Enable additional logging'),
- _('Puts extra debugging information into the system log'));
+ o = s.taboption('igd', form.Value, 'port', _('SOAP/HTTP port'));
+ o.datatype = 'port';
+ o.placeholder = '5000';
+ o.depends('enable_upnp', '1');
+ o.retain = true;
- o = s.taboption('advanced', form.Value, 'upnp_lease_file', _('Service lease file'));
- o.depends('keep-translation', 'to-disable-as-rare-use');
+ o = s.taboption('igd', form.Value, 'notify_interval', _('Notify interval'),
+ _('A 900 s interval sends %s announcements with the minimum %s header',
+ 'A 900 s interval sends %s (%s = SSDP) announcements with the minimum %s (%s = Cache-Control: max-age=1800) header')
+ .format('<abbr title="Simple Service Discovery Protocol">SSDP</abbr>', '<code>Cache-Control: max-age=1800</code>'));
+ o.datatype = 'min(900)';
+ o.placeholder = '900';
+ o.depends('enable_upnp', '1');
o.retain = true;
s = m.section(form.GridSection, 'perm_rule', _('Service Access Control List'),
From dc6910d395c2eed5b27c5f974a4db63a9253d0e3 Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 3/7] luci-app-upnp: revision and adapt to new UCI options
The following settings UCI options been added or changed, and the
previous options are migrated on updating:
- Only display `Active Port Maps` if the service is enabled and `Access
Control List` if it is used
- Enable protocols (`enable_protocols`): Combined UI option added
- Allow CGNAT/STUN (`allow_cgnat`): Accept new values for IPv4 CGNAT use
and update help with newer wording of RFC 5780
- STUN server (`stun_host`): Allow port inclusion
- STUN port: Removed, as now accepted in STUN server
- Override external IPv4 (`external_ip`): UI option added for CGNAT use
- Allow third-party mapping (`allow_third_party_mapping`): Inverted from
secure mode and optionally extended to PCP
- Log level (`log_output`): Allow info log level
- UPnP IGD compatibility (`upnp_igd_compat`): Reworded/extensible
- Download/upload speed (`download_kbps`/`upload_kbps`): In kbit/s and
datatype set, now, interface link speed by default
- Router/friendly name (`friendly_name`): UI option added to set name
displayed in Windows Explorer, model/serial number removed
- Enable Networks / Access Control (`internal_network`): Section added
to select the enabled networks and their access control. By:
- Internal network (`interface`): UI option added to select the
local/internal (LAN) network interface to enable the service for
- Access defaults (`access_defaults`): UI option added to select
access defaults for ports that all devices on the network can map
- Accept extra ports (`accept_ports`): UI option added to accept these
ports or port ranges on the network as well
- Reject ports (`reject_ports`): UI option added to reject ports on
the network; overrides other settings
- Check ACL (`check_acl`): UI option added to check the ACL entries
first; these extend/override the defaults
- Slightly improve introduction text
More details on changed options can be found in the dependent package PR
Fixes: https://redirect.github.com/openwrt/luci/issues/8313
Fixes: https://redirect.github.com/openwrt/luci/issues/8677
Depends on: https://redirect.github.com/openwrt/packages/pull/28765
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
luci-app-upnp/Makefile | 2 +-
.../luci-static/resources/view/upnp/upnp.js | 202 +++++++++++++-----
.../root/usr/share/rpcd/ucode/luci.upnp | 4 +-
3 files changed, 151 insertions(+), 57 deletions(-)
diff --git a/luci-app-upnp/Makefile b/luci-app-upnp/Makefile
index 80e668764d96..601122426a89 100644
--- a/luci-app-upnp/Makefile
+++ b/luci-app-upnp/Makefile
@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
-LUCI_TITLE:=UPnP IGD & PCP/NAT-PMP configuration module | Universal Plug and Play
+LUCI_TITLE:=LuCI support for UPnP IGD & PCP/NAT-PMP service | originally: Universal Plug and Play
LUCI_DEPENDS:=+luci-base +miniupnpd +rpcd-mod-ucode
PKG_LICENSE:=Apache-2.0
diff --git a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
index 0f8a4e84ea18..1dab80bef4b9 100644
--- a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
+++ b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
@@ -6,6 +6,7 @@
'require ui';
'require rpc';
'require form';
+'require tools.widgets as widgets';
const callInitAction = rpc.declare({
object: 'luci',
@@ -87,8 +88,8 @@ return view.extend({
'<a href="https://en.wikipedia.org/wiki/Port_Control_Protocol" target="_blank" rel="noreferrer"><abbr title="Port Control Protocol">PCP</abbr></a>',
'<a href="https://en.wikipedia.org/wiki/NAT_Port_Mapping_Protocol" target="_blank" rel="noreferrer"><abbr title="NAT Port Mapping Protocol">NAT-PMP</abbr></a>');
m = new form.Map('upnpd', _('UPnP IGD & PCP/NAT-PMP Service'),
- _('The %s protocols allow clients on the local network to configure port maps/forwards on the router autonomously.',
- 'The %s (%s = UPnP IGD & PCP/NAT-PMP) protocols allow clients on the local network to configure port maps/forwards on the router autonomously.')
+ _('The %s protocols/service enable permitted devices on local networks to autonomously set up IPv4/IPv6 port maps/forwards on this router.',
+ 'The %s (%s = UPnP IGD & PCP/NAT-PMP) protocols/service enable permitted devices on local networks to autonomously set up IPv4/IPv6 port maps/forwards on this router.')
.format(protocols)
);
if (!uci.get('upnpd', 'config')) {
@@ -100,6 +101,7 @@ return view.extend({
}
s = m.section(form.GridSection, '_active_rules');
+ s.disable = uci.get('upnpd', 'config', 'enabled') == '0';
s.render = L.bind(function(view, section_id) {
const table = E('table', { 'class': 'table cbi-section-table', 'id': 'upnp_status_table' }, [
@@ -151,42 +153,50 @@ return view.extend({
_('Enable the autonomous port mapping service'));
o.rmempty = false;
- o = s.taboption('setup', form.Flag, 'enable_upnp', _('Enable UPnP IGD protocol'));
- o.default = '1';
-
- o = s.taboption('setup', form.Flag, 'enable_natpmp', _('Enable PCP/NAT-PMP protocols'));
- o.default = '1';
-
- s.taboption('advanced', form.Flag, 'ext_allow_private_ipv4', _('Allow private IPv4'),
- _('Enable forwarding for private/reserved IPv4 address'));
-
- o = s.taboption('setup', form.Flag, 'igdv1', _('UPnP IGDv1 compatibility mode'),
- _('Advertise as IGDv1 (IPv4 only) device instead of IGDv2'));
- o.default = '1';
- o.rmempty = false;
- o.depends('enable_upnp', '1');
+ o = s.taboption('setup', form.ListValue, 'enable_protocols', _('Enable protocols'));
+ o.value('all', _('All protocols'));
+ o.value('upnp-igd', _('UPnP IGD'));
+ o.value('pcp+nat-pmp', _('PCP and NAT-PMP'));
+ o.default = 'all';
+ o.widget = 'radio';
+
+ o = s.taboption('setup', form.ListValue, 'upnp_igd_compat', _('UPnP IGD compatibility'),
+ _('Set compatibility mode (act as device) to workaround IGDv2-incompatible clients; %s are known to only work with %s (or) <br />Emulate/report a specific/different device to workaround/support/handle/bypass/assist/mitigate... (Alternative text welcome)').format('Sony PS, Activision CoD…', 'IGDv1'));
+ o.value('igdv1', _('IGDv1 (IPv4 only)'));
+ o.value('igdv2', _('IGDv2 (with workarounds)'));
+ o.depends('enable_protocols', 'upnp-igd');
+ o.depends('enable_protocols', 'all');
o.retain = true;
- s.taboption('advanced', form.Flag, 'use_stun', _('Use %s', 'Use %s (%s = STUN)')
- .format('<a href="https://en.wikipedia.org/wiki/STUN" target="_blank" rel="noreferrer"><abbr title="Session Traversal Utilities for NAT">STUN</abbr></a>'),
- _('To detect the public IPv4 address for unrestricted full-cone/one-to-one NATs'));
-
- o = s.taboption('advanced', form.Value, 'stun_host', _('STUN host'));
- o.datatype = 'host';
- o.depends('use_stun', '1');
+ o = s.taboption('advanced', form.RichListValue, 'allow_cgnat', _('Allow %s/%s', 'Allow %s/%s (%s = CGNAT, %s = STUN)')
+ .format('<a href="https://en.wikipedia.org/wiki/Carrier-grade_NAT" target="_blank" rel="noreferrer"><abbr title="Carrier-grade NAT">CGNAT</abbr></a>',
+ '<a href="https://en.wikipedia.org/wiki/STUN" target="_blank" rel="noreferrer"><abbr title="Session Traversal Utilities for NAT">STUN</abbr></a>'),
+ _('Allow use of unrestricted endpoint-independent (1:1) CGNATs and detect the public IPv4'));
+ o.value('', _('Disabled'), _('Manually override external IPv4 with public'));
+ o.value('1', _('Enabled'), _('Filtering test currently requires an extra firewall rule'));
+ o.value('allow-filtered', _('Enabled') + ' (' + _('allow filtered') + ')', _('Workaround filtered IPv4 CGNAT test result'));
+ o.value('report-private-ipv4', _('Disabled') + ' (' + _('report private IPv4, avoid') + ')', _('No STUN public IPv4 detection; client issues'));
+ o.optional = true;
+
+ o = s.taboption('advanced', form.Value, 'stun_host', _('STUN server'));
+ o.datatype = 'or(hostname,hostport,ip4addr("nomask"))';
+ o.placeholder = 'stun.nextcloud.com';
+ o.depends('allow_cgnat', '1');
+ o.depends('allow_cgnat', 'allow-filtered');
o.retain = true;
- o = s.taboption('advanced', form.Value, 'stun_port', _('STUN port'));
- o.datatype = 'port';
- o.placeholder = '3478';
- o.depends('use_stun', '1');
- o.retain = true;
+ o = s.taboption('advanced', form.Value, 'external_ip', _('Override external IPv4'),
+ _('Report custom public/external (WAN) IPv4 address'));
+ o.datatype = 'ip4addr("nomask")';
+ o.depends('allow_cgnat', '');
+ o.depends('allow_cgnat', 'report-private-ipv4');
- o = s.taboption('advanced', form.Flag, 'secure_mode', _('Enable secure mode'),
- _('Allow adding port maps for requesting IP addresses only'));
- o.default = '1';
- o.depends('enable_upnp', '1');
- o.retain = true;
+ o = s.taboption('advanced', form.ListValue, 'allow_third_party_mapping', _('Allow third-party mapping'),
+ _('Allow adding port maps for non-requesting IP addresses; use with care'));
+ o.value('', _('Disabled') + ' (' + _('recommended') + ')');
+ o.value('1', _('Enabled'));
+ o.value('upnp-igd', _('Enabled') + ' (' + _('UPnP IGD only') + ')');
+ o.value('pcp', _('Enabled') + ' (' + _('PCP only') + ')');
s.taboption('advanced', form.Flag, 'ipv6_disable', _('Disable IPv6 mapping'));
@@ -195,46 +205,69 @@ return view.extend({
o.depends('keep-translation', 'to-disable-as-rare-use');
o.retain = true;
- s.taboption('advanced', form.Flag, 'log_output', _('Enable additional logging'),
- _('Puts extra debugging information into the system log'));
+ o = s.taboption('advanced', form.ListValue, 'log_output', _('Log level'));
+ o.value('default', _('Default'));
+ o.value('info', _('Info'));
+ o.value('debug', _('Debug'));
+ o.default = 'default';
+ o.widget = 'radio';
- o = s.taboption('advanced', form.Value, 'upnp_lease_file', _('Service lease file'));
+ o = s.taboption('advanced', form.Value, 'lease_file', _('Service lease file'));
o.depends('keep-translation', 'to-disable-as-rare-use');
o.retain = true;
- o = s.taboption('igd', form.Value, 'download', _('Download speed'),
- _('Report maximum download speed in kByte/s'));
- o.depends('enable_upnp', '1');
+ o = s.taboption('igd', form.Value, 'download_kbps', _('Download speed'),
+ _('Report maximum connection speed in kbit/s'));
+ o.datatype = 'uinteger';
+ o.placeholder = _('Default interface link speed');
+ o.depends('enable_protocols', 'upnp-igd');
+ o.depends('enable_protocols', 'all');
o.retain = true;
- o = s.taboption('igd', form.Value, 'upload', _('Upload speed'),
- _('Report maximum upload speed in kByte/s'));
- o.depends('enable_upnp', '1');
+ o = s.taboption('igd', form.Value, 'upload_kbps', _('Upload speed'),
+ _('Report maximum connection speed in kbit/s'));
+ o.datatype = 'uinteger';
+ o.placeholder = _('Default interface link speed');
+ o.depends('enable_protocols', 'upnp-igd');
+ o.depends('enable_protocols', 'all');
+ o.retain = true;
+
+ o = s.taboption('igd', form.Value, 'friendly_name', _('Router/friendly name'));
+ o.placeholder = 'OpenWrt UPnP IGD & PCP';
+ o.depends('enable_protocols', 'upnp-igd');
+ o.depends('enable_protocols', 'all');
o.retain = true;
o = s.taboption('igd', form.Value, 'model_number', _('Announced model number'));
- o.depends('enable_upnp', '1');
+ // o.depends('enable_protocols', 'upnp-igd');
+ // o.depends('enable_protocols', 'all');
+ o.depends('keep-translation', 'to-disable-as-rare-use');
o.retain = true;
o = s.taboption('igd', form.Value, 'serial_number', _('Announced serial number'));
- o.depends('enable_upnp', '1');
+ // o.depends('enable_protocols', 'upnp-igd');
+ // o.depends('enable_protocols', 'all');
+ o.depends('keep-translation', 'to-disable-as-rare-use');
o.retain = true;
o = s.taboption('igd', form.Value, 'presentation_url', _('Router/presentation URL'),
_('Report custom router web interface URL'));
o.placeholder = 'http://192.168.1.1/';
- o.depends('enable_upnp', '1');
+ o.depends('enable_protocols', 'upnp-igd');
+ o.depends('enable_protocols', 'all');
o.retain = true;
o = s.taboption('igd', form.Value, 'uuid', _('Device UUID'));
- // o.depends('enable_upnp', '1');
+ // o.depends('enable_protocols', 'upnp-igd');
+ // o.depends('enable_protocols', 'all');
o.depends('keep-translation', 'to-disable-as-rare-use');
o.retain = true;
- o = s.taboption('igd', form.Value, 'port', _('SOAP/HTTP port'));
+ o = s.taboption('igd', form.Value, 'http_port', _('SOAP/HTTP port'));
o.datatype = 'port';
o.placeholder = '5000';
- o.depends('enable_upnp', '1');
+ o.depends('enable_protocols', 'upnp-igd');
+ o.depends('enable_protocols', 'all');
o.retain = true;
o = s.taboption('igd', form.Value, 'notify_interval', _('Notify interval'),
@@ -243,7 +276,57 @@ return view.extend({
.format('<abbr title="Simple Service Discovery Protocol">SSDP</abbr>', '<code>Cache-Control: max-age=1800</code>'));
o.datatype = 'min(900)';
o.placeholder = '900';
- o.depends('enable_upnp', '1');
+ o.depends('enable_protocols', 'upnp-igd');
+ o.depends('enable_protocols', 'all');
+ o.retain = true;
+
+ s = m.section(form.GridSection, 'internal_network', '<h5>' + _('Enable Networks / Access Control') + '</h5>',
+ _('Select local/internal (LAN) network interfaces to enable the service for.') + ' ' +
+ _('Set access control defaults for ports that all devices on a network can map.') + ' ' +
+ _('IPv6 is currently always accepted unless disabled. (Alternative text welcome)'));
+ s.anonymous = true;
+ s.addremove = true;
+ s.cloneable = true;
+ s.sortable = true;
+ s.nodescriptions = true;
+ s.modaltitle = _('UPnP IGD & PCP/NAT-PMP') + ' - ' + _('Edit Network Access Control Settings');
+
+ o = s.option(widgets.NetworkSelect, 'interface', _('Internal network'),
+ _('Select the local/internal (LAN) network interface to enable the service for'));
+ o.nocreate = true;
+ o.editable = true;
+ o.rmempty = false;
+ o.retain = true;
+ o.filter = function(section_id, value) {
+ return (value == 'wan' || value == 'wan6') ? '' : value;
+ };
+
+ o = s.option(form.ListValue, 'access_defaults', _('Access defaults'),
+ _('Set access control defaults for ports that all devices on the network can map'));
+ o.value('', _('None / accept extra ports only'));
+ o.value('accept-high-ports', _('Accept ports >= 1024'));
+ o.value('accept-web+high-ports', _('Accept HTTP/HTTPS + ports >= 1024'));
+ o.value('accept-web-ports', _('Accept HTTP/HTTPS ports'));
+ o.value('accept-all-ports', _('Accept all ports'));
+ o.editable = true;
+ o.retain = true;
+
+ o = s.option(form.Value, 'accept_ports', _('Accept extra ports'));
+ o.datatype = 'list(portrange)';
+ o.retain = true;
+
+ o = s.option(form.Value, 'reject_ports', _('Reject ports'),
+ _('Reject unsafe/insecure/risky FTP/Telnet/DCE/NetBIOS/SMB/RDP ports on the network by default; overrides other settings; use %s for none').format('<code>0</code>'));
+ o.datatype = 'list(portrange)';
+ o.placeholder = '21 23 135 137-139 445 3389';
+ o.modalonly = true;
+ o.retain = true;
+
+ o = s.option(form.Flag, 'check_acl', _('Check ACL'),
+ _('Extend or override access defaults by device-specific permissions using the access control list (ACL)') + '<br />' +
+ _('Sequence:') + ' 1. ' + _('Reject ports') + ', 2. ' + _('ACL entries (if checked)') + ', 3. ' + _('Access defaults') + ', 4. ' + _('Accept extra ports'));
+ o.default = '1';
+ o.editable = true;
o.retain = true;
s = m.section(form.GridSection, 'perm_rule', _('Service Access Control List'),
@@ -251,6 +334,15 @@ return view.extend({
s.anonymous = true;
s.addremove = true;
s.sortable = true;
+ // To do: ACL part of extra tab with dependency on option as immediately, and network section part of service setup tab
+ let acl_used = false;
+ for (let ifnr = 0; uci.get('upnpd', `@internal_network[${ifnr}]`, 'interface'); ifnr++) {
+ if (uci.get('upnpd', `@internal_network[${ifnr}]`, 'check_acl') != '0') {
+ acl_used = true;
+ break;
+ }
+ }
+ s.disable = !acl_used;
s.option(form.Value, 'comment', _('Comment'));
@@ -271,11 +363,13 @@ return view.extend({
o.value('deny', _('Deny'));
return m.render().then(L.bind(function(m, nodes) {
- poll.add(L.bind(function() {
- return Promise.all([
- callUpnpGetStatus()
- ]).then(L.bind(this.poll_status, this, nodes));
- }, this), 5);
+ if (uci.get('upnpd', 'config', 'enabled') != '0') {
+ poll.add(L.bind(function() {
+ return Promise.all([
+ callUpnpGetStatus()
+ ]).then(L.bind(this.poll_status, this, nodes));
+ }, this), 5);
+ }
return nodes;
}, this, m));
}
diff --git a/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp b/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
index 56ae951ceb03..a7bf8e6f5d86 100644
--- a/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
+++ b/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
@@ -16,7 +16,7 @@ const methods = {
const ubus = connect();
const rules = [];
const leases = [];
- const leasefile = open(uci.get('upnpd', 'config', 'upnp_lease_file') || '/var/run/miniupnpd.leases', 'r');
+ const leasefile = open(uci.get('upnpd', 'config', 'lease_file') || '/var/run/miniupnpd.leases', 'r');
if (leasefile) {
for (let line = leasefile.read('line'); length(line); line = leasefile.read('line')) {
const record = split(line, ':', 6);
@@ -109,7 +109,7 @@ const methods = {
const idx = +req.args?.token;
if (idx > 0) {
const uci = cursor();
- const leasefile = uci.get('upnpd', 'config', 'upnp_lease_file') || '/var/run/miniupnpd.leases';
+ const leasefile = uci.get('upnpd', 'config', 'lease_file') || '/var/run/miniupnpd.leases';
if (access(leasefile)) {
system(['sed', '-i', '-e', `${idx}d`, leasefile]);
system(['/etc/init.d/miniupnpd', 'restart']);
From e203add9f4372d85f97b4222abe69066a08cb571 Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 4/7] luci-app-upnp: rename UCI section to settings v2.0
Rename UCI section `config` (v1.0) -> `settings` (v2.0), helps on
migration and to distinguish the updated config from the previous one
(to merge with prior)
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
.../htdocs/luci-static/resources/view/upnp/upnp.js | 10 +++++-----
.../luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp | 4 ++--
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
index 1dab80bef4b9..e5f1245636af 100644
--- a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
+++ b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
@@ -92,16 +92,16 @@ return view.extend({
'The %s (%s = UPnP IGD & PCP/NAT-PMP) protocols/service enable permitted devices on local networks to autonomously set up IPv4/IPv6 port maps/forwards on this router.')
.format(protocols)
);
- if (!uci.get('upnpd', 'config')) {
+ if (!uci.get('upnpd', 'settings')) {
ui.addNotification(null, E('div', '<h4>' + _('No suitable configuration was found!') + '</h4><p>' +
- _('No suitable config (LuCI app %s) found in %s. A related package update (daemon or LuCI app) may be missing.').format('v1.0', '<code>/etc/config/upnpd</code>') + '<br />' +
+ _('No suitable config (LuCI app %s) found in %s. A related package update (daemon or LuCI app) may be missing.').format('v2.0', '<code>/etc/config/upnpd</code>') + '<br />' +
_('Use the software package manager, update lists, and install the related update. Config is migrated with the daemon package update.') + '</p>' +
'<a class="btn" href="/cgi-bin/luci/admin/system/package-manager?query=UPnP%20IGD%20&amp;%20PCP/NAT-PMP">' + _('Go to package manager…') + '</a>'), 'warning');
m.readonly = true;
}
s = m.section(form.GridSection, '_active_rules');
- s.disable = uci.get('upnpd', 'config', 'enabled') == '0';
+ s.disable = uci.get('upnpd', 'settings', 'enabled') == '0';
s.render = L.bind(function(view, section_id) {
const table = E('table', { 'class': 'table cbi-section-table', 'id': 'upnp_status_table' }, [
@@ -143,7 +143,7 @@ return view.extend({
]);
}, o, this);
- s = m.section(form.NamedSection, 'config', 'upnpd', _('Service Settings'));
+ s = m.section(form.NamedSection, 'settings', 'upnpd', _('Service Settings'));
s.addremove = false;
s.tab('setup', _('Service Setup'));
s.tab('advanced', _('Advanced Settings'));
@@ -363,7 +363,7 @@ return view.extend({
o.value('deny', _('Deny'));
return m.render().then(L.bind(function(m, nodes) {
- if (uci.get('upnpd', 'config', 'enabled') != '0') {
+ if (uci.get('upnpd', 'settings', 'enabled') != '0') {
poll.add(L.bind(function() {
return Promise.all([
callUpnpGetStatus()
diff --git a/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp b/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
index a7bf8e6f5d86..e38f5a26352d 100644
--- a/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
+++ b/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
@@ -16,7 +16,7 @@ const methods = {
const ubus = connect();
const rules = [];
const leases = [];
- const leasefile = open(uci.get('upnpd', 'config', 'lease_file') || '/var/run/miniupnpd.leases', 'r');
+ const leasefile = open(uci.get('upnpd', 'settings', 'lease_file') || '/var/run/miniupnpd.leases', 'r');
if (leasefile) {
for (let line = leasefile.read('line'); length(line); line = leasefile.read('line')) {
const record = split(line, ':', 6);
@@ -109,7 +109,7 @@ const methods = {
const idx = +req.args?.token;
if (idx > 0) {
const uci = cursor();
- const leasefile = uci.get('upnpd', 'config', 'lease_file') || '/var/run/miniupnpd.leases';
+ const leasefile = uci.get('upnpd', 'settings', 'lease_file') || '/var/run/miniupnpd.leases';
if (access(leasefile)) {
system(['sed', '-i', '-e', `${idx}d`, leasefile]);
system(['/etc/init.d/miniupnpd', 'restart']);
From 88c8ccce976eb54ed4d3e6cea00b18660eb3eec7 Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 5/7] luci-app-upnp: update ACL options, migrate section
- Disableable and cloneable ACL entries, always translated `Action`
- Improve UI with direct editability, clearer help wording, and rename
to `Access Control List`
- The ACL is now rejected last if not accepted by access defaults. Add
(disabled) ACL template entries on migration
- Migrate ACL entries to the new section name `acl_entry`
- The following ACL UCI options been added or changed, and the previous
options are migrated on updating:
acl_entry UCI options | Change | Previous name
----------------------------|----------------------------|--------------
action | New/updated values (1) |
int_port | Remove colon separator (2) | int_ports
ext_port | Remove colon separator (2) | ext_ports
descr_filter | New option (3) |
1. Allow disabled, and update action option to use the nftables terms
(allow/deny -> accept/reject). To avoid adding inverted actions when
changing via LuCI, ensure any missing are set, as LuCI and UCI had
not matching action defaults. Missing actions are now ignored/logged
2. Ensure that the hyphen (-) is only used as a port range separator by
migration, as the colon (:) is not valid in LuCI
3. Add missing UCI option to set a regular expression to check for a
UPnP IGD IPv4 port map description, and fix the current collision
with the comment field which was not noticed due to a daemon bug
https://redirect.github.com/openwrt/packages/pull/24495
https://redirect.github.com/miniupnp/miniupnp/pull/853
(to merge with prior)
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
.../luci-static/resources/view/upnp/upnp.js | 46 +++++++++++++++----
1 file changed, 36 insertions(+), 10 deletions(-)
diff --git a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
index e5f1245636af..87c7c2a25fe0 100644
--- a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
+++ b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
@@ -329,11 +329,14 @@ return view.extend({
o.editable = true;
o.retain = true;
- s = m.section(form.GridSection, 'perm_rule', _('Service Access Control List'),
- _('ACL specify which client addresses and ports can be mapped, IPv6 always allowed.'));
+ s = m.section(form.GridSection, 'acl_entry', '<h5>' + _('Access Control List') + '</h5>',
+ _('The access control list (ACL) specifies which IP addresses and ports can be mapped.') + ' ' +
+ _('ACL entries are checked in order, then rejected if not matched and not accepted by access defaults. (should be part of extra tab)'));
s.anonymous = true;
s.addremove = true;
+ s.cloneable = true;
s.sortable = true;
+ s.modaltitle = _('UPnP IGD & PCP/NAT-PMP') + ' - ' + _('Edit ACL Entry');
// To do: ACL part of extra tab with dependency on option as immediately, and network section part of service setup tab
let acl_used = false;
for (let ifnr = 0; uci.get('upnpd', `@internal_network[${ifnr}]`, 'interface'); ifnr++) {
@@ -344,23 +347,46 @@ return view.extend({
}
s.disable = !acl_used;
- s.option(form.Value, 'comment', _('Comment'));
+ o = s.option(form.Value, 'comment', _('Comment'));
+ o.default = _('unspecified');
o = s.option(form.Value, 'int_addr', _('IP address'));
o.datatype = 'ip4addr';
- o.placeholder = '0.0.0.0/0';
+ o.rmempty = false;
+ o.editable = true;
+ o.retain = true;
+ o.modalonly = false;
+
+ o = s.option(form.Value, 'int_addr', _('IP address'),
+ _('Enter device\'s IPv4 address, or an address with netmask'));
+ o.datatype = 'ip4addr';
+ o.rmempty = false;
+ o.retain = true;
+ o.modalonly = true;
- o = s.option(form.Value, 'int_ports', _('Port'));
+ o = s.option(form.Value, 'int_port', _('Port'));
o.datatype = 'portrange';
- o.placeholder = '1-65535';
+ o.placeholder = '1-65535 (' + _('any port') + ')';
+ o.editable = true;
+ o.retain = true;
- o = s.option(form.Value, 'ext_ports', _('External port'));
+ o = s.option(form.Value, 'ext_port', _('External port'));
o.datatype = 'portrange';
- o.placeholder = '1-65535';
+ o.placeholder = '1-65535 (' + _('any port') + ')';
+ o.editable = true;
+ o.retain = true;
+
+ o = s.option(form.Value, 'descr_filter', _('Description filter'),
+ _('Regular expression to check description of UPnP IGD IPv4 port maps'));
+ o.placeholder = '.* (' + _('any description') + ')';
+ o.modalonly = true;
o = s.option(form.ListValue, 'action', _('Action'));
- o.value('allow', _('Allow'));
- o.value('deny', _('Deny'));
+ o.value('accept', _('Accept'));
+ o.value('reject', _('Reject'));
+ o.value('disabled', _('Disabled'));
+ o.editable = true;
+ o.retain = true;
return m.render().then(L.bind(function(m, nodes) {
if (uci.get('upnpd', 'settings', 'enabled') != '0') {
From 095283e5b52745251d12f8254f3257f4c4cfe7f8 Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 6/7] luci-app-upnp: list active IPv6 port maps
Supports nftables and ip6tables; IPv6 port maps not yet deletable, as
not parsing extra IPv6 lease file
(Note: Daemon's IPv6 lease file differs from the IPv4 format in order
and separator. With IPv4, the remote IP field is missing and not
retained during daemon restarts. IMHO, it would be better to have a
complete/combined IPv4/IPv6 lease file upstream, with unique mapping IDs
for reliable deletion in the 1st field, with a standardised order and
more fields, and then use it without rewriting the parsing.)
(to merge with prior)
Fixes: https://redirect.github.com/openwrt/luci/issues/2985
Fixes: https://redirect.github.com/openwrt/luci/issues/5197
Fixes: https://redirect.github.com/openwrt/luci/pull/8567
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
.../resources/view/status/include/80_upnp.js | 8 +++-
.../luci-static/resources/view/upnp/upnp.js | 16 ++++++--
.../root/usr/share/rpcd/ucode/luci.upnp | 40 ++++++++++++++++++-
3 files changed, 57 insertions(+), 7 deletions(-)
diff --git a/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js b/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
index c82a108232de..7d06b2a56d6d 100644
--- a/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
+++ b/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
@@ -70,15 +70,19 @@ return baseclass.extend({
rule.proto,
expires_str,
'%h'.format(rule.descr),
- E('button', {
+ rule.num != '0' ? E('button', {
'class': 'btn cbi-button-remove',
'click': L.bind(handleDelRule, this, rule.num),
'title': _('Delete')
+ }, [_('Delete')]) : E('button', {
+ 'class': 'btn cbi-button-remove',
+ 'title': _('Not yet deletable; UPnP IGDv2 IPv6 port maps expire sooner, have no description'),
+ 'style': 'cursor: not-allowed'
}, [_('Delete')])
];
});
- cbi_update_table(table, rows, E('em', _('There are no active port maps.')));
+ cbi_update_table(table, rows, E('em', _('There are no active IPv4/IPv6 port maps')));
return table;
}
diff --git a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
index 87c7c2a25fe0..2f790e6c4a12 100644
--- a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
+++ b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
@@ -68,15 +68,19 @@ return view.extend({
rule.proto,
expires_str,
'%h'.format(rule.descr),
- E('button', {
+ rule.num != '0' ? E('button', {
'class': 'btn cbi-button-remove',
'click': L.bind(handleDelRule, this, rule.num),
'title': _('Delete')
+ }, [_('Delete')]) : E('button', {
+ 'class': 'btn cbi-button-remove',
+ 'title': _('Not yet deletable; UPnP IGDv2 IPv6 port maps expire sooner, have no description'),
+ 'style': 'cursor: not-allowed'
}, [_('Delete')])
];
});
- cbi_update_table(nodes.querySelector('#upnp_status_table'), rows, E('em', _('There are no active port maps.')));
+ cbi_update_table(nodes.querySelector('#upnp_status_table'), rows, E('em', _('There are no active IPv4/IPv6 port maps')));
},
render: function(data) {
@@ -128,15 +132,19 @@ return view.extend({
rule.proto,
'', // expires
'%h'.format(rule.descr),
- E('button', {
+ rule.num != '0' ? E('button', {
'class': 'btn cbi-button-remove',
'click': L.bind(handleDelRule, this, rule.num),
'title': _('Delete')
+ }, [_('Delete')]) : E('button', {
+ 'class': 'btn cbi-button-remove',
+ 'title': _('Not yet deletable; UPnP IGDv2 IPv6 port maps expire sooner, have no description'),
+ 'style': 'cursor: not-allowed'
}, [_('Delete')])
];
});
- cbi_update_table(table, rows, E('em', _('There are no active port maps.')));
+ cbi_update_table(table, rows, E('em', _('There are no active IPv4/IPv6 port maps')));
return E('div', { 'class': 'cbi-section cbi-tblsection' }, [
E('h3', _('Active Port Maps')), table
diff --git a/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp b/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
index e38f5a26352d..b9bf72809f66 100644
--- a/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
+++ b/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
@@ -59,6 +59,23 @@ const methods = {
}
}
ipt.close();
+
+ const iptipv6 = popen('ip6tables --line-numbers -xnvL MINIUPNPD 2>/dev/null');
+ if (iptipv6) {
+ for (let line = iptipv6.read('line'); length(line); line = iptipv6.read('line')) {
+ let m = match(line, /^([0-9]+) .+ ([0-9a-f:]+) ([ut].p) dpt:([0-9]+)/);
+ if (m) {
+ push(rules, {
+ num: '0',
+ intaddr: arrtoip(iptoarr(m[2])),
+ intport: +m[4],
+ extport: +m[4],
+ proto: uc(m[3])
+ });
+ }
+ }
+ iptipv6.close();
+ }
}
const nft = popen('nft --handle list chain inet fw4 upnp_prerouting 2>/dev/null');
@@ -77,6 +94,27 @@ const methods = {
}
}
nft.close();
+
+ const nftipv6 = popen('nft --handle list chain inet fw4 upnp_forward 2>/dev/null');
+ if (nftipv6) {
+ for (let line = nftipv6.read('line'), num = 1; length(line); line = nftipv6.read('line')) {
+ let m = match(line, /^\t\tiif ".+" th dport ([0-9]+) @nh,192,128 0x([0-9a-f]+) @nh,48,8 (0x6|0x11) accept/);
+ if (m) {
+ push(rules, {
+ num: '0',
+ intaddr: arrtoip(iptoarr(join(':', [substr(m[2], 0, 4),
+ substr(m[2], 4, 4), substr(m[2], 8, 4), substr(m[2], 12, 4), substr(m[2], 16, 4),
+ substr(m[2], 20, 4), substr(m[2], 24, 4), substr(m[2], 28, 4)
+ ]))),
+ intport: +m[1],
+ extport: +m[1],
+ proto: (m[3] == '0x6') ? 'TCP' : 'UDP'
+ });
+ num++;
+ }
+ }
+ nftipv6.close();
+ }
}
return ubus.defer('luci-rpc', 'getHostHints', {}, function(rc, host_hints) {
@@ -92,7 +130,7 @@ const methods = {
}
}
for (let mac, hint in host_hints) {
- if (rule.intaddr in hint.ipaddrs) {
+ if (rule.intaddr in hint.ipaddrs || rule.intaddr in hint.ip6addrs) {
rule.host_hint = hint.name;
break;
}
From 5550b4a056860acd9ba6509b80f52ebeda47dbce Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 7/7] luci-app-upnp: service-wide access control settings
This implements variant B
upnpd.settings UCI options | Change | Previous name
-----------------------------|---------------------------|--------------
access_defaults | New option (1) |
accept_ports | New option (1) |
reject_ports | New option (2) |
check_acl | New option (1) |
Notes:
1. New options added for default ports that all devices can map,
client-specific permissions using the access control list (ACL) can
extend/override the defaults. Access defaults: accept-high-ports
accept-web+high-ports/accept-web-ports/accept-all-ports
2. Reject ports; overrides other settings. By default reject unsafe:
FTP (21), Telnet (23), DCE/NetBIOS/SMB (135/137-139/445), RDP (3389)
(to merge with prior)
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
.../luci-static/resources/view/upnp/upnp.js | 106 ++++++++----------
1 file changed, 45 insertions(+), 61 deletions(-)
diff --git a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
index 2f790e6c4a12..68de0e56a807 100644
--- a/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
+++ b/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
@@ -154,6 +154,7 @@ return view.extend({
s = m.section(form.NamedSection, 'settings', 'upnpd', _('Service Settings'));
s.addremove = false;
s.tab('setup', _('Service Setup'));
+ s.tab('access_control', _('Access Control'));
s.tab('advanced', _('Advanced Settings'));
s.tab('igd', _('UPnP IGD Adjustments'));
@@ -168,6 +169,18 @@ return view.extend({
o.default = 'all';
o.widget = 'radio';
+ o = s.taboption('setup', widgets.NetworkSelect, 'internal_iface', _('Enable networks'),
+ _('Select local/internal (LAN) network interfaces to enable the service for'));
+ o.nocreate = true;
+ o.multiple = true;
+ o.rmempty = false;
+ o.filter = function(section_id, value) {
+ return (value == 'wan' || value == 'wan6') ? '' : value;
+ };
+ o.write = function(section_id, formvalue) {
+ uci.set('upnpd', section_id, 'internal_iface', Array.isArray(formvalue) ? formvalue.join(' ') : formvalue);
+ };
+
o = s.taboption('setup', form.ListValue, 'upnp_igd_compat', _('UPnP IGD compatibility'),
_('Set compatibility mode (act as device) to workaround IGDv2-incompatible clients; %s are known to only work with %s (or) <br />Emulate/report a specific/different device to workaround/support/handle/bypass/assist/mitigate... (Alternative text welcome)').format('Sony PS, Activision CoD…', 'IGDv1'));
o.value('igdv1', _('IGDv1 (IPv4 only)'));
@@ -176,6 +189,35 @@ return view.extend({
o.depends('enable_protocols', 'all');
o.retain = true;
+ o = s.taboption('access_control', form.ListValue, 'access_defaults', _('Access defaults'),
+ _('Set access control defaults for ports that all devices can map'));
+ o.value('', _('None / accept extra ports only'));
+ o.value('accept-high-ports', _('Accept ports >= 1024'));
+ o.value('accept-web+high-ports', _('Accept HTTP/HTTPS + ports >= 1024'));
+ o.value('accept-web-ports', _('Accept HTTP/HTTPS ports'));
+ o.value('accept-all-ports', _('Accept all ports'));
+
+ o = s.taboption('access_control', form.Value, 'accept_ports', _('Accept extra ports'));
+ o.datatype = 'list(portrange)';
+
+ o = s.taboption('access_control', form.Value, 'reject_ports', _('Reject ports'),
+ _('Reject unsafe/insecure/risky FTP/Telnet/DCE/NetBIOS/SMB/RDP ports by default; overrides other settings; use %s for none').format('<code>0</code>'));
+ o.datatype = 'list(portrange)';
+ o.placeholder = '21 23 135 137-139 445 3389';
+ o.modalonly = true;
+
+ o = s.taboption('access_control', form.Flag, 'check_acl', _('Check ACL'),
+ _('Extend or override access defaults by device-specific permissions using the access control list (ACL)') + '<br />' +
+ _('Sequence:') + ' 1. ' + _('Reject ports') + ', 2. ' + _('ACL entries (if checked)') + ', 3. ' + _('Access defaults') + ', 4. ' + _('Accept extra ports'));
+ o.default = '1';
+ o.onchange = function(ev, section_id, value) {
+ let acl = document.getElementById('cbi-upnpd-acl_entry');
+ value == 0 ? acl.style.display = 'none' : acl.style.display = 'block';
+ };
+
+ s.taboption('access_control', form.Flag, 'ipv6_disable', _('Disable IPv6 mapping'),
+ _('IPv6 is currently always accepted unless disabled'));
+
o = s.taboption('advanced', form.RichListValue, 'allow_cgnat', _('Allow %s/%s', 'Allow %s/%s (%s = CGNAT, %s = STUN)')
.format('<a href="https://en.wikipedia.org/wiki/Carrier-grade_NAT" target="_blank" rel="noreferrer"><abbr title="Carrier-grade NAT">CGNAT</abbr></a>',
'<a href="https://en.wikipedia.org/wiki/STUN" target="_blank" rel="noreferrer"><abbr title="Session Traversal Utilities for NAT">STUN</abbr></a>'),
@@ -206,8 +248,6 @@ return view.extend({
o.value('upnp-igd', _('Enabled') + ' (' + _('UPnP IGD only') + ')');
o.value('pcp', _('Enabled') + ' (' + _('PCP only') + ')');
- s.taboption('advanced', form.Flag, 'ipv6_disable', _('Disable IPv6 mapping'));
-
o = s.taboption('advanced', form.Flag, 'system_uptime', _('Report system instead of service uptime'));
o.default = '1';
o.depends('keep-translation', 'to-disable-as-rare-use');
@@ -288,72 +328,16 @@ return view.extend({
o.depends('enable_protocols', 'all');
o.retain = true;
- s = m.section(form.GridSection, 'internal_network', '<h5>' + _('Enable Networks / Access Control') + '</h5>',
- _('Select local/internal (LAN) network interfaces to enable the service for.') + ' ' +
- _('Set access control defaults for ports that all devices on a network can map.') + ' ' +
- _('IPv6 is currently always accepted unless disabled. (Alternative text welcome)'));
- s.anonymous = true;
- s.addremove = true;
- s.cloneable = true;
- s.sortable = true;
- s.nodescriptions = true;
- s.modaltitle = _('UPnP IGD & PCP/NAT-PMP') + ' - ' + _('Edit Network Access Control Settings');
-
- o = s.option(widgets.NetworkSelect, 'interface', _('Internal network'),
- _('Select the local/internal (LAN) network interface to enable the service for'));
- o.nocreate = true;
- o.editable = true;
- o.rmempty = false;
- o.retain = true;
- o.filter = function(section_id, value) {
- return (value == 'wan' || value == 'wan6') ? '' : value;
- };
-
- o = s.option(form.ListValue, 'access_defaults', _('Access defaults'),
- _('Set access control defaults for ports that all devices on the network can map'));
- o.value('', _('None / accept extra ports only'));
- o.value('accept-high-ports', _('Accept ports >= 1024'));
- o.value('accept-web+high-ports', _('Accept HTTP/HTTPS + ports >= 1024'));
- o.value('accept-web-ports', _('Accept HTTP/HTTPS ports'));
- o.value('accept-all-ports', _('Accept all ports'));
- o.editable = true;
- o.retain = true;
-
- o = s.option(form.Value, 'accept_ports', _('Accept extra ports'));
- o.datatype = 'list(portrange)';
- o.retain = true;
-
- o = s.option(form.Value, 'reject_ports', _('Reject ports'),
- _('Reject unsafe/insecure/risky FTP/Telnet/DCE/NetBIOS/SMB/RDP ports on the network by default; overrides other settings; use %s for none').format('<code>0</code>'));
- o.datatype = 'list(portrange)';
- o.placeholder = '21 23 135 137-139 445 3389';
- o.modalonly = true;
- o.retain = true;
-
- o = s.option(form.Flag, 'check_acl', _('Check ACL'),
- _('Extend or override access defaults by device-specific permissions using the access control list (ACL)') + '<br />' +
- _('Sequence:') + ' 1. ' + _('Reject ports') + ', 2. ' + _('ACL entries (if checked)') + ', 3. ' + _('Access defaults') + ', 4. ' + _('Accept extra ports'));
- o.default = '1';
- o.editable = true;
- o.retain = true;
-
s = m.section(form.GridSection, 'acl_entry', '<h5>' + _('Access Control List') + '</h5>',
_('The access control list (ACL) specifies which IP addresses and ports can be mapped.') + ' ' +
- _('ACL entries are checked in order, then rejected if not matched and not accepted by access defaults. (should be part of extra tab)'));
+ _('ACL entries are checked in order, then rejected if not matched and not accepted by access defaults. (To do: should be part of access control tab)'));
s.anonymous = true;
s.addremove = true;
s.cloneable = true;
s.sortable = true;
s.modaltitle = _('UPnP IGD & PCP/NAT-PMP') + ' - ' + _('Edit ACL Entry');
- // To do: ACL part of extra tab with dependency on option as immediately, and network section part of service setup tab
- let acl_used = false;
- for (let ifnr = 0; uci.get('upnpd', `@internal_network[${ifnr}]`, 'interface'); ifnr++) {
- if (uci.get('upnpd', `@internal_network[${ifnr}]`, 'check_acl') != '0') {
- acl_used = true;
- break;
- }
- }
- s.disable = !acl_used;
+ // To do: ACL part of access control tab and hide (or dependency on option) instead of disable as immediately, and to not break onchange function
+ s.disable = uci.get('upnpd', 'settings', 'check_acl') == '0';
o = s.option(form.Value, 'comment', _('Comment'));
o.default = _('unspecified');
--- a/miniupnpd/Makefile
+++ b/miniupnpd/Makefile
@@ -8,12 +8,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=miniupnpd
-PKG_VERSION:=2.3.9
-PKG_RELEASE:=3
+PKG_VERSION:=2.3.10
+PKG_RELEASE:=1
PKG_SOURCE_URL:=https://github.com/miniupnp/miniupnp/releases/download/miniupnpd_$(subst .,_,$(PKG_VERSION))
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
-PKG_HASH:=66cb3c3d697ab2bb3a61d3c48628166d6ba328d7c2dbeb95898fdf2a3202af7b
+PKG_HASH:=f9c34ed3632fb60cd248dd5897bd98479a103a75688b056ca2f069e68ab32987
PKG_MAINTAINER:=Alexandru Ardelean <ardeleanalex@gmail.com>
PKG_LICENSE:=BSD-3-Clause
From 1bca0ee5caa52f493a4e6cef12d2665ba246ecbe Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 02/10] miniupnpd: patch for UPnP IGDv2 Microsoft/Apple
compatibility
- Add workaround to list port maps with the Windows IGDv2-incompatible
client by returning an infinite (0) lease duration. To fix listing and
editing via GUI (Explorer/Network), if daemon was compiled with IGDv2
- Extend detection to older versions of Windows and add Xbox
- Detect Apple IGDv2-incompatible clients and apply existing workaround,
that only caused problems if PCP/NAT-PMP (prioritised) was disabled
(to merge with prior)
Link: https://github.com/Self-Hosting-Group/miniupnp/tree/upnp-igdv2-compat
Link: https://github.com/Self-Hosting-Group/wiki/wiki/Port-Mapping-Protocols-Overview#compatibility-issues
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
.../patches/10-upnp-igdv2-compat.patch | 80 +++++++++++++++++++
1 file changed, 80 insertions(+)
create mode 100644 miniupnpd/patches/10-upnp-igdv2-compat.patch
diff --git a/miniupnpd/patches/10-upnp-igdv2-compat.patch b/miniupnpd/patches/10-upnp-igdv2-compat.patch
new file mode 100644
index 00000000000000..2307aeb83ceb74
--- /dev/null
+++ b/miniupnpd/patches/10-upnp-igdv2-compat.patch
@@ -0,0 +1,80 @@
+From 38e01919d585b0fd663d2b3249963b336061cbd3 Mon Sep 17 00:00:00 2001
+From: Self-Hosting-Group
+ <155233284+Self-Hosting-Group@users.noreply.github.com>
+Date: Wed, 25 Mar 2026 00:00:00 +0000
+Subject: [PATCH] miniupnpd: UPnP IGDv2 Microsoft/Apple compatibility
+
+* Add workaround to list port maps with the Windows IGDv2-incompatible
+ client by returning an infinite (0) lease duration. To fix listing and
+ editing via GUI (Explorer/Network), if daemon was compiled with IGDv2
+* Extend detection to older versions of Windows and add Xbox
+* Detect Apple IGDv2-incompatible clients and apply existing workaround,
+ that only caused problems if PCP/NAT-PMP (prioritised) was disabled
+
+Link: https://github.com/Self-Hosting-Group/miniupnp/tree/upnp-igdv2-compat
+Link: https://github.com/Self-Hosting-Group/wiki/wiki/Port-Mapping-Protocols-Overview#compatibility-issues
+---
+ upnphttp.c | 19 ++++++++++++++++---
+ upnpsoap.c | 16 ++++++++++++++++
+ 2 files changed, 32 insertions(+), 3 deletions(-)
+
+--- a/upnphttp.c
++++ b/upnphttp.c
+@@ -304,9 +304,22 @@ ParseHttpHeaders(struct upnphttp * h)
+ }
+ else if(strncasecmp(line, "user-agent:", 11) == 0)
+ {
+- /* - User-Agent: Microsoft-Windows/10.0 UPnP/1.0
+- * - User-Agent: FDSSDP */
+- if(strcasestr(line + 11, "microsoft") != NULL || strstr(line + 11, "FDSSDP") != NULL) {
++ /* Detect Microsoft UPnP IGDv2-incompatible clients that only support IGDv1 routers,
++ * and Windows requires extra UDA 1.x (Win XP 1.0), via User-Agent SOAP/HTTP header:
++ * - Microsoft-Windows/10.0 UPnP/1.0 (Win >=10)
++ * - Microsoft-Windows/6.1 UPnP/1.0 (Win 7)
++ * - FDSSDP (Win >=Vista for GET)
++ * - Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1) (Win XP/Vista for GET)
++ * - Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x) (Win XP/Vista for POST)
++ * - Xbox/2.0.17559.0 UPnP/1.0 Xbox/2.0.17559.0
++ * Detect Apple UPnP IGDv2-incompatible clients that only support IGDv1 routers:
++ * - Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1) (for GET)
++ * - Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x) (for POST) */
++ if (((strstr(line + 11, "Microsoft-Windows/") != NULL ||
++ strstr(line + 11, "Xbox/") != NULL) &&
++ strstr(line + 11, " UPnP/1.0") != NULL) ||
++ strstr(line + 11, "FDSSDP") != NULL ||
++ strstr(line + 11, "Mozilla/4.0 (compatible; UPnP/1.0; Windows") != NULL) {
+ h->respflags |= FLAG_MS_CLIENT;
+ }
+ }
+--- a/upnpsoap.c
++++ b/upnpsoap.c
+@@ -854,6 +854,14 @@ GetSpecificPortMappingEntry(struct upnph
+ #ifdef ENABLE_PCP
+ hide_pcp_nonce(desc);
+ #endif
++#ifdef IGD_V2
++ /* Workaround to list port maps with the Windows IGDv2-incompatible client
++ * by returning an infinite (0) lease duration. To fix listing and editing
++ * via GUI (Explorer/Network), if daemon was compiled with IGDv2 */
++ if (h->respflags & FLAG_MS_CLIENT) {
++ leaseduration = 0;
++ }
++#endif
+ bodylen = snprintf(body, sizeof(body), resp,
+ action, ns/*SERVICE_TYPE_WANIPC*/,
+ (unsigned int)iport, int_ip, desc, leaseduration,
+@@ -1124,6 +1132,14 @@ GetGenericPortMappingEntry(struct upnpht
+ #ifdef ENABLE_PCP
+ hide_pcp_nonce(desc);
+ #endif
++#ifdef IGD_V2
++ /* Workaround to list port maps with the Windows IGDv2-incompatible client
++ * by returning an infinite (0) lease duration. To fix listing and editing
++ * via GUI (Explorer/Network), if daemon was compiled with IGDv2 */
++ if (h->respflags & FLAG_MS_CLIENT) {
++ leaseduration = 0;
++ }
++#endif
+ bodylen = snprintf(body, sizeof(body), resp,
+ action, ns, /*SERVICE_TYPE_WANIPC,*/ rhost,
+ (unsigned int)eport, protocol, (unsigned int)iport, iaddr, desc,
From 4606783f9fa877e5fd6da150ee96067a71f60966 Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 03/10] miniupnpd: patch to improve logging
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Clearer logging of enabled protocols/ports, IPv6 mapping, and UPnP IGD
compatibility mode… in start banner
- Log warnings with an enabled allow third-party mapping option
- Less verbose logging by changing the log level of many normally
occurring, internal messages, or those not relevant to users, to debug
or info
- Comment out log filling messages when internal interfaces lose link,
and for e.g. `rule with label '%s' is not a IGD pinhole`, as normally
occurring
(to merge with prior)
Fixes: https://redirect.github.com/openwrt/packages/issues/17601
Fixes: https://redirect.github.com/openwrt/packages/issues/26483
Link: https://github.com/Self-Hosting-Group/miniupnp/tree/improve-logging
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
.../patches/20-improve-logging.patch | 1186 +++++++++++++++++
1 file changed, 1186 insertions(+)
create mode 100644 miniupnpd/patches/20-improve-logging.patch
diff --git a/miniupnpd/patches/20-improve-logging.patch b/miniupnpd/patches/20-improve-logging.patch
new file mode 100644
index 00000000000000..ee0673535c5cef
--- /dev/null
+++ b/miniupnpd/patches/20-improve-logging.patch
@@ -0,0 +1,1186 @@
+From 38e01919d585b0fd663d2b3249963b336061cbd3 Mon Sep 17 00:00:00 2001
+From: Self-Hosting-Group
+ <155233284+Self-Hosting-Group@users.noreply.github.com>
+Date: Tue, 12 May 2026 00:00:00 +0000
+Subject: [PATCH] miniupnpd: Improve logging
+
+* Clearer logging of enabled protocols/ports, IPv6 mapping, and UPnP IGD
+ compatibility mode… in start banner
+* Log warnings with an enabled allow third-party mapping option
+* Less verbose logging by changing the log level of many normally
+ occurring or internal messages, or those not relevant to users, to
+ debug or info
+* Comment out log-filling messages when internal interfaces lose link,
+ and for e.g. `rule with label '%s' is not a IGD pinhole`, as normally
+ occurring
+
+Fixes: https://redirect.github.com/openwrt/packages/issues/17601
+Fixes: https://redirect.github.com/openwrt/packages/issues/26483
+Link: https://github.com/Self-Hosting-Group/miniupnp/tree/improve-logging
+---
+ asyncsendto.c | 4 +--
+ getifaddr.c | 4 +--
+ minissdp.c | 24 ++++++-------
+ miniupnpd.c | 20 +++++------
+ natpmp.c | 16 ++++-----
+ netfilter/iptcrdr.c | 10 +++---
+ netfilter/iptpinhole.c | 4 +--
+ netfilter_nft/nftnlrdr.c | 2 +-
+ netfilter_nft/nftnlrdr_misc.c | 8 ++---
+ netfilter_nft/nftpinhole.c | 10 +++---
+ pcpserver.c | 18 +++++-----
+ pf/pfpinhole.c | 8 ++---
+ upnphttp.c | 6 ++--
+ upnppinhole.c | 48 ++++++++++++-------------
+ upnpredirect.c | 20 +++++------
+ upnpsoap.c | 28 +++++++--------
+ 16 files changed, 115 insertions(+), 115 deletions(-)
+
+--- a/asyncsendto.c
++++ b/asyncsendto.c
+@@ -254,9 +254,9 @@ int try_sendto(fd_set * writefds)
+ /* uncatched error */
+ if(sockaddr_to_string(elt->dest_addr, addr_str, sizeof(addr_str)) <= 0)
+ addr_str[0] = '\0';
+- syslog(LOG_ERR, "%s(sock=%d, len=%u, dest=%s): sendto: %m",
++ /*syslog(LOG_DEBUG, "%s(sock=%d, len=%u, dest=%s): sendto: %m",
+ "try_sendto", elt->sockfd, (unsigned)elt->len,
+- addr_str);
++ addr_str);*/
+ ret--;
+ }
+ } else if((int)n != (int)elt->len) {
+--- a/getifaddr.c
++++ b/getifaddr.c
+@@ -74,7 +74,7 @@ getifaddr(const char * ifname, char * bu
+ } else {
+ r = GETIFADDR_IOCTL_ERROR;
+ }
+- syslog(LOG_ERR, "ioctl(s, SIOCGIFADDR, ...): %m");
++ syslog(LOG_DEBUG, "ioctl(s, SIOCGIFADDR, ...): %m");
+ close(s);
+ return r;
+ }
+@@ -149,7 +149,7 @@ getifaddr(const char * ifname, char * bu
+ if(addr) *addr = ((struct sockaddr_in *)candidate->ifa_addr)->sin_addr;
+ if(mask) *mask = ((struct sockaddr_in *)candidate->ifa_netmask)->sin_addr;
+ } else {
+- syslog(LOG_WARNING, "no AF_INET address found for %s", ifname);
++ syslog(LOG_DEBUG, "no AF_INET address found for %s", ifname);
+ freeifaddrs(ifap);
+ return GETIFADDR_NO_ADDRESS;
+ }
+--- a/minissdp.c
++++ b/minissdp.c
+@@ -741,8 +741,8 @@ SendSSDPNotify(int s, const struct socka
+ }
+ n = sendto_or_schedule(s, bufr, l, 0, dest, dest_len);
+ if(n < 0) {
+- syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s,
+- host ? host : "NULL");
++ /*syslog(LOG_DEBUG, "sendto(udp_notify=%d, %s): %m", s,
++ host ? host : "NULL");*/
+ } else if(n != l) {
+ syslog(LOG_NOTICE, "sendto() sent %d out of %d bytes", n, l);
+ }
+@@ -752,8 +752,8 @@ SendSSDPNotify(int s, const struct socka
+ * discovery messages SHOULD NOT be sent more than three times. */
+ n = sendto_schedule(s, bufr, l, 0, dest, dest_len, 250);
+ if(n < 0) {
+- syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s,
+- host ? host : "NULL");
++ /*syslog(LOG_DEBUG, "sendto(udp_notify=%d, %s): %m", s,
++ host ? host : "NULL");*/
+ }
+ }
+
+@@ -1058,7 +1058,7 @@ ProcessSSDPData(int s, const char *bufr,
+ }
+ if(lan_addr == NULL)
+ {
+- syslog(LOG_WARNING, "SSDP packet sender %s (if_index=%d) not from a LAN, ignoring",
++ syslog(LOG_DEBUG, "SSDP packet sender %s (if_index=%d) not from a LAN, ignoring",
+ sender_str, source_if);
+ return;
+ }
+@@ -1161,7 +1161,7 @@ ProcessSSDPData(int s, const char *bufr,
+ sender_str );*/
+ if(st && (st_len > 0))
+ {
+- syslog(LOG_INFO, "SSDP M-SEARCH from %s ST: %.*s",
++ syslog(LOG_DEBUG, "SSDP M-SEARCH from %s ST: %.*s",
+ sender_str, st_len, st);
+ /* find in which sub network the client is */
+ #ifdef ENABLE_IPV6
+@@ -1274,7 +1274,7 @@ ProcessSSDPData(int s, const char *bufr,
+ else
+ snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version);
+ #endif
+- syslog(LOG_INFO, "Single search found");
++ syslog(LOG_DEBUG, "Single search found");
+ #ifdef DELAY_MSEARCH_RESPONSE
+ delay = random() / (1 + RAND_MAX / (1000 * mx_value));
+ #ifdef DEBUG
+@@ -1303,7 +1303,7 @@ ProcessSSDPData(int s, const char *bufr,
+ #ifdef DELAY_MSEARCH_RESPONSE
+ unsigned int delay_increment = (mx_value * 1000) / 15;
+ #endif
+- syslog(LOG_INFO, "ssdp:all found");
++ syslog(LOG_DEBUG, "ssdp:all found");
+ for(i=0; known_service_types[i].s; i++)
+ {
+ #ifdef DELAY_MSEARCH_RESPONSE
+@@ -1361,7 +1361,7 @@ ProcessSSDPData(int s, const char *bufr,
+ #endif
+ if(0 == memcmp(st, uuidvalue_igd, l))
+ {
+- syslog(LOG_INFO, "ssdp:uuid (IGD) found");
++ syslog(LOG_DEBUG, "ssdp:uuid (IGD) found");
+ SendSSDPResponse(s, sender, st, st_len, "",
+ announced_host, http_port,
+ #ifdef ENABLE_HTTPS
+@@ -1371,7 +1371,7 @@ ProcessSSDPData(int s, const char *bufr,
+ }
+ else if(0 == memcmp(st, uuidvalue_wan, l))
+ {
+- syslog(LOG_INFO, "ssdp:uuid (WAN) found");
++ syslog(LOG_DEBUG, "ssdp:uuid (WAN) found");
+ SendSSDPResponse(s, sender, st, st_len, "",
+ announced_host, http_port,
+ #ifdef ENABLE_HTTPS
+@@ -1381,7 +1381,7 @@ ProcessSSDPData(int s, const char *bufr,
+ }
+ else if(0 == memcmp(st, uuidvalue_wcd, l))
+ {
+- syslog(LOG_INFO, "ssdp:uuid (WCD) found");
++ syslog(LOG_DEBUG, "ssdp:uuid (WCD) found");
+ SendSSDPResponse(s, sender, st, st_len, "",
+ announced_host, http_port,
+ #ifdef ENABLE_HTTPS
+@@ -1442,7 +1442,7 @@ SendSSDPbyebye(int s, const struct socka
+ n = sendto_or_schedule(s, bufr, l, 0, dest, destlen);
+ if(n < 0)
+ {
+- syslog(LOG_ERR, "sendto(udp_shutdown=%d) to %s: %m", s, dest_str);
++ syslog(LOG_DEBUG, "sendto(udp_shutdown=%d) to %s: %m", s, dest_str);
+ return -1;
+ }
+ else if(n != l)
+--- a/miniupnpd.c
++++ b/miniupnpd.c
+@@ -489,7 +489,7 @@ ProcessIncomingHTTP(int shttpl, const ch
+ if(get_lan_for_peer((struct sockaddr *)&clientname) == NULL)
+ {
+ /* The peer is not a LAN ! */
+- syslog(LOG_WARNING,
++ syslog(LOG_DEBUG,
+ "%s peer %s is not from a LAN, closing the connection",
+ protocol, addr_str);
+ close(shttp);
+@@ -859,7 +859,7 @@ set_startup_time(void)
+ }
+ else
+ {
+- syslog(LOG_INFO, "system uptime is %lu seconds", uptime);
++ syslog(LOG_DEBUG, "system uptime is %lu seconds", uptime);
+ }
+ fclose(f);
+ startup_time -= uptime;
+@@ -2109,6 +2109,22 @@ init(int argc, char * * argv, struct run
+ pidfilename = NULL;
+ #endif
+
++syslog(LOG_NOTICE, "MiniUPnP daemon " MINIUPNPD_VERSION " starting, enable protocols %s%s%s, ext_ifname=%s BOOTID=%u",
++ GETFLAG(ENABLEUPNPMASK) ? "UPnP IGD" : "",
++#ifdef ENABLE_NATPMP
++ GETFLAG(ENABLEUPNPMASK) && GETFLAG(ENABLENATPMPMASK) ? " & " : "",
++#ifdef ENABLE_PCP
++ GETFLAG(ENABLENATPMPMASK) ? "PCP/NAT-PMP" : "",
++#else
++ GETFLAG(ENABLENATPMPMASK) ? "NAT-PMP" : "",
++#endif
++#else
++ "", "",
++#endif
++ ext_if_name, upnp_bootid);
++syslog(LOG_INFO, "More information at https://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/");
++syslog(LOG_NOTICE, "Extra logging with log level info (-v) or debug (-v -v)");
++
+ #ifdef USE_SYSTEMD
+ if (systemd_flag) {
+ int r = sd_notify(0,
+@@ -2121,7 +2137,7 @@ init(int argc, char * * argv, struct run
+
+ #ifdef ENABLE_LEASEFILE
+ /*remove(lease_file);*/
+- syslog(LOG_INFO, "Reloading rules from lease file");
++ syslog(LOG_INFO, "Reloading port maps from lease file");
+ reload_from_lease_file();
+ #ifdef ENABLE_UPNPPINHOLE
+ reload_from_lease_file6();
+@@ -2409,21 +2425,9 @@ main(int argc, char * * argv)
+ return 0;
+ }
+
+- syslog(LOG_INFO, "version " MINIUPNPD_VERSION " starting%s%sext if %s BOOTID=%u",
+-#ifdef ENABLE_NATPMP
+-#ifdef ENABLE_PCP
+- GETFLAG(ENABLENATPMPMASK) ? " NAT-PMP/PCP " : " ",
+-#else
+- GETFLAG(ENABLENATPMPMASK) ? " NAT-PMP " : " ",
+-#endif
+-#else
+- " ",
+-#endif
+- GETFLAG(ENABLEUPNPMASK) ? "UPnP-IGD " : "",
+- ext_if_name, upnp_bootid);
+ #ifdef ENABLE_IPV6
+ if (strcmp(ext_if_name6, ext_if_name) != 0) {
+- syslog(LOG_INFO, "specific IPv6 ext if %s", ext_if_name6);
++ syslog(LOG_INFO, "Separate ext_ifname6=%s set", ext_if_name6);
+ }
+ #endif
+
+@@ -2467,7 +2471,7 @@ main(int argc, char * * argv)
+ return 1;
+ }
+ v.port = listen_port;
+- syslog(LOG_NOTICE, "HTTP listening on port %d", v.port);
++ syslog(LOG_NOTICE, "Listening for UPnP IGD (SOAP/HTTP) traffic on port %d/TCP, SSDP 1900/UDP", v.port);
+ #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
+ if(!GETFLAG(IPV6DISABLEDMASK))
+ {
+@@ -2493,7 +2497,7 @@ main(int argc, char * * argv)
+ return 1;
+ }
+ v.https_port = listen_port;
+- syslog(LOG_NOTICE, "HTTPS listening on port %d", v.https_port);
++ syslog(LOG_NOTICE, "Listening for UPnP IGD (SOAP/HTTPS) traffic on port %d/TCP", v.https_port);
+ #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
+ shttpsl_v4 = OpenAndConfHTTPSocket(&listen_port, 0);
+ if(shttpsl_v4 < 0)
+@@ -2507,11 +2511,11 @@ main(int argc, char * * argv)
+ if(!GETFLAG(IPV6DISABLEDMASK)) {
+ if(find_ipv6_addr(lan_addrs.lh_first ? lan_addrs.lh_first->ifname : NULL,
+ ipv6_addr_for_http_with_brackets, sizeof(ipv6_addr_for_http_with_brackets)) > 0) {
+- syslog(LOG_NOTICE, "HTTP IPv6 address given to control points : %s",
++ syslog(LOG_NOTICE, "IPv6 address given to UPnP IGD clients: %s",
+ ipv6_addr_for_http_with_brackets);
+ } else {
+ memcpy(ipv6_addr_for_http_with_brackets, "[::1]", 6);
+- syslog(LOG_WARNING, "no HTTP IPv6 address, disabling IPv6");
++ syslog(LOG_DEBUG, "no HTTP IPv6 address, disabling IPv6");
+ SETFLAG(IPV6DISABLEDMASK);
+ }
+ }
+@@ -2563,7 +2567,7 @@ main(int argc, char * * argv)
+ if(SendSSDPGoodbye(snotify, addr_count * 2) < 0)
+ #endif
+ {
+- syslog(LOG_WARNING, "Failed to broadcast good-bye notifications");
++ syslog(LOG_DEBUG, "Failed to broadcast good-bye notifications");
+ }
+ }
+ #endif /* UPNP_STRICT */
+@@ -2588,16 +2592,16 @@ main(int argc, char * * argv)
+ if(OpenAndConfNATPMPSockets(snatpmp) < 0)
+ #ifdef ENABLE_PCP
+ {
+- syslog(LOG_ERR, "Failed to open sockets for NAT-PMP/PCP.");
++ syslog(LOG_ERR, "Failed to open port 5351/UDP for PCP/NAT-PMP");
+ } else {
+- syslog(LOG_NOTICE, "Listening for NAT-PMP/PCP traffic on port %u",
++ syslog(LOG_NOTICE, "Listening for PCP/NAT-PMP traffic on port %u/UDP",
+ NATPMP_PORT);
+ }
+ #else
+ {
+- syslog(LOG_ERR, "Failed to open sockets for NAT PMP.");
++ syslog(LOG_ERR, "Failed to open port 5351/UDP for NAT-PMP");
+ } else {
+- syslog(LOG_NOTICE, "Listening for NAT-PMP traffic on port %u",
++ syslog(LOG_NOTICE, "Listening for NAT-PMP traffic on port %u/UDP",
+ NATPMP_PORT);
+ }
+ #endif
+@@ -2706,6 +2710,30 @@ main(int argc, char * * argv)
+ }
+ #endif /* HAS_LIBCAP_NG */
+
++if (GETFLAG(ENABLEUPNPMASK) && !GETFLAG(SECUREMODEMASK))
++ syslog(LOG_WARNING, "WARNING: Allow adding port maps for non-requesting IP addresses via UPnP IGD, as secure_mode=no set");
++#ifdef ENABLE_PCP
++if (GETFLAG(ENABLENATPMPMASK) && GETFLAG(PCP_ALLOWTHIRDPARTYMASK))
++ syslog(LOG_WARNING, "WARNING: Allow adding port maps for non-requesting IP addresses via PCP, as pcp_allow_thirdparty=yes set");
++#endif
++#ifdef ENABLE_IPV6
++if (GETFLAG(IPV6DISABLEDMASK))
++ syslog(LOG_NOTICE, "IPv6 mapping disabled");
++#else
++syslog(LOG_NOTICE, "IPv6 mapping disabled");
++#endif
++if (GETFLAG(ENABLEUPNPMASK)) {
++#ifdef IGD_V2
++ if (GETFLAG(FORCEIGDDESCV1MASK)) {
++ syslog(LOG_NOTICE, "UPnP IGD compatibility mode set to IGDv1 (IPv4 only)");
++ } else {
++ syslog(LOG_NOTICE, "UPnP IGD compatibility mode set to IGDv2 (with workarounds)");
++ }
++#else
++ syslog(LOG_NOTICE, "UPnP IGD compatibility mode set to IGDv1 (IPv4 only)");
++#endif
++}
++
+ #ifdef USE_SYSTEMD
+ if (v.systemd_notify) {
+ upnp_update_status();
+@@ -3016,7 +3044,7 @@ main(int argc, char * * argv)
+ }
+ i = try_sendto(&writeset);
+ if(i < 0) {
+- syslog(LOG_ERR, "try_sendto failed to send %d packets", -i);
++ /*syslog(LOG_DEBUG, "try_sendto failed to send %d packets", -i);*/
+ }
+ #ifdef USE_MINIUPNPDCTL
+ for(ectl = ctllisthead.lh_first; ectl;)
+@@ -3116,7 +3144,7 @@ main(int argc, char * * argv)
+ if(lan_addr == NULL) {
+ char sender_str[64];
+ sockaddr_to_string((struct sockaddr *)&senderaddr, sender_str, sizeof(sender_str));
+- syslog(LOG_WARNING, "NAT-PMP packet sender %s not from a LAN, ignoring",
++ syslog(LOG_DEBUG, "NAT-PMP packet sender %s not from a LAN, ignoring",
+ sender_str);
+ continue;
+ }
+@@ -3139,7 +3167,7 @@ main(int argc, char * * argv)
+ if(lan_addr == NULL) {
+ char sender_str[64];
+ sockaddr_to_string((struct sockaddr *)&senderaddr, sender_str, sizeof(sender_str));
+- syslog(LOG_WARNING, "NAT-PMP packet sender %s not from a LAN, ignoring",
++ syslog(LOG_DEBUG, "NAT-PMP packet sender %s not from a LAN, ignoring",
+ sender_str);
+ continue;
+ }
+@@ -3173,7 +3201,7 @@ main(int argc, char * * argv)
+ /* process SSDP packets */
+ if(sudp >= 0 && FD_ISSET(sudp, &readset))
+ {
+- /*syslog(LOG_INFO, "Received UDP Packet");*/
++ /*syslog(LOG_DEBUG, "Received UDP Packet");*/
+ #ifdef ENABLE_HTTPS
+ ProcessSSDPRequest(sudp, (unsigned short)v.port, (unsigned short)v.https_port);
+ #else
+@@ -3183,7 +3211,7 @@ main(int argc, char * * argv)
+ #ifdef ENABLE_IPV6
+ if(sudpv6 >= 0 && FD_ISSET(sudpv6, &readset))
+ {
+- syslog(LOG_INFO, "Received UDP Packet (IPv6)");
++ /*syslog(LOG_DEBUG, "Received UDP Packet (IPv6)");*/
+ #ifdef ENABLE_HTTPS
+ ProcessSSDPRequest(sudpv6, (unsigned short)v.port, (unsigned short)v.https_port);
+ #else
+@@ -3285,7 +3313,7 @@ main(int argc, char * * argv)
+
+ shutdown:
+
+- syslog(LOG_NOTICE, "shutting down MiniUPnPd");
++ syslog(LOG_NOTICE, "Shutting down MiniUPnPd");
+ #ifdef USE_SYSTEMD
+ if (v.systemd_notify) {
+ sd_notify(0,
+@@ -3304,7 +3332,7 @@ shutdown:
+ if(SendSSDPGoodbye(snotify, addr_count * 2) < 0)
+ #endif
+ {
+- syslog(LOG_ERR, "Failed to broadcast good-bye notifications");
++ syslog(LOG_DEBUG, "Failed to broadcast good-bye notifications");
+ }
+ }
+ /* try to send pending packets */
+--- a/natpmp.c
++++ b/natpmp.c
+@@ -106,7 +106,7 @@ static void FillPublicAddressResponse(un
+ resp[3] = 3; /* Network Failure (e.g. NAT box itself
+ * has not obtained a DHCP lease) */
+ } else if(getifaddr(ext_if_name, tmp, INET_ADDRSTRLEN, &addr, NULL) < 0) {
+- syslog(LOG_ERR, "Failed to get IP for interface %s", ext_if_name);
++ syslog(LOG_DEBUG, "Failed to get IP for interface %s", ext_if_name);
+ resp[3] = 3; /* Network Failure (e.g. NAT box itself
+ * has not obtained a DHCP lease) */
+ } else if (!GETFLAG(ALLOWPRIVATEIPV4MASK) && addr_is_reserved(&addr)) {
+@@ -231,7 +231,7 @@ void ProcessIncomingNATPMPPacket(int s,
+ syslog(LOG_ERR, "inet_ntop(natpmp): %m");
+ }
+
+- syslog(LOG_INFO, "NAT-PMP request received from %s:%hu %dbytes",
++ syslog(LOG_DEBUG, "NAT-PMP request received from %s:%hu %d bytes",
+ senderaddrstr, ntohs(senderaddr->sin_port), n);
+
+ if(n<2 || ((((req[1]-1)&~1)==0) && n<12)) {
+@@ -260,7 +260,7 @@ void ProcessIncomingNATPMPPacket(int s,
+ resp[3] = 1; /* unsupported version */
+ } else switch(req[1]) {
+ case 0: /* Public address request */
+- syslog(LOG_INFO, "NAT-PMP public address request");
++ syslog(LOG_DEBUG, "NAT-PMP public address request");
+ FillPublicAddressResponse(resp, senderaddr->sin_addr.s_addr);
+ resplen = 12;
+ break;
+@@ -319,7 +319,7 @@ void ProcessIncomingNATPMPPacket(int s,
+ resp[3] = 2; /* Not Authorized/Refused */
+ break;
+ } else {
+- syslog(LOG_INFO, "NAT-PMP %s port %hu mapping removed",
++ syslog(LOG_DEBUG, "NAT-PMP %s port %hu mapping removed",
+ proto2==IPPROTO_TCP?"TCP":"UDP", eport2);
+ index--;
+ }
+@@ -340,7 +340,7 @@ void ProcessIncomingNATPMPPacket(int s,
+ eport_first = eport;
+ } else if(eport == eport_first) { /* no eport available */
+ if(any_eport_allowed == 0) { /* all eports rejected by permissions */
+- syslog(LOG_ERR, "No allowed eport for NAT-PMP %hu %s->%s:%hu",
++ syslog(LOG_INFO, "No allowed eport for NAT-PMP %hu %s->%s:%hu",
+ eport, proto_itoa(proto), senderaddrstr, iport);
+ resp[3] = 2; /* Not Authorized/Refused */
+ } else { /* at least one eport allowed (but none available) */
+@@ -443,7 +443,7 @@ void SendNATPMPPublicAddressChangeNotifi
+ FillPublicAddressResponse(notif, 0);
+ if(notif[3])
+ {
+- syslog(LOG_WARNING, "%s: cannot get public IP address, stopping",
++ syslog(LOG_DEBUG, "%s: cannot get public IP address, stopping",
+ "SendNATPMPPublicAddressChangeNotification");
+ return;
+ }
+@@ -471,7 +471,7 @@ void SendNATPMPPublicAddressChangeNotifi
+ (struct sockaddr *)&sockname, sizeof(struct sockaddr_in));
+ if(n < 0)
+ {
+- syslog(LOG_ERR, "%s: sendto(s_udp=%d, port=%d): %m",
++ syslog(LOG_DEBUG, "%s: sendto(s_udp=%d, port=%d): %m",
+ "SendNATPMPPublicAddressChangeNotification", sockets[j], NATPMP_PORT);
+ return;
+ }
+@@ -481,7 +481,7 @@ void SendNATPMPPublicAddressChangeNotifi
+ (struct sockaddr *)&sockname, sizeof(struct sockaddr_in));
+ if(n < 0)
+ {
+- syslog(LOG_ERR, "%s: sendto(s_udp=%d, port=%d): %m",
++ syslog(LOG_DEBUG, "%s: sendto(s_udp=%d, port=%d): %m",
+ "SendNATPMPPublicAddressChangeNotification", sockets[j], NATPMP_NOTIF_PORT);
+ return;
+ }
+--- a/netfilter/iptcrdr.c
++++ b/netfilter/iptcrdr.c
+@@ -711,7 +711,7 @@ delete_filter_rule(const char * ifname,
+ continue;
+ }
+ index = i;
+- /*syslog(LOG_INFO, "Trying to delete filter rule at index %u", index);*/
++ /*syslog(LOG_DEBUG, "Trying to delete filter rule at index %u", index);*/
+ r = delete_rule_and_commit(index, h, miniupnpd_forward_chain, "delete_filter_rule");
+ h = NULL;
+ break;
+@@ -804,7 +804,7 @@ delete_redirect_and_filter_rules(unsigne
+ #endif
+ if(r == 0)
+ {
+- syslog(LOG_INFO, "Trying to delete nat rule at index %u", index);
++ syslog(LOG_DEBUG, "Trying to delete nat rule at index %u", index);
+ /* Now delete both rules */
+ /* first delete the nat rule */
+ h = iptc_init("nat");
+@@ -848,7 +848,7 @@ delete_redirect_and_filter_rules(unsigne
+ if(iaddr != e->ip.dst.s_addr)
+ continue;
+ index = i;
+- syslog(LOG_INFO, "Trying to delete filter rule at index %u", index);
++ syslog(LOG_DEBUG, "Trying to delete filter rule at index %u", index);
+ r = delete_rule_and_commit(index, h, miniupnpd_forward_chain, "delete_filter_rule");
+ h = NULL;
+ break;
+@@ -906,7 +906,7 @@ delete_redirect_and_filter_rules(unsigne
+ }
+
+ index = i;
+- syslog(LOG_INFO, "Trying to delete peer rule at index %u", index);
++ syslog(LOG_DEBUG, "Trying to delete peer rule at index %u", index);
+ r2 = delete_rule_and_commit(index, h, miniupnpd_nat_postrouting_chain, "delete_peer_rule");
+ h = NULL;
+ break;
+@@ -958,7 +958,7 @@ delete_redirect_and_filter_rules(unsigne
+ if(iaddr != e->ip.src.s_addr)
+ continue;
+ index = i;
+- syslog(LOG_INFO, "Trying to delete dscp rule at index %u", index);
++ syslog(LOG_DEBUG, "Trying to delete dscp rule at index %u", index);
+ r2 = delete_rule_and_commit(index, h, miniupnpd_nat_chain, "delete_dscp_rule");
+ h = NULL;
+ break;
+--- a/netfilter/iptpinhole.c
++++ b/netfilter/iptpinhole.c
+@@ -295,14 +295,14 @@ find_pinhole(const char * ifname,
+
+ if(rem_host && (rem_host[0] != '\0')) {
+ if (inet_pton(AF_INET6, rem_host, &saddr) < 1) {
+- syslog(LOG_WARNING, "Failed to parse INET6 address \"%s\"", rem_host);
++ syslog(LOG_INFO, "Failed to parse INET6 address \"%s\"", rem_host);
+ memset(&saddr, 0, sizeof(struct in6_addr));
+ }
+ } else {
+ memset(&saddr, 0, sizeof(struct in6_addr));
+ }
+ if (inet_pton(AF_INET6, int_client, &daddr) < 1) {
+- syslog(LOG_WARNING, "Failed to parse INET6 address \"%s\"", int_client);
++ syslog(LOG_INFO, "Failed to parse INET6 address \"%s\"", int_client);
+ memset(&daddr, 0, sizeof(struct in6_addr));
+ }
+ for(p = pinhole_list.lh_first; p != NULL; p = p->entries.le_next) {
+--- a/netfilter_nft/nftnlrdr.c
++++ b/netfilter_nft/nftnlrdr.c
+@@ -353,7 +353,7 @@ delete_redirect_and_filter_rules(unsigne
+ }
+ }
+ } else {
+- syslog(LOG_WARNING, "%s: redirect rule with eport=%hu proto %d NOT FOUND",
++ syslog(LOG_INFO, "%s: redirect rule with eport=%hu proto %d NOT FOUND",
+ "delete_redirect_and_filter_rules", eport, proto);
+ }
+
+--- a/netfilter_nft/nftnlrdr_misc.c
++++ b/netfilter_nft/nftnlrdr_misc.c
+@@ -104,7 +104,7 @@ nft_mnl_connect(void)
+ return -1;
+ }
+ mnl_portid = mnl_socket_get_portid(mnl_sock);
+- syslog(LOG_INFO, "mnl_socket bound, port_id=%u", mnl_portid);
++ syslog(LOG_DEBUG, "mnl_socket bound, port_id=%u", mnl_portid);
+ return 0;
+ }
+
+@@ -745,7 +745,7 @@ refresh_nft_cache(struct rule_list *head
+ errno = 0;
+ ret = mnl_cb_run(buf, n, mnl_seq, mnl_portid, table_cb, &data);
+ if (ret <= -1 /*== MNL_CB_ERROR*/) {
+- syslog(LOG_ERR, "%s: mnl_cb_run returned %d: %m",
++ syslog(LOG_DEBUG, "%s: mnl_cb_run returned %d: %m",
+ "refresh_nft_cache", ret);
+ return -1;
+ }
+@@ -1279,7 +1279,7 @@ nft_send_rule(struct nftnl_rule * rule,
+
+ result = send_batch(batch);
+ if (result < 0) {
+- syslog(LOG_ERR, "%s(%p, %d, %d) send_batch failed %d",
++ syslog(LOG_DEBUG, "%s(%p, %d, %d) send_batch failed %d",
+ "nft_send_rule", rule, (int)cmd, (int)chain_type, result);
+ }
+ }
+@@ -1462,7 +1462,7 @@ send_batch(struct mnl_nlmsg_batch *batch
+ errno = 0;
+ ret = mnl_cb_run(buf, n, 0, mnl_portid, NULL, NULL);
+ if (ret <= -1 /*== MNL_CB_ERROR*/) {
+- syslog(LOG_ERR, "%s: mnl_cb_run returned %d: %m",
++ syslog(LOG_DEBUG, "%s: mnl_cb_run returned %d: %m",
+ "send_batch", ret);
+ return -4;
+ }
+--- a/netfilter_nft/nftpinhole.c
++++ b/netfilter_nft/nftpinhole.c
+@@ -129,7 +129,7 @@ find_pinhole(const char * ifname,
+
+ if (rem_host && rem_host[0] != '\0' && rem_host[0] != '*') {
+ if (inet_pton(AF_INET6, rem_host, &saddr) < 1) {
+- syslog(LOG_WARNING, "Failed to parse INET6 address \"%s\"", rem_host);
++ syslog(LOG_INFO, "Failed to parse INET6 address \"%s\"", rem_host);
+ memset(&saddr, 0, sizeof(struct in6_addr));
+ }
+ } else {
+@@ -137,7 +137,7 @@ find_pinhole(const char * ifname,
+ }
+
+ if (inet_pton(AF_INET6, int_client, &daddr) < 1) {
+- syslog(LOG_WARNING, "Failed to parse INET6 address \"%s\"", int_client);
++ syslog(LOG_INFO, "Failed to parse INET6 address \"%s\"", int_client);
+ memset(&daddr, 0, sizeof(struct in6_addr));
+ }
+
+@@ -159,7 +159,7 @@ find_pinhole(const char * ifname,
+ (0 == memcmp(&daddr, &p->daddr6, sizeof(struct in6_addr)))) {
+
+ if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) {
+- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc);
++ /*syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc);*/
+ continue;
+ }
+
+@@ -396,7 +396,7 @@ get_pinhole_info(unsigned short uid,
+ if (timestamp) {
+ int uid_temp;
+ if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid_temp, &ts) != 2) {
+- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc);
++ /*syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc);*/
+ continue;
+ }
+
+@@ -460,7 +460,7 @@ clean_pinhole_list(unsigned int * next_t
+ continue;
+
+ if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) {
+- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc);
++ /*syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc);*/
+ continue;
+ }
+
+--- a/pcpserver.c
++++ b/pcpserver.c
+@@ -1086,7 +1086,7 @@ static void CreatePCPMap(pcp_info_t *pcp
+ else
+ r = CreatePCPMap_NAT(pcp_msg_info);
+ pcp_msg_info->result_code = r;
+- syslog(r == PCP_SUCCESS ? LOG_INFO : LOG_ERR,
++ syslog(LOG_INFO,
+ "PCP MAP: %s mapping %s %hu->%s:%hu '%s'",
+ r == PCP_SUCCESS ? "added" : "failed to add",
+ proto_itoa(pcp_msg_info->protocol),
+@@ -1128,7 +1128,7 @@ static void DeletePCPMap(pcp_info_t *pcp
+ if(0 != strcmp(desc, pcp_msg_info->desc)) {
+ /* nonce does not match */
+ pcp_msg_info->result_code = PCP_ERR_NOT_AUTHORIZED;
+- syslog(LOG_ERR, "Unauthorized to remove PCP mapping internal port %hu, protocol %s",
++ syslog(LOG_INFO, "Unauthorized to remove PCP mapping internal port %hu, protocol %s",
+ iport, proto_itoa(pcp_msg_info->protocol));
+ return;
+ } else {
+@@ -1146,14 +1146,14 @@ static void DeletePCPMap(pcp_info_t *pcp
+ desc, sizeof(desc),
+ NULL /* lifetime */);
+ if (uid < 0) {
+- syslog(LOG_ERR, "Failed to find mapping to %s:%hu, protocol %s",
++ syslog(LOG_INFO, "Failed to find mapping to %s:%hu, protocol %s",
+ pcp_msg_info->mapped_str, iport, proto_itoa(pcp_msg_info->protocol));
+ return;
+ } else {
+ if(0 != strcmp(desc, pcp_msg_info->desc)) {
+ /* nonce does not match */
+ pcp_msg_info->result_code = PCP_ERR_NOT_AUTHORIZED;
+- syslog(LOG_ERR, "Unauthorized to remove PCP mapping internal port %hu, protocol %s",
++ syslog(LOG_INFO, "Unauthorized to remove PCP mapping internal port %hu, protocol %s",
+ iport, proto_itoa(pcp_msg_info->protocol));
+ return;
+ } else {
+@@ -1168,7 +1168,7 @@ static void DeletePCPMap(pcp_info_t *pcp
+ syslog(LOG_INFO, "PCP: %s port %hu mapping removed",
+ proto==IPPROTO_TCP?"TCP":"UDP", (pcp_msg_info->is_fw ? iport : eport2));
+ } else {
+- syslog(LOG_ERR, "Failed to remove PCP mapping to %s:%hu %s",
++ syslog(LOG_INFO, "Failed to remove PCP mapping to %s:%hu %s",
+ pcp_msg_info->mapped_str, iport, proto_itoa(proto));
+ pcp_msg_info->result_code = PCP_ERR_NO_RESOURCES;
+ }
+@@ -1391,7 +1391,7 @@ static int processPCPRequest(void * req,
+ CreatePCPMap(pcp_msg_info);
+ }
+ } else {
+- syslog(LOG_ERR, "PCP: Invalid PCP v2 MAP message.");
++ syslog(LOG_DEBUG, "PCP: Invalid PCP v2 MAP message.");
+ return pcp_msg_info->result_code;
+ }
+
+@@ -1601,7 +1601,7 @@ int ProcessIncomingPCPPacket(int s, unsi
+ if (!GETFLAG(PCP_ALLOWTHIRDPARTYMASK)) {
+ lan_addr = get_lan_for_peer(senderaddr);
+ if(lan_addr == NULL) {
+- syslog(LOG_WARNING, "PCP packet sender %s not from a LAN, ignoring",
++ syslog(LOG_DEBUG, "PCP packet sender %s not from a LAN, ignoring",
+ addr_str);
+ return 0;
+ }
+@@ -1722,7 +1722,7 @@ void PCPSendUnsolicitedAnnounce(int * so
+ }
+ len = sendto_or_schedule(sockets[i], buff, PCP_MIN_LEN, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
+ if( len < 0 ) {
+- syslog(LOG_ERR, "PCPSendUnsolicitedAnnounce(sockets[%d]) sendto(): %m", i);
++ syslog(LOG_DEBUG, "PCPSendUnsolicitedAnnounce(sockets[%d]) sendto(): %m", i);
+ }
+ }
+ #ifdef ENABLE_IPV6
+@@ -1733,7 +1733,7 @@ void PCPSendUnsolicitedAnnounce(int * so
+ addr6.sin6_port = htons(5350);
+ len = sendto_or_schedule(socket6, buff, PCP_MIN_LEN, 0, (struct sockaddr *)&addr6, sizeof(struct sockaddr_in6));
+ if( len < 0 ) {
+- syslog(LOG_ERR, "PCPSendUnsolicitedAnnounce() IPv6 sendto(): %m");
++ syslog(LOG_DEBUG, "PCPSendUnsolicitedAnnounce() IPv6 sendto(): %m");
+ }
+ }
+ #endif /* ENABLE_IPV6 */
+--- a/pf/pfpinhole.c
++++ b/pf/pfpinhole.c
+@@ -246,12 +246,12 @@ int find_pinhole(const char * ifname,
+ (0 == memcmp(&daddr, &RULE.dst.addr.v.a.addr.v6, sizeof(struct in6_addr)))) {
+ #ifdef USE_LIBPFCTL
+ if(sscanf(RULE.label[0], PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) {
+- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label[0]);
++ /*syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label[0]);*/
+ continue;
+ }
+ #else /* USE_LIBPFCTL */
+ if(sscanf(RULE.label, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) {
+- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label);
++ /*syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label);*/
+ continue;
+ }
+ #endif /* USE_LIBPFCTL */
+@@ -565,7 +565,7 @@ int clean_pinhole_list(unsigned int * ne
+ return -1;
+ }
+ if(sscanf(RULE.label[0], PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) {
+- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label[0]);
++ /*syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label[0]);*/
+ continue;
+ }
+ #else /* USE_LIBPFCTL */
+@@ -576,7 +576,7 @@ int clean_pinhole_list(unsigned int * ne
+ return -1;
+ }
+ if(sscanf(RULE.label, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) {
+- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label);
++ /*syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label);*/
+ continue;
+ }
+ #endif /* USE_LIBPFCTL */
+--- a/upnphttp.c
++++ b/upnphttp.c
+@@ -514,7 +514,7 @@ ProcessHTTPPOST_upnphttp(struct upnphttp
+ if(h->req_soapActionOff > 0)
+ {
+ /* we can process the request */
+- syslog(LOG_INFO, "SOAPAction: %.*s",
++ syslog(LOG_DEBUG, "SOAPAction: %.*s",
+ h->req_soapActionLen, h->req_buf + h->req_soapActionOff);
+ ExecuteSoapAction(h,
+ h->req_buf + h->req_soapActionOff,
+@@ -819,7 +819,7 @@ ProcessHttpQuery_upnphttp(struct upnphtt
+ for(i = 0; i<15 && *p != '\r'; i++)
+ HttpVer[i] = *(p++);
+ HttpVer[i] = '\0';
+- syslog(LOG_INFO, "HTTP REQUEST from %s : %s %s (%s)",
++ syslog(LOG_DEBUG, "HTTP REQUEST from %s : %s %s (%s)",
+ h->clientaddr_str, HttpCommand, HttpUrl, HttpVer);
+ ParseHttpHeaders(h);
+ if(h->req_HostOff > 0 && h->req_HostLen > 0) {
+@@ -899,7 +899,7 @@ ProcessHttpQuery_upnphttp(struct upnphtt
+ return;
+ }
+ #endif
+- syslog(LOG_NOTICE, "%s not found, responding ERROR 404", HttpUrl);
++ syslog(LOG_DEBUG, "%s not found, responding ERROR 404", HttpUrl);
+ Send404(h);
+ }
+ #ifdef ENABLE_EVENTS
+--- a/upnppinhole.c
++++ b/upnppinhole.c
+@@ -110,7 +110,7 @@ lease_file6_add(const char * rem_client,
+
+ fd = fopen( lease_file6, "a");
+ if (fd==NULL) {
+- syslog(LOG_ERR, "could not open lease file: %s", lease_file);
++ syslog(LOG_DEBUG, "could not open lease file: %s", lease_file);
+ return -1;
+ }
+
+@@ -189,13 +189,13 @@ lease_file6_update(int uid, unsigned int
+ proto = line;
+ p = strchr(line, ';');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ p2 = strchr(p, ';');
+ if(!p2) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p2++) = '\0';
+@@ -203,13 +203,13 @@ lease_file6_update(int uid, unsigned int
+ int_client = p;
+ p = strchr(p2, ';');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ p2 = strchr(p, ';');
+ if(!p2) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p2++) = '\0';
+@@ -217,20 +217,20 @@ lease_file6_update(int uid, unsigned int
+ rem_client = p;
+ p = strchr(p2, ';');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ p2 = strchr(p, ';');
+ if(!p2) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p2++) = '\0';
+ desc = strchr(p2, ';');
+ uid_rule = atoi(p);
+ if(!desc) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(desc++) = '\0';
+@@ -385,49 +385,49 @@ int lease_file6_expire(void)
+ // Internal Host
+ p = strchr(line, ';');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ // Internal Port
+ p2 = strchr(p, ';');
+ if(!p2) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p2++) = '\0';
+ // External Host
+ p = strchr(p2, ';');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ // External Port
+ p2 = strchr(p, ';');
+ if(!p2) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p2++) = '\0';
+ // uid
+ p = strchr(p2, ';');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ // Timestamp
+ p2 = strchr(p, ';');
+ if(!p2) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p2++) = '\0';
+ // descr
+ desc = strchr(p2, ';');
+ if(!desc) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(desc++) = '\0';
+@@ -476,7 +476,7 @@ int reload_from_lease_file6(void)
+ if(!lease_file6) return -1;
+ fd = fopen( lease_file6, "r");
+ if (fd==NULL) {
+- syslog(LOG_ERR, "could not open lease file: %s", lease_file6);
++ syslog(LOG_DEBUG, "could not open lease file: %s", lease_file6);
+ return -1;
+ }
+ if(unlink(lease_file6) < 0) {
+@@ -491,13 +491,13 @@ int reload_from_lease_file6(void)
+ proto = line;
+ p = strchr(line, ';');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ p2 = strchr(p, ';');
+ if(!p2) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p2++) = '\0';
+@@ -505,13 +505,13 @@ int reload_from_lease_file6(void)
+ int_client = p;
+ p = strchr(p2, ';');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ p2 = strchr(p, ';');
+ if(!p2) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p2++) = '\0';
+@@ -519,20 +519,20 @@ int reload_from_lease_file6(void)
+ rem_client = p;
+ p = strchr(p2, ';');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ p2 = strchr(p, ';');
+ if(!p2) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p2++) = '\0';
+ desc = strchr(p2, ';');
+ uid = atoi(p);
+ if(!desc) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(desc++) = '\0';
+@@ -549,7 +549,7 @@ int reload_from_lease_file6(void)
+
+ if(timestamp > 0) {
+ if(timestamp <= (unsigned int)current_unix_time) {
+- syslog(LOG_NOTICE, "already expired lease in lease file");
++ syslog(LOG_INFO, "already expired lease in lease file");
+ continue;
+ } else {
+ leaseduration = timestamp - current_unix_time;
+--- a/upnpredirect.c
++++ b/upnpredirect.c
+@@ -66,7 +66,7 @@ lease_file_add(unsigned short eport,
+
+ fd = fopen( lease_file, "a");
+ if (fd==NULL) {
+- syslog(LOG_ERR, "could not open lease file: %s", lease_file);
++ syslog(LOG_DEBUG, "could not open lease file: %s", lease_file);
+ return -1;
+ }
+
+@@ -168,7 +168,7 @@ int reload_from_lease_file(void)
+ if(!lease_file) return -1;
+ fd = fopen( lease_file, "r");
+ if (fd==NULL) {
+- syslog(LOG_ERR, "could not open lease file: %s", lease_file);
++ syslog(LOG_DEBUG, "could not open lease file: %s", lease_file);
+ return -1;
+ }
+ if(unlink(lease_file) < 0) {
+@@ -184,33 +184,33 @@ int reload_from_lease_file(void)
+ proto = line;
+ p = strchr(line, ':');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ iaddr = strchr(p, ':');
+ if(!iaddr) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(iaddr++) = '\0';
+ eport = (unsigned short)atoi(p);
+ p = strchr(iaddr, ':');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ iport = (unsigned short)atoi(p);
+ p = strchr(p, ':');
+ if(!p) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(p++) = '\0';
+ desc = strchr(p, ':');
+ if(!desc) {
+- syslog(LOG_ERR, "unrecognized data in lease file");
++ syslog(LOG_DEBUG, "unrecognized data in lease file");
+ continue;
+ }
+ *(desc++) = '\0';
+@@ -231,7 +231,7 @@ int reload_from_lease_file(void)
+ timestamp += current_time; /* convert to our time */
+ #else
+ if(timestamp <= (unsigned int)current_unix_time) {
+- syslog(LOG_NOTICE, "already expired lease in lease file (%hu=>%s:%hu %s)",
++ syslog(LOG_INFO, "already expired lease in lease file (%hu=>%s:%hu %s)",
+ eport, iaddr, iport, proto);
+ continue;
+ } else {
+@@ -391,7 +391,7 @@ upnp_redirect(const char * rhost, unsign
+ #endif /* CHECK_PORTINUSE */
+ } else {
+ timestamp = (leaseduration > 0) ? upnp_time() + leaseduration : 0;
+- syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
++ syslog(LOG_DEBUG, "redirecting port %hu to %s:%hu protocol %s for: %s",
+ eport, iaddr, iport, protocol, desc);
+ return upnp_redirect_internal(rhost, eport, iaddr, iport, proto,
+ desc, timestamp);
+@@ -641,7 +641,7 @@ get_upnp_rules_state_list(int max_rules_
+ {
+ if(tmp->to_remove)
+ {
+- syslog(LOG_NOTICE, "remove port mapping %hu %s because it has expired",
++ syslog(LOG_INFO, "remove port mapping %hu %s because it has expired",
+ tmp->eport, proto_itoa(tmp->proto));
+ _upnp_delete_redir(tmp->eport, tmp->proto);
+ *p = tmp->next;
+--- a/upnpsoap.c
++++ b/upnpsoap.c
+@@ -491,7 +491,7 @@ AddPortMapping(struct upnphttp * h, cons
+ }
+ else
+ {
+- syslog(LOG_ERR, "Failed to convert hostname '%s' to ip address", int_ip);
++ syslog(LOG_INFO, "Failed to convert hostname '%s' to ip address", int_ip);
+ ClearNameValueList(&data);
+ SoapError(h, 402, "Invalid Args");
+ return;
+@@ -690,7 +690,7 @@ AddAnyPortMapping(struct upnphttp * h, c
+ }
+ else
+ {
+- syslog(LOG_ERR, "Failed to convert hostname '%s' to ip address", int_ip);
++ syslog(LOG_INFO, "Failed to convert hostname '%s' to ip address", int_ip);
+ ClearNameValueList(&data);
+ SoapError(h, 402, "Invalid Args");
+ return;
+@@ -847,7 +847,7 @@ GetSpecificPortMappingEntry(struct upnph
+ }
+ else
+ {
+- syslog(LOG_INFO, "%s: rhost='%s' %s %s found => %s:%u desc='%s' duration=%u",
++ syslog(LOG_DEBUG, "%s: rhost='%s' %s %s found => %s:%u desc='%s' duration=%u",
+ action,
+ r_host ? r_host : "NULL", ext_port, protocol, int_ip,
+ (unsigned int)iport, desc, leaseduration);
+@@ -931,7 +931,7 @@ DeletePortMapping(struct upnphttp * h, c
+ return;
+ }
+
+- syslog(LOG_INFO, "%s: external port: %hu, protocol: %s",
++ syslog(LOG_DEBUG, "%s: external port: %hu, protocol: %s",
+ action, eport, protocol);
+
+ /* if in secure mode, check the IP
+@@ -1047,7 +1047,7 @@ DeletePortMappingRange(struct upnphttp *
+ for(i = 0; i < number; i++)
+ {
+ r = upnp_delete_redirection(port_list[i], protocol);
+- syslog(LOG_INFO, "%s: deleting external port: %hu, protocol: %s: %s",
++ syslog(LOG_DEBUG, "%s: deleting external port: %hu, protocol: %s: %s",
+ action, port_list[i], protocol, r < 0 ? "failed" : "ok");
+ }
+ free(port_list);
+@@ -1112,7 +1112,7 @@ GetGenericPortMappingEntry(struct upnpht
+ return;
+ }
+
+- syslog(LOG_INFO, "%s: index=%d", action, (int)index);
++ syslog(LOG_DEBUG, "%s: index=%d", action, (int)index);
+
+ rhost[0] = '\0';
+ r = upnp_get_redirection_infos_by_index((int)index, &eport, protocol, &iport,
+@@ -1635,7 +1635,7 @@ PinholeVerification(struct upnphttp * h,
+ int r;
+ struct addrinfo hints, *ai, *p;
+
+- syslog(LOG_INFO, "%s: InternalClient %s is not an IPv6, assume hostname and convert",
++ syslog(LOG_DEBUG, "%s: InternalClient %s is not an IPv6, assume hostname and convert",
+ "PinholeVerification", int_ip);
+
+ memset(&hints, 0, sizeof(hints));
+@@ -1655,8 +1655,8 @@ PinholeVerification(struct upnphttp * h,
+ {
+ result_ip = ((struct sockaddr_in6 *)p->ai_addr)->sin6_addr;
+ if (inet_ntop(AF_INET6, &result_ip, int_ip, sizeof(struct in6_addr)) == NULL)
+- syslog(LOG_WARNING, "%s: inet_ntop(): %m", "PinholeVerification");
+- syslog(LOG_INFO, "%s: InternalClient resolved as %s",
++ syslog(LOG_DEBUG, "%s: inet_ntop(): %m", "PinholeVerification");
++ syslog(LOG_DEBUG, "%s: InternalClient resolved as %s",
+ "PinholeVerification", int_ip);
+ found = 1;
+ }
+@@ -1672,7 +1672,7 @@ PinholeVerification(struct upnphttp * h,
+ freeaddrinfo(ai);
+ if (!found)
+ {
+- syslog(LOG_NOTICE, "%s: No IPv6 address for hostname '%s'",
++ syslog(LOG_INFO, "%s: No IPv6 address for hostname '%s'",
+ "PinholeVerification", int_ip);
+ SoapError(h, 402, "Invalid Args");
+ return -1;
+@@ -1680,7 +1680,7 @@ PinholeVerification(struct upnphttp * h,
+ }
+ else
+ {
+- syslog(LOG_WARNING, "%s: Failed to convert hostname '%s' to IP address : %s",
++ syslog(LOG_INFO, "%s: Failed to convert hostname '%s' to IP address : %s",
+ "PinholeVerification", int_ip, gai_strerror(r));
+ SoapError(h, 402, "Invalid Args");
+ return -1;
+@@ -1813,7 +1813,7 @@ AddPinhole(struct upnphttp * h, const ch
+ inet_ntop(AF_INET6,
+ &(((struct sockaddr_in6 *)p->ai_addr)->sin6_addr),
+ rem_ip, sizeof(rem_ip));
+- syslog(LOG_INFO, "resolved '%s' to '%s'", rem_host, rem_ip);
++ syslog(LOG_DEBUG, "resolved '%s' to '%s'", rem_host, rem_ip);
+ rem_host = rem_ip;
+ break;
+ }
+@@ -1822,7 +1822,7 @@ AddPinhole(struct upnphttp * h, const ch
+ }
+ else
+ {
+- syslog(LOG_WARNING, "AddPinhole : getaddrinfo(%s) : %s",
++ syslog(LOG_INFO, "AddPinhole : getaddrinfo(%s) : %s",
+ rem_host, gai_strerror(err));
+ #if 0
+ SoapError(h, 402, "Invalid Args");
+@@ -2481,7 +2481,7 @@ SoapError(struct upnphttp * h, int errCo
+ char body[2048];
+ int bodylen;
+
+- syslog(LOG_INFO, "Returning UPnPError %d: %s", errCode, errDesc);
++ syslog(LOG_DEBUG, "Returning UPnPError %d: %s", errCode, errDesc);
+ bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc);
+ BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen);
+ SendRespAndClose_upnphttp(h);
From 159e795432dd4da1ef464156570d5447c401ea5c Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 04/10] miniupnpd: package revision and new UCI options
The following settings UCI options been added or changed, and the
previous options are migrated on updating:
upnpd.config UCI options | Change | Previous name
-----------------------------|---------------------------|--------------
enabled | Match default (1) |
enable_protocols=upnp-igd | Combined option | enable_upnp=1
enable_protocols=pcp+nat-pmp | Combined option | enable_natpmp=1
allow_cgnat | Accept extra values (2) | use_stun
stun_host | Accept port inclusion (3) |
stun_port | Removed, included in host |
allow_third_party_mapping=0 | Inverted/extended to PCP | secure_mode=1
log_output | Allow info log level |
lease_file | Set by default + IPv6 (4) | upnp_lease_file
upnp_igd_compat=igdv1 | Renamed/match default (1) | igdv1=1
download_kbps | In kbit/s and renamed (5) | download
upload_kbps | In kbit/s and renamed (5) | upload
friendly_name | New option, router name |
http_port | Renamed, rem. if default | port
notify_interval | Removed if <900s, minimum |
internal_iface | Migrated, new section |
internal_network UCI options | Change | Previous name
-----------------------------|---------------------------|--------------
interface | New option |
access_defaults | New option (6) |
accept_ports | New option (6) |
reject_ports | New option (7) |
check_acl | New option (6) |
Notes:
1. Init UCI default now matches LuCI and initial config file defaults
for: enabled=0 and upnp_igd_compat=igdv1
2. Accept extra values for IPv4 CGNAT use, migrate option from X-Wrt and
only use STUN when necessary with a private/CGNAT external IPv4
3. Remove known unsupported STUN servers and set compatible by default
4. Configure undocumented daemon option `lease_file6=${lease_file}-ipv6`
so that active IPv6 port maps are not lost when service restarts,
e.g. by deleting an active port map. Remove option if UCI default set
5. Gets converted, config file now defaults to interface link speed
instead of 8/4 Mbit/s, which is removed on migration
6. New options added for default ports that all devices on a network can
map, client-specific permissions using the access control list (ACL)
can extend/override the defaults. Access defaults: accept-high-ports
accept-web+high-ports/accept-web-ports/accept-all-ports
7. Reject ports; overrides other settings. By default reject unsafe:
FTP (21), Telnet (23), DCE/NetBIOS/SMB (135/137-139/445), RDP (3389)
Code refactoring:
- Add a function for logging and output to stderr, and extend logging
- Revise daemon init/config-gen slightly by declare all UCI options
(incl. booleans) according to the same principle and remove
`upnpd_write_bool`
- Document and reformat default `/etc/config/upnpd` UCI config file
- Don't configure/flush unnecessary `upnp_postrouting` nftables chain
Fixes: https://redirect.github.com/openwrt/packages/issues/17413
Fixes: https://redirect.github.com/openwrt/packages/issues/29314
Depends on: https://redirect.github.com/openwrt/luci/pull/8415
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
miniupnpd/Makefile | 2 +
miniupnpd/files/miniupnpd.init | 213 ++++++++++++------
.../chain-post/srcnat/20-miniupnpd.nft | 1 -
.../nftables.d/table-post/20-miniupnpd.nft | 1 -
.../files/upnpd-migration.uci-defaults | 159 +++++++++++++
miniupnpd/files/upnpd.config | 56 +++--
6 files changed, 335 insertions(+), 97 deletions(-)
delete mode 100644 miniupnpd/files/nftables.d/chain-post/srcnat/20-miniupnpd.nft
create mode 100644 miniupnpd/files/upnpd-migration.uci-defaults
diff --git a/miniupnpd/Makefile b/miniupnpd/Makefile
index 042a49faf32803..33c27862b309a4 100644
--- a/miniupnpd/Makefile
+++ b/miniupnpd/Makefile
@@ -98,8 +98,10 @@ define Package/miniupnpd/install/Default
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_DIR) $(1)/etc/hotplug.d/iface
+ $(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/miniupnpd $(1)/usr/sbin/miniupnpd
$(INSTALL_BIN) ./files/miniupnpd.init $(1)/etc/init.d/miniupnpd
+ $(INSTALL_BIN) ./files/upnpd-migration.uci-defaults $(1)/etc/uci-defaults/99-miniupnpd-upnpd-migration
$(INSTALL_CONF) ./files/upnpd.config $(1)/etc/config/upnpd
$(INSTALL_DATA) ./files/miniupnpd.hotplug $(1)/etc/hotplug.d/iface/50-miniupnpd
endef
diff --git a/miniupnpd/files/miniupnpd.init b/miniupnpd/files/miniupnpd.init
index 861bec0d3124f9..5dd786a50462d5 100644
--- a/miniupnpd/files/miniupnpd.init
+++ b/miniupnpd/files/miniupnpd.init
@@ -42,53 +42,44 @@ conf_rule_add() {
echo "$action $ext_start${ext_end:+-}$ext_end $int_addr $int_start${int_end:+-}$int_end #$comment"
}
-upnpd_write_bool() {
- local opt="$1"
- local def="${2:-0}"
- local alt="${3:-$opt}"
- local val
-
- config_get_bool val config "$opt" "$def"
- if [ "$val" -eq 0 ]; then
- echo "$alt=no"
- else
- echo "$alt=yes"
- fi
-}
-
upnpd() {
config_load "upnpd"
- local external_iface external_iface6 external_zone external_ip internal_iface
- local upload download log_output port config_file serial_number model_number
- local use_stun stun_host stun_port uuid notify_interval presentation_url
- local upnp_lease_file upnp_lease_file6 ipv6_disable ext_allow_private_ipv4
+ local external_iface external_iface6 external_zone external_ip
+ local upload_kbps download_kbps log_output http_port config_file serial_number model_number
+ local allow_cgnat stun_host uuid notify_interval presentation_url
+ local lease_file ipv6_disable
+ local enable_protocols allow_third_party_mapping system_uptime upnp_igd_compat
+ local friendly_name
local enabled
- config_get_bool enabled config enabled 1
- [ "$enabled" -eq 0 ] && return 1
-
+ config_get enabled config enabled 0
+ if [ "$enabled" != "1" ]; then
+ log "Service disabled, enabled UCI option not set"
+ return 1
+ fi
config_get external_iface config external_iface
config_get external_iface6 config external_iface6
config_get external_zone config external_zone
config_get external_ip config external_ip
- config_get internal_iface config internal_iface
- config_get port config port 5000
- config_get upload config upload
- config_get download config download
- config_get_bool log_output config log_output 0
+ config_get http_port config http_port 5000
+ config_get upload_kbps config upload_kbps
+ config_get download_kbps config download_kbps
+ config_get log_output config log_output
config_get config_file config config_file
config_get serial_number config serial_number
config_get model_number config model_number
config_get uuid config uuid
- config_get use_stun config use_stun 0
- config_get stun_host config stun_host
- config_get stun_port config stun_port
+ config_get allow_cgnat config allow_cgnat 0
+ config_get stun_host config stun_host stun.nextcloud.com
config_get notify_interval config notify_interval
config_get presentation_url config presentation_url
- config_get upnp_lease_file config upnp_lease_file
- config_get upnp_lease_file6 config upnp_lease_file6
+ config_get lease_file config lease_file /var/run/miniupnpd.leases
config_get ipv6_disable config ipv6_disable 0
- config_get ext_allow_private_ipv4 config ext_allow_private_ipv4 0
+ config_get enable_protocols config enable_protocols all
+ config_get allow_third_party_mapping config allow_third_party_mapping 0
+ config_get system_uptime config system_uptime 1
+ config_get upnp_igd_compat config upnp_igd_compat igdv1
+ config_get friendly_name config friendly_name "OpenWrt UPnP IGD & PCP"
local conf ifname ifname6
@@ -121,55 +112,66 @@ upnpd() {
local tmpconf="/var/etc/miniupnpd.conf"
conf="$tmpconf"
mkdir -p /var/etc
-
+ if [ "$ifname" = "" ]; then
+ log "No external network interface found, not starting" daemon.err
+ return 1
+ fi
+ if ! uci -q get upnpd.@internal_network[0].interface >/dev/null; then
+ log "No internal networks configured, not starting" daemon.err
+ return 1
+ fi
{
echo "ext_ifname=$ifname"
echo "ext_ifname6=$ifname6"
[ -n "$external_ip" ] && echo "ext_ip=$external_ip"
- local iface
- for iface in ${internal_iface:-lan}; do
- local device
- network_get_device device "$iface" && echo "listening_ip=$device"
- done
-
- config_load "upnpd"
- upnpd_write_bool enable_natpmp 1
- upnpd_write_bool enable_upnp 1
- upnpd_write_bool secure_mode 1
- upnpd_write_bool system_uptime 1
- upnpd_write_bool igdv1 0 force_igd_desc_v1
- upnpd_write_bool use_stun 0 ext_perform_stun
- upnpd_write_bool ipv6_disable $ipv6_disable
- upnpd_write_bool ext_allow_private_ipv4 $ext_allow_private_ipv4
-
- [ "$use_stun" -eq 0 ] || {
- [ -n "$stun_host" ] && echo "ext_stun_host=$stun_host"
- [ -n "$stun_port" ] && echo "ext_stun_port=$stun_port"
- }
+ [ "$enable_protocols" = "all" ] && echo "enable_upnp=yes" && echo "enable_pcp_pmp=yes"
+ [ "$enable_protocols" = "upnp-igd" ] && echo "enable_upnp=yes" && echo "enable_pcp_pmp=no"
+ [ "$enable_protocols" = "pcp+nat-pmp" ] && echo "enable_upnp=no" && echo "enable_pcp_pmp=yes"
+ [ "$allow_third_party_mapping" = "0" ] && echo "secure_mode=yes" && echo "pcp_allow_thirdparty=no"
+ [ "$allow_third_party_mapping" = "1" ] && echo "secure_mode=no" && echo "pcp_allow_thirdparty=yes"
+ [ "$allow_third_party_mapping" = "upnp-igd" ] && echo "secure_mode=no" && echo "pcp_allow_thirdparty=no"
+ [ "$allow_third_party_mapping" = "pcp" ] && echo "secure_mode=yes" && echo "pcp_allow_thirdparty=yes"
+ [ "$system_uptime" = "0" ] && echo "system_uptime=no" || echo "system_uptime=yes"
+ [ "$upnp_igd_compat" = "igdv1" ] && echo "force_igd_desc_v1=yes" || echo "force_igd_desc_v1=no"
+ # Only perform an STUN CGNAT test if necessary, with a private/CGNAT-reserved external IPv4
+ local extipv4 extipv4private
+ extipv4="$(ip -4 addr show dev "$ifname" | grep inet | head -1 | sed -E "s/.*inet ([0-9.]+).*/\1/")"
+ case "$extipv4" in
+ 10.* | 100.6[4-9].* | 100.[7-9][0-9].* | 100.1[0-1][0-9].* | 100.12[0-7].* | 172.1[6-9].* | \
+ 172.2[0-9].* | 172.3[0-1].* | 192.0.0.[1-6] | 192.168.* | 198.1[89].*) extipv4private=1 ;;
+ esac
+ [ "$extipv4private" = "1" ] && log "Private/CGNAT-reserved external IPv4 detected ($extipv4)"
+ if [ "$extipv4private" = "1" ] && [ "$allow_cgnat" != "0" ]; then
+ [ "$allow_cgnat" = "1" ] && echo "ext_perform_stun=yes"
+ [ "$allow_cgnat" = "allow-filtered" ] && echo "ext_perform_stun=allow-filtered"
+ # Avoid next option, as no STUN public IPv4 detection, required by clients (PCP/NAT-PMP among others)
+ [ "$allow_cgnat" = "report-private-ipv4" ] && echo "ext_allow_private_ipv4=yes"
+ echo "ext_stun_host=${stun_host%%:*}"
+ [ "${stun_host%%:*}" != "${stun_host##*:}" ] && echo "ext_stun_port=${stun_host##*:}"
+ fi
+ [ "$ipv6_disable" = "0" ] && echo "ipv6_disable=no" || echo "ipv6_disable=yes"
- [ -n "$upload" ] && [ -n "$download" ] && {
- echo "bitrate_down=$((download * 1024 * 8))"
- echo "bitrate_up=$((upload * 1024 * 8))"
- }
+ [ -n "$download_kbps" ] && echo "bitrate_down=$((download_kbps * 1000))"
+ [ -n "$upload_kbps" ] && echo "bitrate_up=$((upload_kbps * 1000))"
- [ -n "$upnp_lease_file" ] && touch "$upnp_lease_file" && echo "lease_file=$upnp_lease_file"
- [ -n "$upnp_lease_file6" ] && touch "$upnp_lease_file6" && echo "lease_file6=$upnp_lease_file6"
+ touch "$lease_file" && echo "lease_file=$lease_file"
+ [ "$ipv6_disable" = "0" ] && touch "${lease_file}-ipv6" && echo "lease_file6=${lease_file}-ipv6"
+ [ -n "$friendly_name" ] && echo "friendly_name=$friendly_name"
[ -n "$presentation_url" ] && echo "presentation_url=$presentation_url"
[ -n "$notify_interval" ] && echo "notify_interval=$notify_interval"
- [ -n "$serial_number" ] && echo "serial=$serial_number"
- [ -n "$model_number" ] && echo "model_number=$model_number"
- [ -n "$port" ] && echo "port=$port"
+ echo "serial=$serial_number"
+ echo "model_number=$model_number"
+ echo "http_port=$http_port"
[ -z "$uuid" ] && {
+ log "Generate UPnP IGD UUID"
uuid="$(cat /proc/sys/kernel/random/uuid)"
uci set upnpd.config.uuid="$uuid"
uci commit upnpd
}
- [ "$uuid" = "nocli" ] || echo "uuid=$uuid"
-
- config_foreach conf_rule_add perm_rule
+ [ "$uuid" != "nocli" ] && echo "uuid=$uuid" || log "uuid=nocli deprecated, set to 00000000-0000-0000-0000-000000000000 instead"
if [ "$FW" = "fw4" ]; then
#When using nftables configure miniupnpd to use its own table and chains
@@ -177,9 +179,14 @@ upnpd() {
echo "upnp_nat_table_name=fw4"
echo "upnp_forward_chain=upnp_forward"
echo "upnp_nat_chain=upnp_prerouting"
- echo "upnp_nat_postrouting_chain=upnp_postrouting"
+ #echo "upnp_nat_postrouting_chain=upnp_postrouting"
fi
+ echo "# Enable internal networks / access control"
+ config_foreach upnpd_add_int_network_and_defaults internal_network pre-acl
+ config_foreach conf_rule_add perm_rule
+ config_foreach upnpd_add_int_network_and_defaults internal_network post-acl
+
} > "$tmpconf"
fi
@@ -190,15 +197,14 @@ upnpd() {
else
iptables -L MINIUPNPD >/dev/null 2>&1 || fw3 reload
fi
- else
- logger -t "upnp daemon" "external interface not found, not starting"
fi
procd_open_instance
procd_set_param file "$conf" "/etc/config/firewall"
procd_set_param command "$PROG"
procd_append_param command -f "$conf"
- [ "$log_output" = "1" ] && procd_append_param command -d
+ [ "$log_output" = "info" ] && procd_append_param command -v
+ [ "$log_output" = "debug" ] && procd_append_param command -v -v
procd_close_instance
}
@@ -211,7 +217,7 @@ stop_service() {
else
nft flush chain inet fw4 upnp_forward 2>/dev/null
nft flush chain inet fw4 upnp_prerouting 2>/dev/null
- nft flush chain inet fw4 upnp_postrouting 2>/dev/null
+ #nft flush chain inet fw4 upnp_postrouting 2>/dev/null
fi
}
@@ -223,3 +229,70 @@ start_service() {
service_triggers() {
procd_add_reload_trigger "upnpd"
}
+
+log() {
+ logger -s -p "${2:-daemon.notice}" -t "miniupnpd-init" "$1" || echo "miniupnpd-init: $1" >&2
+}
+
+upnpd_add_int_network_and_defaults() {
+ local cfg="$1"
+ local interface access_defaults accept_ports reject_ports check_acl
+ config_get interface "$cfg" interface
+ config_get access_defaults "$cfg" access_defaults none
+ config_get accept_ports "$cfg" accept_ports
+ config_get reject_ports "$cfg" reject_ports "21 23 135 137-139 445 3389"
+ config_get check_acl "$cfg" check_acl 1
+ local device subnet rejectport accessdefaultsports acceptport
+ network_get_device device "$interface"
+ network_get_subnet subnet "$interface"
+ [ "$subnet" = "" ] && log "Cannot get IPv4 subnet for network $interface, network ignored" daemon.warn && return 0
+ if [ "$2" = "pre-acl" ]; then
+ echo "# Enable internal network $interface ($device) with access defaults $access_defaults and check ACL $check_acl"
+ echo "listening_ip=$device"
+ for rejectport in $reject_ports; do
+ if [ "$rejectport" -ge "1" ] 2>/dev/null && [ "$rejectport" -le "65535" ] 2>/dev/null ||
+ {
+ [ "${rejectport%%-*}" -ge "1" ] 2>/dev/null &&
+ [ "${rejectport%%-*}" -le "65535" ] 2>/dev/null &&
+ [ "${rejectport##*-}" -ge "1" ] 2>/dev/null &&
+ [ "${rejectport##*-}" -le "65535" ] 2>/dev/null &&
+ [ "${rejectport##*-}" -ge "${rejectport%%-*}" ] 2>/dev/null
+ }; then
+ echo "deny $rejectport $subnet $rejectport # Reject port $rejectport on $interface"
+ else
+ log "Invalid port or port range ($rejectport) in reject_ports ignored" daemon.warn
+ fi
+ done
+ fi
+ if { [ "$2" = "post-acl" ] && [ "$check_acl" = "1" ]; } ||
+ { [ "$2" = "pre-acl" ] && [ "$check_acl" = "0" ]; }; then
+ if [ "$access_defaults" = "accept-high-ports" ]; then
+ accessdefaultsports="1024-65535"
+ elif [ "$access_defaults" = "accept-web+high-ports" ]; then
+ accessdefaultsports="80 443 1024-65535"
+ elif [ "$access_defaults" = "accept-web-ports" ]; then
+ accessdefaultsports="80 443"
+ elif [ "$access_defaults" = "accept-all-ports" ]; then
+ accessdefaultsports="1-65535"
+ elif [ "$access_defaults" != "none" ]; then
+ log "Invalid access_defaults ($access_defaults) ignored" daemon.warn
+ fi
+ for acceptport in $accessdefaultsports $accept_ports; do
+ if [ "$acceptport" -ge "1" ] 2>/dev/null && [ "$acceptport" -le "65535" ] 2>/dev/null ||
+ {
+ [ "${acceptport%%-*}" -ge "1" ] 2>/dev/null &&
+ [ "${acceptport%%-*}" -le "65535" ] 2>/dev/null &&
+ [ "${acceptport##*-}" -ge "1" ] 2>/dev/null &&
+ [ "${acceptport##*-}" -le "65535" ] 2>/dev/null &&
+ [ "${acceptport##*-}" -ge "${acceptport%%-*}" ] 2>/dev/null
+ }; then
+ echo "allow $acceptport $subnet $acceptport # Accept port $acceptport on $interface"
+ else
+ log "Invalid port or port range ($acceptport) in accept_ports ignored" daemon.warn
+ fi
+ done
+ fi
+ if [ "$2" = "pre-acl" ] && [ "$check_acl" = "0" ]; then
+ echo "deny 1-65535 $subnet 1-65535 # Reject ACL by default on $interface"
+ fi
+}
diff --git a/miniupnpd/files/nftables.d/chain-post/srcnat/20-miniupnpd.nft b/miniupnpd/files/nftables.d/chain-post/srcnat/20-miniupnpd.nft
deleted file mode 100644
index 2d0051e202f7b1..00000000000000
--- a/miniupnpd/files/nftables.d/chain-post/srcnat/20-miniupnpd.nft
+++ /dev/null
@@ -1 +0,0 @@
-jump upnp_postrouting comment "Hook into miniupnpd postrouting chain";
diff --git a/miniupnpd/files/nftables.d/table-post/20-miniupnpd.nft b/miniupnpd/files/nftables.d/table-post/20-miniupnpd.nft
index 7d914e5c84fdf3..3d787fba88d0da 100644
--- a/miniupnpd/files/nftables.d/table-post/20-miniupnpd.nft
+++ b/miniupnpd/files/nftables.d/table-post/20-miniupnpd.nft
@@ -1,3 +1,2 @@
chain upnp_forward {}
chain upnp_prerouting {}
-chain upnp_postrouting {}
diff --git a/miniupnpd/files/upnpd-migration.uci-defaults b/miniupnpd/files/upnpd-migration.uci-defaults
new file mode 100644
index 00000000000000..19445c9198994b
--- /dev/null
+++ b/miniupnpd/files/upnpd-migration.uci-defaults
@@ -0,0 +1,159 @@
+#!/bin/sh
+
+log() {
+ logger -s -p "${2:-daemon.notice}" -t "upnpd" "$1" || echo "upnpd: $1" >&2
+}
+
+log "Check UCI options in /etc/config/upnpd to be migrated to v2.0"
+
+# Set missing enabled option to fix previously different defaults in LuCI/config (0) and init UCI (1)
+if ! uci -q get upnpd.config.enabled >/dev/null; then
+ uci -q set upnpd.config.enabled="1"
+fi
+
+# Migrate boolean options to only use 0/1 for LuCI support
+for option in enabled ipv6_disable system_uptime; do
+ if uci -q get upnpd.config.$option >/dev/null; then
+ uci get upnpd.config.$option | grep -q -E -x "0|off|false|no|disabled" && uci set upnpd.config.$option="0"
+ uci get upnpd.config.$option | grep -q -E -x "1|on|true|yes|enabled" && uci set upnpd.config.$option="1"
+ fi
+done
+
+# Migrate enable_upnp/enable_natpmp -> enable_protocols: Combined option
+if uci -q get upnpd.config.enable_upnp >/dev/null || uci -q get upnpd.config.enable_natpmp >/dev/null; then
+ log "enable_upnp/enable_natpmp -> enable_protocols: Combined option"
+ if ! uci -q get upnpd.config.enable_upnp | grep -q -E -x "0|off|false|no|disabled"; then
+ uci -q get upnpd.config.enable_natpmp | grep -q -E -x "0|off|false|no|disabled" &&
+ uci set upnpd.config.enable_protocols="upnp-igd" ||
+ uci set upnpd.config.enable_protocols="all"
+ elif ! uci -q get upnpd.config.enable_natpmp | grep -q -E -x "0|off|false|no|disabled"; then
+ uci set upnpd.config.enable_protocols="pcp+nat-pmp"
+ else
+ uci set upnpd.config.enable_protocols="all"
+ uci set upnpd.config.enabled="0"
+ fi
+ uci -q delete upnpd.config.enable_upnp
+ uci -q delete upnpd.config.enable_natpmp
+fi
+
+# Rename use_stun -> allow_cgnat
+if uci -q get upnpd.config.use_stun >/dev/null; then
+ log "use_stun -> allow_cgnat"
+ uci rename upnpd.config.use_stun="allow_cgnat"
+fi
+
+# Migrate force_forwarding=1 (in X-Wrt since 2021) to new similar option allow_cgnat=allow-filtered for cross-upgrades
+if uci -q get upnpd.config.force_forwarding >/dev/null; then
+ log "force_forwarding=1 -> allow_cgnat=allow-filtered: New option"
+ uci get upnpd.config.force_forwarding | grep -q -E -x "1|on|true|yes|enabled" &&
+ uci set upnpd.config.allow_cgnat="allow-filtered"
+ uci delete upnpd.config.force_forwarding
+fi
+
+# Remove known unsupported (not CGNAT filtering test capable) STUN servers and include stun_port in stun_host
+if uci -q get upnpd.config.stun_host | grep -q -E "stun[0-9]?.l.google.com|stun.cloudflare.com"; then
+ log "stun_host: Unsupported STUN server ($(uci -q get upnpd.config.stun_host)) set, remove to set default"
+ uci delete upnpd.config.stun_host
+ # Workaround for false negative filtered STUN CGNAT test results for unsupported servers before daemon 2.3.10
+ uci set upnpd.config.allow_cgnat="allow-filtered"
+ uci -q delete upnpd.config.stun_port
+elif uci -q get upnpd.config.stun_port >/dev/null; then
+ uci -q get upnpd.config.stun_host >/dev/null && [ "$(uci -q get upnpd.config.stun_port)" != "3478" ] &&
+ log "stun_port: Include stun_port in stun_host, and remove option" &&
+ uci set upnpd.config.stun_host="$(uci -q get upnpd.config.stun_host | cut -d ":" -f 1):$(uci -q get upnpd.config.stun_port)"
+ uci delete upnpd.config.stun_port
+fi
+
+# Migrate secure_mode=1/0 -> allow_third_party_mapping=0/upnp-igd: Invert and extend to PCP
+if uci -q get upnpd.config.secure_mode >/dev/null; then
+ log "secure_mode=1/0 -> allow_third_party_mapping=0/upnp-igd: Invert and extend to PCP"
+ uci get upnpd.config.secure_mode | grep -q -E -x "0|off|false|no|disabled" &&
+ uci set upnpd.config.allow_third_party_mapping="upnp-igd" ||
+ uci set upnpd.config.allow_third_party_mapping="0"
+ uci delete upnpd.config.secure_mode
+fi
+
+# Migrate log_output=0/1 -> log_output=default/debug: Now info also allowed
+if uci -q get upnpd.config.log_output >/dev/null; then
+ log "log_output=0/1 -> log_output=default/debug: Now info also allowed"
+ uci get upnpd.config.log_output | grep -q -E -x "1|on|true|yes|enabled" &&
+ uci set upnpd.config.log_output="debug"
+ uci get upnpd.config.log_output | grep -q -E -x "0|off|false|no|disabled" &&
+ uci set upnpd.config.log_output="default"
+fi
+
+# Rename upnp_lease_file -> lease_file: To original daemon name, and remove if UCI default set
+if uci -q get upnpd.config.upnp_lease_file >/dev/null; then
+ if [ "$(uci -q get upnpd.config.upnp_lease_file)" = "/var/run/miniupnpd.leases" ]; then
+ log "upnp_lease_file -> lease_file: Remove option as UCI default set"
+ uci delete upnpd.config.upnp_lease_file
+ else
+ log "upnp_lease_file -> lease_file"
+ uci rename upnpd.config.upnp_lease_file="lease_file"
+ fi
+fi
+if uci -q get upnpd.config.upnp_lease_file6 >/dev/null; then
+ uci delete upnpd.config.upnp_lease_file6
+fi
+
+# Migrate igdv1=1/0 -> upnp_igd_compat=igdv1/igdv2: Extensible/clearer
+if uci -q get upnpd.config.igdv1 >/dev/null; then
+ log "igdv1=1/0 -> upnp_igd_compat=igdv1/igdv2"
+ uci get upnpd.config.igdv1 | grep -q -E -x "1|on|true|yes|enabled" &&
+ uci set upnpd.config.upnp_igd_compat="igdv1" ||
+ uci set upnpd.config.upnp_igd_compat="igdv2"
+ uci delete upnpd.config.igdv1
+fi
+
+# Migrate download/upload -> download_kbps/upload_kbps: Convert to kbit/s
+if uci -q get upnpd.config.download >/dev/null; then
+ download="$(uci -q get upnpd.config.download)"
+ if [ "$download" != "1024" ] && [ "$download" -ge "1" ] 2>/dev/null; then
+ log "download -> download_kbps: Convert to kbit/s"
+ download_kbps="$((download * 8 * 1000 / 1024))"
+ uci set upnpd.config.download_kbps="$download_kbps"
+ fi
+ uci delete upnpd.config.download
+fi
+if uci -q get upnpd.config.upload >/dev/null; then
+ upload="$(uci -q get upnpd.config.upload)"
+ if [ "$upload" != "512" ] && [ "$upload" -ge "1" ] 2>/dev/null; then
+ log "upload -> upload_kbps: Convert to kbit/s"
+ upload_kbps="$((upload * 8 * 1000 / 1024))"
+ uci set upnpd.config.upload_kbps="$upload_kbps"
+ fi
+ uci delete upnpd.config.upload
+fi
+
+# Rename port -> http_port: Remove if UCI default set
+if uci -q get upnpd.config.port >/dev/null; then
+ if [ "$(uci -q get upnpd.config.port)" = "5000" ]; then
+ log "port -> http_port: Remove option as UCI default set"
+ uci delete upnpd.config.port
+ else
+ log "port -> http_port"
+ uci rename upnpd.config.port="http_port"
+ fi
+fi
+
+# Migrate notify_interval <= 900 s: Remove to set minimum of 900 (default)
+if [ "$(uci -q get upnpd.config.notify_interval)" -le "900" ] 2>/dev/null; then
+ log "notify_interval <= 900 s: Remove to set minimum of 900 (default)"
+ uci delete upnpd.config.notify_interval
+fi
+
+# Migrate internal_iface option to new internal_network section
+if ! uci -q get upnpd.@internal_network[0] >/dev/null; then
+ ifnr=0
+ for interface in $(uci -q get upnpd.config.internal_iface || echo lan); do
+ log "Create new internal_network section for $interface"
+ uci add upnpd internal_network >/dev/null
+ uci set upnpd.@internal_network[$ifnr].interface="$interface"
+ ifnr=$((ifnr + 1))
+ done
+ uci -q delete upnpd.config.internal_iface
+fi
+
+uci commit upnpd >/dev/null
+
+exit 0
diff --git a/miniupnpd/files/upnpd.config b/miniupnpd/files/upnpd.config
index c4546e018f5de7..6344a60071bd72 100644
--- a/miniupnpd/files/upnpd.config
+++ b/miniupnpd/files/upnpd.config
@@ -1,29 +1,35 @@
-config upnpd config
- option enabled 0
- option enable_natpmp 1
- option enable_upnp 1
- option secure_mode 1
- option log_output 0
- option download 1024
- option upload 512
-#by default, looked up dynamically from ubus
-# option external_iface wan
- option internal_iface lan
- option port 5000
- option upnp_lease_file /var/run/miniupnpd.leases
- option upnp_lease_file6 /var/run/miniupnpd.leases6
- option igdv1 1
+# UPnP IGD & PCP/NAT-PMP Service Settings (v2.0)
+
+config upnpd 'config'
+ option enabled '0'
+ # Can be set to all/upnp-igd/pcp+nat-pmp
+ option enable_protocols 'all'
+ #option ipv6_disable '0'
+ #option allow_third_party_mapping '0'
+ # Extra logging by setting to info or debug (previously 1)
+ option log_output 'default'
+ option upnp_igd_compat 'igdv1'
+
+# Enable Networks / Access Control
+
+config internal_network
+ option interface 'lan'
+ option access_defaults 'accept-high-ports' # default none
+ option check_acl '0' # default 1
+
+# Access Control List
+# Empty ACL rejected alone, IPv6 accepted unless disabled
config perm_rule
- option action allow
- option ext_ports 1024-65535
- option int_addr 0.0.0.0/0 # Does not override secure_mode
- option int_ports 1024-65535
- option comment "Allow high ports"
+ option comment 'Allow high ports'
+ option int_addr '0.0.0.0/0'
+ option int_ports '1024-65535'
+ option ext_ports '1024-65535'
+ option action 'allow'
config perm_rule
- option action deny
- option ext_ports 0-65535
- option int_addr 0.0.0.0/0
- option int_ports 0-65535
- option comment "Default deny"
+ option comment 'Default deny'
+ option int_addr '0.0.0.0/0'
+ option int_ports '1-65535'
+ option ext_ports '1-65535'
+ option action 'deny'
From 39860c186473551ec4a0156cbf8cc1f73b139b12 Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 05/10] miniupnpd: group/rearrange config-gen, refactoring
- Group and rearrange UCI option declaration and config-gen by
function/LuCI UI, and comment
- Encode required XML entities of text UPnP IGD config options until the
daemon does so using the created function `xml_encode`
- Only generate UPnP IGD config if the protocol is enabled
(to merge with prior)
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
miniupnpd/files/miniupnpd.hotplug | 2 +-
miniupnpd/files/miniupnpd.init | 125 +++++++++++++-------------
2 files changed, 66 insertions(+), 61 deletions(-)
diff --git a/miniupnpd/files/miniupnpd.hotplug b/miniupnpd/files/miniupnpd.hotplug
index 607a32bdc6885b..5d34c441a829ea 100644
--- a/miniupnpd/files/miniupnpd.hotplug
+++ b/miniupnpd/files/miniupnpd.hotplug
@@ -40,6 +40,6 @@ fi
[ "$DEVICE" != "$ifname" ] && [ "$DEVICE" != "$ifname6" ] && exit 0
-grep -qs "^ext_ifname=$ifname" "$tmpconf" && grep -qs "^ext_ifname6=$ifname6" "$tmpconf" && exit 0
+grep -qs "^ext_ifname=$ifname" "$tmpconf" && grep -qs "^ext_ifname6=${ifname6:-$ifname}" "$tmpconf" && exit 0
/etc/init.d/miniupnpd restart
diff --git a/miniupnpd/files/miniupnpd.init b/miniupnpd/files/miniupnpd.init
index 5dd786a50462d5..969b22e60cacc4 100644
--- a/miniupnpd/files/miniupnpd.init
+++ b/miniupnpd/files/miniupnpd.init
@@ -29,7 +29,6 @@ conf_rule_add() {
local cfg="$1"
local action int_addr
local ext_start ext_end int_start int_end comment
-
config_get action "$cfg" action "deny" # allow or deny
upnpd_get_port_range "ext" "$cfg" ext_ports "0-65535" # external ports: x, x-y, x:y
config_get int_addr "$cfg" int_addr "0.0.0.0/0" # ip or network and subnet mask (internal)
@@ -44,45 +43,45 @@ conf_rule_add() {
upnpd() {
config_load "upnpd"
- local external_iface external_iface6 external_zone external_ip
- local upload_kbps download_kbps log_output http_port config_file serial_number model_number
- local allow_cgnat stun_host uuid notify_interval presentation_url
- local lease_file ipv6_disable
- local enable_protocols allow_third_party_mapping system_uptime upnp_igd_compat
- local friendly_name
-
local enabled
config_get enabled config enabled 0
if [ "$enabled" != "1" ]; then
log "Service disabled, enabled UCI option not set"
return 1
fi
- config_get external_iface config external_iface
- config_get external_iface6 config external_iface6
- config_get external_zone config external_zone
- config_get external_ip config external_ip
- config_get http_port config http_port 5000
- config_get upload_kbps config upload_kbps
- config_get download_kbps config download_kbps
- config_get log_output config log_output
- config_get config_file config config_file
- config_get serial_number config serial_number
- config_get model_number config model_number
- config_get uuid config uuid
+ # Daemon
+ local enable_protocols allow_cgnat stun_host allow_third_party_mapping ipv6_disable system_uptime log_output lease_file config_file
+ config_get enable_protocols config enable_protocols all
config_get allow_cgnat config allow_cgnat 0
config_get stun_host config stun_host stun.nextcloud.com
- config_get notify_interval config notify_interval
- config_get presentation_url config presentation_url
- config_get lease_file config lease_file /var/run/miniupnpd.leases
- config_get ipv6_disable config ipv6_disable 0
- config_get enable_protocols config enable_protocols all
config_get allow_third_party_mapping config allow_third_party_mapping 0
+ config_get ipv6_disable config ipv6_disable 0
config_get system_uptime config system_uptime 1
+ config_get log_output config log_output
+ config_get lease_file config lease_file /var/run/miniupnpd.leases
+ config_get config_file config config_file
+
+ # UPnP IGD
+ local upnp_igd_compat download_kbps upload_kbps friendly_name model_number serial_number presentation_url uuid http_port notify_interval
config_get upnp_igd_compat config upnp_igd_compat igdv1
+ config_get download_kbps config download_kbps
+ config_get upload_kbps config upload_kbps
config_get friendly_name config friendly_name "OpenWrt UPnP IGD & PCP"
+ config_get model_number config model_number
+ config_get serial_number config serial_number
+ config_get presentation_url config presentation_url
+ config_get uuid config uuid
+ config_get http_port config http_port 5000
+ config_get notify_interval config notify_interval
- local conf ifname ifname6
+ # External network interface
+ local external_iface external_iface6 external_zone external_ip
+ config_get external_iface config external_iface
+ config_get external_iface6 config external_iface6
+ config_get external_zone config external_zone
+ config_get external_ip config external_ip
+ local conf ifname ifname6
. /lib/functions/network.sh
if [ -n "$external_iface" ] ; then
@@ -120,20 +119,6 @@ upnpd() {
log "No internal networks configured, not starting" daemon.err
return 1
fi
- {
- echo "ext_ifname=$ifname"
- echo "ext_ifname6=$ifname6"
- [ -n "$external_ip" ] && echo "ext_ip=$external_ip"
-
- [ "$enable_protocols" = "all" ] && echo "enable_upnp=yes" && echo "enable_pcp_pmp=yes"
- [ "$enable_protocols" = "upnp-igd" ] && echo "enable_upnp=yes" && echo "enable_pcp_pmp=no"
- [ "$enable_protocols" = "pcp+nat-pmp" ] && echo "enable_upnp=no" && echo "enable_pcp_pmp=yes"
- [ "$allow_third_party_mapping" = "0" ] && echo "secure_mode=yes" && echo "pcp_allow_thirdparty=no"
- [ "$allow_third_party_mapping" = "1" ] && echo "secure_mode=no" && echo "pcp_allow_thirdparty=yes"
- [ "$allow_third_party_mapping" = "upnp-igd" ] && echo "secure_mode=no" && echo "pcp_allow_thirdparty=no"
- [ "$allow_third_party_mapping" = "pcp" ] && echo "secure_mode=yes" && echo "pcp_allow_thirdparty=yes"
- [ "$system_uptime" = "0" ] && echo "system_uptime=no" || echo "system_uptime=yes"
- [ "$upnp_igd_compat" = "igdv1" ] && echo "force_igd_desc_v1=yes" || echo "force_igd_desc_v1=no"
# Only perform an STUN CGNAT test if necessary, with a private/CGNAT-reserved external IPv4
local extipv4 extipv4private
extipv4="$(ip -4 addr show dev "$ifname" | grep inet | head -1 | sed -E "s/.*inet ([0-9.]+).*/\1/")"
@@ -142,6 +127,11 @@ upnpd() {
172.2[0-9].* | 172.3[0-1].* | 192.0.0.[1-6] | 192.168.* | 198.1[89].*) extipv4private=1 ;;
esac
[ "$extipv4private" = "1" ] && log "Private/CGNAT-reserved external IPv4 detected ($extipv4)"
+ {
+ echo "# Daemon"
+ [ "$enable_protocols" = "all" ] && echo "enable_upnp=yes" && echo "enable_pcp_pmp=yes"
+ [ "$enable_protocols" = "upnp-igd" ] && echo "enable_upnp=yes" && echo "enable_pcp_pmp=no"
+ [ "$enable_protocols" = "pcp+nat-pmp" ] && echo "enable_upnp=no" && echo "enable_pcp_pmp=yes"
if [ "$extipv4private" = "1" ] && [ "$allow_cgnat" != "0" ]; then
[ "$allow_cgnat" = "1" ] && echo "ext_perform_stun=yes"
[ "$allow_cgnat" = "allow-filtered" ] && echo "ext_perform_stun=allow-filtered"
@@ -150,31 +140,37 @@ upnpd() {
echo "ext_stun_host=${stun_host%%:*}"
[ "${stun_host%%:*}" != "${stun_host##*:}" ] && echo "ext_stun_port=${stun_host##*:}"
fi
+ [ "$allow_third_party_mapping" = "0" ] && echo "secure_mode=yes" && echo "pcp_allow_thirdparty=no"
+ [ "$allow_third_party_mapping" = "1" ] && echo "secure_mode=no" && echo "pcp_allow_thirdparty=yes"
+ [ "$allow_third_party_mapping" = "upnp-igd" ] && echo "secure_mode=no" && echo "pcp_allow_thirdparty=no"
+ [ "$allow_third_party_mapping" = "pcp" ] && echo "secure_mode=yes" && echo "pcp_allow_thirdparty=yes"
[ "$ipv6_disable" = "0" ] && echo "ipv6_disable=no" || echo "ipv6_disable=yes"
-
- [ -n "$download_kbps" ] && echo "bitrate_down=$((download_kbps * 1000))"
- [ -n "$upload_kbps" ] && echo "bitrate_up=$((upload_kbps * 1000))"
-
+ [ "$system_uptime" = "0" ] && echo "system_uptime=no" || echo "system_uptime=yes"
touch "$lease_file" && echo "lease_file=$lease_file"
[ "$ipv6_disable" = "0" ] && touch "${lease_file}-ipv6" && echo "lease_file6=${lease_file}-ipv6"
- [ -n "$friendly_name" ] && echo "friendly_name=$friendly_name"
- [ -n "$presentation_url" ] && echo "presentation_url=$presentation_url"
- [ -n "$notify_interval" ] && echo "notify_interval=$notify_interval"
- echo "serial=$serial_number"
- echo "model_number=$model_number"
- echo "http_port=$http_port"
-
- [ -z "$uuid" ] && {
- log "Generate UPnP IGD UUID"
- uuid="$(cat /proc/sys/kernel/random/uuid)"
- uci set upnpd.config.uuid="$uuid"
- uci commit upnpd
- }
- [ "$uuid" != "nocli" ] && echo "uuid=$uuid" || log "uuid=nocli deprecated, set to 00000000-0000-0000-0000-000000000000 instead"
+ if [ "$enable_protocols" = "upnp-igd" ] || [ "$enable_protocols" = "all" ]; then
+ echo "# UPnP IGD"
+ [ "$upnp_igd_compat" = "igdv1" ] && echo "force_igd_desc_v1=yes" || echo "force_igd_desc_v1=no"
+ [ -n "$download_kbps" ] && echo "bitrate_down=$((download_kbps * 1000))"
+ [ -n "$upload_kbps" ] && echo "bitrate_up=$((upload_kbps * 1000))"
+ [ -n "$friendly_name" ] && echo "friendly_name=$(xml_encode "$friendly_name")"
+ [ -n "$model_number" ] && echo "model_number=$(xml_encode "$model_number")" || echo "model_number="
+ [ -n "$serial_number" ] && echo "serial=$(xml_encode "$serial_number")" || echo "serial="
+ [ -n "$presentation_url" ] && echo "presentation_url=$presentation_url"
+ [ -z "$uuid" ] && {
+ log "Generate UPnP IGD UUID"
+ uuid="$(cat /proc/sys/kernel/random/uuid)"
+ uci set upnpd.config.uuid="$uuid"
+ uci commit upnpd
+ }
+ [ "$uuid" != "nocli" ] && echo "uuid=$uuid" || log "uuid=nocli deprecated, set to 00000000-0000-0000-0000-000000000000 instead"
+ echo "http_port=$http_port"
+ [ -n "$notify_interval" ] && echo "notify_interval=$notify_interval"
+ fi
if [ "$FW" = "fw4" ]; then
- #When using nftables configure miniupnpd to use its own table and chains
+ echo "# Firewall backend"
echo "upnp_table_name=fw4"
echo "upnp_nat_table_name=fw4"
echo "upnp_forward_chain=upnp_forward"
@@ -182,6 +178,11 @@ upnpd() {
#echo "upnp_nat_postrouting_chain=upnp_postrouting"
fi
+ echo "# External network interface"
+ echo "ext_ifname=$ifname"
+ echo "ext_ifname6=${ifname6:-$ifname}"
+ [ -n "$external_ip" ] && echo "ext_ip=$external_ip"
+
echo "# Enable internal networks / access control"
config_foreach upnpd_add_int_network_and_defaults internal_network pre-acl
config_foreach conf_rule_add perm_rule
@@ -191,7 +192,6 @@ upnpd() {
fi
if [ -n "$ifname" ]; then
- # start firewall
if [ "$FW" = "fw4" ]; then
nft -s -t -n list chain inet fw4 upnp_forward >/dev/null 2>&1 || fw4 reload
else
@@ -234,6 +234,11 @@ log() {
logger -s -p "${2:-daemon.notice}" -t "miniupnpd-init" "$1" || echo "miniupnpd-init: $1" >&2
}
+xml_encode() {
+ # Encode required XML entities of text UPnP IGD config options until the daemon does so
+ echo "$1" | sed "s/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g"
+}
+
upnpd_add_int_network_and_defaults() {
local cfg="$1"
local interface access_defaults accept_ports reject_ports check_acl
From dde9664570cac7236cb243b663ca0ce2c61f5c35 Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 06/10] miniupnpd: rename UCI section to settings v2.0
Rename UCI section `config` (v1.0) -> `settings` (v2.0), helps on
migration and to distinguish the updated config from the previous one
(to merge with prior)
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
miniupnpd/files/firewall3.include | 4 +-
miniupnpd/files/miniupnpd.hotplug | 6 +--
miniupnpd/files/miniupnpd.init | 50 +++++++++----------
.../files/upnpd-migration.uci-defaults | 13 +++++
miniupnpd/files/upnpd.config | 2 +-
5 files changed, 44 insertions(+), 31 deletions(-)
diff --git a/miniupnpd/files/firewall3.include b/miniupnpd/files/firewall3.include
index 4fd483974306c1..53ca8dc716cc2f 100644
--- a/miniupnpd/files/firewall3.include
+++ b/miniupnpd/files/firewall3.include
@@ -48,11 +48,11 @@ add_extzone_rules() {
# By default, user configuration is king.
-for ext_iface in $(uci -q get upnpd.config.external_iface); do
+for ext_iface in $(uci -q get upnpd.settings.external_iface); do
add_extzone_rules $(fw3 -q network "$ext_iface")
done
-add_extzone_rules $(uci -q get upnpd.config.external_zone)
+add_extzone_rules $(uci -q get upnpd.settings.external_zone)
[ "$ADDED" -ne 0 ] && exit 0
diff --git a/miniupnpd/files/miniupnpd.hotplug b/miniupnpd/files/miniupnpd.hotplug
index 5d34c441a829ea..20ee344c9a0e72 100644
--- a/miniupnpd/files/miniupnpd.hotplug
+++ b/miniupnpd/files/miniupnpd.hotplug
@@ -10,9 +10,9 @@
[ "$ACTION" != "ifup" ] && /etc/init.d/miniupnpd running && exit 0
tmpconf="/var/etc/miniupnpd.conf"
-external_iface=$(uci -q get upnpd.config.external_iface)
-external_iface6=$(uci -q get upnpd.config.external_iface6)
-external_zone=$(uci -q get upnpd.config.external_zone)
+external_iface=$(uci -q get upnpd.settings.external_iface)
+external_iface6=$(uci -q get upnpd.settings.external_iface6)
+external_zone=$(uci -q get upnpd.settings.external_zone)
[ -x "$(command -v nft)" ] && FW="fw4" || FW="fw3"
. /lib/functions/network.sh
diff --git a/miniupnpd/files/miniupnpd.init b/miniupnpd/files/miniupnpd.init
index 969b22e60cacc4..80aa789c039803 100644
--- a/miniupnpd/files/miniupnpd.init
+++ b/miniupnpd/files/miniupnpd.init
@@ -44,42 +44,42 @@ conf_rule_add() {
upnpd() {
config_load "upnpd"
local enabled
- config_get enabled config enabled 0
+ config_get enabled settings enabled 0
if [ "$enabled" != "1" ]; then
log "Service disabled, enabled UCI option not set"
return 1
fi
# Daemon
local enable_protocols allow_cgnat stun_host allow_third_party_mapping ipv6_disable system_uptime log_output lease_file config_file
- config_get enable_protocols config enable_protocols all
- config_get allow_cgnat config allow_cgnat 0
- config_get stun_host config stun_host stun.nextcloud.com
- config_get allow_third_party_mapping config allow_third_party_mapping 0
- config_get ipv6_disable config ipv6_disable 0
- config_get system_uptime config system_uptime 1
- config_get log_output config log_output
- config_get lease_file config lease_file /var/run/miniupnpd.leases
- config_get config_file config config_file
+ config_get enable_protocols settings enable_protocols all
+ config_get allow_cgnat settings allow_cgnat 0
+ config_get stun_host settings stun_host stun.nextcloud.com
+ config_get allow_third_party_mapping settings allow_third_party_mapping 0
+ config_get ipv6_disable settings ipv6_disable 0
+ config_get system_uptime settings system_uptime 1
+ config_get log_output settings log_output
+ config_get lease_file settings lease_file /var/run/miniupnpd.leases
+ config_get config_file settings config_file
# UPnP IGD
local upnp_igd_compat download_kbps upload_kbps friendly_name model_number serial_number presentation_url uuid http_port notify_interval
- config_get upnp_igd_compat config upnp_igd_compat igdv1
- config_get download_kbps config download_kbps
- config_get upload_kbps config upload_kbps
- config_get friendly_name config friendly_name "OpenWrt UPnP IGD & PCP"
- config_get model_number config model_number
- config_get serial_number config serial_number
- config_get presentation_url config presentation_url
- config_get uuid config uuid
- config_get http_port config http_port 5000
- config_get notify_interval config notify_interval
+ config_get upnp_igd_compat settings upnp_igd_compat igdv1
+ config_get download_kbps settings download_kbps
+ config_get upload_kbps settings upload_kbps
+ config_get friendly_name settings friendly_name "OpenWrt UPnP IGD & PCP"
+ config_get model_number settings model_number
+ config_get serial_number settings serial_number
+ config_get presentation_url settings presentation_url
+ config_get uuid settings uuid
+ config_get http_port settings http_port 5000
+ config_get notify_interval settings notify_interval
# External network interface
local external_iface external_iface6 external_zone external_ip
- config_get external_iface config external_iface
- config_get external_iface6 config external_iface6
- config_get external_zone config external_zone
- config_get external_ip config external_ip
+ config_get external_iface settings external_iface
+ config_get external_iface6 settings external_iface6
+ config_get external_zone settings external_zone
+ config_get external_ip settings external_ip
local conf ifname ifname6
. /lib/functions/network.sh
@@ -161,7 +161,7 @@ upnpd() {
[ -z "$uuid" ] && {
log "Generate UPnP IGD UUID"
uuid="$(cat /proc/sys/kernel/random/uuid)"
- uci set upnpd.config.uuid="$uuid"
+ uci set upnpd.settings.uuid="$uuid"
uci commit upnpd
}
[ "$uuid" != "nocli" ] && echo "uuid=$uuid" || log "uuid=nocli deprecated, set to 00000000-0000-0000-0000-000000000000 instead"
diff --git a/miniupnpd/files/upnpd-migration.uci-defaults b/miniupnpd/files/upnpd-migration.uci-defaults
index 19445c9198994b..efde6ca1232212 100644
--- a/miniupnpd/files/upnpd-migration.uci-defaults
+++ b/miniupnpd/files/upnpd-migration.uci-defaults
@@ -4,7 +4,12 @@ log() {
logger -s -p "${2:-daemon.notice}" -t "upnpd" "$1" || echo "upnpd: $1" >&2
}
+# Skip migration with existing settings (v2.0) or with no config (v1.0) UCI section
+# Enables the creation of a merged v1.0/v2.0 config file
+{ uci -q get upnpd.settings >/dev/null || ! uci -q get upnpd.config >/dev/null; } && exit 0
+
log "Check UCI options in /etc/config/upnpd to be migrated to v2.0"
+cp /etc/config/upnpd /tmp
# Set missing enabled option to fix previously different defaults in LuCI/config (0) and init UCI (1)
if ! uci -q get upnpd.config.enabled >/dev/null; then
@@ -154,6 +159,14 @@ if ! uci -q get upnpd.@internal_network[0] >/dev/null; then
uci -q delete upnpd.config.internal_iface
fi
+# Rename UCI section config -> settings (v2.0)
+if uci -q get upnpd.config >/dev/null; then
+ log "Rename UCI section config -> settings (v2.0)" && uci rename upnpd.config="settings" ||
+ log "Error renaming the UCI section" daemon.err
+fi
+
uci commit upnpd >/dev/null
+log "Previous v1.0 config file copied to /tmp/upnpd (kept until reboot)"
+
exit 0
diff --git a/miniupnpd/files/upnpd.config b/miniupnpd/files/upnpd.config
index 6344a60071bd72..2245b0a6f015f8 100644
--- a/miniupnpd/files/upnpd.config
+++ b/miniupnpd/files/upnpd.config
@@ -1,6 +1,6 @@
# UPnP IGD & PCP/NAT-PMP Service Settings (v2.0)
-config upnpd 'config'
+config upnpd 'settings'
option enabled '0'
# Can be set to all/upnp-igd/pcp+nat-pmp
option enable_protocols 'all'
From 19bbb0dd7e9c878e39fe830e173dc54c150dd2a1 Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 07/10] miniupnpd: update ACL options, migrate section
- The ACL is now rejected last if not accepted by access defaults. Add
(disabled) ACL template entries on migration
- Migrate ACL entries to the new section name `acl_entry`
- The following ACL UCI options been added or changed, and the previous
options are migrated on updating:
acl_entry UCI options | Change | Previous name
----------------------------|----------------------------|--------------
action | New/updated values (1) |
int_port | Remove colon separator (2) | int_ports
ext_port | Remove colon separator (2) | ext_ports
descr_filter | New option (3) |
1. Allow disabled, and update action option to use the nftables terms
(allow/deny -> accept/reject). To avoid adding inverted actions when
changing via LuCI, ensure any missing are set, as LuCI and UCI had
not matching action defaults. Missing actions are now ignored/logged
2. Ensure that the hyphen (-) is only used as a port range separator by
migration, as the colon (:) is not valid in LuCI
3. Add missing UCI option to set a regular expression to check for a
UPnP IGD IPv4 port map description, and fix the current collision
with the comment field which was not noticed due to a daemon bug
https://redirect.github.com/openwrt/packages/pull/24495
https://redirect.github.com/miniupnp/miniupnp/pull/853
- Refactoring by adding a more universal usable `is_port_or_range`
function instead of `upnpd_get_port_range` and check if it has a valid
range, and removes a shellcheck warning
- Rename `conf_rule_add` function to `upnpd_add_acl_entry`
(to merge with prior)
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
miniupnpd/files/miniupnpd.init | 86 ++++++++-----------
.../files/upnpd-migration.uci-defaults | 86 +++++++++++++++++++
miniupnpd/files/upnpd.config | 22 ++---
3 files changed, 131 insertions(+), 63 deletions(-)
diff --git a/miniupnpd/files/miniupnpd.init b/miniupnpd/files/miniupnpd.init
index 80aa789c039803..b4e09eb84067db 100644
--- a/miniupnpd/files/miniupnpd.init
+++ b/miniupnpd/files/miniupnpd.init
@@ -7,38 +7,39 @@ USE_PROCD=1
PROG=/usr/sbin/miniupnpd
[ -x "$(command -v nft)" ] && FW="fw4" || FW="fw3"
-upnpd_get_port_range() {
- local var="$1"; shift
- local val
-
- config_get val "$@"
-
- case "$val" in
- [0-9]*[:-][0-9]*)
- export -n -- "${var}_start=${val%%[:-]*}"
- export -n -- "${var}_end=${val##*[:-]}"
- ;;
- [0-9]*)
- export -n -- "${var}_start=$val"
- export -n -- "${var}_end="
- ;;
- esac
+is_port_or_range() {
+ [ "$1" = "0" ] && [ "$2" != "allowport0" ] && return 1
+ [ "$1" -ge "1" ] 2>/dev/null && [ "$1" -le "65535" ] 2>/dev/null && return 0
+ [ "$2" = "allowport0" ] && local minport=0 || local minport=1
+ [ "${1%%-*}" -ge "$minport" ] 2>/dev/null && [ "${1%%-*}" -le "65535" ] 2>/dev/null &&
+ [ "${1##*-}" -ge "$minport" ] 2>/dev/null && [ "${1##*-}" -le "65535" ] 2>/dev/null &&
+ [ "${1##*-}" -ge "${1%%-*}" ] 2>/dev/null && return 0 || return 1
}
-conf_rule_add() {
+upnpd_add_acl_entry() {
local cfg="$1"
- local action int_addr
- local ext_start ext_end int_start int_end comment
- config_get action "$cfg" action "deny" # allow or deny
- upnpd_get_port_range "ext" "$cfg" ext_ports "0-65535" # external ports: x, x-y, x:y
- config_get int_addr "$cfg" int_addr "0.0.0.0/0" # ip or network and subnet mask (internal)
- upnpd_get_port_range "int" "$cfg" int_ports "0-65535" # internal ports: x, x-y, x:y or range
- config_get comment "$cfg" comment "ACL" # comment
-
- # Make a single IP IP/32 so that miniupnpd.conf can use it.
- [ "${int_addr%/*}" = "$int_addr" ] && int_addr="$int_addr/32"
-
- echo "$action $ext_start${ext_end:+-}$ext_end $int_addr $int_start${int_end:+-}$int_end #$comment"
+ local comment int_addr int_port ext_port descr_filter action
+ config_get comment "$cfg" comment "unspecified" # comment
+ config_get int_addr "$cfg" int_addr "0.0.0.0/0" # IPv4 or network and subnet mask (internal)
+ config_get int_port "$cfg" int_port "1-65535" # internal port/range: x or x-y
+ config_get ext_port "$cfg" ext_port "1-65535" # external port/range: x or x-y
+ config_get descr_filter "$cfg" descr_filter # description regex filter (must be built in)
+ config_get action "$cfg" action # accept/reject/disabled
+ ! is_port_or_range "$int_port" allowport0 &&
+ log "ACL entry: Invalid port or port range ($int_port) in int_port ignored" daemon.warn && int_port=1-65535
+ ! is_port_or_range "$ext_port" allowport0 &&
+ log "ACL entry: Invalid port or port range ($ext_port) in ext_port ignored" daemon.warn && ext_port=1-65535
+ [ "$descr_filter" != "" ] && descr_filter=" \"$descr_filter\""
+ if [ "$action" = "accept" ]; then
+ action=allow
+ elif [ "$action" = "reject" ]; then
+ action=deny
+ elif [ "$action" != "disabled" ]; then
+ log "ACL entry: Entry with invalid action ($action) ignored" daemon.warn
+ action=disabled
+ fi
+ [ "$action" = "disabled" ] && return 0
+ echo "$action $ext_port $int_addr $int_port${descr_filter} # $comment"
}
upnpd() {
@@ -185,8 +186,9 @@ upnpd() {
echo "# Enable internal networks / access control"
config_foreach upnpd_add_int_network_and_defaults internal_network pre-acl
- config_foreach conf_rule_add perm_rule
+ config_foreach upnpd_add_acl_entry acl_entry
config_foreach upnpd_add_int_network_and_defaults internal_network post-acl
+ echo "deny 1-65535 0.0.0.0/0 1-65535 # Reject ACL by default"
} > "$tmpconf"
fi
@@ -255,18 +257,8 @@ upnpd_add_int_network_and_defaults() {
echo "# Enable internal network $interface ($device) with access defaults $access_defaults and check ACL $check_acl"
echo "listening_ip=$device"
for rejectport in $reject_ports; do
- if [ "$rejectport" -ge "1" ] 2>/dev/null && [ "$rejectport" -le "65535" ] 2>/dev/null ||
- {
- [ "${rejectport%%-*}" -ge "1" ] 2>/dev/null &&
- [ "${rejectport%%-*}" -le "65535" ] 2>/dev/null &&
- [ "${rejectport##*-}" -ge "1" ] 2>/dev/null &&
- [ "${rejectport##*-}" -le "65535" ] 2>/dev/null &&
- [ "${rejectport##*-}" -ge "${rejectport%%-*}" ] 2>/dev/null
- }; then
- echo "deny $rejectport $subnet $rejectport # Reject port $rejectport on $interface"
- else
+ is_port_or_range "$rejectport" allowport0 && echo "deny $rejectport $subnet $rejectport # Reject port $rejectport on $interface" ||
log "Invalid port or port range ($rejectport) in reject_ports ignored" daemon.warn
- fi
done
fi
if { [ "$2" = "post-acl" ] && [ "$check_acl" = "1" ]; } ||
@@ -283,18 +275,8 @@ upnpd_add_int_network_and_defaults() {
log "Invalid access_defaults ($access_defaults) ignored" daemon.warn
fi
for acceptport in $accessdefaultsports $accept_ports; do
- if [ "$acceptport" -ge "1" ] 2>/dev/null && [ "$acceptport" -le "65535" ] 2>/dev/null ||
- {
- [ "${acceptport%%-*}" -ge "1" ] 2>/dev/null &&
- [ "${acceptport%%-*}" -le "65535" ] 2>/dev/null &&
- [ "${acceptport##*-}" -ge "1" ] 2>/dev/null &&
- [ "${acceptport##*-}" -le "65535" ] 2>/dev/null &&
- [ "${acceptport##*-}" -ge "${acceptport%%-*}" ] 2>/dev/null
- }; then
- echo "allow $acceptport $subnet $acceptport # Accept port $acceptport on $interface"
- else
+ is_port_or_range "$acceptport" && echo "allow $acceptport $subnet $acceptport # Accept port $acceptport on $interface" ||
log "Invalid port or port range ($acceptport) in accept_ports ignored" daemon.warn
- fi
done
fi
if [ "$2" = "pre-acl" ] && [ "$check_acl" = "0" ]; then
diff --git a/miniupnpd/files/upnpd-migration.uci-defaults b/miniupnpd/files/upnpd-migration.uci-defaults
index efde6ca1232212..ac0c8d3245c175 100644
--- a/miniupnpd/files/upnpd-migration.uci-defaults
+++ b/miniupnpd/files/upnpd-migration.uci-defaults
@@ -147,6 +147,90 @@ if [ "$(uci -q get upnpd.config.notify_interval)" -le "900" ] 2>/dev/null; then
uci delete upnpd.config.notify_interval
fi
+# Migrate ACL to new section, note that an empty ACL is now rejected alone
+# a) Empty/unmodified ACL: Set access defaults, add/update template entries
+# b) Modified ACL:
+# - Add missing entry action to avoid adding inverted actions when changing via LuCI
+# - Update entry action allow/deny -> accept/reject
+# - Update entry port options to only use the LuCI (and daemon) supported hyphen (-) as port range separator
+# - Not using access defaults, add template entries
+if uci -q get upnpd.@acl_entry[0] >/dev/null; then
+ log "Error migrating ACL, as the new UCI section already exists" daemon.err
+elif ! uci -q get upnpd.@perm_rule[0] >/dev/null; then
+ log "Empty ACL: Set access defaults, add templates, empty ACL rejected alone"
+ access_defaults=accept-all-ports
+ check_acl=0
+ addtemplateentries=1
+elif ! uci -q get upnpd.@perm_rule[2] >/dev/null &&
+ [ "$(uci -q get upnpd.@perm_rule[0].int_addr)" = "0.0.0.0/0" ] &&
+ [ "$(uci -q get upnpd.@perm_rule[0].int_ports)" = "1024-65535" ] &&
+ [ "$(uci -q get upnpd.@perm_rule[0].ext_ports)" = "1024-65535" ] &&
+ [ "$(uci -q get upnpd.@perm_rule[0].action)" = "allow" ] &&
+ [ "$(uci -q get upnpd.@perm_rule[1].int_addr)" = "0.0.0.0/0" ] &&
+ [ "$(uci -q get upnpd.@perm_rule[1].int_ports)" = "0-65535" ] &&
+ [ "$(uci -q get upnpd.@perm_rule[1].ext_ports)" = "0-65535" ] &&
+ [ "$(uci -q get upnpd.@perm_rule[1].action)" = "deny" ]; then
+ log "Unmodified ACL: Set access defaults, empty ACL rejected alone"
+ access_defaults=accept-high-ports
+ check_acl=0
+ addtemplateentries=1
+ uci delete upnpd.@perm_rule[-1]
+ uci delete upnpd.@perm_rule[-1]
+else
+ log "Modified ACL: Migrate entries/section, empty ACL rejected alone"
+ addtemplateentries=1
+ entrynr=0
+ while uci -q get upnpd.@perm_rule[$entrynr] >/dev/null; do
+ comment="$(uci -q get upnpd.@perm_rule[$entrynr].comment)"
+ int_addr="$(uci -q get upnpd.@perm_rule[$entrynr].int_addr)"
+ int_port="$(uci -q get upnpd.@perm_rule[$entrynr].int_ports)"
+ ext_port="$(uci -q get upnpd.@perm_rule[$entrynr].ext_ports)"
+ action="$(uci -q get upnpd.@perm_rule[$entrynr].action)"
+ echo "$int_port" | grep -q ":" &&
+ log "ACL entry: Update int_port to use hyphen (-) as port range separator" &&
+ int_port="$(echo "$int_port" | tr ":" "-")"
+ echo "$ext_port" | grep -q ":" &&
+ log "ACL entry: Update ext_port to use hyphen (-) as port range separator" &&
+ ext_port="$(echo "$ext_port" | tr ":" "-")"
+ [ "$action" = "" ] && log "ACL entry: Add missing action option" && action=reject
+ [ "$action" = "allow" ] && action=accept
+ [ "$action" = "deny" ] && action=reject
+ uci batch >/dev/null <<-EOF
+ add upnpd acl_entry
+ set upnpd.@acl_entry[-1].comment="${comment:-unspecified}"
+ set upnpd.@acl_entry[-1].int_addr="${int_addr:-0.0.0.0/0}"
+ set upnpd.@acl_entry[-1].int_port="$int_port"
+ set upnpd.@acl_entry[-1].ext_port="$ext_port"
+ set upnpd.@acl_entry[-1].action="$action"
+ EOF
+ entrynr=$((entrynr + 1))
+ done
+ if [ "${int_addr:-0.0.0.0/0}" = "0.0.0.0/0" ] && [ "${int_port:-0-65535}" = "0-65535" ] &&
+ [ "${ext_port:-0-65535}" = "0-65535" ] && [ "$action" = "reject" ]; then
+ log "ACL entry: Remove no longer useful reject by default entry"
+ uci delete upnpd.@acl_entry[-1]
+ fi
+ while uci -q delete upnpd.@perm_rule[-1]; do :; done
+fi
+if [ "$addtemplateentries" = "1" ]; then
+ uci batch >/dev/null <<-EOF
+ add upnpd acl_entry
+ add upnpd acl_entry
+ set upnpd.@acl_entry[-2].comment="High ports"
+ set upnpd.@acl_entry[-2].int_addr="0.0.0.0/0"
+ set upnpd.@acl_entry[-2].int_port="1024-65535"
+ set upnpd.@acl_entry[-2].ext_port="1024-65535"
+ set upnpd.@acl_entry[-2].action="disabled"
+ set upnpd.@acl_entry[-1].comment="Low/system ports"
+ set upnpd.@acl_entry[-1].int_addr="0.0.0.0/0"
+ set upnpd.@acl_entry[-1].int_port="1-1023"
+ set upnpd.@acl_entry[-1].ext_port="1-1023"
+ set upnpd.@acl_entry[-1].action="disabled"
+ EOF
+ uci -q get upnpd.@acl_entry[-3] >/dev/null &&
+ uci reorder upnpd.@acl_entry[-2]=0 && uci reorder upnpd.@acl_entry[-1]=1
+fi
+
# Migrate internal_iface option to new internal_network section
if ! uci -q get upnpd.@internal_network[0] >/dev/null; then
ifnr=0
@@ -154,6 +238,8 @@ if ! uci -q get upnpd.@internal_network[0] >/dev/null; then
log "Create new internal_network section for $interface"
uci add upnpd internal_network >/dev/null
uci set upnpd.@internal_network[$ifnr].interface="$interface"
+ [ "$access_defaults" != "" ] && uci set upnpd.@internal_network[$ifnr].access_defaults="$access_defaults"
+ [ "$check_acl" = "0" ] && uci set upnpd.@internal_network[$ifnr].check_acl="0"
ifnr=$((ifnr + 1))
done
uci -q delete upnpd.config.internal_iface
diff --git a/miniupnpd/files/upnpd.config b/miniupnpd/files/upnpd.config
index 2245b0a6f015f8..74fc64b58e9bf6 100644
--- a/miniupnpd/files/upnpd.config
+++ b/miniupnpd/files/upnpd.config
@@ -18,18 +18,18 @@ config internal_network
option check_acl '0' # default 1
# Access Control List
-# Empty ACL rejected alone, IPv6 accepted unless disabled
+# Empty ACL rejected alone, IPv6 accepted unless disabled, action: accept/reject
-config perm_rule
- option comment 'Allow high ports'
+config acl_entry
+ option comment 'High ports'
option int_addr '0.0.0.0/0'
- option int_ports '1024-65535'
- option ext_ports '1024-65535'
- option action 'allow'
+ option int_port '1024-65535'
+ option ext_port '1024-65535'
+ option action 'disabled'
-config perm_rule
- option comment 'Default deny'
+config acl_entry
+ option comment 'Low/system ports'
option int_addr '0.0.0.0/0'
- option int_ports '1-65535'
- option ext_ports '1-65535'
- option action 'deny'
+ option int_port '1-1023'
+ option ext_port '1-1023'
+ option action 'disabled'
From b58735acf4c5419987bde2d8947e67d14bcd7b9e Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 08/10] miniupnpd: separate service start and config-gen
- Remove `config_foreach upnpd "upnpd"` and replace it with regular
function call, as init was not designed for a multi-instance setup, as
the same `tmpconf` will be used/overwritten, and non-anonymous section
- Move code to make the custom vs. config file generation decision
earlier, and only perform external interface detection with the second
one, and rename function `upnpd` to `upnpd_generate_config`
- Replace unnecessary `if` cases with `elif` in init/hotplug
- Exit with 1 on errors to get an inactive service status
- Use `procd_add_reload_trigger "firewall"` instead of listening
`/etc/config/firewall`
(to merge with prior)
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
miniupnpd/files/miniupnpd.hotplug | 33 +++---
miniupnpd/files/miniupnpd.init | 146 +++++++++++++-------------
2 files changed, 86 insertions(+), 93 deletions(-)
diff --git a/miniupnpd/files/miniupnpd.hotplug b/miniupnpd/files/miniupnpd.hotplug
index 20ee344c9a0e72..e86097e86010a7 100644
--- a/miniupnpd/files/miniupnpd.hotplug
+++ b/miniupnpd/files/miniupnpd.hotplug
@@ -1,11 +1,9 @@
+#!/bin/sh
/etc/init.d/miniupnpd enabled || exit 0
-# If miniupnpd is not running:
-# - check on _any_ event (event updates may contribute to network_find_wan*)
-
-# If miniupnpd _is_ running:
-# - check only on ifup (otherwise lease updates etc would cause
-# miniupnpd state loss)
+# If daemon is:
+# - not running: check on any event (event updates may contribute to network_find_wan*)
+# - running: check only on ifup (otherwise lease updates etc. would cause daemon state loss)
[ "$ACTION" != "ifup" ] && /etc/init.d/miniupnpd running && exit 0
@@ -16,26 +14,19 @@ external_zone=$(uci -q get upnpd.settings.external_zone)
[ -x "$(command -v nft)" ] && FW="fw4" || FW="fw3"
. /lib/functions/network.sh
-
-if [ -n "$external_iface" ] ; then
+if [ -n "$external_iface" ]; then
network_get_device ifname "$external_iface"
+elif [ -n "$external_zone" ]; then
+ ifname=$($FW -q zone "$external_zone" 2>/dev/null | head -1)
else
- if [ -n "$external_zone" ] ; then
- ifname=$($FW -q zone "$external_zone" 2>/dev/null | head -1)
- else
- network_find_wan external_iface && \
- network_get_device ifname "$external_iface"
- fi
+ network_find_wan external_iface && network_get_device ifname "$external_iface"
fi
-if [ -n "$external_iface6" ] ; then
+if [ -n "$external_iface6" ]; then
network_get_device ifname6 "$external_iface6"
+elif [ -n "$external_zone" ]; then
+ ifname6=$($FW -q zone "$external_zone" 2>/dev/null | head -1)
else
- if [ -n "$external_zone" ] ; then
- ifname6=$($FW -q zone "$external_zone" 2>/dev/null | head -1)
- else
- network_find_wan6 external_iface6 && \
- network_get_device ifname6 "$external_iface6"
- fi
+ network_find_wan6 external_iface6 && network_get_device ifname6 "$external_iface6"
fi
[ "$DEVICE" != "$ifname" ] && [ "$DEVICE" != "$ifname6" ] && exit 0
diff --git a/miniupnpd/files/miniupnpd.init b/miniupnpd/files/miniupnpd.init
index b4e09eb84067db..a66587153d2706 100644
--- a/miniupnpd/files/miniupnpd.init
+++ b/miniupnpd/files/miniupnpd.init
@@ -42,25 +42,16 @@ upnpd_add_acl_entry() {
echo "$action $ext_port $int_addr $int_port${descr_filter} # $comment"
}
-upnpd() {
- config_load "upnpd"
- local enabled
- config_get enabled settings enabled 0
- if [ "$enabled" != "1" ]; then
- log "Service disabled, enabled UCI option not set"
- return 1
- fi
+upnpd_generate_config() {
# Daemon
- local enable_protocols allow_cgnat stun_host allow_third_party_mapping ipv6_disable system_uptime log_output lease_file config_file
+ local enable_protocols allow_cgnat stun_host allow_third_party_mapping ipv6_disable system_uptime lease_file
config_get enable_protocols settings enable_protocols all
config_get allow_cgnat settings allow_cgnat 0
config_get stun_host settings stun_host stun.nextcloud.com
config_get allow_third_party_mapping settings allow_third_party_mapping 0
config_get ipv6_disable settings ipv6_disable 0
config_get system_uptime settings system_uptime 1
- config_get log_output settings log_output
config_get lease_file settings lease_file /var/run/miniupnpd.leases
- config_get config_file settings config_file
# UPnP IGD
local upnp_igd_compat download_kbps upload_kbps friendly_name model_number serial_number presentation_url uuid http_port notify_interval
@@ -82,53 +73,51 @@ upnpd() {
config_get external_zone settings external_zone
config_get external_ip settings external_ip
- local conf ifname ifname6
+ local ifname ifname6
. /lib/functions/network.sh
-
- if [ -n "$external_iface" ] ; then
+ if [ -n "$external_iface" ]; then
network_get_device ifname "$external_iface"
+ elif [ -n "$external_zone" ]; then
+ ifname=$($FW -q zone "$external_zone" 2>/dev/null | head -1)
else
- if [ -n "$external_zone" ] ; then
- ifname=$($FW -q zone "$external_zone" 2>/dev/null | head -1)
- else
- network_find_wan external_iface && \
- network_get_device ifname "$external_iface"
- fi
+ network_find_wan external_iface && network_get_device ifname "$external_iface"
fi
- if [ -n "$external_iface6" ] ; then
+ if [ -n "$external_iface6" ]; then
network_get_device ifname6 "$external_iface6"
+ elif [ -n "$external_zone" ]; then
+ ifname6=$($FW -q zone "$external_zone" 2>/dev/null | head -1)
else
- if [ -n "$external_zone" ] ; then
- ifname6=$($FW -q zone "$external_zone" 2>/dev/null | head -1)
- else
- network_find_wan6 external_iface6 && \
- network_get_device ifname6 "$external_iface6"
- fi
+ network_find_wan6 external_iface6 && network_get_device ifname6 "$external_iface6"
+ fi
+ if [ "$ifname" = "" ]; then
+ log "No external network interface found, not starting" daemon.err
+ return 1
+ fi
+ # Workaround for daemon bug with UPnP IGDv2 if IPv6 is not ready at start
+ if [ "$ipv6_disable" = "0" ] && [ "$(uci -q get firewall.@defaults[0].disable_ipv6)" != "1" ] &&
+ [ "$(uci -q get network.wan6.disabled)" != "1" ]; then
+ local pass=0
+ while ! ip -6 addr show dev "${ifname6:-$ifname}" | grep -q "inet6 [23]"; do
+ log "IPv6 not ready yet; delay start"
+ sleep 5
+ pass=$((pass + 1))
+ [ "$pass" = "4" ] && log "IPv6 GUA not yet available, UPnP IGD mapping not possible" && break
+ done
+ fi
+ if ! uci -q get upnpd.@internal_network[0].interface >/dev/null; then
+ log "No internal networks configured, not starting" daemon.err
+ return 1
fi
+ # Only perform an STUN CGNAT test if necessary, with a private/CGNAT-reserved external IPv4
+ local extipv4 extipv4private
+ extipv4="$(ip -4 addr show dev "$ifname" | grep inet | head -1 | sed -E "s/.*inet ([0-9.]+).*/\1/")"
+ case "$extipv4" in
+ 10.* | 100.6[4-9].* | 100.[7-9][0-9].* | 100.1[0-1][0-9].* | 100.12[0-7].* | 172.1[6-9].* | \
+ 172.2[0-9].* | 172.3[0-1].* | 192.0.0.[1-6] | 192.168.* | 198.1[89].*) extipv4private=1 ;;
+ esac
+ [ "$extipv4private" = "1" ] && log "Private/CGNAT-reserved external IPv4 detected ($extipv4)"
- if [ -n "$config_file" ]; then
- conf="$config_file"
- else
- local tmpconf="/var/etc/miniupnpd.conf"
- conf="$tmpconf"
- mkdir -p /var/etc
- if [ "$ifname" = "" ]; then
- log "No external network interface found, not starting" daemon.err
- return 1
- fi
- if ! uci -q get upnpd.@internal_network[0].interface >/dev/null; then
- log "No internal networks configured, not starting" daemon.err
- return 1
- fi
- # Only perform an STUN CGNAT test if necessary, with a private/CGNAT-reserved external IPv4
- local extipv4 extipv4private
- extipv4="$(ip -4 addr show dev "$ifname" | grep inet | head -1 | sed -E "s/.*inet ([0-9.]+).*/\1/")"
- case "$extipv4" in
- 10.* | 100.6[4-9].* | 100.[7-9][0-9].* | 100.1[0-1][0-9].* | 100.12[0-7].* | 172.1[6-9].* | \
- 172.2[0-9].* | 172.3[0-1].* | 192.0.0.[1-6] | 192.168.* | 198.1[89].*) extipv4private=1 ;;
- esac
- [ "$extipv4private" = "1" ] && log "Private/CGNAT-reserved external IPv4 detected ($extipv4)"
- {
+ {
echo "# Daemon"
[ "$enable_protocols" = "all" ] && echo "enable_upnp=yes" && echo "enable_pcp_pmp=yes"
[ "$enable_protocols" = "upnp-igd" ] && echo "enable_upnp=yes" && echo "enable_pcp_pmp=no"
@@ -190,32 +179,15 @@ upnpd() {
config_foreach upnpd_add_int_network_and_defaults internal_network post-acl
echo "deny 1-65535 0.0.0.0/0 1-65535 # Reject ACL by default"
- } > "$tmpconf"
- fi
-
- if [ -n "$ifname" ]; then
- if [ "$FW" = "fw4" ]; then
- nft -s -t -n list chain inet fw4 upnp_forward >/dev/null 2>&1 || fw4 reload
- else
- iptables -L MINIUPNPD >/dev/null 2>&1 || fw3 reload
- fi
- fi
-
- procd_open_instance
- procd_set_param file "$conf" "/etc/config/firewall"
- procd_set_param command "$PROG"
- procd_append_param command -f "$conf"
- [ "$log_output" = "info" ] && procd_append_param command -v
- [ "$log_output" = "debug" ] && procd_append_param command -v -v
- procd_close_instance
+ } >"$1"
}
stop_service() {
if [ "$FW" = "fw3" ]; then
- iptables -t nat -F MINIUPNPD 2>/dev/null
- iptables -t nat -F MINIUPNPD-POSTROUTING 2>/dev/null
iptables -t filter -F MINIUPNPD 2>/dev/null
[ -x /usr/sbin/ip6tables ] && ip6tables -t filter -F MINIUPNPD 2>/dev/null
+ iptables -t nat -F MINIUPNPD 2>/dev/null
+ iptables -t nat -F MINIUPNPD-POSTROUTING 2>/dev/null
else
nft flush chain inet fw4 upnp_forward 2>/dev/null
nft flush chain inet fw4 upnp_prerouting 2>/dev/null
@@ -225,11 +197,41 @@ stop_service() {
start_service() {
config_load "upnpd"
- config_foreach upnpd "upnpd"
+ local enabled config_file log_output conf
+ config_get enabled settings enabled 0
+ config_get config_file settings config_file
+ config_get log_output settings log_output
+ if [ "$enabled" != "1" ]; then
+ log "Service disabled, enabled UCI option not set"
+ return 1
+ fi
+
+ if [ -n "$config_file" ]; then
+ conf="$config_file"
+ else
+ local tmpconf="/var/etc/miniupnpd.conf"
+ conf="$tmpconf"
+ mkdir -p /var/etc
+ upnpd_generate_config "$tmpconf" || return 1
+ fi
+
+ if [ "$FW" = "fw4" ]; then
+ nft -s -t -n list chain inet fw4 upnp_forward >/dev/null 2>&1 || fw4 reload
+ else
+ iptables -L MINIUPNPD >/dev/null 2>&1 || fw3 reload
+ fi
+
+ procd_open_instance
+ procd_set_param file "$conf"
+ procd_set_param command "$PROG"
+ procd_append_param command -f "$conf"
+ [ "$log_output" = "info" ] && procd_append_param command -v
+ [ "$log_output" = "debug" ] && procd_append_param command -v -v
+ procd_close_instance
}
service_triggers() {
- procd_add_reload_trigger "upnpd"
+ procd_add_reload_trigger "upnpd" "firewall"
}
log() {
From 562d07c55e1e8d46e6ec45f4397c9a02ba5429c5 Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 09/10] miniupnpd: rearrange init, format `firewall3.include`
- Arrange `start_service` and main init functions first
- Format `firewall3.include` using shfmt
(to merge with prior)
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
miniupnpd/files/firewall3.include | 44 +++----
miniupnpd/files/miniupnpd.init | 164 +++++++++++++-------------
2 files changed, 104 insertions(+), 104 deletions(-)
diff --git a/miniupnpd/files/firewall3.include b/miniupnpd/files/firewall3.include
index 53ca8dc716cc2f..8fc2144db31932 100644
--- a/miniupnpd/files/firewall3.include
+++ b/miniupnpd/files/firewall3.include
@@ -20,36 +20,36 @@ iptables_prepend_rule() {
local chain="$3"
local target="$4"
- $iptables "$IPTARGS" -t "$table" -I "$chain" $($iptables "$IPTARGS" -t "$table" --line-numbers -nL "$chain" | \
+ $iptables "$IPTARGS" -t "$table" -I "$chain" $($iptables "$IPTARGS" -t "$table" --line-numbers -nL "$chain" |
sed -ne '$s/[^0-9].*//p') -j "$target"
}
ADDED=0
add_extzone_rules() {
- local ext_zone="$1"
-
- [ -z "$ext_zone" ] && return
-
- # IPv4 - due to NAT, need to add both to nat and filter table
- # need to insert as penultimate rule for input & forward & postrouting since final rule might be a fw3 REJECT
- iptables_prepend_rule "$IPTABLES" filter "zone_${ext_zone}_input" MINIUPNPD
- iptables_prepend_rule "$IPTABLES" filter "zone_${ext_zone}_forward" MINIUPNPD
- $IPTABLES -t nat -A "zone_${ext_zone}_prerouting" -j MINIUPNPD
- iptables_prepend_rule "$IPTABLES" nat "zone_${ext_zone}_postrouting" MINIUPNPD-POSTROUTING
-
- # IPv6 if available - filter only
- [ -x $IP6TABLES ] && {
- iptables_prepend_rule "$IP6TABLES" filter "zone_${ext_zone}_input" MINIUPNPD
- iptables_prepend_rule "$IP6TABLES" filter "zone_${ext_zone}_forward" MINIUPNPD
- }
- ADDED=$(($ADDED + 1))
+ local ext_zone="$1"
+
+ [ -z "$ext_zone" ] && return
+
+ # IPv4 - due to NAT, need to add both to nat and filter table
+ # need to insert as penultimate rule for input & forward & postrouting since final rule might be a fw3 REJECT
+ iptables_prepend_rule "$IPTABLES" filter "zone_${ext_zone}_input" MINIUPNPD
+ iptables_prepend_rule "$IPTABLES" filter "zone_${ext_zone}_forward" MINIUPNPD
+ $IPTABLES -t nat -A "zone_${ext_zone}_prerouting" -j MINIUPNPD
+ iptables_prepend_rule "$IPTABLES" nat "zone_${ext_zone}_postrouting" MINIUPNPD-POSTROUTING
+
+ # IPv6 if available - filter only
+ [ -x $IP6TABLES ] && {
+ iptables_prepend_rule "$IP6TABLES" filter "zone_${ext_zone}_input" MINIUPNPD
+ iptables_prepend_rule "$IP6TABLES" filter "zone_${ext_zone}_forward" MINIUPNPD
+ }
+ ADDED=$(($ADDED + 1))
}
# By default, user configuration is king.
for ext_iface in $(uci -q get upnpd.settings.external_iface); do
- add_extzone_rules $(fw3 -q network "$ext_iface")
+ add_extzone_rules $(fw3 -q network "$ext_iface")
done
add_extzone_rules $(uci -q get upnpd.settings.external_zone)
@@ -66,7 +66,7 @@ network_find_wan wan_iface
network_find_wan6 wan6_iface
for ext_iface in $wan_iface $wan6_iface; do
- # fw3 -q network fails on sub-interfaces => map to device first
- network_get_device ext_device $ext_iface
- add_extzone_rules $(fw3 -q device "$ext_device")
+ # fw3 -q network fails on sub-interfaces => map to device first
+ network_get_device ext_device $ext_iface
+ add_extzone_rules $(fw3 -q device "$ext_device")
done
diff --git a/miniupnpd/files/miniupnpd.init b/miniupnpd/files/miniupnpd.init
index a66587153d2706..a6611c0e732e0d 100644
--- a/miniupnpd/files/miniupnpd.init
+++ b/miniupnpd/files/miniupnpd.init
@@ -7,39 +7,56 @@ USE_PROCD=1
PROG=/usr/sbin/miniupnpd
[ -x "$(command -v nft)" ] && FW="fw4" || FW="fw3"
-is_port_or_range() {
- [ "$1" = "0" ] && [ "$2" != "allowport0" ] && return 1
- [ "$1" -ge "1" ] 2>/dev/null && [ "$1" -le "65535" ] 2>/dev/null && return 0
- [ "$2" = "allowport0" ] && local minport=0 || local minport=1
- [ "${1%%-*}" -ge "$minport" ] 2>/dev/null && [ "${1%%-*}" -le "65535" ] 2>/dev/null &&
- [ "${1##*-}" -ge "$minport" ] 2>/dev/null && [ "${1##*-}" -le "65535" ] 2>/dev/null &&
- [ "${1##*-}" -ge "${1%%-*}" ] 2>/dev/null && return 0 || return 1
+start_service() {
+ config_load "upnpd"
+ local enabled config_file log_output conf
+ config_get enabled settings enabled 0
+ config_get config_file settings config_file
+ config_get log_output settings log_output
+ if [ "$enabled" != "1" ]; then
+ log "Service disabled, enabled UCI option not set"
+ return 1
+ fi
+
+ if [ -n "$config_file" ]; then
+ conf="$config_file"
+ else
+ local tmpconf="/var/etc/miniupnpd.conf"
+ conf="$tmpconf"
+ mkdir -p /var/etc
+ upnpd_generate_config "$tmpconf" || return 1
+ fi
+
+ if [ "$FW" = "fw4" ]; then
+ nft -s -t -n list chain inet fw4 upnp_forward >/dev/null 2>&1 || fw4 reload
+ else
+ iptables -L MINIUPNPD >/dev/null 2>&1 || fw3 reload
+ fi
+
+ procd_open_instance
+ procd_set_param file "$conf"
+ procd_set_param command "$PROG"
+ procd_append_param command -f "$conf"
+ [ "$log_output" = "info" ] && procd_append_param command -v
+ [ "$log_output" = "debug" ] && procd_append_param command -v -v
+ procd_close_instance
}
-upnpd_add_acl_entry() {
- local cfg="$1"
- local comment int_addr int_port ext_port descr_filter action
- config_get comment "$cfg" comment "unspecified" # comment
- config_get int_addr "$cfg" int_addr "0.0.0.0/0" # IPv4 or network and subnet mask (internal)
- config_get int_port "$cfg" int_port "1-65535" # internal port/range: x or x-y
- config_get ext_port "$cfg" ext_port "1-65535" # external port/range: x or x-y
- config_get descr_filter "$cfg" descr_filter # description regex filter (must be built in)
- config_get action "$cfg" action # accept/reject/disabled
- ! is_port_or_range "$int_port" allowport0 &&
- log "ACL entry: Invalid port or port range ($int_port) in int_port ignored" daemon.warn && int_port=1-65535
- ! is_port_or_range "$ext_port" allowport0 &&
- log "ACL entry: Invalid port or port range ($ext_port) in ext_port ignored" daemon.warn && ext_port=1-65535
- [ "$descr_filter" != "" ] && descr_filter=" \"$descr_filter\""
- if [ "$action" = "accept" ]; then
- action=allow
- elif [ "$action" = "reject" ]; then
- action=deny
- elif [ "$action" != "disabled" ]; then
- log "ACL entry: Entry with invalid action ($action) ignored" daemon.warn
- action=disabled
+stop_service() {
+ if [ "$FW" = "fw3" ]; then
+ iptables -t filter -F MINIUPNPD 2>/dev/null
+ [ -x /usr/sbin/ip6tables ] && ip6tables -t filter -F MINIUPNPD 2>/dev/null
+ iptables -t nat -F MINIUPNPD 2>/dev/null
+ iptables -t nat -F MINIUPNPD-POSTROUTING 2>/dev/null
+ else
+ nft flush chain inet fw4 upnp_forward 2>/dev/null
+ nft flush chain inet fw4 upnp_prerouting 2>/dev/null
+ #nft flush chain inet fw4 upnp_postrouting 2>/dev/null
fi
- [ "$action" = "disabled" ] && return 0
- echo "$action $ext_port $int_addr $int_port${descr_filter} # $comment"
+}
+
+service_triggers() {
+ procd_add_reload_trigger "upnpd" "firewall"
}
upnpd_generate_config() {
@@ -182,58 +199,6 @@ upnpd_generate_config() {
} >"$1"
}
-stop_service() {
- if [ "$FW" = "fw3" ]; then
- iptables -t filter -F MINIUPNPD 2>/dev/null
- [ -x /usr/sbin/ip6tables ] && ip6tables -t filter -F MINIUPNPD 2>/dev/null
- iptables -t nat -F MINIUPNPD 2>/dev/null
- iptables -t nat -F MINIUPNPD-POSTROUTING 2>/dev/null
- else
- nft flush chain inet fw4 upnp_forward 2>/dev/null
- nft flush chain inet fw4 upnp_prerouting 2>/dev/null
- #nft flush chain inet fw4 upnp_postrouting 2>/dev/null
- fi
-}
-
-start_service() {
- config_load "upnpd"
- local enabled config_file log_output conf
- config_get enabled settings enabled 0
- config_get config_file settings config_file
- config_get log_output settings log_output
- if [ "$enabled" != "1" ]; then
- log "Service disabled, enabled UCI option not set"
- return 1
- fi
-
- if [ -n "$config_file" ]; then
- conf="$config_file"
- else
- local tmpconf="/var/etc/miniupnpd.conf"
- conf="$tmpconf"
- mkdir -p /var/etc
- upnpd_generate_config "$tmpconf" || return 1
- fi
-
- if [ "$FW" = "fw4" ]; then
- nft -s -t -n list chain inet fw4 upnp_forward >/dev/null 2>&1 || fw4 reload
- else
- iptables -L MINIUPNPD >/dev/null 2>&1 || fw3 reload
- fi
-
- procd_open_instance
- procd_set_param file "$conf"
- procd_set_param command "$PROG"
- procd_append_param command -f "$conf"
- [ "$log_output" = "info" ] && procd_append_param command -v
- [ "$log_output" = "debug" ] && procd_append_param command -v -v
- procd_close_instance
-}
-
-service_triggers() {
- procd_add_reload_trigger "upnpd" "firewall"
-}
-
log() {
logger -s -p "${2:-daemon.notice}" -t "miniupnpd-init" "$1" || echo "miniupnpd-init: $1" >&2
}
@@ -243,6 +208,15 @@ xml_encode() {
echo "$1" | sed "s/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g"
}
+is_port_or_range() {
+ [ "$1" = "0" ] && [ "$2" != "allowport0" ] && return 1
+ [ "$1" -ge "1" ] 2>/dev/null && [ "$1" -le "65535" ] 2>/dev/null && return 0
+ [ "$2" = "allowport0" ] && local minport=0 || local minport=1
+ [ "${1%%-*}" -ge "$minport" ] 2>/dev/null && [ "${1%%-*}" -le "65535" ] 2>/dev/null &&
+ [ "${1##*-}" -ge "$minport" ] 2>/dev/null && [ "${1##*-}" -le "65535" ] 2>/dev/null &&
+ [ "${1##*-}" -ge "${1%%-*}" ] 2>/dev/null && return 0 || return 1
+}
+
upnpd_add_int_network_and_defaults() {
local cfg="$1"
local interface access_defaults accept_ports reject_ports check_acl
@@ -285,3 +259,29 @@ upnpd_add_int_network_and_defaults() {
echo "deny 1-65535 $subnet 1-65535 # Reject ACL by default on $interface"
fi
}
+
+upnpd_add_acl_entry() {
+ local cfg="$1"
+ local comment int_addr int_port ext_port descr_filter action
+ config_get comment "$cfg" comment "unspecified" # comment
+ config_get int_addr "$cfg" int_addr "0.0.0.0/0" # IPv4 or network and subnet mask (internal)
+ config_get int_port "$cfg" int_port "1-65535" # internal port/range: x or x-y
+ config_get ext_port "$cfg" ext_port "1-65535" # external port/range: x or x-y
+ config_get descr_filter "$cfg" descr_filter # description regex filter (must be built in)
+ config_get action "$cfg" action # accept/reject/disabled
+ ! is_port_or_range "$int_port" allowport0 &&
+ log "ACL entry: Invalid port or port range ($int_port) in int_port ignored" daemon.warn && int_port=1-65535
+ ! is_port_or_range "$ext_port" allowport0 &&
+ log "ACL entry: Invalid port or port range ($ext_port) in ext_port ignored" daemon.warn && ext_port=1-65535
+ [ "$descr_filter" != "" ] && descr_filter=" \"$descr_filter\""
+ if [ "$action" = "accept" ]; then
+ action=allow
+ elif [ "$action" = "reject" ]; then
+ action=deny
+ elif [ "$action" != "disabled" ]; then
+ log "ACL entry: Entry with invalid action ($action) ignored" daemon.warn
+ action=disabled
+ fi
+ [ "$action" = "disabled" ] && return 0
+ echo "$action $ext_port $int_addr $int_port${descr_filter} # $comment"
+}
From 397f547a4cda0a6ed83ecbb9dfd6a3f0252294a7 Mon Sep 17 00:00:00 2001
From: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
Date: Tue, 14 Jul 2026 00:00:00 +0000
Subject: [PATCH 10/10] miniupnpd: service-wide access control settings
This implements variant B
upnpd.settings UCI options | Change | Previous name
-----------------------------|---------------------------|--------------
access_defaults | New option (1) |
accept_ports | New option (1) |
reject_ports | New option (2) |
check_acl | New option (1) |
Notes:
1. New options added for default ports that all devices can map,
client-specific permissions using the access control list (ACL) can
extend/override the defaults. Access defaults: accept-high-ports
accept-web+high-ports/accept-web-ports/accept-all-ports
2. Reject ports; overrides other settings. By default reject unsafe:
FTP (21), Telnet (23), DCE/NetBIOS/SMB (135/137-139/445), RDP (3389)
(to merge with prior)
Signed-off-by: Self Hosting Group <selfhostinggroup-git+openwrt@shost.ing>
---
miniupnpd/files/miniupnpd.init | 98 ++++++++-----------
.../files/upnpd-migration.uci-defaults | 16 +--
miniupnpd/files/upnpd.config | 11 +--
3 files changed, 49 insertions(+), 76 deletions(-)
diff --git a/miniupnpd/files/miniupnpd.init b/miniupnpd/files/miniupnpd.init
index a6611c0e732e0d..d7256603f0c34c 100644
--- a/miniupnpd/files/miniupnpd.init
+++ b/miniupnpd/files/miniupnpd.init
@@ -61,15 +61,22 @@ service_triggers() {
upnpd_generate_config() {
# Daemon
- local enable_protocols allow_cgnat stun_host allow_third_party_mapping ipv6_disable system_uptime lease_file
+ local enable_protocols allow_cgnat stun_host allow_third_party_mapping system_uptime lease_file
config_get enable_protocols settings enable_protocols all
config_get allow_cgnat settings allow_cgnat 0
config_get stun_host settings stun_host stun.nextcloud.com
config_get allow_third_party_mapping settings allow_third_party_mapping 0
- config_get ipv6_disable settings ipv6_disable 0
config_get system_uptime settings system_uptime 1
config_get lease_file settings lease_file /var/run/miniupnpd.leases
+ # Access control
+ local access_defaults accept_ports reject_ports check_acl ipv6_disable
+ config_get access_defaults settings access_defaults none
+ config_get accept_ports settings accept_ports
+ config_get reject_ports settings reject_ports "21 23 135 137-139 445 3389"
+ config_get check_acl settings check_acl 1
+ config_get ipv6_disable settings ipv6_disable 0
+
# UPnP IGD
local upnp_igd_compat download_kbps upload_kbps friendly_name model_number serial_number presentation_url uuid http_port notify_interval
config_get upnp_igd_compat settings upnp_igd_compat igdv1
@@ -83,12 +90,13 @@ upnpd_generate_config() {
config_get http_port settings http_port 5000
config_get notify_interval settings notify_interval
- # External network interface
- local external_iface external_iface6 external_zone external_ip
+ # Network interfaces
+ local external_iface external_iface6 external_zone external_ip internal_iface
config_get external_iface settings external_iface
config_get external_iface6 settings external_iface6
config_get external_zone settings external_zone
config_get external_ip settings external_ip
+ config_get internal_iface settings internal_iface lan
local ifname ifname6
. /lib/functions/network.sh
@@ -121,10 +129,6 @@ upnpd_generate_config() {
[ "$pass" = "4" ] && log "IPv6 GUA not yet available, UPnP IGD mapping not possible" && break
done
fi
- if ! uci -q get upnpd.@internal_network[0].interface >/dev/null; then
- log "No internal networks configured, not starting" daemon.err
- return 1
- fi
# Only perform an STUN CGNAT test if necessary, with a private/CGNAT-reserved external IPv4
local extipv4 extipv4private
extipv4="$(ip -4 addr show dev "$ifname" | grep inet | head -1 | sed -E "s/.*inet ([0-9.]+).*/\1/")"
@@ -151,7 +155,6 @@ upnpd_generate_config() {
[ "$allow_third_party_mapping" = "1" ] && echo "secure_mode=no" && echo "pcp_allow_thirdparty=yes"
[ "$allow_third_party_mapping" = "upnp-igd" ] && echo "secure_mode=no" && echo "pcp_allow_thirdparty=no"
[ "$allow_third_party_mapping" = "pcp" ] && echo "secure_mode=yes" && echo "pcp_allow_thirdparty=yes"
- [ "$ipv6_disable" = "0" ] && echo "ipv6_disable=no" || echo "ipv6_disable=yes"
[ "$system_uptime" = "0" ] && echo "system_uptime=no" || echo "system_uptime=yes"
touch "$lease_file" && echo "lease_file=$lease_file"
[ "$ipv6_disable" = "0" ] && touch "${lease_file}-ipv6" && echo "lease_file6=${lease_file}-ipv6"
@@ -191,9 +194,37 @@ upnpd_generate_config() {
[ -n "$external_ip" ] && echo "ext_ip=$external_ip"
echo "# Enable internal networks / access control"
- config_foreach upnpd_add_int_network_and_defaults internal_network pre-acl
- config_foreach upnpd_add_acl_entry acl_entry
- config_foreach upnpd_add_int_network_and_defaults internal_network post-acl
+ [ "$ipv6_disable" = "0" ] && echo "ipv6_disable=no" || echo "ipv6_disable=yes"
+ local rejectport
+ for rejectport in $reject_ports; do
+ is_port_or_range "$rejectport" allowport0 && echo "deny $rejectport 0.0.0.0/0 $rejectport # Reject port $rejectport" ||
+ log "Invalid port or port range ($rejectport) in reject_ports ignored" daemon.warn
+ done
+ [ "$check_acl" = "1" ] && echo "# Access control list" && config_foreach upnpd_add_acl_entry acl_entry
+ local iface
+ for iface in $internal_iface; do
+ local device subnet accessdefaultsports acceptport
+ network_get_device device "$iface"
+ network_get_subnet subnet "$iface"
+ [ "$subnet" = "" ] && log "Cannot get IPv4 subnet for network $iface, network ignored" daemon.warn && continue
+ echo "# Enable internal network $iface ($device) with access defaults $access_defaults and check ACL $check_acl"
+ echo "listening_ip=$device"
+ if [ "$access_defaults" = "accept-high-ports" ]; then
+ accessdefaultsports="1024-65535"
+ elif [ "$access_defaults" = "accept-web+high-ports" ]; then
+ accessdefaultsports="80 443 1024-65535"
+ elif [ "$access_defaults" = "accept-web-ports" ]; then
+ accessdefaultsports="80 443"
+ elif [ "$access_defaults" = "accept-all-ports" ]; then
+ accessdefaultsports="1-65535"
+ elif [ "$access_defaults" != "none" ]; then
+ log "Invalid access_defaults ($access_defaults) ignored" daemon.warn
+ fi
+ for acceptport in $accessdefaultsports $accept_ports; do
+ is_port_or_range "$acceptport" && echo "allow $acceptport $subnet $acceptport # Accept port $acceptport on $iface" ||
+ log "Invalid port or port range ($acceptport) in accept_ports ignored" daemon.warn
+ done
+ done
echo "deny 1-65535 0.0.0.0/0 1-65535 # Reject ACL by default"
} >"$1"
@@ -217,49 +248,6 @@ is_port_or_range() {
[ "${1##*-}" -ge "${1%%-*}" ] 2>/dev/null && return 0 || return 1
}
-upnpd_add_int_network_and_defaults() {
- local cfg="$1"
- local interface access_defaults accept_ports reject_ports check_acl
- config_get interface "$cfg" interface
- config_get access_defaults "$cfg" access_defaults none
- config_get accept_ports "$cfg" accept_ports
- config_get reject_ports "$cfg" reject_ports "21 23 135 137-139 445 3389"
- config_get check_acl "$cfg" check_acl 1
- local device subnet rejectport accessdefaultsports acceptport
- network_get_device device "$interface"
- network_get_subnet subnet "$interface"
- [ "$subnet" = "" ] && log "Cannot get IPv4 subnet for network $interface, network ignored" daemon.warn && return 0
- if [ "$2" = "pre-acl" ]; then
- echo "# Enable internal network $interface ($device) with access defaults $access_defaults and check ACL $check_acl"
- echo "listening_ip=$device"
- for rejectport in $reject_ports; do
- is_port_or_range "$rejectport" allowport0 && echo "deny $rejectport $subnet $rejectport # Reject port $rejectport on $interface" ||
- log "Invalid port or port range ($rejectport) in reject_ports ignored" daemon.warn
- done
- fi
- if { [ "$2" = "post-acl" ] && [ "$check_acl" = "1" ]; } ||
- { [ "$2" = "pre-acl" ] && [ "$check_acl" = "0" ]; }; then
- if [ "$access_defaults" = "accept-high-ports" ]; then
- accessdefaultsports="1024-65535"
- elif [ "$access_defaults" = "accept-web+high-ports" ]; then
- accessdefaultsports="80 443 1024-65535"
- elif [ "$access_defaults" = "accept-web-ports" ]; then
- accessdefaultsports="80 443"
- elif [ "$access_defaults" = "accept-all-ports" ]; then
- accessdefaultsports="1-65535"
- elif [ "$access_defaults" != "none" ]; then
- log "Invalid access_defaults ($access_defaults) ignored" daemon.warn
- fi
- for acceptport in $accessdefaultsports $accept_ports; do
- is_port_or_range "$acceptport" && echo "allow $acceptport $subnet $acceptport # Accept port $acceptport on $interface" ||
- log "Invalid port or port range ($acceptport) in accept_ports ignored" daemon.warn
- done
- fi
- if [ "$2" = "pre-acl" ] && [ "$check_acl" = "0" ]; then
- echo "deny 1-65535 $subnet 1-65535 # Reject ACL by default on $interface"
- fi
-}
-
upnpd_add_acl_entry() {
local cfg="$1"
local comment int_addr int_port ext_port descr_filter action
diff --git a/miniupnpd/files/upnpd-migration.uci-defaults b/miniupnpd/files/upnpd-migration.uci-defaults
index ac0c8d3245c175..60f80d54fffa28 100644
--- a/miniupnpd/files/upnpd-migration.uci-defaults
+++ b/miniupnpd/files/upnpd-migration.uci-defaults
@@ -212,6 +212,8 @@ else
fi
while uci -q delete upnpd.@perm_rule[-1]; do :; done
fi
+[ "$access_defaults" != "" ] && uci set upnpd.config.access_defaults="$access_defaults"
+[ "$check_acl" = "0" ] && uci set upnpd.config.check_acl="0"
if [ "$addtemplateentries" = "1" ]; then
uci batch >/dev/null <<-EOF
add upnpd acl_entry
@@ -231,20 +233,6 @@ if [ "$addtemplateentries" = "1" ]; then
uci reorder upnpd.@acl_entry[-2]=0 && uci reorder upnpd.@acl_entry[-1]=1
fi
-# Migrate internal_iface option to new internal_network section
-if ! uci -q get upnpd.@internal_network[0] >/dev/null; then
- ifnr=0
- for interface in $(uci -q get upnpd.config.internal_iface || echo lan); do
- log "Create new internal_network section for $interface"
- uci add upnpd internal_network >/dev/null
- uci set upnpd.@internal_network[$ifnr].interface="$interface"
- [ "$access_defaults" != "" ] && uci set upnpd.@internal_network[$ifnr].access_defaults="$access_defaults"
- [ "$check_acl" = "0" ] && uci set upnpd.@internal_network[$ifnr].check_acl="0"
- ifnr=$((ifnr + 1))
- done
- uci -q delete upnpd.config.internal_iface
-fi
-
# Rename UCI section config -> settings (v2.0)
if uci -q get upnpd.config >/dev/null; then
log "Rename UCI section config -> settings (v2.0)" && uci rename upnpd.config="settings" ||
diff --git a/miniupnpd/files/upnpd.config b/miniupnpd/files/upnpd.config
index 74fc64b58e9bf6..1ae67365486063 100644
--- a/miniupnpd/files/upnpd.config
+++ b/miniupnpd/files/upnpd.config
@@ -4,19 +4,16 @@ config upnpd 'settings'
option enabled '0'
# Can be set to all/upnp-igd/pcp+nat-pmp
option enable_protocols 'all'
+ # Enable networks, multiple can be specified, separated by a space
+ option internal_iface 'lan'
+ option access_defaults 'accept-high-ports' # default none
+ option check_acl '0' # default 1
#option ipv6_disable '0'
#option allow_third_party_mapping '0'
# Extra logging by setting to info or debug (previously 1)
option log_output 'default'
option upnp_igd_compat 'igdv1'
-# Enable Networks / Access Control
-
-config internal_network
- option interface 'lan'
- option access_defaults 'accept-high-ports' # default none
- option check_acl '0' # default 1
-
# Access Control List
# Empty ACL rejected alone, IPv6 accepted unless disabled, action: accept/reject