summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-mod-network/htdocs/luci-static
diff options
context:
space:
mode:
Diffstat (limited to 'modules/luci-mod-network/htdocs/luci-static')
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js158
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js266
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js32
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js89
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js157
5 files changed, 556 insertions, 146 deletions
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 11d2162d11..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,8 +673,8 @@ 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 = '';
@@ -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 5059b389b4..4f71d2d989 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.getDevices()
]);
},
@@ -240,6 +243,7 @@ return view.extend({
hosts = hosts_duids_pools[0],
duids = hosts_duids_pools[1],
pools = hosts_duids_pools[2],
+ ndevs = hosts_duids_pools[3],
m, s, o, ss, so;
m = new form.Map('dhcp', _('DHCP and DNS'),
@@ -250,11 +254,14 @@ return view.extend({
s.addremove = false;
s.tab('general', _('General Settings'));
+ s.tab('relay', _('Relay'));
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('hosts', _('Hostnames'));
+ s.tab('srvhosts', _('SRV'));
+ s.tab('mxhosts', _('MX'));
s.tab('ipsets', _('IP Sets'));
s.taboption('general', form.Flag, 'domainneeded',
@@ -287,13 +294,16 @@ 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';
@@ -340,6 +350,66 @@ 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, 'id', _('ID'));
+ so.rmempty = false;
+ so.optional = true;
+
+ so = ss.option(widgets.NetworkSelect, 'interface', _('Interface'));
+ so.optional = true;
+ so.rmempty = false;
+ so.placeholder = 'lan';
+
+ so = ss.option(form.Value, 'local_addr', _('Listen address'));
+ so.rmempty = false;
+ so.datatype = 'ipaddr';
+
+ for (var family = 4; family <= 6; family += 2) {
+ for (var i = 0; i < ndevs.length; i++) {
+ var addrs = (family == 6) ? ndevs[i].getIP6Addrs() : ndevs[i].getIPAddrs();
+ for (var j = 0; j < addrs.length; j++)
+ so.value(addrs[j].split('/')[0]);
+ }
+ }
+
+ 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 Listen addr and Relay To must be specified.');
+
+ if ((validation.parseIPv6(m) && validation.parseIPv6(n)) ||
+ validation.parseIPv4(m) && validation.parseIPv4(n))
+ return true;
+ else
+ return _('Listen and Relay To IP family must be homogeneous.')
+ };
+
s.taboption('files', form.Flag, 'readethers',
_('Use <code>/etc/ethers</code>'),
_('Read <code>/etc/ethers</code> to configure the DHCP server.'));
@@ -384,8 +454,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 +555,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 +628,72 @@ 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('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 +725,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 +742,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 +769,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 +830,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 +863,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. 8 chars).'));
+ so.datatype = 'and(rangelength(0,8),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__');
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 da7cd95bdc..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) {
@@ -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 e5376d07c8..82bf269e8a 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
@@ -13,16 +13,6 @@
var isReadonlyView = !L.hasViewPermission() || null;
-function strcmp(a, b) {
- if (a < b)
- return -1;
-
- if (a > b)
- return 1;
-
- return 0;
-}
-
function count_changes(section_id) {
var changes = ui.changes.changes, n = 0;
@@ -238,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();
@@ -498,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) {
@@ -545,7 +552,7 @@ return view.extend({
var protocols = network.getProtocols();
protocols.sort(function(a, b) {
- return strcmp(a.getProtocol(), b.getProtocol());
+ return L.naturalCompare(a.getProtocol(), b.getProtocol());
});
o = s.taboption('general', form.DummyValue, '_ifacestat_modal', _('Status'));
@@ -825,6 +832,13 @@ return view.extend({
}
};
+ 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';
@@ -895,6 +909,10 @@ 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.'));
@@ -946,13 +964,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';
@@ -1101,7 +1119,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() {
@@ -1209,7 +1227,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),
@@ -1263,7 +1281,7 @@ return view.extend({
s.cfgsections = function() {
var sections = uci.sections('network', 'device'),
- section_ids = sections.sort(function(a, b) { return strcmp(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)
@@ -1341,6 +1359,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);
};
@@ -1492,21 +1513,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/wireless.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
index 2704ee474b..a099746548 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'));
@@ -1146,20 +1141,27 @@ 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.optional = true;
- o.placeholder = radioNet.getActiveBSSID();
- o.datatype = 'macaddr';
+ 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;
@@ -1178,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';
@@ -1431,6 +1433,38 @@ return view.extend({
o.rmempty = true;
o.password = true;
+ /* 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.nobridges = 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;
@@ -1447,6 +1481,10 @@ return view.extend({
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');
@@ -1510,66 +1548,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,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.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,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.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');