Skip to content

Commit e72aa4a

Browse files
luci-app-upnp: revision and adapt to updated package 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`): Allow new option for IPv4 CGNAT use (allow filtered), and updated 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 output level (`log_output`): Allow info log level, and reworded - 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 preset (`access_preset`): UI option added to select an access control preset for ports that all devices on this network can map - Accept extra ports (`accept_ports`): UI option added to accept these ports or port ranges on this network as well - Reject ports (`reject_ports`): UI option added to reject ports on this network; override other settings - Ignore ACL (`ignore_acl`): UI option added to define whether the ACL entries should not be checked before a preset; can extend/override a preset - Slightly improve introduction text More details on changed options can be found in the dependent package PR Depends on: https://redirect.github.com/openwrt/packages/pull/24988 Signed-off-by: Self-Hosting-Group <[email protected]>
1 parent 32375f5 commit e72aa4a

File tree

3 files changed

+157
-53
lines changed

3 files changed

+157
-53
lines changed

applications/luci-app-upnp/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
include $(TOPDIR)/rules.mk
88

9-
LUCI_TITLE:=UPnP IGD & PCP/NAT-PMP configuration module | Universal Plug and Play
9+
LUCI_TITLE:=LuCI support for UPnP IGD & PCP/NAT-PMP service | formerly: Universal Plug and Play
1010
LUCI_DEPENDS:=+luci-base +miniupnpd +rpcd-mod-ucode
1111

1212
PKG_LICENSE:=Apache-2.0

applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js

Lines changed: 155 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
'require ui';
77
'require rpc';
88
'require form';
9+
'require tools.widgets as widgets';
910

1011
const callInitAction = rpc.declare({
1112
object: 'luci',
@@ -87,8 +88,8 @@ return view.extend({
8788
'<a href="https://en.wikipedia.org/wiki/Port_Control_Protocol" target="_blank" rel="noreferrer"><abbr title="Port Control Protocol">PCP</abbr></a>',
8889
'<a href="https://en.wikipedia.org/wiki/NAT_Port_Mapping_Protocol" target="_blank" rel="noreferrer"><abbr title="NAT Port Mapping Protocol">NAT-PMP</abbr></a>');
8990
m = new form.Map('upnpd', _('UPnP IGD & PCP/NAT-PMP Service'),
90-
_('The %s protocols allow clients on the local network to configure port maps/forwards on the router autonomously.',
91-
'The %s (%s = UPnP IGD & PCP/NAT-PMP) protocols allow clients on the local network to configure port maps/forwards on the router autonomously.')
91+
_('The %s protocols/service enable permitted devices on local networks to autonomously set up port maps (forwards) on this router.',
92+
'The %s (%s = UPnP IGD & PCP/NAT-PMP) protocols/service enable permitted devices on local networks to autonomously set up port maps (forwards) on this router.')
9293
.format(protocols)
9394
);
9495
if (!uci.get('upnpd', 'config')) {
@@ -100,6 +101,7 @@ return view.extend({
100101
}
101102

102103
s = m.section(form.GridSection, '_active_rules');
104+
s.disable = uci.get('upnpd', 'config', 'enabled') == '0';
103105

104106
s.render = L.bind(function(view, section_id) {
105107
const table = E('table', { 'class': 'table cbi-section-table', 'id': 'upnp_status_table' }, [
@@ -150,39 +152,49 @@ return view.extend({
150152
_('Enable the autonomous port mapping service'));
151153
o.rmempty = false;
152154

153-
o = s.taboption('setup', form.Flag, 'enable_upnp', _('Enable UPnP IGD protocol'));
154-
o.default = '1';
155-
156-
o = s.taboption('setup', form.Flag, 'enable_natpmp', _('Enable PCP/NAT-PMP protocols'));
157-
o.default = '1';
158-
159-
o = s.taboption('setup', form.Flag, 'igdv1', _('UPnP IGDv1 compatibility mode'),
160-
_('Advertise as IGDv1 (IPv4 only) device instead of IGDv2'));
161-
o.default = '1';
162-
o.rmempty = false;
163-
o.depends('enable_upnp', '1');
155+
o = s.taboption('setup', form.ListValue, 'enable_protocols', _('Enable protocols'));
156+
o.value('all', _('All protocols'));
157+
o.value('upnp-igd', _('UPnP IGD'));
158+
o.value('pcp+nat-pmp', _('PCP and NAT-PMP'));
159+
o.default = 'all';
160+
o.widget = 'radio';
161+
162+
o = s.taboption('setup', form.ListValue, 'upnp_igd_compat', _('UPnP IGD compatibility'),
163+
_('Set compatibility mode (act as device) to workaround IGDv2-incompatible clients; %s only work with %s (or) <br>Emulate/report a specific/different device to workaround/support/handle/bypass/assist/mitigate... (alternative text welcome)').format('Sony PS / CoD', 'IGDv1'));
164+
o.value('igdv1', _('IGDv1 (IPv4 only)'));
165+
o.value('igdv2', _('IGDv2 (with workarounds)'));
166+
o.depends('enable_protocols', 'upnp-igd');
167+
o.depends('enable_protocols', 'all');
164168
o.retain = true;
165169

166-
s.taboption('advanced', form.Flag, 'use_stun', _('Use %s', 'Use %s (%s = STUN)')
167-
.format('<a href="https://en.wikipedia.org/wiki/STUN" target="_blank" rel="noreferrer"><abbr title="Session Traversal Utilities for NAT">STUN</abbr></a>'),
168-
_('To detect the public IPv4 address for unrestricted full-cone/one-to-one NATs'));
169-
170-
o = s.taboption('advanced', form.Value, 'stun_host', _('STUN host'));
171-
o.datatype = 'host';
172-
o.depends('use_stun', '1');
170+
o = s.taboption('advanced', form.RichListValue, 'allow_cgnat', _('Allow %s/%s', 'Allow %s/%s (%s = CGNAT, %s = STUN)')
171+
.format('<a href="https://en.wikipedia.org/wiki/Carrier-grade_NAT" target="_blank" rel="noreferrer"><abbr title="Carrier-grade NAT">CGNAT</abbr></a>',
172+
'<a href="https://en.wikipedia.org/wiki/STUN" target="_blank" rel="noreferrer"><abbr title="Session Traversal Utilities for NAT">STUN</abbr></a>'),
173+
_('Allow use of unrestricted endpoint-independent (1:1) CGNATs and detect the public IPv4'));
174+
o.value('', _('Disabled'), _('Manually override external IPv4 to allow a private IP'));
175+
o.value('1', _('Enabled'), _('Filtering test currently requires an extra firewall rule'));
176+
o.value('allow-filtered', _('Enabled') + ' (' + _('allow filtered') + ')', _('Allow filtered IPv4 CGNAT test result'));
177+
o.optional = true;
178+
179+
o = s.taboption('advanced', form.Value, 'stun_host', _('STUN server'));
180+
o.datatype = 'or(hostname,hostport,ip4addr("nomask"))';
181+
o.placeholder = 'stun.nextcloud.com';
182+
o.depends('allow_cgnat', '1');
183+
o.depends('allow_cgnat', 'allow-filtered');
173184
o.retain = true;
174185

175-
o = s.taboption('advanced', form.Value, 'stun_port', _('STUN port'));
176-
o.datatype = 'port';
177-
o.placeholder = '3478';
178-
o.depends('use_stun', '1');
179-
o.retain = true;
186+
o = s.taboption('advanced', form.Value, 'external_ip', _('Override external IPv4'),
187+
_('Report custom public/external (WAN) IPv4 address'));
188+
o.datatype = 'ip4addr("nomask")';
189+
o.placeholder = '(203.1.2.3)';
190+
o.depends('allow_cgnat', '');
180191

181-
o = s.taboption('advanced', form.Flag, 'secure_mode', _('Enable secure mode'),
182-
_('Allow adding port maps for requesting IP addresses only'));
183-
o.default = '1';
184-
o.depends('enable_upnp', '1');
185-
o.retain = true;
192+
o = s.taboption('advanced', form.ListValue, 'allow_third_party_mapping', _('Allow third-party mapping'),
193+
_('Allow adding port maps for non-requesting IP addresses'));
194+
o.value('', _('Disabled'));
195+
o.value('1', _('Enabled'));
196+
o.value('upnp-igd', _('Enabled') + ' (' + _('UPnP IGD only') + ')');
197+
o.value('pcp', _('Enabled') + ' (' + _('PCP only') + ')');
186198

187199
s.taboption('advanced', form.Flag, 'ipv6_disable', _('Disable IPv6 mapping'));
188200

@@ -191,46 +203,69 @@ return view.extend({
191203
o.depends('to-disable-as-rarely-used', '1');
192204
o.retain = true;
193205

194-
s.taboption('advanced', form.Flag, 'log_output', _('Enable additional logging'),
195-
_('Puts extra debugging information into the system log'));
206+
o = s.taboption('advanced', form.ListValue, 'log_output', _('Log output level'));
207+
o.value('default', _('Default'));
208+
o.value('info', _('Info'));
209+
o.value('debug', _('Debug'));
210+
o.default = 'default';
211+
o.widget = 'radio';
196212

197-
o = s.taboption('advanced', form.Value, 'upnp_lease_file', _('Service lease file'));
213+
o = s.taboption('advanced', form.Value, 'lease_file', _('Service lease file'));
198214
o.depends('to-disable-as-rarely-used', '1');
199215
o.retain = true;
200216

201-
o = s.taboption('igd', form.Value, 'download', _('Download speed'),
202-
_('Report maximum download speed in kByte/s'));
203-
o.depends('enable_upnp', '1');
217+
o = s.taboption('igd', form.Value, 'download_kbps', _('Download speed'),
218+
_('Report maximum connection speed in kbit/s'));
219+
o.datatype = 'uinteger';
220+
o.placeholder = _('Default interface link speed');
221+
o.depends('enable_protocols', 'upnp-igd');
222+
o.depends('enable_protocols', 'all');
223+
o.retain = true;
224+
225+
o = s.taboption('igd', form.Value, 'upload_kbps', _('Upload speed'),
226+
_('Report maximum connection speed in kbit/s'));
227+
o.datatype = 'uinteger';
228+
o.placeholder = _('Default interface link speed');
229+
o.depends('enable_protocols', 'upnp-igd');
230+
o.depends('enable_protocols', 'all');
204231
o.retain = true;
205232

206-
o = s.taboption('igd', form.Value, 'upload', _('Upload speed'),
207-
_('Report maximum upload speed in kByte/s'));
208-
o.depends('enable_upnp', '1');
233+
o = s.taboption('igd', form.Value, 'friendly_name', _('Router/friendly name'));
234+
o.placeholder = 'OpenWrt UPnP IGD & PCP';
235+
o.depends('enable_protocols', 'upnp-igd');
236+
o.depends('enable_protocols', 'all');
209237
o.retain = true;
210238

211239
o = s.taboption('igd', form.Value, 'model_number', _('Announced model number'));
212-
o.depends('enable_upnp', '1');
240+
// o.depends('enable_protocols', 'upnp-igd');
241+
// o.depends('enable_protocols', 'all');
242+
o.depends('to-disable-as-rarely-used', '1');
213243
o.retain = true;
214244

215245
o = s.taboption('igd', form.Value, 'serial_number', _('Announced serial number'));
216-
o.depends('enable_upnp', '1');
246+
// o.depends('enable_protocols', 'upnp-igd');
247+
// o.depends('enable_protocols', 'all');
248+
o.depends('to-disable-as-rarely-used', '1');
217249
o.retain = true;
218250

219251
o = s.taboption('igd', form.Value, 'presentation_url', _('Router/presentation URL'),
220252
_('Report custom router web interface URL'));
221253
o.placeholder = 'http://192.168.1.1/';
222-
o.depends('enable_upnp', '1');
254+
o.depends('enable_protocols', 'upnp-igd');
255+
o.depends('enable_protocols', 'all');
223256
o.retain = true;
224257

225258
o = s.taboption('igd', form.Value, 'uuid', _('Device UUID'));
226-
// o.depends('enable_upnp', '1');
259+
// o.depends('enable_protocols', 'upnp-igd');
260+
// o.depends('enable_protocols', 'all');
227261
o.depends('to-disable-as-rarely-used', '1');
228262
o.retain = true;
229263

230-
o = s.taboption('igd', form.Value, 'port', _('SOAP/HTTP port'));
264+
o = s.taboption('igd', form.Value, 'http_port', _('SOAP/HTTP port'));
231265
o.datatype = 'port';
232266
o.placeholder = '5000';
233-
o.depends('enable_upnp', '1');
267+
o.depends('enable_protocols', 'upnp-igd');
268+
o.depends('enable_protocols', 'all');
234269
o.retain = true;
235270

236271
o = s.taboption('igd', form.Value, 'notify_interval', _('Notify interval'),
@@ -239,14 +274,81 @@ return view.extend({
239274
.format('<abbr title="Simple Service Discovery Protocol">SSDP</abbr>', '<code>Cache-Control: max-age=1800</code>'));
240275
o.datatype = 'min(900)';
241276
o.placeholder = '900';
242-
o.depends('enable_upnp', '1');
277+
o.depends('enable_protocols', 'upnp-igd');
278+
o.depends('enable_protocols', 'all');
279+
o.retain = true;
280+
281+
s = m.section(form.GridSection, 'internal_network', '<h5>' + _('Enable Networks / Access Control') + '</h5>',
282+
_('Select local/internal (LAN) network interfaces to enable the service for.') + ' ' +
283+
_('Use an access control preset for ports that all devices on a network can map.') + ' ' +
284+
_('Alternatively, add client-specific permissions using the access control list (ACL), which can also extend/override a preset.') + ' ' +
285+
_('IPv6 is currently always accepted unless disabled. (alternative text welcome)'));
286+
s.anonymous = true;
287+
s.addremove = true;
288+
s.cloneable = true;
289+
s.sortable = true;
290+
s.nodescriptions = true;
291+
s.modaltitle = _('UPnP IGD & PCP') + ' - ' + _('Edit Network Access Control Settings');
292+
293+
o = s.option(widgets.NetworkSelect, 'interface', _('Internal network'),
294+
_('Select the local/internal (LAN) network interface to enable the service for'));
295+
o.exclude = 'wan'; // wan6 should also be excluded
296+
o.nocreate = true;
297+
o.editable = true;
298+
o.retain = true;
299+
o.validate = function(section_id, value) {
300+
// Commented out, as it causes issues with cloning
301+
//let netcount = 0;
302+
//for (let ifnr = 0; uci.get('upnpd', `@internal_network[${ifnr}]`, 'interface'); ifnr++) {
303+
// if (uci.get('upnpd', `@internal_network[${ifnr}]`, 'interface') == value) netcount++;
304+
//};
305+
return (value == '' || value == 'wan' || value == 'wan6') ? '' : true;
306+
};
307+
308+
o = s.option(form.ListValue, 'access_preset', _('Access preset'));
309+
o.value('', _('None / accept extra ports only'));
310+
o.value('accept-high-ports', _('Accept ports >= 1024'));
311+
o.value('accept-web+high-ports', _('Accept HTTP/HTTPS + ports >= 1024'));
312+
o.value('accept-web-ports', _('Accept HTTP/HTTPS ports only'));
313+
o.value('accept-all-ports', _('Accept all ports'));
314+
o.editable = true;
315+
o.retain = true;
316+
317+
o = s.option(form.Value, 'accept_ports', _('Accept extra ports'));
318+
o.retain = true;
319+
o.validate = function(section_id, value) {
320+
return value.search(/^[0-9 -]*$/) != -1 ? true : _('Expecting: %s').format(_('valid port or port range (port1-port2)'));
321+
};
322+
323+
o = s.option(form.Value, 'reject_ports', _('Reject ports'),
324+
_('Reject unsafe/insecure/risky FTP/Telnet/DCE/NetBIOS/SMB/RDP ports on this network by default; override other settings; use space for none'));
325+
o.placeholder = '21 23 135 137-139 445 3389';
326+
o.modalonly = true;
327+
o.retain = true;
328+
o.validate = function(section_id, value) {
329+
return value.search(/^[0-9 -]*$/) != -1 ? true : _('Expecting: %s').format(_('valid port or port range (port1-port2)'));
330+
};
331+
332+
o = s.option(form.Flag, 'ignore_acl', _('Ignore ACL'),
333+
_('Whether the ACL entries should not be checked before a preset; can extend/override a preset') + '<br>' +
334+
_('Sequence: 1. Reject ports, 2. ACL entries (if used), 3. Preset ports, 4. Accept extra ports'));
335+
o.editable = true;
243336
o.retain = true;
244337

245338
s = m.section(form.GridSection, 'perm_rule', _('Service Access Control List'),
246339
_('ACL specify which client addresses and ports can be mapped, IPv6 always allowed.'));
247340
s.anonymous = true;
248341
s.addremove = true;
249342
s.sortable = true;
343+
// Preferably: ACL part of extra tab with depends for section as immediately, and network section part of service setup tab. Nice to have: Add button (+input) calls function and opens modal pre-filled
344+
let acl_used = false;
345+
for (let ifnr = 0; uci.get('upnpd', `@internal_network[${ifnr}]`, 'interface'); ifnr++) {
346+
if (!uci.get('upnpd', `@internal_network[${ifnr}]`, 'ignore_acl') == '1') {
347+
acl_used = true;
348+
break;
349+
}
350+
}
351+
s.disable = !acl_used;
250352

251353
s.option(form.Value, 'comment', _('Comment'));
252354

@@ -267,11 +369,13 @@ return view.extend({
267369
o.value('deny', _('Deny'));
268370

269371
return m.render().then(L.bind(function(m, nodes) {
270-
poll.add(L.bind(function() {
271-
return Promise.all([
272-
callUpnpGetStatus()
273-
]).then(L.bind(this.poll_status, this, nodes));
274-
}, this), 5);
372+
if (uci.get('upnpd', 'config', 'enabled') != '0') {
373+
poll.add(L.bind(function() {
374+
return Promise.all([
375+
callUpnpGetStatus()
376+
]).then(L.bind(this.poll_status, this, nodes));
377+
}, this), 5);
378+
}
275379
return nodes;
276380
}, this, m));
277381
}

applications/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { connect } from 'ubus';
1010
import { cursor } from 'uci';
1111

1212
const uci = cursor();
13-
const leasefilepath = uci.get('upnpd', 'config', 'upnp_lease_file') || '/run/miniupnpd.leases';
13+
const leasefilepath = uci.get('upnpd', 'config', 'lease_file') || '/run/miniupnpd.leases';
1414

1515
const methods = {
1616
get_status: {

0 commit comments

Comments
 (0)