summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-mod-network
diff options
context:
space:
mode:
Diffstat (limited to 'modules/luci-mod-network')
-rw-r--r--modules/luci-mod-network/Makefile2
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js162
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js309
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js34
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js174
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js56
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js20
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js187
-rw-r--r--modules/luci-mod-network/root/usr/share/rpcd/acl.d/luci-mod-network.json4
9 files changed, 739 insertions, 209 deletions
diff --git a/modules/luci-mod-network/Makefile b/modules/luci-mod-network/Makefile
index bf5627d64d..b382d800d9 100644
--- a/modules/luci-mod-network/Makefile
+++ b/modules/luci-mod-network/Makefile
@@ -7,7 +7,7 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Network Administration
-LUCI_DEPENDS:=+luci-base +libiwinfo-lua +rpcd-mod-iwinfo
+LUCI_DEPENDS:=+luci-base +rpcd-mod-iwinfo
PKG_LICENSE:=Apache-2.0
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js b/modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js
index cb453a3085..092bbbc14a 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js
@@ -1,4 +1,5 @@
'use strict';
+'require fs';
'require ui';
'require dom';
'require uci';
@@ -149,25 +150,77 @@ function updatePlaceholders(opt, section_id) {
}
}
+var cbiFlagTristate = form.ListValue.extend({
+ __init__: function(/* ... */) {
+ this.super('__init__', arguments);
+ this.keylist = [ '', '0!', '1!' ];
+ this.vallist = [ _('automatic'), _('disabled'), _('enabled') ];
+ },
+
+ load: function(section_id) {
+ var invert = false, sysfs = this.sysfs;
+
+ if (sysfs) {
+ if (sysfs.charAt(0) == '!') {
+ invert = true;
+ sysfs = sysfs.substring(1);
+ }
+
+ return L.resolveDefault(fs.read(sysfs), '').then(L.bind(function(res) {
+ res = (res || '').trim();
+
+ if (res == '0')
+ this.sysfs_default = invert;
+ else if (res == '1')
+ this.sysfs_default = !invert;
+
+ return this.super('load', [section_id]);
+ }, this));
+ }
+
+ return this.super('load', [section_id]);
+ },
+
+ write: function(section_id, formvalue) {
+ if (formvalue == '1!')
+ return this.super('write', [section_id, '1']);
+ else if (formvalue == '0!')
+ return this.super('write', [section_id, '0']);
+ else
+ return this.super('remove', [section_id]);
+ },
+
+ renderWidget: function(section_id, option_index, cfgvalue) {
+ var sysdef = this.sysfs_default;
+
+ if (this.sysfs_default !== null) {
+ this.keylist[0] = sysdef ? '1' : '0';
+ this.vallist[0] = sysdef ? _('automatic (enabled)') : _('automatic (disabled)');
+ }
+
+ return this.super('renderWidget', [section_id, option_index, cfgvalue]);
+ }
+});
+
var cbiTagValue = form.Value.extend({
renderWidget: function(section_id, option_index, cfgvalue) {
var widget = new ui.Dropdown(cfgvalue || ['-'], {
'-': E([], [
E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '—' ]),
- E('span', { 'class': 'hide-close' }, [ _('Do not participate', 'VLAN port state') ])
+ E('span', { 'class': 'hide-close' }, [ _('Not Member', 'VLAN port state') ])
]),
'u': E([], [
- E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'u' ]),
- E('span', { 'class': 'hide-close' }, [ _('Egress untagged', 'VLAN port state') ])
+ E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'U' ]),
+ E('span', { 'class': 'hide-close' }, [ _('Untagged', 'VLAN port state') ])
]),
't': E([], [
- E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 't' ]),
- E('span', { 'class': 'hide-close' }, [ _('Egress tagged', 'VLAN port state') ])
+ E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'T' ]),
+ E('span', { 'class': 'hide-close' }, [ _('Tagged', 'VLAN port state') ])
]),
'*': E([], [
E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '*' ]),
- E('span', { 'class': 'hide-close' }, [ _('Primary VLAN ID', 'VLAN port state') ])
+ E('span', { 'class': 'hide-close' }, [ _('Is Primary VLAN', 'VLAN port state') ])
])
}, {
id: this.cbid(section_id),
@@ -331,6 +384,7 @@ return baseclass.extend({
addDeviceOptions: function(s, dev, isNew) {
var parent_dev = dev ? dev.getParent() : null,
+ devname = dev ? dev.getName() : null,
o, ss;
s.tab('devgeneral', _('General device options'));
@@ -421,7 +475,7 @@ return baseclass.extend({
vid = this.section.formvalue(section_id, 'vid'),
name = this.section.getUIElement(section_id, 'name_complex');
- if (base && vid && name && !name.isChanged()) {
+ if (base && vid && name && !name.isChanged() && isNew) {
name.setValue('%s.%d'.format(base, vid));
name.triggerValidation();
}
@@ -619,16 +673,16 @@ return baseclass.extend({
o.placeholder = dev ? dev._devstate('qlen') : '';
o.datatype = 'uinteger';
- o = this.replaceOption(s, 'devadvanced', form.Flag, 'promisc', _('Enable promiscuous mode'));
- o.default = o.disabled;
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'promisc', _('Enable promiscuous mode'));
+ o.sysfs_default = (dev && dev.dev && dev.dev.flags) ? dev.dev.flags.promisc : null;
o = this.replaceOption(s, 'devadvanced', form.ListValue, 'rpfilter', _('Reverse path filter'));
o.default = '';
o.value('', _('disabled'));
o.value('loose', _('Loose filtering'));
o.value('strict', _('Strict filtering'));
- o.cfgvalue = function(section_id) {
- var val = form.ListValue.prototype.cfgvalue.apply(this, [section_id]);
+ o.cfgvalue = function(/* ... */) {
+ var val = form.ListValue.prototype.cfgvalue.apply(this, arguments);
switch (val || '') {
case 'loose':
@@ -644,11 +698,17 @@ return baseclass.extend({
}
};
- o = this.replaceOption(s, 'devadvanced', form.Flag, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses'));
- o.default = o.disabled;
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses'));
+ o.sysfs = '/proc/sys/net/ipv4/conf/%s/accept_local'.format(devname || 'default');
- o = this.replaceOption(s, 'devadvanced', form.Flag, 'sendredirects', _('Send ICMP redirects'));
- o.default = o.enabled;
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'sendredirects', _('Send ICMP redirects'));
+ o.sysfs = '/proc/sys/net/ipv4/conf/%s/send_redirects'.format(devname || 'default');
+
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'arp_accept ', _('Honor gratuitous ARP'), _('When enabled, new ARP table entries are added from received gratuitous APR requests or replies, otherwise only preexisting table entries are updated, but no new hosts are learned.'));
+ o.sysfs = '/proc/sys/net/ipv4/conf/%s/arp_accept'.format(devname || 'default');
+
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'drop_gratuitous_arp', _('Drop gratuitous ARP'), _('Drop all gratuitous ARP frames, for example if there’s a known good ARP proxy on the network and such frames need not be used or in the case of 802.11, must not be used to prevent attacks.'));
+ o.sysfs = '/proc/sys/net/ipv4/conf/%s/drop_gratuitous_arp'.format(devname || 'default');
o = this.replaceOption(s, 'devadvanced', form.Value, 'neighreachabletime', _('Neighbour cache validity'), _('Time in milliseconds'));
o.placeholder = '30000';
@@ -662,59 +722,75 @@ return baseclass.extend({
o.placeholder = '0';
o.datatype = 'uinteger';
- o = this.replaceOption(s, 'devgeneral', form.Flag, 'ipv6', _('Enable IPv6'));
+ o = this.replaceOption(s, 'devgeneral', cbiFlagTristate, 'ipv6', _('Enable IPv6'));
+ o.sysfs = '!/proc/sys/net/ipv6/conf/%s/disable_ipv6'.format(devname || 'default');
o.migrate = false;
- o.default = o.enabled;
+
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'ip6segmentrouting', _('Enable IPv6 segment routing'));
+ o.sysfs = '/proc/sys/net/ipv6/conf/%s/seg6_enabled'.format(devname || 'default');
+ o.depends('ipv6', /1/);
+
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'drop_unsolicited_na', _('Drop unsolicited NA'), _('Drop all unsolicited neighbor advertisements, for example if there’s a known good NA proxy on the network and such frames need not be used or in the case of 802.11, must not be used to prevent attacks.'));
+ o.sysfs = '/proc/sys/net/ipv6/conf/%s/drop_unsolicited_na'.format(devname || 'default');
+ o.depends('ipv6', /1/);
o = this.replaceOption(s, 'devgeneral', form.Value, 'mtu6', _('IPv6 MTU'));
o.datatype = 'max(9200)';
- o.depends('ipv6', '1');
+ o.depends('ipv6', /1/);
o = this.replaceOption(s, 'devgeneral', form.Value, 'dadtransmits', _('DAD transmits'), _('Amount of Duplicate Address Detection probes to send'));
o.placeholder = '1';
o.datatype = 'uinteger';
- o.depends('ipv6', '1');
+ o.depends('ipv6', /1/);
- o = this.replaceOption(s, 'devadvanced', form.Flag, 'multicast', _('Enable multicast support'));
- o.default = o.enabled;
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'multicast', _('Enable multicast support'));
+ o.sysfs_default = (dev && dev.dev && dev.dev.flags) ? dev.dev.flags.multicast : null;
o = this.replaceOption(s, 'devadvanced', form.ListValue, 'igmpversion', _('Force IGMP version'));
o.value('', _('No enforcement'));
o.value('1', _('Enforce IGMPv1'));
o.value('2', _('Enforce IGMPv2'));
o.value('3', _('Enforce IGMPv3'));
- o.depends('multicast', '1');
+ o.depends('multicast', /1/);
o = this.replaceOption(s, 'devadvanced', form.ListValue, 'mldversion', _('Force MLD version'));
o.value('', _('No enforcement'));
o.value('1', _('Enforce MLD version 1'));
o.value('2', _('Enforce MLD version 2'));
- o.depends('multicast', '1');
+ o.depends('multicast', /1/);
if (isBridgePort(dev)) {
- o = this.replaceOption(s, 'brport', form.Flag, 'learning', _('Enable MAC address learning'));
- o.default = o.enabled;
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'learning', _('Enable MAC address learning'));
+ o.sysfs = '/sys/class/net/%s/brport/learning'.format(devname || 'default');
- o = this.replaceOption(s, 'brport', form.Flag, 'unicast_flood', _('Enable unicast flooding'));
- o.default = o.enabled;
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'unicast_flood', _('Enable unicast flooding'));
+ o.sysfs = '/sys/class/net/%s/brport/unicast_flood'.format(devname || 'default');
- o = this.replaceOption(s, 'brport', form.Flag, 'isolate', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled'));
- o.default = o.disabled;
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'isolate', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled'));
+ o.sysfs = '/sys/class/net/%s/brport/isolated'.format(devname || 'default');
o = this.replaceOption(s, 'brport', form.ListValue, 'multicast_router', _('Multicast routing'));
o.value('', _('Never'));
o.value('1', _('Learn'));
o.value('2', _('Always'));
- o.depends('multicast', '1');
+ o.depends('multicast', /1/);
+
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'multicast_to_unicast', _('Multicast to unicast'), _('Forward multicast packets as unicast packets on this device.'));
+ o.sysfs = '/sys/class/net/%s/brport/multicast_to_unicast'.format(devname || 'default');
+ o.depends('multicast', /1/);
- o = this.replaceOption(s, 'brport', form.Flag, 'multicast_to_unicast', _('Multicast to unicast'), _('Forward multicast packets as unicast packets on this device.'));
- o.default = o.disabled;
- o.depends('multicast', '1');
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'multicast_fast_leave', _('Enable multicast fast leave'));
+ o.sysfs = '/sys/class/net/%s/brport/multicast_fast_leave'.format(devname || 'default');
+ o.depends('multicast', /1/);
- o = this.replaceOption(s, 'brport', form.Flag, 'multicast_fast_leave', _('Enable multicast fast leave'));
- o.default = o.disabled;
- o.depends('multicast', '1');
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'drop_v4_unicast_in_l2_multicast', _('Drop nested IPv4 unicast'), _('Drop layer 2 multicast frames containing IPv4 unicast packets.'));
+ o.sysfs = '/proc/sys/net/ipv4/conf/%s/drop_unicast_in_l2_multicast'.format(devname || 'default');
+ o.depends('multicast', /1/);
+
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'drop_v6_unicast_in_l2_multicast', _('Drop nested IPv6 unicast'), _('Drop layer 2 multicast frames containing IPv6 unicast packets.'));
+ o.sysfs = '/proc/sys/net/ipv6/conf/%s/drop_unicast_in_l2_multicast'.format(devname || 'default');
+ o.depends('multicast', /1/);
}
o = this.replaceOption(s, 'bridgevlan', form.Flag, 'vlan_filtering', _('Enable VLAN filtering'));
@@ -780,6 +856,8 @@ return baseclass.extend({
return network.instantiateDevice(port)
}).filter(function(dev) {
return dev.getType() != 'wifi' || dev.isUp();
+ }).sort(function(a, b) {
+ return L.naturalCompare(a.getName(), b.getName());
});
this.children = this.children.filter(function(opt) { return !opt.option.match(/^port_/) });
@@ -893,18 +971,6 @@ return baseclass.extend({
for (var port_name in seen_ports)
ports.push(port_name);
- ports.sort(function(a, b) {
- var m1 = a.match(/^(.+?)([0-9]*)$/),
- m2 = b.match(/^(.+?)([0-9]*)$/);
-
- if (m1[1] < m2[1])
- return -1;
- else if (m1[1] > m2[1])
- return 1;
- else
- return +(m1[2] || 0) - +(m2[2] || 0);
- });
-
ss.updatePorts(ports);
},
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
index fc7dc5b51a..da0eeabb5a 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
@@ -126,7 +126,7 @@ function validateHostname(sid, s) {
if (s.length > 256)
return _('Expecting: %s').format(_('valid hostname'));
- var labels = s.replace(/^\.+|\.$/g, '').split(/\./);
+ var labels = s.replace(/^\*?\.?|\.$/g, '').split(/\./);
for (var i = 0; i < labels.length; i++)
if (!labels[i].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
@@ -156,13 +156,15 @@ function validateServerSpec(sid, s) {
if (s == null || s == '')
return true;
- var m = s.match(/^(?:\/(.+)\/)?(.*)$/);
+ var m = s.match(/^(\/.*\/)?(.*)$/);
if (!m)
return _('Expecting: %s').format(_('valid hostname'));
- var res = validateAddressList(sid, m[1]);
- if (res !== true)
- return res;
+ if (m[1] != '//' && m[1] != '/#/') {
+ var res = validateAddressList(sid, m[1]);
+ if (res !== true)
+ return res;
+ }
if (m[2] == '' || m[2] == '#')
return true;
@@ -231,7 +233,8 @@ return view.extend({
return Promise.all([
callHostHints(),
callDUIDHints(),
- getDHCPPools()
+ getDHCPPools(),
+ network.getNetworks()
]);
},
@@ -240,6 +243,7 @@ return view.extend({
hosts = hosts_duids_pools[0],
duids = hosts_duids_pools[1],
pools = hosts_duids_pools[2],
+ networks = hosts_duids_pools[3],
m, s, o, ss, so;
m = new form.Map('dhcp', _('DHCP and DNS'),
@@ -250,12 +254,16 @@ return view.extend({
s.addremove = false;
s.tab('general', _('General Settings'));
- s.tab('files', _('Resolv and Hosts Files'));
- s.tab('pxe_tftp', _('PXE/TFTP Settings'));
s.tab('advanced', _('Advanced Settings'));
s.tab('leases', _('Static Leases'));
+ s.tab('files', _('Resolv and Hosts Files'));
s.tab('hosts', _('Hostnames'));
s.tab('ipsets', _('IP Sets'));
+ s.tab('relay', _('Relay'));
+ s.tab('srvhosts', _('SRV'));
+ s.tab('mxhosts', _('MX'));
+ s.tab('cnamehosts', _('CNAME'));
+ s.tab('pxe_tftp', _('PXE/TFTP Settings'));
s.taboption('general', form.Flag, 'domainneeded',
_('Domain required'),
@@ -287,19 +295,22 @@ return view.extend({
o = s.taboption('general', form.DynamicList, 'address',
_('Addresses'),
- _('List of domains to force to an IP address.'));
+ _('Resolve specified FQDNs to an IP.') + '<br />' +
+ _('Syntax: <code>/fqdn[/fqdn…]/[ipaddr]</code>.') + '<br />' +
+ _('<code>/#/</code> matches any domain. <code>/example.com/</code> returns NXDOMAIN.') + '<br />' +
+ _('<code>/example.com/#</code> returns NULL addresses (<code>0.0.0.0</code> and <code>::</code>) for example.com and its subdomains.'));
o.optional = true;
- o.placeholder = '/router.local/192.168.0.1';
+ o.placeholder = '/router.local/router.lan/192.168.0.1';
o = s.taboption('general', form.DynamicList, 'ipset',
_('IP sets'),
- _('List of IP sets to populate with the specified domain IPs.'));
+ _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.'));
o.optional = true;
o.placeholder = '/example.org/ipset,ipset6';
o = s.taboption('general', form.Flag, 'rebind_protection',
_('Rebind protection'),
- _('Discard upstream RFC1918 responses.'));
+ _('Discard upstream responses containing <a href="%s">RFC1918</a> addresses.').format('https://datatracker.ietf.org/doc/html/rfc1918'));
o.rmempty = false;
o = s.taboption('general', form.Flag, 'rebind_localhost',
@@ -340,6 +351,70 @@ return view.extend({
o.optional = true;
o.placeholder = 'loopback';
+ o = s.taboption('relay', form.SectionValue, '__relays__', form.TableSection, 'relay', null,
+ _('Relay DHCP requests elsewhere. OK: v4↔v4, v6↔v6. Not OK: v4↔v6, v6↔v4.')
+ + '<br />' + _('Note: you may also need a DHCP Proxy (currently unavailable) when specifying a non-standard Relay To port(<code>addr#port</code>).')
+ + '<br />' + _('You may add multiple unique Relay To on the same Listen addr.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+
+ so = ss.option(form.Value, 'local_addr', _('Relay from'));
+ so.rmempty = false;
+ so.datatype = 'ipaddr';
+
+ for (var family = 4; family <= 6; family += 2) {
+ for (var i = 0; i < networks.length; i++) {
+ if (networks[i].getName() != 'loopback') {
+ var addrs = (family == 6) ? networks[i].getIP6Addrs() : networks[i].getIPAddrs();
+ for (var j = 0; j < addrs.length; j++) {
+ var addr = addrs[j].split('/')[0];
+ so.value(addr, E([], [
+ addr, ' (',
+ widgets.NetworkSelect.prototype.renderIfaceBadge(networks[i]),
+ ')'
+ ]));
+ }
+ }
+ }
+ }
+
+ so = ss.option(form.Value, 'server_addr', _('Relay to address'));
+ so.rmempty = false;
+ so.optional = false;
+ so.placeholder = '192.168.10.1#535';
+
+ so.validate = function(section, value) {
+ var m = this.section.formvalue(section, 'local_addr'),
+ n = this.section.formvalue(section, 'server_addr'),
+ p;
+ if (n != null && n != '')
+ p = n.split('#');
+ if (p.length > 1 && !/^[0-9]+$/.test(p[1]))
+ return _('Expected port number.');
+ else
+ n = p[0];
+
+ if ((m == null || m == '') && (n == null || n == ''))
+ return _('Both "Relay from" and "Relay to address" must be specified.');
+
+ if ((validation.parseIPv6(m) && validation.parseIPv6(n)) ||
+ validation.parseIPv4(m) && validation.parseIPv4(n))
+ return true;
+ else
+ return _('Address families of "Relay from" and "Relay to address" must match.')
+ };
+
+ so = ss.option(widgets.NetworkSelect, 'interface', _('Only accept replies via'));
+ so.optional = true;
+ so.rmempty = false;
+ so.placeholder = 'lan';
+
s.taboption('files', form.Flag, 'readethers',
_('Use <code>/etc/ethers</code>'),
_('Read <code>/etc/ethers</code> to configure the DHCP server.'));
@@ -384,8 +459,20 @@ return view.extend({
o.default = o.enabled;
s.taboption('advanced', form.Flag, 'filterwin2k',
- _('Filter useless'),
- _('Do not forward queries that cannot be answered by public resolvers.'));
+ _('Filter SRV/SOA service discovery'),
+ _('Filters SRV/SOA service discovery, to avoid triggering dial-on-demand links.') + '<br />' +
+ _('May prevent VoIP or other services from working.'));
+
+ o = s.taboption('advanced', form.Flag, 'filter_aaaa',
+ _('Filter IPv6 AAAA records'),
+ _('Remove IPv6 addresses from the results and only return IPv4 addresses.') + '<br />' +
+ _('Can be useful if ISP has IPv6 nameservers but does not provide IPv6 routing.'));
+ o.optional = true;
+
+ o = s.taboption('advanced', form.Flag, 'filter_a',
+ _('Filter IPv4 A records'),
+ _('Remove IPv4 addresses from the results and only return IPv6 addresses.'));
+ o.optional = true;
s.taboption('advanced', form.Flag, 'localise_queries',
_('Localise queries'),
@@ -473,7 +560,7 @@ return view.extend({
_('Number of cached DNS entries, 10000 is maximum, 0 is no caching.'));
o.optional = true;
o.datatype = 'range(0,10000)';
- o.placeholder = 150;
+ o.placeholder = 1000;
o = s.taboption('pxe_tftp', form.Flag, 'enable_tftp',
_('Enable TFTP server'),
@@ -546,6 +633,93 @@ return view.extend({
so.value(index, '%s (Domain: %s, Local: %s)'.format(index, val.domain || '?', val.local || '?'));
});
+ o = s.taboption('srvhosts', form.SectionValue, '__srvhosts__', form.TableSection, 'srvhost', null,
+ _('Bind service records to a domain name: specify the location of services. See <a href="%s">RFC2782</a>.').format('https://datatracker.ietf.org/doc/html/rfc2782')
+ + '<br />' + _('_service: _sip, _ldap, _imap, _stun, _xmpp-client, … . (Note: while _http is possible, no browsers support SRV records.)')
+ + '<br />' + _('_proto: _tcp, _udp, _sctp, _quic, … .')
+ + '<br />' + _('You may add multiple records for the same Target.')
+ + '<br />' + _('Larger weights (of the same prio) are given a proportionately higher probability of being selected.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+
+ so = ss.option(form.Value, 'srv', _('SRV'), _('Syntax: <code>_service._proto.example.com</code>.'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = '_sip._tcp.example.com';
+
+ so = ss.option(form.Value, 'target', _('Target'), _('CNAME or fqdn'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'sip.example.com';
+
+ so = ss.option(form.Value, 'port', _('Port'));
+ so.rmempty = false;
+ so.datatype = 'port';
+ so.placeholder = '5060';
+
+ so = ss.option(form.Value, 'class', _('Priority'), _('Ordinal: lower comes first.'));
+ so.rmempty = true;
+ so.datatype = 'range(0,65535)';
+ so.placeholder = '10';
+
+ so = ss.option(form.Value, 'weight', _('Weight'));
+ so.rmempty = true;
+ so.datatype = 'range(0,65535)';
+ so.placeholder = '50';
+
+ o = s.taboption('mxhosts', form.SectionValue, '__mxhosts__', form.TableSection, 'mxhost', null,
+ _('Bind service records to a domain name: specify the location of services.')
+ + '<br />' + _('You may add multiple records for the same domain.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+
+ so = ss.option(form.Value, 'domain', _('Domain'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'example.com';
+
+ so = ss.option(form.Value, 'relay', _('Relay'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'relay.example.com';
+
+ so = ss.option(form.Value, 'pref', _('Priority'), _('Ordinal: lower comes first.'));
+ so.rmempty = true;
+ so.datatype = 'range(0,65535)';
+ so.placeholder = '0';
+
+ o = s.taboption('cnamehosts', form.SectionValue, '__cname__', form.TableSection, 'cname', null,
+ _('Set an alias for a hostname.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+
+ so = ss.option(form.Value, 'cname', _('Domain'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'www.example.com';
+
+ so = ss.option(form.Value, 'target', _('Target'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'example.com';
+
o = s.taboption('hosts', form.SectionValue, '__hosts__', form.GridSection, 'domain', null,
_('Hostnames are used to bind a domain name to an IP address. This setting is redundant for hostnames already configured with static leases, but it can be useful to rebind an FQDN.'));
@@ -577,7 +751,7 @@ return view.extend({
});
o = s.taboption('ipsets', form.SectionValue, '__ipsets__', form.GridSection, 'ipset', null,
- _('List of IP sets to populate with the specified domain IPs.'));
+ _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.'));
ss = o.subsection;
@@ -594,16 +768,22 @@ return view.extend({
so.datatype = 'hostname';
o = s.taboption('leases', form.SectionValue, '__leases__', form.GridSection, 'host', null,
- _('Static leases are used to assign fixed IP addresses and symbolic hostnames to DHCP clients. They are also required for non-dynamic interface configurations where only hosts with a corresponding lease are served.') + '<br />' +
- _('Use the <em>Add</em> Button to add a new lease entry. The <em>MAC address</em> identifies the host, the <em>IPv4 address</em> specifies the fixed address to use, and the <em>Hostname</em> is assigned as a symbolic name to the requesting host. The optional <em>Lease time</em> can be used to set non-standard host-specific lease time, e.g. 12h, 3d or infinite.'));
+ _('Static leases are used to assign fixed IP addresses and symbolic hostnames to DHCP clients. They are also required for non-dynamic interface configurations where only hosts with a corresponding lease are served.') + '<br /><br />' +
+ _('Use the <em>Add</em> Button to add a new lease entry. The <em>MAC address</em> identifies the host, the <em>IPv4 address</em> specifies the fixed address to use, and the <em>Hostname</em> is assigned as a symbolic name to the requesting host. The optional <em>Lease time</em> can be used to set non-standard host-specific lease time, e.g. 12h, 3d or infinite.') + '<br /><br />' +
+ _('The tag construct filters which host directives are used; more than one tag can be provided, in this case the request must match all of them. Tagged directives are used in preference to untagged ones. Note that one of mac, duid or hostname still needs to be specified (can be a wildcard).'));
ss = o.subsection;
ss.addremove = true;
ss.anonymous = true;
ss.sortable = true;
+ ss.nodescriptions = true;
+ ss.max_cols = 8;
+ ss.modaltitle = _('Edit static lease');
- so = ss.option(form.Value, 'name', _('Hostname'));
+ so = ss.option(form.Value, 'name',
+ _('Hostname'),
+ _('Optional hostname to assign'));
so.validate = validateHostname;
so.rmempty = true;
so.write = function(section, value) {
@@ -615,20 +795,35 @@ return view.extend({
uci.unset('dhcp', section, 'dns');
};
- so = ss.option(form.Value, 'mac', _('MAC address'));
- so.datatype = 'list(macaddr)';
+ so = ss.option(form.Value, 'mac',
+ _('MAC address(es)'),
+ _('The hardware address(es) of this entry/host, separated by spaces.') + '<br /><br />' +
+ _('In DHCPv4, it is possible to include more than one mac address. This allows an IP address to be associated with multiple macaddrs, and dnsmasq abandons a DHCP lease to one of the macaddrs when another asks for a lease. It only works reliably if only one of the macaddrs is active at any time.'));
+ //As a special case, in DHCPv4, it is possible to include more than one hardware address. eg: --dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.2 This allows an IP address to be associated with multiple hardware addresses, and gives dnsmasq permission to abandon a DHCP lease to one of the hardware addresses when another one asks for a lease
+ so.validate = function(section_id, value) {
+ var macaddrs = L.toArray(value);
+
+ for (var i = 0; i < macaddrs.length; i++)
+ if (!macaddrs[i].match(/^([a-fA-F0-9]{2}|\*):([a-fA-F0-9]{2}:|\*:){4}(?:[a-fA-F0-9]{2}|\*)$/))
+ return _('Expecting a valid MAC address, optionally including wildcards');
+
+ return true;
+ };
so.rmempty = true;
so.cfgvalue = function(section) {
var macs = L.toArray(uci.get('dhcp', section, 'mac')),
result = [];
for (var i = 0, mac; (mac = macs[i]) != null; i++)
- if (/^([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2})$/.test(mac))
- result.push('%02X:%02X:%02X:%02X:%02X:%02X'.format(
+ if (/^([0-9a-fA-F]{1,2}|\*):([0-9a-fA-F]{1,2}|\*):([0-9a-fA-F]{1,2}|\*):([0-9a-fA-F]{1,2}|\*):([0-9a-fA-F]{1,2}|\*):([0-9a-fA-F]{1,2}|\*)$/.test(mac)) {
+ var m = [
parseInt(RegExp.$1, 16), parseInt(RegExp.$2, 16),
parseInt(RegExp.$3, 16), parseInt(RegExp.$4, 16),
- parseInt(RegExp.$5, 16), parseInt(RegExp.$6, 16)));
+ parseInt(RegExp.$5, 16), parseInt(RegExp.$6, 16)
+ ];
+ result.push(m.map(function(n) { return isNaN(n) ? '*' : '%02X'.format(n) }).join(':'));
+ }
return result.length ? result.join(' ') : null;
};
so.renderWidget = function(section_id, option_index, cfgvalue) {
@@ -661,7 +856,8 @@ return view.extend({
so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
});
- so = ss.option(form.Value, 'ip', _('IPv4 address'));
+ so = ss.option(form.Value, 'ip', _('IPv4 address'), _('The IP address to be used for this host, or <em>ignore</em> to ignore any DHCP request from this host.'));
+ so.value('ignore', _('Ignore'));
so.datatype = 'or(ip4addr,"ignore")';
so.validate = function(section, value) {
var m = this.section.formvalue(section, 'mac'),
@@ -693,16 +889,60 @@ return view.extend({
so.value(ipv4, ipaddrs[ipv4] ? '%s (%s)'.format(ipv4, ipaddrs[ipv4]) : ipv4);
});
- so = ss.option(form.Value, 'leasetime', _('Lease time'));
+ so = ss.option(form.Value, 'leasetime',
+ _('Lease time'),
+ _('Host-specific lease time, e.g. <code>5m</code>, <code>3h</code>, <code>7d</code>.'));
so.rmempty = true;
-
- so = ss.option(form.Value, 'duid', _('DUID'));
+ so.value('5m', _('5m (5 minutes)'));
+ so.value('3h', _('3h (3 hours)'));
+ so.value('12h', _('12h (12 hours - default)'));
+ so.value('7d', _('7d (7 days)'));
+ so.value('infinite', _('infinite (lease does not expire)'));
+
+ so = ss.option(form.Value, 'duid',
+ _('DUID'),
+ _('The DHCPv6-DUID (DHCP unique identifier) of this host.'));
so.datatype = 'and(rangelength(20,36),hexstring)';
Object.keys(duids).forEach(function(duid) {
so.value(duid, '%s (%s)'.format(duid, duids[duid].hostname || duids[duid].macaddr || duids[duid].ip6addr || '?'));
});
- so = ss.option(form.Value, 'hostid', _('IPv6 suffix (hex)'));
+ so = ss.option(form.Value, 'hostid',
+ _('IPv6-Suffix (hex)'),
+ _('The IPv6 interface identifier (address suffix) as hexadecimal number (max. 16 chars).'));
+ so.datatype = 'and(rangelength(0,16),hexstring)';
+
+ so = ss.option(form.DynamicList, 'tag',
+ _('Tag'),
+ _('Assign new, freeform tags to this entry.'));
+
+ so = ss.option(form.DynamicList, 'match_tag',
+ _('Match Tag'),
+ _('When a host matches an entry then the special tag <em>known</em> is set. Use <em>known</em> to match all known hosts.') + '<br /><br />' +
+ _('Ignore requests from unknown machines using <em>!known</em>.') + '<br /><br />' +
+ _('If a host matches an entry which cannot be used because it specifies an address on a different subnet, the tag <em>known-othernet</em> is set.'));
+ so.value('known', _('known'));
+ so.value('!known', _('!known (not known)'));
+ so.value('known-othernet', _('known-othernet (on different subnet)'));
+ so.optional = true;
+
+ so = ss.option(form.Value, 'instance',
+ _('Instance'),
+ _('Dnsmasq instance to which this DHCP host section is bound. If unspecified, the section is valid for all dnsmasq instances.'));
+ so.optional = true;
+
+ Object.values(L.uci.sections('dhcp', 'dnsmasq')).forEach(function(val, index) {
+ so.value(index, '%s (Domain: %s, Local: %s)'.format(index, val.domain || '?', val.local || '?'));
+ });
+
+
+ so = ss.option(form.Flag, 'broadcast',
+ _('Broadcast'),
+ _('Force broadcast DHCP response.'));
+
+ so = ss.option(form.Flag, 'dns',
+ _('Forward/reverse DNS'),
+ _('Add static forward and reverse DNS entries for this host.'));
o = s.taboption('leases', CBILeaseStatus, '__status__');
@@ -726,8 +966,17 @@ return view.extend({
else
exp = '%t'.format(lease.expires);
+ var hint = lease.macaddr ? hosts[lease.macaddr] : null,
+ name = hint ? hint.name : null,
+ host = null;
+
+ if (name && lease.hostname && lease.hostname != name)
+ host = '%s (%s)'.format(lease.hostname, name);
+ else if (lease.hostname)
+ host = lease.hostname;
+
return [
- lease.hostname || '?',
+ host || '-',
lease.ipaddr,
lease.macaddr,
exp
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js
index f436118cf9..1bfa95501a 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js
@@ -14,8 +14,7 @@ return view.extend({
buttons[i].setAttribute('disabled', 'true');
return fs.exec(exec, args).then(function(res) {
- var out = document.querySelector('.command-output');
- out.style.display = '';
+ var out = document.querySelector('textarea');
dom.content(out, [ res.stdout || '', res.stderr || '' ]);
}).catch(function(err) {
@@ -37,7 +36,7 @@ return view.extend({
handleTraceroute: function(ev, cmd) {
var exec = cmd || 'traceroute',
addr = ev.currentTarget.parentNode.previousSibling.value,
- args = (exec == 'traceroute') ? [ '-4', '-q', '1', '-w', '1', '-n', addr ] : [ '-q', '1', '-w', '2', '-n', addr ];
+ args = (exec == 'traceroute') ? [ '-4', '-q', '1', '-w', '1', '-n', '-m', String(L.env.rpctimeout || 20), addr ] : [ '-q', '1', '-w', '2', '-n', addr ];
return this.handleCommand(exec, args);
},
@@ -75,11 +74,9 @@ return view.extend({
ping_host = uci.get('luci', 'diag', 'ping') || 'openwrt.org',
route_host = uci.get('luci', 'diag', 'route') || 'openwrt.org';
- return E([], [
- E('h2', {}, [ _('Network Utilities') ]),
- E('table', { 'class': 'table' }, [
+ var table = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr' }, [
- E('td', { 'class': 'td left' }, [
+ E('td', { 'class': 'td left', 'style': 'overflow:initial' }, [
E('input', {
'style': 'margin:5px 0',
'type': 'text',
@@ -102,7 +99,7 @@ return view.extend({
])
]),
- E('td', { 'class': 'td left' }, [
+ E('td', { 'class': 'td left', 'style': 'overflow:initial' }, [
E('input', {
'style': 'margin:5px 0',
'type': 'text',
@@ -156,9 +153,26 @@ return view.extend({
])
]) : E([]),
])
- ]),
- E('pre', { 'class': 'command-output', 'style': 'display:none' })
+ ]);
+
+ var view = E('div', { 'class': 'cbi-map'}, [
+ E('h2', {}, [ _('Diagnostics') ]),
+ E('div', { 'class': 'cbi-map-descr'}, _('Execution of various network commands to check the connection and name resolution to other systems.')),
+ table,
+ E('div', {'class': 'cbi-section'}, [
+ E('div', { 'id' : 'command-output'},
+ E('textarea', {
+ 'id': 'widget.command-output',
+ 'style': 'width: 100%; font-family:monospace; white-space:pre',
+ 'readonly': true,
+ 'wrap': 'off',
+ 'rows': '20'
+ })
+ )
+ ])
]);
+
+ return view;
},
handleSaveApply: null,
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
index 52ad6e9b6f..3748f04b05 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
@@ -228,6 +228,23 @@ function get_netmask(s, use_cfgvalue) {
return subnetmask;
}
+function has_peerdns(proto) {
+ switch (proto) {
+ case 'dhcp':
+ case 'dhcpv6':
+ case 'qmi':
+ case 'ppp':
+ case 'pppoe':
+ case 'pppoa':
+ case 'pptp':
+ case 'openvpn':
+ case 'sstp':
+ return true;
+ }
+
+ return false;
+}
+
var cbiRichListValue = form.ListValue.extend({
renderWidget: function(section_id, option_index, cfgvalue) {
var choices = this.transformChoices();
@@ -488,7 +505,7 @@ return view.extend({
};
s.modaltitle = function(section_id) {
- return _('Interfaces') + ' » ' + section_id.toUpperCase();
+ return _('Interfaces') + ' » ' + section_id;
};
s.renderRowActions = function(section_id) {
@@ -535,7 +552,7 @@ return view.extend({
var protocols = network.getProtocols();
protocols.sort(function(a, b) {
- return a.getProtocol() > b.getProtocol();
+ return L.naturalCompare(a.getProtocol(), b.getProtocol());
});
o = s.taboption('general', form.DummyValue, '_ifacestat_modal', _('Status'));
@@ -723,12 +740,35 @@ return view.extend({
var hybrid_downstream_desc = _('Operate in <em>relay mode</em> if a designated master interface is configured and active, otherwise fall back to <em>server mode</em>.'),
ndp_downstream_desc = _('Operate in <em>relay mode</em> if a designated master interface is configured and active, otherwise disable <abbr title="Neighbour Discovery Protocol">NDP</abbr> proxying.'),
hybrid_master_desc = _('Operate in <em>relay mode</em> if an upstream IPv6 prefix is present, otherwise disable service.'),
+ ra_server_allowed = true,
checked = this.formvalue(section_id),
dhcpv6 = this.section.getOption('dhcpv6').getUIElement(section_id),
ndp = this.section.getOption('ndp').getUIElement(section_id),
ra = this.section.getOption('ra').getUIElement(section_id);
- if (checked == '1' || protoval != 'static') {
+ /* Assume that serving RAs by default is fine, but disallow it for certain
+ interface protocols such as DHCP, DHCPv6 or the various PPP flavors.
+ The intent is to only allow RA serving for interface protocols doing
+ some kind of static IP config over something resembling a layer 2
+ ethernet device. */
+ switch (protoval) {
+ case 'dhcp':
+ case 'dhcpv6':
+ case '3g':
+ case 'l2tp':
+ case 'ppp':
+ case 'pppoa':
+ case 'pppoe':
+ case 'pptp':
+ case 'pppossh':
+ case 'ipip':
+ case 'gre':
+ case 'grev6':
+ ra_server_allowed = false;
+ break;
+ }
+
+ if (checked == '1' || !ra_server_allowed) {
dhcpv6.node.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
if (dhcpv6.getValue() == 'server')
@@ -746,7 +786,7 @@ return view.extend({
ndp.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_master_desc;
}
else {
- if (protoval == 'static') {
+ if (ra_server_allowed) {
dhcpv6.node.querySelector('li[data-value="server"]').removeAttribute('unselectable');
ra.node.querySelector('li[data-value="server"]').removeAttribute('unselectable');
}
@@ -805,9 +845,23 @@ return view.extend({
return flags.length ? flags : [ 'other-config' ];
};
so.remove = function(section_id) {
- uci.set('dhcp', section_id, 'ra_flags', [ 'none' ]);
+ var existing = L.toArray(uci.get('dhcp', section_id, 'ra_flags'));
+ if (this.isActive(section_id)) {
+ if (existing.length != 1 || existing[0] != 'none')
+ uci.set('dhcp', section_id, 'ra_flags', [ 'none' ]);
+ }
+ else if (existing.length) {
+ uci.unset('dhcp', section_id, 'ra_flags');
+ }
};
+ so = ss.taboption('ipv6-ra', form.Value, 'ra_pref64', _('NAT64 prefix'), _('Announce NAT64 prefix in <abbr title="Router Advertisement">RA</abbr> messages.'));
+ so.optional = true;
+ so.datatype = 'cidr6';
+ so.placeholder = '64:ff9b::/96';
+ so.depends('ra', 'server');
+ so.depends({ ra: 'hybrid', master: '0' });
+
so = ss.taboption('ipv6-ra', form.Value, 'ra_maxinterval', _('Max <abbr title="Router Advertisement">RA</abbr> interval'), _('Maximum time allowed between sending unsolicited <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr>. Default is 600 seconds.'));
so.optional = true;
so.datatype = 'uinteger';
@@ -835,15 +889,17 @@ return view.extend({
so.depends('ra', 'server');
so.depends({ ra: 'hybrid', master: '0' });
so.load = function(section_id) {
- var dev = ifc.getL3Device();
+ var dev = ifc.getL3Device(),
+ path = dev ? "/proc/sys/net/ipv6/conf/%s/mtu".format(dev.getName()) : null;
- if (dev) {
- var path = "/proc/sys/net/ipv6/conf/%s/mtu".format(dev.getName());
+ return Promise.all([
+ dev ? L.resolveDefault(fs.read(path), dev.getMTU()) : null,
+ this.super('load', [section_id])
+ ]).then(L.bind(function(res) {
+ this.placeholder = +res[0];
- return L.resolveDefault(fs.read(path), dev.getMTU()).then(L.bind(function(data) {
- this.placeholder = data;
- }, this));
- }
+ return res[1];
+ }, this));
};
so = ss.taboption('ipv6-ra', form.Value, 'ra_hoplimit', _('<abbr title="Router Advertisement">RA</abbr> Hop Limit'), _('The maximum hops to be published in <abbr title="Router Advertisement">RA</abbr> messages. Maximum is 255 hops.'));
@@ -852,15 +908,17 @@ return view.extend({
so.depends('ra', 'server');
so.depends({ ra: 'hybrid', master: '0' });
so.load = function(section_id) {
- var dev = ifc.getL3Device();
+ var dev = ifc.getL3Device(),
+ path = dev ? "/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev.getName()) : null;
- if (dev) {
- var path = "/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev.getName());
+ return Promise.all([
+ dev ? L.resolveDefault(fs.read(path), 64) : null,
+ this.super('load', [section_id])
+ ]).then(L.bind(function(res) {
+ this.placeholder = +res[0];
- return L.resolveDefault(fs.read(path), 64).then(L.bind(function(data) {
- this.placeholder = data;
- }, this));
- }
+ return res[1];
+ }, this));
};
@@ -874,22 +932,32 @@ return view.extend({
_('Forward DHCPv6 messages between the designated master interface and downstream interfaces.'));
so.value('hybrid', _('hybrid mode'), ' ');
+ so = ss.taboption('ipv6', form.Value, 'dhcpv6_pd_min_len', _('<abbr title="Prefix Delegation">PD</abbr> minimum length'),
+ _('Configures the minimum delegated prefix length assigned to a requesting downstream router, potentially overriding a requested prefix length. If left unspecified, the device will assign the smallest available prefix greater than or equal to the requested prefix.'));
+ so.datatype = 'range(1,62)';
+ so.depends('dhcpv6', 'server');
so = ss.taboption('ipv6', form.DynamicList, 'dns', _('Announced IPv6 DNS servers'),
_('Specifies a fixed list of IPv6 DNS server addresses to announce via DHCPv6. If left unspecified, the device will announce itself as IPv6 DNS server unless the <em>Local IPv6 DNS server</em> option is disabled.'));
so.datatype = 'ip6addr("nomask")'; /* restrict to IPv6 only for now since dnsmasq (DHCPv4) does not honour this option */
+ so.depends('ra', 'server');
+ so.depends({ ra: 'hybrid', master: '0' });
so.depends('dhcpv6', 'server');
so.depends({ dhcpv6: 'hybrid', master: '0' });
so = ss.taboption('ipv6', form.Flag, 'dns_service', _('Local IPv6 DNS server'),
_('Announce this device as IPv6 DNS server.'));
so.default = so.enabled;
+ so.depends({ ra: 'server', dns: /^$/ });
+ so.depends({ ra: 'hybrid', dns: /^$/, master: '0' });
so.depends({ dhcpv6: 'server', dns: /^$/ });
so.depends({ dhcpv6: 'hybrid', dns: /^$/, master: '0' });
so = ss.taboption('ipv6', form.DynamicList, 'domain', _('Announced DNS domains'),
_('Specifies a fixed list of DNS search domains to announce via DHCPv6. If left unspecified, the local device DNS search domain will be announced.'));
so.datatype = 'hostname';
+ so.depends('ra', 'server');
+ so.depends({ ra: 'hybrid', master: '0' });
so.depends('dhcpv6', 'server');
so.depends({ dhcpv6: 'hybrid', master: '0' });
@@ -903,7 +971,7 @@ return view.extend({
so.value('hybrid', _('hybrid mode'), ' ');
- so = ss.taboption('ipv6', form.Flag, 'ndproxy_routing', _('Learn routes'), _('Setup routes for proxied IPv6 neighbours.'));
+ so = ss.taboption('ipv6', form.Flag, 'ndproxy_routing', _('Learn routes'), _('Set up routes for proxied IPv6 neighbours.'));
so.default = so.enabled;
so.depends('ndp', 'relay');
so.depends('ndp', 'hybrid');
@@ -911,6 +979,18 @@ return view.extend({
so = ss.taboption('ipv6', form.Flag, 'ndproxy_slave', _('NDP-Proxy slave'), _('Set interface as NDP-Proxy external slave. Default is off.'));
so.depends({ ndp: 'relay', master: '0' });
so.depends({ ndp: 'hybrid', master: '0' });
+
+ so = ss.taboption('ipv6', form.Value, 'preferred_lifetime', _('IPv6 Prefix Lifetime'), _('Preferred lifetime for a prefix.'));
+ so.optional = true;
+ so.placeholder = '12h';
+ so.value('5m', _('5m (5 minutes)'));
+ so.value('3h', _('3h (3 hours)'));
+ so.value('12h', _('12h (12 hours - default)'));
+ so.value('7d', _('7d (7 days)'));
+
+ //This is a ra_* setting, but its placement is more logical/findable under IPv6 settings.
+ so = ss.taboption('ipv6', form.Flag, 'ra_useleasetime', _('Follow IPv4 Lifetime'), _('DHCPv4 <code>leasetime</code> is used as limit and preferred lifetime of the IPv6 prefix.'));
+ so.optional = true;
}
ifc.renderFormOptions(s);
@@ -919,13 +999,13 @@ return view.extend({
o = nettools.replaceOption(s, 'advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
o.default = o.enabled;
- if (protoval != 'static') {
+ if (has_peerdns(protoval)) {
o = nettools.replaceOption(s, 'advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
o.default = o.enabled;
}
o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- if (protoval != 'static')
+ if (has_peerdns(protoval))
o.depends('peerdns', '0');
o.datatype = 'ipaddr';
@@ -1074,7 +1154,7 @@ return view.extend({
proto, name, device;
protocols.sort(function(a, b) {
- return a.getProtocol() > b.getProtocol();
+ return L.naturalCompare(a.getProtocol(), b.getProtocol());
});
s2.render = function() {
@@ -1148,6 +1228,9 @@ return view.extend({
protoclass.addDevice(device.formvalue('_new_'));
m.children[0].addedSection = section_id;
+
+ ui.hideModal();
+ ui.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
}).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval));
});
})
@@ -1179,7 +1262,7 @@ return view.extend({
'class': 'ifacebox-head',
'style': firewall.getZoneColorStyle(zone),
'title': zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned')
- }, E('strong', net.getName().toUpperCase())),
+ }, E('strong', net.getName())),
E('div', {
'class': 'ifacebox-body',
'id': '%s-ifc-devices'.format(section_id),
@@ -1233,7 +1316,7 @@ return view.extend({
s.cfgsections = function() {
var sections = uci.sections('network', 'device'),
- section_ids = sections.sort(function(a, b) { return a.name > b.name }).map(function(s) { return s['.name'] });
+ section_ids = sections.sort(function(a, b) { return L.naturalCompare(a.name, b.name) }).map(function(s) { return s['.name'] });
for (var i = 0; i < netDevs.length; i++) {
if (sections.filter(function(s) { return s.name == netDevs[i].getName() }).length)
@@ -1311,6 +1394,9 @@ return view.extend({
for (var i = 0; i < map.addedVLANs.length; i++)
uci.remove('network', map.addedVLANs[i]);
+ if (this.addedSection)
+ uci.remove('network', this.addedSection);
+
return form.GridSection.prototype.handleModalCancel.apply(this, arguments);
};
@@ -1430,7 +1516,7 @@ return view.extend({
mac = dev ? dev.getMAC() : null;
return val ? E('strong', {
- 'data-tooltip': _('The value is overridden by configuration. Original: %s').format(mac || _('unknown'))
+ 'data-tooltip': _('The value is overridden by configuration.')
}, [ val.toUpperCase() ]) : (mac || '-');
};
@@ -1442,7 +1528,7 @@ return view.extend({
mtu = dev ? dev.getMTU() : null;
return val ? E('strong', {
- 'data-tooltip': _('The value is overridden by configuration. Original: %s').format(mtu || _('unknown'))
+ 'data-tooltip': _('The value is overridden by configuration.')
}, [ val ]) : (mtu || '-').toString();
};
@@ -1462,21 +1548,27 @@ return view.extend({
s.anonymous = true;
o = s.option(form.ListValue, 'annex', _('Annex'));
- o.value('a', _('Annex A + L + M (all)'));
- o.value('b', _('Annex B (all)'));
- o.value('j', _('Annex J (all)'));
- o.value('m', _('Annex M (all)'));
- o.value('bdmt', _('Annex B G.992.1'));
- o.value('b2', _('Annex B G.992.3'));
- o.value('b2p', _('Annex B G.992.5'));
+ if (dslModemType == 'vdsl') {
+ o.value('a', _('ADSL (all variants) Annex A/L/M + VDSL2 Annex A/B/C'));
+ o.value('b', _('ADSL (all variants) Annex B + VDSL2 Annex A/B/C'));
+ o.value('j', _('ADSL (all variants) Annex B/J + VDSL2 Annex A/B/C'));
+ } else {
+ o.value('a', _('ADSL (all variants) Annex A/L/M'));
+ o.value('b', _('ADSL (all variants) Annex B'));
+ o.value('j', _('ADSL (all variants) Annex B/J'));
+ }
+ o.value('m', _('ADSL (all variants) Annex M'));
o.value('at1', _('ANSI T1.413'));
- o.value('admt', _('Annex A G.992.1'));
- o.value('alite', _('Annex A G.992.2'));
- o.value('a2', _('Annex A G.992.3'));
- o.value('a2p', _('Annex A G.992.5'));
- o.value('l', _('Annex L G.992.3 POTS 1'));
- o.value('m2', _('Annex M G.992.3'));
- o.value('m2p', _('Annex M G.992.5'));
+ o.value('admt', _('ADSL (G.992.1) Annex A'));
+ o.value('bdmt', _('ADSL (G.992.1) Annex B'));
+ o.value('alite', _('Splitterless ADSL (G.992.2) Annex A'));
+ o.value('a2', _('ADSL2 (G.992.3) Annex A'));
+ o.value('b2', _('ADSL2 (G.992.3) Annex B'));
+ o.value('l', _('ADSL2 (G.992.3) Annex L'));
+ o.value('m2', _('ADSL2 (G.992.3) Annex M'));
+ o.value('a2p', _('ADSL2+ (G.992.5) Annex A'));
+ o.value('b2p', _('ADSL2+ (G.992.5) Annex B'));
+ o.value('m2p', _('ADSL2+ (G.992.5) Annex M'));
o = s.option(form.ListValue, 'tone', _('Tone'));
o.value('', _('auto'));
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
index c20574dbff..4004be219a 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
@@ -38,11 +38,12 @@ return view.extend({
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
- o = s.taboption('general', widgets.NetworkSelect, 'interface', _('Interface'));
+ o = s.taboption('general', widgets.NetworkSelect, 'interface', _('Interface'), _('Specifies the logical interface name of the parent (or master) interface this route belongs to'));
o.loopback = true;
o.nocreate = true;
+ o.rmempty = false;
- o = s.taboption('general', form.ListValue, 'type', _('Route type'));
+ o = s.taboption('general', form.ListValue, 'type', _('Route type'), _('Specifies the route type to be created'));
o.modalonly = true;
o.value('', 'unicast');
o.value('local');
@@ -52,8 +53,9 @@ return view.extend({
o.value('prohibit');
o.value('blackhole');
o.value('anycast');
+ o.value('throw');
- o = s.taboption('general', form.Value, 'target', _('Target'));
+ o = s.taboption('general', form.Value, 'target', _('Target'), _('Network address'));
o.rmempty = false;
o.datatype = (family == 6) ? 'cidr6' : 'cidr4';
o.placeholder = (family == 6) ? '::/0' : '0.0.0.0/0';
@@ -72,41 +74,40 @@ return view.extend({
uci.unset('network', section_id, 'netmask');
}
- o = s.taboption('general', form.Value, 'gateway', _('Gateway'));
+ o = s.taboption('general', form.Value, 'gateway', _('Gateway'), _('Specifies the network gateway. If omitted, the gateway from the parent interface is taken if any, otherwise creates a link scope route. If set to 0.0.0.0 no gateway will be specified for the route'));
o.datatype = (family == 6) ? 'ip6addr("nomask")' : 'ip4addr("nomask")';
o.placeholder = (family == 6) ? 'fe80::1' : '192.168.0.1';
- o = s.taboption('advanced', form.Value, 'metric', _('Metric'));
+ o = s.taboption('advanced', form.Value, 'metric', _('Metric'), _('Specifies the route metric to use'));
o.datatype = 'uinteger';
o.placeholder = 0;
o.textvalue = function(section_id) {
- return this.cfgvalue(section_id) || 0;
+ return this.cfgvalue(section_id) || E('em', _('auto'));
};
- o = s.taboption('advanced', form.Value, 'mtu', _('MTU'));
+ o = s.taboption('advanced', form.Value, 'mtu', _('MTU'), _('Defines a specific MTU for this route'));
o.modalonly = true;
o.datatype = 'and(uinteger,range(64,9000))';
o.placeholder = 1500;
- o = s.taboption('advanced', form.Value, 'table', _('Table'));
+ o = s.taboption('advanced', form.Value, 'table', _('Table'), _('The rule target is a table lookup ID: a numeric table index ranging from 0 to 65535 or symbol alias declared in /etc/iproute2/rt_tables. Special aliases local (255), main (254) and default (253) are also valid'));
o.datatype = 'or(uinteger, string)';
for (var i = 0; i < rtTables.length; i++)
o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
o.textvalue = function(section_id) {
- return this.cfgvalue(section_id) || 'main';
+ return this.cfgvalue(section_id) || E('em', _('auto'));
};
- o = s.taboption('advanced', form.Value, 'source', _('Source'));
+ o = s.taboption('advanced', form.Value, 'source', _('Source'), _('Specifies the preferred source address when sending to destinations covered by the target'));
o.modalonly = true;
- o.datatype = (family == 6) ? 'ip6addr("nomask")' : 'ip4addr("nomask")';
- o.placeholder = E('em', _('auto'));
+ o.datatype = (family == 6) ? 'ip6addr' : 'ip4addr';
for (var i = 0; i < netDevs.length; i++) {
var addrs = (family == 6) ? netDevs[i].getIP6Addrs() : netDevs[i].getIPAddrs();
for (var j = 0; j < addrs.length; j++)
o.value(addrs[j].split('/')[0]);
}
- o = s.taboption('advanced', form.Flag, 'onlink', _('On-link'));
+ o = s.taboption('advanced', form.Flag, 'onlink', _('On-link'), _('When enabled, gateway is on-link even if the gateway does not match any interface prefix'));
o.modalonly = true;
o.default = o.disabled;
@@ -126,14 +127,14 @@ return view.extend({
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
- o = s.taboption('general', form.Value, 'priority', _('Priority'));
+ o = s.taboption('general', form.Value, 'priority', _('Priority'), _('Specifies the ordering of the IP rules'));
o.datatype = 'uinteger';
o.placeholder = 30000;
o.textvalue = function(section_id) {
return this.cfgvalue(section_id) || E('em', _('auto'));
};
- o = s.taboption('general', form.ListValue, 'action', _('Rule type'));
+ o = s.taboption('general', form.ListValue, 'action', _('Rule type'), _('Specifies the rule target routing action'));
o.modalonly = true;
o.value('', 'unicast');
o.value('unreachable');
@@ -141,54 +142,59 @@ return view.extend({
o.value('blackhole');
o.value('throw');
- o = s.taboption('general', widgets.NetworkSelect, 'in', _('Incoming interface'));
+ o = s.taboption('general', widgets.NetworkSelect, 'in', _('Incoming interface'), _('Specifies the incoming logical interface name'));
o.loopback = true;
o.nocreate = true;
- o = s.taboption('general', form.Value, 'src', _('Source'));
+ o = s.taboption('general', form.Value, 'src', _('Source'), _('Specifies the source subnet to match (CIDR notation)'));
o.datatype = (family == 6) ? 'cidr6' : 'cidr4';
o.placeholder = (family == 6) ? '::/0' : '0.0.0.0/0';
o.textvalue = function(section_id) {
return this.cfgvalue(section_id) || E('em', _('any'));
};
- o = s.taboption('general', widgets.NetworkSelect, 'out', _('Outgoing interface'));
+ o = s.taboption('general', widgets.NetworkSelect, 'out', _('Outgoing interface'), _('Specifies the outgoing logical interface name'));
o.loopback = true;
o.nocreate = true;
- o = s.taboption('general', form.Value, 'dest', _('Destination'));
+ o = s.taboption('general', form.Value, 'dest', _('Destination'), _('Specifies the destination subnet to match (CIDR notation)'));
o.datatype = (family == 6) ? 'cidr6' : 'cidr4';
o.placeholder = (family == 6) ? '::/0' : '0.0.0.0/0';
o.textvalue = function(section_id) {
return this.cfgvalue(section_id) || E('em', _('any'));
};
- o = s.taboption('general', form.Value, 'lookup', _('Table'));
+ o = s.taboption('general', form.Value, 'lookup', _('Table'), _('The rule target is a table lookup ID: a numeric table index ranging from 0 to 65535 or symbol alias declared in /etc/iproute2/rt_tables. Special aliases local (255), main (254) and default (253) are also valid'));
o.datatype = 'or(uinteger, string)';
for (var i = 0; i < rtTables.length; i++)
o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
- o = s.taboption('advanced', form.Value, 'goto', _('Jump to rule'));
+ o = s.taboption('advanced', form.Value, 'goto', _('Jump to rule'), _('The rule target is a jump to another rule specified by its priority value'));
o.modalonly = true;
o.datatype = 'uinteger';
o.placeholder = 80000;
- o = s.taboption('advanced', form.Value, 'mark', _('Firewall mark'));
+ o = s.taboption('advanced', form.Value, 'mark', _('Firewall mark'), _('Specifies the fwmark and optionally its mask to match, e.g. 0xFF to match mark 255 or 0x0/0x1 to match any even mark value'));
o.modalonly = true;
o.datatype = 'string';
o.placeholder = '0x1/0xf';
- o = s.taboption('advanced', form.Value, 'tos', _('Type of service'));
+ o = s.taboption('advanced', form.Value, 'tos', _('Type of service'), _('Specifies the TOS value to match in IP headers'));
o.modalonly = true;
o.datatype = 'uinteger';
o.placeholder = 10;
- o = s.taboption('advanced', form.Value, 'suppress_prefixlength', _('Prefix suppressor'));
+ o = s.taboption('advanced', form.Value, 'uidrange', _('User identifier'), _('Specifies an individual UID or range of UIDs to match, e.g. 1000 to match corresponding UID or 1000-1005 to inclusively match all UIDs within the corresponding range'));
+ o.modalonly = true;
+ o.datatype = 'string';
+ o.placeholder = '1000-1005';
+
+ o = s.taboption('advanced', form.Value, 'suppress_prefixlength', _('Prefix suppressor'), _('Reject routing decisions that have a prefix length less than or equal to the specified value'));
o.modalonly = true;
o.datatype = (family == 6) ? 'ip6prefix' : 'ip4prefix';
o.placeholder = (family == 6) ? 64 : 24;
- o = s.taboption('advanced', form.Flag, 'invert', _('Invert match'));
+ o = s.taboption('advanced', form.Flag, 'invert', _('Invert match'), _('If set, the meaning of the match options is inverted'));
o.modalonly = true;
o.default = o.disabled;
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js
index 8c24775cab..535a133e78 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js
@@ -224,7 +224,7 @@ return view.extend({
s.filter = function(section_id) {
var device = uci.get('network', section_id, 'device');
- return (device == switch_name);
+ return (device == this.device);
};
s.cfgsections = function() {
@@ -248,7 +248,7 @@ return view.extend({
max_vid = 0;
for (var j = 0; j < sections.length; j++) {
- if (sections[j].device != s.device)
+ if (sections[j].device != this.device)
continue;
var vlan = +sections[j].vlan,
@@ -261,7 +261,7 @@ return view.extend({
max_vid = vid;
}
- uci.set('network', section_id, 'device', s.device);
+ uci.set('network', section_id, 'device', this.device);
uci.set('network', section_id, 'vlan', max_vlan + 1);
if (feat.vid_option)
@@ -270,8 +270,6 @@ return view.extend({
return this.map.save(null, true);
};
- var port_opts = [];
-
o = s.option(form.Value, feat.vid_option || 'vlan', 'VLAN ID');
o.rmempty = false;
o.forcewrite = true;
@@ -299,21 +297,23 @@ return view.extend({
return true;
};
+ var port_opts = o.port_opts = [];
+
o.write = function(section_id, value) {
var topology = this.section.topology,
values = [];
- for (var i = 0; i < port_opts.length; i++) {
- var tagging = port_opts[i].formvalue(section_id),
+ for (var i = 0; i < this.port_opts.length; i++) {
+ var tagging = this.port_opts[i].formvalue(section_id),
portspec = Array.isArray(topology.ports) ? topology.ports[i] : null;
if (tagging == 't')
- values.push(port_opts[i].option + tagging);
+ values.push(this.port_opts[i].option + tagging);
else if (tagging == 'u')
- values.push(port_opts[i].option);
+ values.push(this.port_opts[i].option);
if (portspec && portspec.device) {
- var old_tag = port_opts[i].cfgvalue(section_id),
+ var old_tag = this.port_opts[i].cfgvalue(section_id),
old_vid = this.cfgvalue(section_id);
if (old_tag != tagging || old_vid != value) {
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
index 77f53e2b7d..4c2daf588a 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
@@ -314,24 +314,16 @@ var CBIWifiFrequencyValue = form.Value.extend({
this.channels = {
'2g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
'5g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
- '6g': [],
+ '6g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
'60g': []
};
for (var i = 0; i < data[1].length; i++) {
- var band;
-
- if (data[1][i].mhz >= 2412 && data[1][i].mhz <= 2484)
- band = '2g';
- else if (data[1][i].mhz >= 5160 && data[1][i].mhz <= 5885)
- band = '5g';
- else if (data[1][i].mhz >= 5925 && data[1][i].mhz <= 7125)
- band = '6g';
- else if (data[1][i].mhz >= 58320 && data[1][i].mhz <= 69120)
- band = '60g';
- else
+ if (!data[1][i].band)
continue;
+ var band = '%dg'.format(data[1][i].band);
+
this.channels[band].push(
data[1][i].channel,
'%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
@@ -343,10 +335,10 @@ var CBIWifiFrequencyValue = form.Value.extend({
.reduce(function(o, v) { o[v] = true; return o }, {});
this.modes = [
- '', 'Legacy', true,
+ '', 'Legacy', hwmodelist.a || hwmodelist.b || hwmodelist.g,
'n', 'N', hwmodelist.n,
- 'ac', 'AC', hwmodelist.ac,
- 'ax', 'AX', hwmodelist.ax
+ 'ac', 'AC', L.hasSystemFeature('hostapd', '11ac') && hwmodelist.ac,
+ 'ax', 'AX', L.hasSystemFeature('hostapd', '11ax') && hwmodelist.ax
];
var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
@@ -387,7 +379,8 @@ var CBIWifiFrequencyValue = form.Value.extend({
],
'ax': [
'2g', '2.4 GHz', this.channels['2g'].length > 3,
- '5g', '5 GHz', this.channels['5g'].length > 3
+ '5g', '5 GHz', this.channels['5g'].length > 3,
+ '6g', '6 GHz', this.channels['6g'].length > 3
]
};
}, this));
@@ -480,7 +473,7 @@ var CBIWifiFrequencyValue = form.Value.extend({
this.toggleWifiBand(elem);
bwdt.value = htval;
- chan.value = chval || chan.options[0].value;
+ chan.value = chval || (chan.options[0] ? chan.options[0].value : 'auto');
return elem;
},
@@ -742,7 +735,8 @@ return view.extend({
load: function() {
return Promise.all([
uci.changes(),
- uci.load('wireless')
+ uci.load('wireless'),
+ uci.load('system')
]);
},
@@ -986,6 +980,7 @@ return view.extend({
ss.tab('encryption', _('Wireless Security'));
ss.tab('macfilter', _('MAC-Filter'));
ss.tab('advanced', _('Advanced Settings'));
+ ss.tab('roaming', _('WLAN roaming'), _('Settings for assisting wireless clients in roaming between multiple APs: 802.11r, 802.11k and 802.11v'));
o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
o.value('ap', _('Access Point'));
@@ -1090,6 +1085,7 @@ return view.extend({
o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
o.datatype = 'macaddr';
+ o.retain = true;
o.depends('macfilter', 'allow');
o.depends('macfilter', 'deny');
o.load = function(section_id) {
@@ -1145,16 +1141,28 @@ return view.extend({
o.depends('mode', 'ap-wds');
o.default = o.enabled;
+ /* https://w1.fi/cgit/hostap/commit/?id=34f7c699a6bcb5c45f82ceb6743354ad79296078 */
+ /* multicast_to_unicast https://github.com/openwrt/openwrt/commit/7babb978ad9d7fc29acb1ff86afb1eb343af303a */
+ o = ss.taboption('advanced', form.Flag, 'multicast_to_unicast_all', _('Multi To Unicast'), _('ARP, IPv4 and IPv6 (even 802.1Q) with multicast destination MACs are unicast to the STA MAC address. Note: This is not Directed Multicast Service (DMS) in 802.11v. Note: might break receiver STA multicast expectations.'));
+ o.rmempty = true;
+
o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
o.depends('mode', 'ap');
o.depends('mode', 'ap-wds');
o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
o.optional = true;
+ o.datatype = 'netdevname';
o.placeholder = radioNet.getIfname();
if (/^radio\d+\.network/.test(o.placeholder))
o.placeholder = '';
+ var macaddr = uci.get('wireless', radioNet.getName(), 'macaddr');
+ o = ss.taboption('advanced', form.Value, 'macaddr', _('MAC address'), _('Override default MAC address - the range of usable addresses might be limited by the driver'));
+ o.value('', _('driver default (%s)').format(!macaddr ? radioNet.getActiveBSSID() : _('no override')));
+ o.value('random', _('randomly generated'));
+ o.datatype = "or('random',macaddr)";
+
o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
o.default = o.enabled;
@@ -1172,7 +1180,7 @@ return view.extend({
o.optional = true;
o.datatype = 'uinteger';
- o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
+ o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('802.11v: BSS Max Idle. Units: seconds.'));
o.optional = true;
o.placeholder = 300;
o.datatype = 'uinteger';
@@ -1275,7 +1283,7 @@ return view.extend({
if (has_hostapd || has_supplicant) {
crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
- crypto_modes.push(['psk', 'WPA-PSK', 21]);
+ crypto_modes.push(['psk', 'WPA-PSK', 12]);
}
else {
encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
@@ -1375,7 +1383,7 @@ return view.extend({
else if (hwtype == 'broadcom') {
crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
- crypto_modes.push(['psk', 'WPA-PSK', 21]);
+ crypto_modes.push(['psk', 'WPA-PSK', 12]);
crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
}
@@ -1393,51 +1401,89 @@ return view.extend({
}
- o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
+ o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'host(0)';
- o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
+ o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'port';
+ o.placeholder = '1812';
- o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
+ o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.password = true;
- o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
+ o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'host(0)';
- o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
+ o = ss.taboption('encryption', form.Value, 'acct_port', _('RADIUS Accounting Port'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'port';
+ o.placeholder = '1813';
- o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
+ o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.password = true;
- o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
+ /* extra RADIUS settings start */
+ o = ss.taboption('encryption', form.ListValue, 'dynamic_vlan', _('RADIUS Dynamic VLAN Assignment'), _('Required: Rejects auth if RADIUS server does not provide appropriate VLAN attributes.'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ o.value('0', _('Disabled'));
+ o.value('1', _('Optional'));
+ o.value('2', _('Required'));
+ o.write = function (section_id, value) {
+ return this.super('write', [section_id, (value == 0) ? null: value]);
+ }
+
+ o = ss.taboption('encryption', form.Flag, 'per_sta_vif', _('RADIUS Per STA VLAN'), _('Each STA is assigned its own AP_VLAN interface.'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+
+ //hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off
+ o = ss.taboption('encryption', form.Flag, 'vlan_naming', _('RADIUS VLAN Naming'), _('Off: <code>vlanXXX</code>, e.g., <code>vlan1</code>. On: <code>vlan_tagged_interface.XXX</code>, e.g. <code>eth0.1</code>.'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+
+ o = ss.taboption('encryption', widgets.DeviceSelect, 'vlan_tagged_interface', _('RADIUS VLAN Tagged Interface'), _('E.g. eth0, eth1'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ o.size = 1;
+ o.rmempty = true;
+ o.multiple = false;
+ o.noaliases = true;
+ o.nocreate = true;
+ o.noinactive = true;
+
+ o = ss.taboption('encryption', form.Value, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. <code>br-vlan</code> or <code>brvlan</code>.'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ o.rmempty = true;
+ /* extra RADIUS settings end */
+
+ o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'host(0)';
- o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
+ o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'port';
+ o.placeholder = '3799';
- o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
+ o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.password = true;
+ //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames.
+ o = ss.taboption('encryption', form.Flag, 'rsn_preauth', _('RSN Preauth'), _('Robust Security Network (RSN): Allow roaming preauth for WPA2-EAP networks (and advertise it in WLAN beacons). Only works if the specified network interface is a bridge. Shortens the time-critical reassociation process.'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa2', 'wpa3', 'wpa3-mixed'] });
+
o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
o.depends('encryption', 'psk');
@@ -1501,66 +1547,117 @@ return view.extend({
// Probe 802.11r support (and EAP support as a proxy for Openwrt)
var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
- o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
+ o = ss.taboption('roaming', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
if (has_80211r)
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
o.rmempty = true;
- o = ss.taboption('encryption', form.Value, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.'));
+ o = ss.taboption('roaming', form.Value, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.depends({ ieee80211r: '1' });
o.rmempty = true;
- o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
+ o = ss.taboption('roaming', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
o.depends({ ieee80211r: '1' });
o.placeholder = '4f57';
o.datatype = 'and(hexstring,length(4))';
o.rmempty = true;
- o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
+ o = ss.taboption('roaming', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
o.depends({ ieee80211r: '1' });
o.placeholder = '1000';
o.datatype = 'range(1000,65535)';
o.rmempty = true;
- o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
+ o = ss.taboption('roaming', form.ListValue, 'ft_over_ds', _('FT protocol'));
o.depends({ ieee80211r: '1' });
- o.value('1', _('FT over DS'));
o.value('0', _('FT over the Air'));
+ o.value('1', _('FT over DS'));
o.rmempty = true;
- o = ss.taboption('encryption', form.Flag, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.'));
+ o = ss.taboption('roaming', form.Flag, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.'));
o.depends({ ieee80211r: '1' });
o.default = o.enabled;
o.rmempty = false;
- o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
+ o = ss.taboption('roaming', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
o.depends({ ieee80211r: '1' });
o.placeholder = '10000';
o.datatype = 'uinteger';
o.rmempty = true;
- o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
+ o = ss.taboption('roaming', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
o.depends({ ieee80211r: '1' });
o.placeholder = '00004f577274';
o.datatype = 'and(hexstring,length(12))';
o.rmempty = true;
- o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
+ o = ss.taboption('roaming', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
o.depends({ ieee80211r: '1' });
o.placeholder = '0';
o.rmempty = true;
- o = ss.taboption('encryption', form.DynamicList, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain. <br />Format: MAC-address,NAS-Identifier,128-bit key as hex string. <br />This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.'));
+ o = ss.taboption('roaming', form.DynamicList, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain. <br />Format: MAC-address,NAS-Identifier,256-bit key as hex string. <br />This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.'));
o.depends({ ieee80211r: '1' });
o.rmempty = true;
- o = ss.taboption('encryption', form.DynamicList, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain. <br />Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string. <br />This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.'));
+ o = ss.taboption('roaming', form.DynamicList, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain. <br />Format: MAC-address,R1KH-ID as 6 octets with colons,256-bit key as hex string. <br />This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.'));
o.depends({ ieee80211r: '1' });
o.rmempty = true;
// End of 802.11r options
+ // Probe 802.11k and 802.11v support via EAP support (full hostapd has EAP)
+ if (L.hasSystemFeature('hostapd', 'eap')) {
+ /* 802.11k settings start */ o =
+ ss.taboption('roaming', form.Flag, 'ieee80211k', _('802.11k RRM'), _('Radio Resource Measurement - Sends beacons to assist roaming. Not all clients support this.'));
+ // add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
+ o.depends('mode', 'ap');
+ o.depends('mode', 'ap-wds');
+
+ o = ss.taboption('roaming', form.Flag, 'rrm_neighbor_report', _('Neighbour Report'), _('802.11k: Enable neighbor report via radio measurements.'));
+ o.depends({ ieee80211k: '1' });
+ o.default = o.enabled;
+
+ o = ss.taboption('roaming', form.Flag, 'rrm_beacon_report', _('Beacon Report'), _('802.11k: Enable beacon report via radio measurements.'));
+ o.depends({ ieee80211k: '1' });
+ o.default = o.enabled;
+ /* 802.11k settings end */
+
+ /* 802.11v settings start */
+ o = ss.taboption('roaming', form.ListValue, 'time_advertisement', _('Time advertisement'), _('802.11v: Time Advertisement in management frames.'));
+ o.value('0', _('Disabled'));
+ o.value('2', _('Enabled'));
+ o.write = function (section_id, value) {
+ return this.super('write', [section_id, (value == 2) ? value: null]);
+ }
+
+ //Pull current System TZ setting
+ var tz = uci.get('system', '@system[0]', 'timezone');
+ o = ss.taboption('roaming', form.Value, 'time_zone', _('Time zone'), _('802.11v: Local Time Zone Advertisement in management frames.'));
+ o.value(tz);
+ o.rmempty = true;
+
+ o = ss.taboption('roaming', form.Flag, 'wnm_sleep_mode', _('WNM Sleep Mode'), _('802.11v: Wireless Network Management (WNM) Sleep Mode (extended sleep mode for stations).'));
+ o.rmempty = true;
+
+ /* wnm_sleep_mode_no_keys: https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=bf98faaac8ed24cf7d3d93dd4fcd7304d109363b */
+ o = ss.taboption('roaming', form.Flag, 'wnm_sleep_mode_no_keys', _('WNM Sleep Mode Fixes'), _('802.11v: Wireless Network Management (WNM) Sleep Mode Fixes: Prevents reinstallation attacks.'));
+ o.rmempty = true;
+
+ o = ss.taboption('roaming', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.'));
+ o.rmempty = true;
+
+ /* in master, but not 21.02.1: proxy_arp */
+ o = ss.taboption('roaming', form.Flag, 'proxy_arp', _('ProxyARP'), _('802.11v: Proxy ARP enables non-AP STA to remain in power-save for longer.'));
+ o.rmempty = true;
+
+ /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */
+ }
+ /* 802.11v settings end */
+ }
+
+ if (hwtype == 'mac80211') {
o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
o.value('tls', 'TLS');
o.value('ttls', 'TTLS');
@@ -1964,6 +2061,8 @@ return view.extend({
});
});
}).then(L.bind(function() {
+ ui.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
+
return this.renderMoreOptionsModal(section_id);
}, this));
};
@@ -2165,5 +2264,7 @@ return view.extend({
return E([ nodes, E('h3', _('Associated Stations')), table ]);
}, this, m));
- }
+ },
+
+ handleReset: null
});
diff --git a/modules/luci-mod-network/root/usr/share/rpcd/acl.d/luci-mod-network.json b/modules/luci-mod-network/root/usr/share/rpcd/acl.d/luci-mod-network.json
index 1c553f3992..b377f395f0 100644
--- a/modules/luci-mod-network/root/usr/share/rpcd/acl.d/luci-mod-network.json
+++ b/modules/luci-mod-network/root/usr/share/rpcd/acl.d/luci-mod-network.json
@@ -8,7 +8,9 @@
"/proc/sys/net/ipv6/conf/*/mtu": [ "read" ],
"/proc/sys/net/ipv6/conf/*/hop_limit": [ "read" ],
"/usr/libexec/luci-peeraddr": [ "exec" ],
- "/usr/lib/opkg/info/netifd.control": [ "read" ]
+ "/usr/lib/opkg/info/netifd.control": [ "read" ],
+ "/proc/sys/net/ipv[46]/conf/*": [ "read" ],
+ "/sys/class/net/*/brport/*": [ "read" ]
},
"ubus": {
"file": [ "exec" ],