diff --git a/.github/diy/patches/upnp.patch b/.github/diy/patches/upnp.patch
deleted file mode 100644
index d0e0caa9..00000000
--- a/.github/diy/patches/upnp.patch
+++ /dev/null
@@ -1,5102 +0,0 @@
---- 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(
- 'UPnP IGD',
- 'PCP',
- 'NAT-PMP');
-- 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', '
' + _('No suitable configuration was found!') + '
' +
-+ _('No suitable config (LuCI app %s) found in %s. A related package update (daemon or LuCI app) may be missing.').format('v1.0', '/etc/config/upnpd') + '
' +
-+ _('Use the software package manager, update lists, and install the related update. Config is migrated with the daemon package update.') + '
' +
-+ '' + _('Go to package manager…') + ''), '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('STUN'),
-+ .format('STUN'),
- _('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('SSDP'));
-- 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('SSDP', 'Cache-Control: max-age=1800'));
-+ 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
- // 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
-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
----
- .../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('STUN'),
- _('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('SSDP', 'Cache-Control: max-age=1800'));
-- 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('SSDP', 'Cache-Control: max-age=1800'));
-+ 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
-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
----
- 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({
- 'PCP',
- 'NAT-PMP');
- 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)
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('STUN'),
-- _('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('CGNAT',
-+ 'STUN'),
-+ _('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('SSDP', 'Cache-Control: max-age=1800'));
- 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', '' + _('Enable Networks / Access Control') + '
',
-+ _('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('0'));
-+ 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)') + '
' +
-+ _('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
-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
----
- .../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', '' + _('No suitable configuration was found!') + '
' +
-- _('No suitable config (LuCI app %s) found in %s. A related package update (daemon or LuCI app) may be missing.').format('v1.0', '/etc/config/upnpd') + '
' +
-+ _('No suitable config (LuCI app %s) found in %s. A related package update (daemon or LuCI app) may be missing.').format('v2.0', '/etc/config/upnpd') + '
' +
- _('Use the software package manager, update lists, and install the related update. Config is migrated with the daemon package update.') + '
' +
- '' + _('Go to package manager…') + ''), '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
-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
----
- .../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', '' + _('Access Control List') + '
',
-+ _('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
-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
----
- .../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
-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
----
- .../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)
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('0'));
-+ 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)') + '
' +
-+ _('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('CGNAT',
- 'STUN'),
-@@ -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', '' + _('Enable Networks / Access Control') + '
',
-- _('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('0'));
-- 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)') + '
' +
-- _('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', '' + _('Access Control List') + '
',
- _('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
- PKG_LICENSE:=BSD-3-Clause
-
-From 1bca0ee5caa52f493a4e6cef12d2665ba246ecbe Mon Sep 17 00:00:00 2001
-From: Self Hosting Group
-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
----
- .../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
-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
----
- .../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
-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
----
- 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
-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
----
- 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/&/\&/g; s/\</g; s/>/\>/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
-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
----
- 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
-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
----
- 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
-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
----
- 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
-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
----
- 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/&/\&/g; s/\</g; s/>/\>/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
-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
----
- 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
-