From 8bb6b8377f47f1d18efc75ea7f862af9b8b2f57d Mon Sep 17 00:00:00 2001 From: kiddin9 <48883331+kiddin9@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:34:56 +0800 Subject: [PATCH] Delete .github/diy/patches/upnp.patch --- .github/diy/patches/upnp.patch | 5102 -------------------------------- 1 file changed, 5102 deletions(-) delete mode 100644 .github/diy/patches/upnp.patch 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" -+} -+ - 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" - } - -+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 -