diff --git a/luci-mod-network/Makefile b/luci-mod-network/Makefile
index 6148f98b4..df9682b71 100644
--- a/luci-mod-network/Makefile
+++ b/luci-mod-network/Makefile
@@ -4,7 +4,7 @@
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
-# From https://github.com/openwrt/luci/commit/b88157e69a060ade618e48b30947729310935d61
+# From https://github.com/openwrt/luci/commit/29fe3f5fdad6bc7c72799adff22847a6e257f7c0
include $(TOPDIR)/rules.mk
diff --git a/luci-mod-network/htdocs/luci-static/resources/tools/network.js b/luci-mod-network/htdocs/luci-static/resources/tools/network.js
index dfe9796c4..ab0680e2f 100644
--- a/luci-mod-network/htdocs/luci-static/resources/tools/network.js
+++ b/luci-mod-network/htdocs/luci-static/resources/tools/network.js
@@ -198,7 +198,7 @@ var cbiFlagTristate = form.ListValue.extend({
this.vallist[0] = sysdef ? _('automatic (enabled)') : _('automatic (disabled)');
}
- return this.super('renderWidget', [section_id, option_index, cfgvalue]);
+ return this.super('renderWidget', [section_id, option_index, cfgvalue ? cfgvalue + '!' : null]);
}
});
@@ -364,6 +364,64 @@ var cbiTagValue = form.Value.extend({
});
return baseclass.extend({
+
+ protocols: [ // name, proto number, description
+ { n: 'hopopt', i: 0, d: 'HOPOPT' },
+ { n: 'icmp', i: 1, d: 'ICMP' },
+ { n: 'igmp', i: 2, d: 'IGMP' },
+ { n: 'ggp', i: 3, d: 'GGP' },
+ { n: 'ipencap', i: 4, d: 'IP-ENCAP' },
+ { n: 'st', i: 5, d: 'ST' },
+ { n: 'tcp', i: 6, d: 'TCP' },
+ { n: 'egp', i: 8, d: 'EGP' },
+ { n: 'igp', i: 9, d: 'IGP' },
+ { n: 'pup', i: 12, d: 'PUP' },
+ { n: 'udp', i: 17, d: 'UDP' },
+ { n: 'hmp', i: 20, d: 'HMP' },
+ { n: 'xns-idp', i: 22, d: 'XNS-IDP' },
+ { n: 'rdp', i: 27, d: 'RDP' },
+ { n: 'iso-tp4', i: 29, d: 'ISO-TP4' },
+ { n: 'dccp', i: 33, d: 'DCCP' },
+ { n: 'xtp', i: 36, d: 'XTP' },
+ { n: 'ddp', i: 37, d: 'DDP' },
+ { n: 'idpr-cmtp', i: 38, d: 'IDPR-CMTP' },
+ { n: 'ipv6', i: 41, d: 'IPv6' },
+ { n: 'ipv6-route', i: 43, d: 'IPv6-Route' },
+ { n: 'ipv6-frag', i: 44, d: 'IPv6-Frag' },
+ { n: 'idrp', i: 45, d: 'IDRP' },
+ { n: 'rsvp', i: 46, d: 'RSVP' },
+ { n: 'gre', i: 47, d: 'GRE' },
+ { n: 'esp', i: 50, d: 'IPSEC-ESP' },
+ { n: 'ah', i: 51, d: 'IPSEC-AH' },
+ { n: 'skip', i: 57, d: 'SKIP' },
+ { n: 'icmpv6', i: 58, d: 'IPv6-ICMP' },
+ { n: 'ipv6-nonxt', i: 59, d: 'IPv6-NoNxt' },
+ { n: 'ipv6-opts', i: 60, d: 'IPv6-Opts' },
+ { n: 'rspf', i: 73, d: 'CPHB' },
+ { n: 'vmtp', i: 81, d: 'VMTP' },
+ { n: 'eigrp', i: 88, d: 'EIGRP' },
+ { n: 'ospf', i: 89, d: 'OSPFIGP' },
+ { n: 'ax.25', i: 93, d: 'AX.25' },
+ { n: 'ipip', i: 94, d: 'IPIP' },
+ { n: 'etherip', i: 97, d: 'ETHERIP' },
+ { n: 'encap', i: 98, d: 'ENCAP' },
+ { n: 'pim', i: 103, d: 'PIM' },
+ { n: 'ipcomp', i: 108, d: 'IPCOMP' },
+ { n: 'vrrp', i: 112, d: 'VRRP' },
+ { n: 'l2tp', i: 115, d: 'L2TP' },
+ { n: 'isis', i: 124, d: 'ISIS' },
+ { n: 'sctp', i: 132, d: 'SCTP' },
+ { n: 'fc', i: 133, d: 'FC' },
+ { n: 'mobility-header', i: 135, d: 'Mobility-Header' },
+ { n: 'udplite', i: 136, d: 'UDPLite' },
+ { n: 'mpls-in-ip', i: 137, d: 'MPLS-in-IP' },
+ { n: 'manet', i: 138, d: 'MANET' },
+ { n: 'hip', i: 139, d: 'HIP' },
+ { n: 'shim6', i: 140, d: 'Shim6' },
+ { n: 'wesp', i: 141, d: 'WESP' },
+ { n: 'rohc', i: 142, d: 'ROHC' },
+ ],
+
replaceOption: function(s, tabName, optionClass, optionName, optionTitle, optionDescription) {
var o = s.getOption(optionName);
@@ -392,16 +450,21 @@ return baseclass.extend({
s.tab('brport', _('Bridge port specific options'));
s.tab('bridgevlan', _('Bridge VLAN filtering'));
- o = this.replaceOption(s, 'devgeneral', form.ListValue, 'type', _('Device type'));
+ o = this.replaceOption(s, 'devgeneral', form.ListValue, 'type', _('Device type'),
+ !L.hasSystemFeature('bonding') && isNew ? ''+
+ _('For bonding, install %s').format('kmod-bonding
') + '' : null);
o.readonly = !isNew;
o.value('', _('Network device'));
+ if (L.hasSystemFeature('bonding')) {
+ o.value('bonding', _('Bonding/Aggregation device'));
+ }
o.value('bridge', _('Bridge device'));
o.value('8021q', _('VLAN (802.1q)'));
o.value('8021ad', _('VLAN (802.1ad)'));
o.value('macvlan', _('MAC VLAN'));
o.value('veth', _('Virtual Ethernet'));
o.validate = function(section_id, value) {
- if (value == 'bridge' || value == 'veth')
+ if (value == 'bonding' || value == 'bridge' || value == 'veth')
updatePlaceholders(this.section.getOption('name_complex'), section_id);
return true;
@@ -519,7 +582,397 @@ return baseclass.extend({
o.depends('type', '8021q');
o.depends('type', '8021ad');
- o = this.replaceOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_multi', _('Bridge ports'));
+ o = this.replaceOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_multi-bond', _('Aggregation ports'));
+ o.size = 10;
+ o.rmempty = true;
+ o.multiple = true;
+ o.noaliases = true;
+ o.nobridges = true;
+ o.ucioption = 'ports';
+ o.default = L.toArray(dev ? dev.getPorts() : null).filter(function(p) { return p.getType() != 'wifi' }).map(function(p) { return p.getName() });
+ o.filter = function(section_id, device_name) {
+ var bridge_name = uci.get('network', section_id, 'name'),
+ choice_dev = network.instantiateDevice(device_name),
+ parent_dev = choice_dev.getParent();
+
+ /* only show wifi networks which are already present in "option ifname" */
+ if (choice_dev.getType() == 'wifi') {
+ var ifnames = L.toArray(uci.get('network', section_id, 'ports'));
+
+ for (var i = 0; i < ifnames.length; i++)
+ if (ifnames[i] == device_name)
+ return true;
+
+ return false;
+ }
+
+ return (!parent_dev || parent_dev.getName() != bridge_name);
+ };
+ o.description = _('Specifies the wired ports to attach to this bonding.')
+ o.onchange = function(ev, section_id, values) {
+ ss.updatePorts(values);
+
+ return ss.parse().then(function() {
+ ss.redraw();
+ });
+ };
+ o.depends('type', 'bonding');
+
+ o = this.replaceOption(s, 'devgeneral', form.ListValue, 'policy', _('Bonding Policy'));
+ o.default = 'active-backup';
+ o.value('active-backup', _('Active backup'));
+ o.value('balance-rr', _('Round robin'));
+ o.value('balance-xor', _('Transmit hash - balance-xor'));
+ o.value('broadcast', _('Broadcast'));
+ o.value('802.3ad', _('LACP - 802.3ad'));
+ o.value('balance-tlb', _('Adaptive transmit load balancing'));
+ o.value('balance-alb', _('Adaptive load balancing'));
+ o.cfgvalue = function(/* ... */) {
+ var val = form.ListValue.prototype.cfgvalue.apply(this, arguments);
+
+ switch (val || '') {
+ case 'active-backup':
+ case '0':
+ return 'active-backup';
+
+ case 'balance-rr':
+ case '1':
+ return 'balance-rr';
+
+ case 'balance-xor':
+ case '2':
+ return 'balance-xor';
+
+ case 'broadcast':
+ case '3':
+ return 'broadcast';
+
+ case '802.3ad':
+ case '4':
+ return '802.3ad';
+
+ case 'balance-tlb':
+ case '5':
+ return 'balance-tlb';
+
+ case 'balance-alb':
+ case '6':
+ return 'balance-alb';
+
+ default:
+ return 'active-backup';
+ }
+ };
+ o.depends('type', 'bonding');
+
+ o = this.replaceOption(s, 'devgeneral', form.Flag, 'all_ports_active', _('All ports active'), _('Allow receiving on inactive ports'));
+ o.default = o.disabled;
+ o.depends('type', 'bonding');
+
+ o = this.replaceOption(s, 'devgeneral', widgets.DeviceSelect, '_net_device_primary', _('Primary Device'));
+ o.ucioption = 'primary';
+ o.rmempty = true;
+ o.noaliases = true;
+ o.nobridges = true;
+ o.optional = false;
+ o.depends({'type': 'bonding', 'policy': 'active-backup'});
+
+ o = this.replaceOption(s, 'devadvanced', form.ListValue, 'xmit_hash_policy', _('Slave selection hash policy'));
+ o.default = '';
+ o.value('', '');
+ o.value('layer2', _('Layer 2'));
+ o.value('layer2+3', _('Layer 2+3'));
+ o.value('layer3+4', _('Layer 3+4'));
+ o.value('encap2+3', _('Encap 2+3'));
+ o.value('encap3+4', _('Encap 3+4'));
+ o.cfgvalue = function(/* ... */) {
+ var val = form.ListValue.prototype.cfgvalue.apply(this, arguments);
+
+ switch (val || '') {
+ case 'layer2':
+ case '0':
+ return 'layer2';
+
+ case 'layer2+3':
+ case '1':
+ return 'layer2+3';
+
+ case 'layer3+4':
+ case '2':
+ return 'layer3+4';
+
+ case 'encap2+3':
+ case '4':
+ return 'encap2+3';
+
+ case 'encap3+4':
+ case '5':
+ return 'encap3+4';
+
+ default:
+ return '';
+ }
+ };
+ o.depends({'type': 'bonding', 'policy': 'balance-xor'});
+ o.depends({'type': 'bonding', 'policy': '802.3ad'});
+ o.depends({'type': 'bonding', 'policy': 'balance-tlb'});
+
+ o = this.replaceOption(s, 'devadvanced', form.Value, 'ad_actor_system', _('MAC address for LACPDUs'));
+ o.description = _('This specifies the mac-address for the actor in protocol packet exchanges (LACPDUs). The value cannot be NULL or multicast.');
+ o.datatype = 'macaddr';
+ o.depends({'type': 'bonding', 'policy': '802.3ad'});
+
+ o = this.replaceOption(s, 'devadvanced', form.Value, 'ad_actor_sys_prio', _('Priority'));
+ o.description = _('This specifies the AD system priority');
+ o.placeholder = '65535';
+ o.datatype = 'range(1, 65535)';
+ o.depends({'type': 'bonding', 'policy': '802.3ad'});
+
+ o = this.replaceOption(s, 'devadvanced', form.ListValue, 'ad_select', _('802.3ad aggregation logic'));
+ o.default = '';
+ o.value('', '');
+ o.value('stable', _('Stable'));
+ o.value('bandwidth', _('Bandwidth'));
+ o.value('count', _('Count'));
+ o.cfgvalue = function(/* ... */) {
+ var val = form.ListValue.prototype.cfgvalue.apply(this, arguments);
+
+ switch (val || '') {
+ case 'stable':
+ case '0':
+ return 'stable';
+
+ case 'bandwidth':
+ case '1':
+ return 'bandwidth';
+
+ case 'count':
+ case '2':
+ return 'count';
+
+ default:
+ return '';
+ }
+ };
+ o.depends({'type': 'bonding', 'policy': '802.3ad'});
+
+ o = this.replaceOption(s, 'devadvanced', form.ListValue, 'lacp_rate', _('802.3ad LACPDU packet rate'));
+ o.default = '';
+ o.value('', '');
+ o.value('slow', _('Slow (every 30 seconds)'));
+ o.value('fast', _('Fast (every second)'));
+ o.cfgvalue = function(/* ... */) {
+ var val = form.ListValue.prototype.cfgvalue.apply(this, arguments);
+
+ switch (val || '') {
+ case 'slow':
+ case '0':
+ return 'slow';
+
+ case 'fast':
+ case '1':
+ return 'fast';
+
+ default:
+ return '';
+ }
+ };
+ o.depends({'type': 'bonding', 'policy': '802.3ad'});
+
+ o = this.replaceOption(s, 'devadvanced', form.Value, 'min_links', _('Min Links'), _('Minimum number of active links'));
+ o.placeholder = '1';
+ o.datatype = 'uinteger';
+ o.depends({'type': 'bonding', 'policy': '802.3ad'});
+
+ o = this.replaceOption(s, 'devadvanced', form.Value, 'packets_per_slave', _('Packets per slave'));
+ o.description = _('Number of packets to transmit through a slave before moving to the next one. Slave is chosen at random when 0.');
+ o.placeholder = '1';
+ o.datatype = 'range(1, 65535)';
+ o.depends({'type': 'bonding', 'policy': 'balance-rr'});
+
+ o = this.replaceOption(s, 'devadvanced', form.Value, 'lp_interval', _('Learning packets Interval'));
+ o.description = _('Number of seconds between sent learning packets');
+ o.placeholder = '1';
+ o.datatype = 'uinteger';
+ o.depends({'type': 'bonding', 'policy': 'balance-tlb'});
+ o.depends({'type': 'bonding', 'policy': 'balance-alb'});
+
+ o = this.replaceOption(s, 'devgeneral', form.Flag, 'dynamic_lb', _('Dynamic load balance'), _('distribute traffic according to port load'));
+ o.default = o.disabled;
+ o.depends({'type': 'bonding', 'policy': 'balance-tlb'});
+
+ o = this.replaceOption(s, 'devadvanced', form.Value, 'resend_igmp', _('IGMP reports'));
+ o.description = _('Specifies the number of IGMP membership reports to be issued after a failover event');
+ o.placeholder = '1';
+ o.datatype = 'range(0, 255)';
+ o.depends('type', 'bonding');
+
+ o = this.replaceOption(s, 'devadvanced', form.Value, 'num_peer_notif', _('Peer notifications'));
+ o.description = _('Specify the number of peer notifications to be issued after a failover event.');
+ o.placeholder = '1';
+ o.datatype = 'range(0, 255)';
+ o.depends('type', 'bonding');
+
+ o = this.replaceOption(s, 'devadvanced', form.ListValue, 'primary_reselect', _('Primary port reselection policy'));
+ o.default = '';
+ o.value('', '');
+ o.value('always', _('Always'));
+ o.value('better', _('Better'));
+ o.value('failure', _('Failure'));
+ o.cfgvalue = function(/* ... */) {
+ var val = form.ListValue.prototype.cfgvalue.apply(this, arguments);
+
+ switch (val || '') {
+ case 'always':
+ case '0':
+ return 'always';
+
+ case 'better':
+ case '1':
+ return 'better';
+
+ case 'failure':
+ case '2':
+ return 'failure';
+
+ default:
+ return 'always';
+ }
+ };
+ o.depends('type', 'bonding');
+
+ o = this.replaceOption(s, 'devadvanced', form.ListValue, 'failover_mac', _('MAC address selection policy'));
+ o.default = '';
+ o.value('none', _('none'));
+ o.value('active', _('Active'));
+ o.value('follow', _('Follow'));
+ o.cfgvalue = function(/* ... */) {
+ var val = form.ListValue.prototype.cfgvalue.apply(this, arguments);
+
+ switch (val || '') {
+ case 'none':
+ case '0':
+ return 'none';
+
+ case 'active':
+ case '1':
+ return 'active';
+
+ case 'follow':
+ case '2':
+ return 'follow';
+
+ default:
+ return 'none';
+ }
+ };
+ o.depends('type', 'bonding');
+
+ o = this.replaceOption(s, 'devadvanced', form.ListValue, 'monitor_mode', _('Link monitoring mode'),
+ !L.hasSystemFeature('mii_tool') ? ''+
+ _('Install %s').format('mii-tool
') + '' : null);
+ o.default = '';
+ o.value('arp', _('ARP link monitoring'));
+ o.value('mii', _('MII link monitoring'));
+ o.cfgvalue = function(/* ... */) {
+ var val = form.ListValue.prototype.cfgvalue.apply(this, arguments);
+
+ switch (val || '') {
+ case 'arp':
+ case '1':
+ return 'arp';
+
+ case 'mii':
+ case '2':
+ return 'mii';
+
+ default:
+ return 'mii';
+ }
+ };
+ o.depends('type', 'bonding');
+
+ o = this.replaceOption(s, 'devadvanced', form.Value, 'monitor_interval', _('Monitor Interval'));
+ o.description = _('Specifies the link monitoring frequency in milliseconds');
+ o.placeholder = '100';
+ o.datatype = 'uinteger';
+ o.depends('type', 'bonding');
+
+ o = this.replaceOption(s, 'devadvanced', form.DynamicList, 'arp_target', _('ARP monitor target IP address'));
+ o.datatype = 'ipaddr';
+ o.depends({'type': 'bonding', 'monitor_mode': 'arp'});
+
+ o = this.replaceOption(s, 'devgeneral', form.Flag, 'arp_all_targets', _('All ARP Targets'), _('All ARP targets must be reachable to consider the link valid'));
+ o.default = o.disabled;
+ o.depends({'type': 'bonding', 'monitor_mode': 'arp'});
+
+ o = this.replaceOption(s, 'devadvanced', form.ListValue, 'arp_validate', _('ARP validation policy'));
+ o.default = '';
+ o.value('none', _('None'));
+ o.value('active', _('Active'));
+ o.value('backup', _('Backup'));
+ o.value('all', _('All'));
+ o.value('filter', _('Filter'));
+ o.value('filter_active', _('Filter active'));
+ o.value('filter_backup', _('Filter backup'));
+ o.cfgvalue = function(/* ... */) {
+ var val = form.ListValue.prototype.cfgvalue.apply(this, arguments);
+
+ switch (val || '') {
+ case 'none':
+ case '0':
+ return 'none';
+
+ case 'active':
+ case '1':
+ return 'active';
+
+ case 'backup':
+ case '2':
+ return 'backup';
+
+ case 'all':
+ case '3':
+ return 'all';
+
+ case 'filter':
+ case '4':
+ return 'filter';
+
+ case 'filter_active':
+ case '5':
+ return 'filter_active';
+
+ case 'filter_backup':
+ case '6':
+ return 'filter_backup';
+
+ default:
+ return 'none';
+ }
+ };
+ o.depends({'type': 'bonding', 'policy': 'balance-rr' , 'monitor_mode': 'arp'});
+ o.depends({'type': 'bonding', 'policy': 'active-backup' , 'monitor_mode': 'arp'});
+ o.depends({'type': 'bonding', 'policy': 'balance-xor' , 'monitor_mode': 'arp'});
+ o.depends({'type': 'bonding', 'policy': 'broadcast' , 'monitor_mode': 'arp'});
+
+ o = this.replaceOption(s, 'devadvanced', form.Flag, 'use_carrier', _('Use Carrier'), _('Use carrier status instead of MII result'));
+ o.default = o.disabled;
+ o.depends({'type': 'bonding', 'monitor_mode': 'mii'});
+
+ o = this.replaceOption(s, 'devadvanced', form.Value, 'updelay', _('Monitor link-up delay'));
+ o.description = _('Delay before enabling port after MII link up event (msec)');
+ o.placeholder = '0';
+ o.datatype = 'uinteger';
+ o.depends({'type': 'bonding', 'monitor_mode': 'mii'});
+
+ o = this.replaceOption(s, 'devadvanced', form.Value, 'downdelay', _('Monitor link-down delay'));
+ o.description = _('Delay before enabling port after MII link down event (msec)');
+ o.placeholder = '0';
+ o.datatype = 'uinteger';
+ o.depends({'type': 'bonding', 'monitor_mode': 'mii'});
+
+ o = this.replaceOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_multi-bridge', _('Bridge ports'));
o.size = 10;
o.rmempty = true;
o.multiple = true;
@@ -673,9 +1126,6 @@ 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', form.Flag, 'autoneg', _('Autonegociation'));
o.default = o.enabled;
@@ -708,6 +1158,9 @@ return baseclass.extend({
o.value('full', _('full'));
o.depends('autoneg', '0');
+ 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'));
@@ -736,7 +1189,7 @@ return baseclass.extend({
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 = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'arp_accept', _('Honor gratuitous ARP'), _('When enabled, new ARP table entries are added from received gratuitous ARP 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.'));
@@ -760,8 +1213,6 @@ return baseclass.extend({
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');
@@ -798,7 +1249,6 @@ return baseclass.extend({
o.depends('multicast', /1/);
if (isBridgePort(dev)) {
- o = this.replaceOption(s, 'brport', form.Flag, 'learning', _('Enable MAC address learning'));
o = this.replaceOption(s, 'brport', cbiFlagTristate, 'learning', _('Enable MAC address learning'));
o.sysfs = '/sys/class/net/%s/brport/learning'.format(devname || 'default');
@@ -958,6 +1408,15 @@ return baseclass.extend({
}, this));
};
+ ss.handleRemove = function(section_id) {
+ this.map.data.remove('network', section_id);
+ s.map.addedVLANs = (s.map.addedVLANs || []).filter(function(sid) {
+ return sid != section_id;
+ });
+
+ return this.redraw();
+ };
+
o = ss.option(form.Value, 'vlan', _('VLAN ID'));
o.datatype = 'range(1, 4094)';
diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js b/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
index f6ffc7d5f..dcd86585a 100644
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
+++ b/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
@@ -89,6 +89,21 @@ function calculateNetwork(addr, mask) {
];
}
+function generateDnsmasqInstanceEntry(data) {
+ const nameValueMap = new Map(Object.entries(data));
+ let formatString = nameValueMap.get('.index') + ' (' + _('Name') + (nameValueMap.get('.anonymous') ? ': dnsmasq[' + nameValueMap.get('.index') + ']': ': ' + nameValueMap.get('.name'));
+
+ if (data.domain) {
+ formatString += ', ' + _('Domain') + ': ' + data.domain;
+ }
+ if (data.local) {
+ formatString += ', ' + _('Local') + ': ' + data.local;
+ }
+ formatString += ')';
+
+ return [nameValueMap.get('.name'), formatString];
+}
+
function getDHCPPools() {
return uci.load('dhcp').then(function() {
let sections = uci.sections('dhcp', 'dhcp'),
@@ -194,6 +209,34 @@ function validateServerSpec(sid, s) {
return true;
}
+function expandAndFormatMAC(macs) {
+ let result = [];
+
+ macs.forEach(mac => {
+ if (isValidMAC(mac)) {
+ const expandedMac = mac.split(':').map(part => {
+ return (part.length === 1 && part !== '*') ? '0' + part : part;
+ }).join(':').toUpperCase();
+ result.push(expandedMac);
+ }
+ });
+
+ return result.length ? result : null;
+}
+
+function isValidMAC(sid, s) {
+ if (!s)
+ return true;
+
+ let macaddrs = L.toArray(s);
+
+ for (var i = 0; i < macaddrs.length; i++)
+ if (!macaddrs[i].match(/^(([0-9a-f]{1,2}|\*)[:-]){5}([0-9a-f]{1,2}|\*)$/i))
+ return _('Expecting a valid MAC address, optionally including wildcards') + _('; invalid MAC: ') + macaddrs[i];
+
+ return true;
+}
+
function validateMACAddr(pools, sid, s) {
if (s == null || s == '')
return true;
@@ -225,7 +268,7 @@ function validateMACAddr(pools, sid, s) {
}
}
- return true;
+ return isValidMAC(sid, s);
}
return view.extend({
@@ -234,7 +277,8 @@ return view.extend({
callHostHints(),
callDUIDHints(),
getDHCPPools(),
- network.getNetworks()
+ network.getNetworks(),
+ uci.load('firewall')
]);
},
@@ -244,61 +288,217 @@ return view.extend({
duids = hosts_duids_pools[1],
pools = hosts_duids_pools[2],
networks = hosts_duids_pools[3],
- m, s, o, ss, so;
+ m, s, o, ss, so, dnss;
+
+ let noi18nstrings = {
+ etc_hosts: '/etc/hosts
',
+ etc_ethers: '/etc/ethers
',
+ localhost_v6: '::1
',
+ loopback_slash_8_v4: '127.0.0.0/8
',
+ not_found: 'Not found
',
+ nxdomain: 'NXDOMAIN
',
+ rfc_1918_link: 'RFC1918',
+ rfc_4193_link: 'RFC4193',
+ rfc_4291_link: 'RFC4291',
+ rfc_6303_link: 'RFC6303',
+ reverse_arpa: '*.IN-ADDR.ARPA,*.IP6.ARPA
',
+ servers_file_entry01: 'server=1.2.3.4
',
+ servers_file_entry02: 'server=/domain/1.2.3.4
',
+
+ };
+
+ const recordtypes = [
+ 'ANY',
+ 'A',
+ 'AAAA',
+ 'ALIAS',
+ 'CAA',
+ 'CERT',
+ 'CNAME',
+ 'DS',
+ 'HINFO',
+ 'HIP',
+ 'HTTPS',
+ 'KEY',
+ 'LOC',
+ 'MX',
+ 'NAPTR',
+ 'NS',
+ 'OPENPGPKEY',
+ 'PTR',
+ 'RP',
+ 'SIG',
+ 'SOA',
+ 'SRV',
+ 'SSHFP',
+ 'SVCB',
+ 'TLSA',
+ 'TXT',
+ 'URI',
+ ]
+
+ function customi18n(template, values) {
+ if (!values)
+ values = noi18nstrings;
+ return template.replace(/\{(\w+)\}/g, (match, key) => values[key] || match);
+ };
m = new form.Map('dhcp', _('DHCP and DNS'),
_('Dnsmasq is a lightweight DHCP server and DNS forwarder.'));
s = m.section(form.TypedSection, 'dnsmasq');
- s.anonymous = true;
- s.addremove = false;
+ s.anonymous = false;
+ s.addremove = true;
+ s.addbtntitle = _('Add server instance', 'Dnsmasq instance');
- s.tab('general', _('General Settings'));
- s.tab('advanced', _('Advanced Settings'));
+ s.renderContents = function(/* ... */) {
+ var renderTask = form.TypedSection.prototype.renderContents.apply(this, arguments),
+ sections = this.cfgsections();
+
+ return Promise.resolve(renderTask).then(function(nodes) {
+ if (sections.length < 2) {
+ nodes.querySelector('#cbi-dhcp-dnsmasq > h3').remove();
+ nodes.querySelector('#cbi-dhcp-dnsmasq > .cbi-section-remove').remove();
+ }
+ else {
+ nodes.querySelectorAll('#cbi-dhcp-dnsmasq > .cbi-section-remove').forEach(function(div, i) {
+ var section = uci.get('dhcp', sections[i]),
+ hline = div.nextElementSibling,
+ btn = div.firstElementChild;
+
+ if (!section || section['.anonymous']) {
+ hline.innerText = i ? _('Unnamed instance #%d', 'Dnsmasq instance').format(i+1) : _('Default instance', 'Dnsmasq instance');
+ btn.innerText = i ? _('Remove instance #%d', 'Dnsmasq instance').format(i+1) : _('Remove default instance', 'Dnsmasq instance');
+ }
+ else {
+ hline.innerText = _('Instance "%q"', 'Dnsmasq instance').format(section['.name']);
+ btn.innerText = _('Remove instance "%q"', 'Dnsmasq instance').format(section['.name']);
+ }
+ });
+ }
+
+ nodes.querySelector('#cbi-dhcp-dnsmasq > .cbi-section-create input').placeholder = _('New instance name…', 'Dnsmasq instance');
+
+ return nodes;
+ });
+ };
+
+
+ s.tab('general', _('General'));
+ s.tab('cache', _('Cache'));
+ s.tab('devices', _('Devices & Ports'));
+ s.tab('dnsrecords', _('DNS Records'));
+ s.tab('dnssecopt', _('DNSSEC'));
+ s.tab('filteropts', _('Filter'));
+ s.tab('forward', _('Forwards'));
+ s.tab('limits', _('Limits'));
+ s.tab('logging', _('Log'));
+ s.tab('files', _('Resolv & Hosts Files'));
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.tab('pxe_tftp', _('PXE/TFTP'));
- s.taboption('general', form.Flag, 'domainneeded',
+ o = s.taboption('cache', form.MultiValue, 'cache_rr',
+ _('Cache arbitrary RR'), _('By default, dnsmasq caches A, AAAA, CNAME and SRV DNS record types.') + '
' +
+ _('This option adds additional record types to the cache.'));
+ o.optional = true;
+ o.create = true;
+ o.multiple = true;
+ o.display_size = 5;
+ recordtypes.forEach(r => {
+ o.value(r);
+ });
+
+ s.taboption('filteropts', form.Flag, 'domainneeded',
_('Domain required'),
- _('Do not forward DNS queries without dots or domain parts.'));
-
+ _('Never forward DNS queries which lack dots or domain parts.') + '
' +
+ customi18n(_('Names not in {etc_hosts} are answered {not_found}.') )
+ );
s.taboption('general', form.Flag, 'authoritative',
_('Authoritative'),
_('This is the only DHCP server in the local network.'));
- s.taboption('general', form.Value, 'local',
- _('Local server'),
- _('Never forward matching domains and subdomains, resolve from DHCP or hosts files only.'));
+ o = s.taboption('general', form.Value, 'local',
+ _('Resolve these locally'),
+ _('Never forward these matching domains or subdomains; resolve from DHCP or hosts files only.'));
+ o.placeholder = '/internal.example.com/private.example.com/example.org';
s.taboption('general', form.Value, 'domain',
_('Local domain'),
_('Local domain suffix appended to DHCP names and hosts file entries.'));
- o = s.taboption('general', form.Flag, 'logqueries',
- _('Log queries'),
- _('Write received DNS queries to syslog.'));
- o.optional = true;
-
+ s.taboption('general', form.Flag, 'expandhosts',
+ _('Expand hosts'),
+ _('Add local domain suffix to names served from hosts files.'));
+
o = s.taboption('general', form.DynamicList, 'server',
_('DNS forwardings'),
_('List of upstream resolvers to forward queries to.'));
- o.optional = true;
o.placeholder = '/example.org/10.1.2.3';
+
+ o = s.taboption('logging', form.Flag, 'logqueries',
+ _('Log queries'),
+ _('Write received DNS queries to syslog.') + ' ' + _('Dump cache on SIGUSR1, include requesting IP.'));
+ o.optional = true;
+
+ o = s.taboption('logging', form.Flag, 'logdhcp',
+ _('Extra DHCP logging'),
+ _('Log all options sent to DHCP clients and the tags used to determine them.'));
+ o.optional = true;
+
+ o = s.taboption('logging', form.Value, 'logfacility',
+ _('Log facility'),
+ _('Set log class/facility for syslog entries.'));
+ o.optional = true;
+ o.value('KERN');
+ o.value('USER');
+ o.value('MAIL');
+ o.value('DAEMON');
+ o.value('AUTH');
+ o.value('LPR');
+ o.value('NEWS');
+ o.value('UUCP');
+ o.value('CRON');
+ o.value('LOCAL0');
+ o.value('LOCAL1');
+ o.value('LOCAL2');
+ o.value('LOCAL3');
+ o.value('LOCAL4');
+ o.value('LOCAL5');
+ o.value('LOCAL6');
+ o.value('LOCAL7');
+ o.value('-', _('stderr'));
+
+ o = s.taboption('forward', form.DynamicList, 'server',
+ _('DNS Forwards'),
+ _('Forward specific domain queries to specific upstream servers.'));
+ o.optional = true;
+ o.placeholder = '/*.example.org/10.1.2.3';
o.validate = validateServerSpec;
o = s.taboption('general', form.DynamicList, 'address',
_('Addresses'),
_('Resolve specified FQDNs to an IP.') + '
' +
- _('Syntax: /fqdn[/fqdn…]/[ipaddr]
.') + '
' +
- _('/#/
matches any domain. /example.com/
returns NXDOMAIN.') + '
' +
- _('/example.com/#
returns NULL addresses (0.0.0.0
and ::
) for example.com and its subdomains.'));
+ customi18n(_('Syntax: {code_syntax}.'),
+ {code_syntax: '/fqdn[/fqdn…]/[ipaddr]
'}) + '
' +
+ customi18n(_('{example_nx} returns {nxdomain}.',
+ 'hint: /example.com/
returns NXDOMAIN
.'),
+ {example_nx: '/example.com/
', nxdomain: 'NXDOMAIN
'}) + '
' +
+ customi18n(_('{any_domain} matches any domain (and returns {nxdomain}).',
+ 'hint: /#/
matches any domain (and returns NXDOMAIN).'),
+ {any_domain:'/#/
', nxdomain: 'NXDOMAIN
'}) + '
' +
+ customi18n(
+ _('{example_null} returns {null_addr} addresses ({null_ipv4}, {null_ipv6}) for {example_com} and its subdomains.',
+ 'hint: /example.com/#
returns NULL addresses (0.0.0.0
, ::
) for example.com and its subdomains.'),
+ { example_null: '/example.com/#
',
+ null_addr: 'NULL
',
+ null_ipv4: '0.0.0.0
',
+ null_ipv6: '::
',
+ example_com: 'example.com
',
+ }
+ )
+ );
o.optional = true;
o.placeholder = '/router.local/router.lan/192.168.0.1';
@@ -308,52 +508,58 @@ return view.extend({
o.filter = function(section_id, name) {
return (section_id.startsWith('omr_dscp') == false);
};
-
o.optional = true;
o.placeholder = '/example.org/ipset,ipset6';
- o = s.taboption('general', form.Flag, 'rebind_protection',
+ o = s.taboption('filteropts', form.Flag, 'rebind_protection',
_('Rebind protection'),
- _('Discard upstream responses containing RFC1918 addresses.').format('https://datatracker.ietf.org/doc/html/rfc1918'));
+ customi18n(_('Discard upstream responses containing {rfc_1918_link} addresses.') ) + '
' +
+ customi18n(_('Discard also upstream responses containing {rfc_4193_link}, Link-Local and private IPv4-Mapped {rfc_4291_link} IPv6 Addresses.') )
+ );
o.rmempty = false;
- o = s.taboption('general', form.Flag, 'rebind_localhost',
+ o = s.taboption('filteropts', form.Flag, 'rebind_localhost',
_('Allow localhost'),
- _('Exempt 127.0.0.0/8
and ::1
from rebinding checks, e.g. for RBL services.'));
+ customi18n(
+ _('Exempt {loopback_slash_8_v4} and {localhost_v6} from rebinding checks, e.g. for RBL services.')
+ )
+ );
o.depends('rebind_protection', '1');
- o = s.taboption('general', form.DynamicList, 'rebind_domain',
+ o = s.taboption('filteropts', form.DynamicList, 'rebind_domain',
_('Domain whitelist'),
- _('List of domains to allow RFC1918 responses for.'));
+ customi18n(_('List of domains to allow {rfc_1918_link} responses for.') )
+ );
o.depends('rebind_protection', '1');
o.optional = true;
o.placeholder = 'ihost.netflix.com';
o.validate = validateAddressList;
- o = s.taboption('general', form.Flag, 'localservice',
+ o = s.taboption('filteropts', form.Flag, 'localservice',
_('Local service only'),
_('Accept DNS queries only from hosts whose address is on a local subnet.'));
o.optional = false;
o.rmempty = false;
- o = s.taboption('general', form.Flag, 'nonwildcard',
+ o = s.taboption('devices', form.Flag, 'nonwildcard',
_('Non-wildcard'),
- _('Bind dynamically to interfaces rather than wildcard address.'));
+ _('Bind only to configured interface addresses, instead of the wildcard address.'));
o.default = o.enabled;
o.optional = false;
o.rmempty = true;
- o = s.taboption('general', form.DynamicList, 'interface',
+ o = s.taboption('devices', widgets.NetworkSelect, 'interface',
_('Listen interfaces'),
_('Listen only on the specified interfaces, and loopback if not excluded explicitly.'));
- o.optional = true;
- o.placeholder = 'lan';
+ o.multiple = true;
+ o.nocreate = true;
- o = s.taboption('general', form.DynamicList, 'notinterface',
+ o = s.taboption('devices', widgets.NetworkSelect, 'notinterface',
_('Exclude interfaces'),
_('Do not listen on the specified interfaces.'));
- o.optional = true;
- o.placeholder = 'loopback';
+ o.loopback = true;
+ o.multiple = true;
+ o.nocreate = true;
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.')
@@ -397,31 +603,36 @@ return view.extend({
var m = this.section.formvalue(section, 'local_addr'),
n = this.section.formvalue(section, 'server_addr'),
p;
- if (n != null && n != '')
- p = n.split('#');
+
+ if (!m || !n) {
+ return _('Both "Relay from" and "Relay to address" must be specified.');
+ }
+ else {
+ 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.')
+ 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.')
+ }
+ return true;
};
+
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 /etc/ethers
'),
- _('Read /etc/ethers
to configure the DHCP server.'));
+ customi18n(_('Use {etc_ethers}') ),
+ customi18n(_('Read {etc_ethers} to configure the DHCP server.') )
+ );
s.taboption('files', form.Value, 'leasefile',
_('Lease file'),
@@ -438,8 +649,21 @@ return view.extend({
o.placeholder = '/tmp/resolv.conf.d/resolv.conf.auto';
o.optional = true;
+ o = s.taboption('files', form.Flag, 'strictorder',
+ _('Strict order'),
+ _('Query upstream resolvers in the order they appear in the resolv file.'));
+ o.optional = true;
+
+ o = s.taboption('files', form.Flag, 'ignore_hosts_dir',
+ _('Ignore hosts files directory'),
+ _('On: use instance specific hosts file only') + '
' +
+ _('Off: use all files in the directory including the instance specific hosts file')
+ );
+ o.optional = true;
+
o = s.taboption('files', form.Flag, 'nohosts',
- _('Ignore /etc/hosts
'));
+ customi18n(_('Ignore {etc_hosts} file') )
+ );
o.optional = true;
o = s.taboption('files', form.DynamicList, 'addnhosts',
@@ -447,125 +671,193 @@ return view.extend({
o.optional = true;
o.placeholder = '/etc/dnsmasq.hosts';
- o = s.taboption('advanced', form.Flag, 'quietdhcp',
+ o = s.taboption('logging', form.Flag, 'quietdhcp',
_('Suppress logging'),
_('Suppress logging of the routine operation for the DHCP protocol.'));
o.optional = true;
+ o.depends('logdhcp', '0');
- o = s.taboption('advanced', form.Flag, 'sequential_ip',
+ o = s.taboption('general', form.Flag, 'sequential_ip',
_('Allocate IPs sequentially'),
_('Allocate IP addresses sequentially, starting from the lowest available address.'));
o.optional = true;
- o = s.taboption('advanced', form.Flag, 'boguspriv',
+ o = s.taboption('filteropts', form.Flag, 'boguspriv',
_('Filter private'),
- _('Do not forward reverse lookups for local networks.'));
+ customi18n(
+ _('Reject reverse lookups to {rfc_6303_link} IP ranges ({reverse_arpa}) not in {etc_hosts}.') )
+ );
o.default = o.enabled;
- s.taboption('advanced', form.Flag, 'filterwin2k',
+ s.taboption('filteropts', form.Flag, 'filterwin2k',
_('Filter SRV/SOA service discovery'),
_('Filters SRV/SOA service discovery, to avoid triggering dial-on-demand links.') + '
' +
_('May prevent VoIP or other services from working.'));
- o = s.taboption('advanced', form.Flag, 'filter_aaaa',
+ o = s.taboption('filteropts', form.Flag, 'filter_aaaa',
_('Filter IPv6 AAAA records'),
_('Remove IPv6 addresses from the results and only return IPv4 addresses.') + '
' +
_('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',
+ o = s.taboption('filteropts', 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',
+ o = s.taboption('filteropts', form.MultiValue, 'filter_rr',
+ _('Filter arbitrary RR'), _('Removes records of the specified type(s) from answers.'));
+ o.optional = true;
+ o.create = true;
+ o.multiple = true;
+ o.display_size = 5;
+ recordtypes.forEach(r => {
+ o.value(r);
+ });
+
+ s.taboption('filteropts', form.Flag, 'localise_queries',
_('Localise queries'),
- _('Return answers to DNS queries matching the subnet from which the query was received if multiple IPs are available.'));
+ customi18n(_('Limit response records (from {etc_hosts}) to those that fall within the subnet of the querying interface.') ) + '
' +
+ _('This prevents unreachable IPs in subnets not accessible to you.') + '
' +
+ _('Note: IPv4 only.'));
if (L.hasSystemFeature('dnsmasq', 'dnssec')) {
- o = s.taboption('advanced', form.Flag, 'dnssec',
+ o = s.taboption('dnssecopt', form.Flag, 'dnssec',
_('DNSSEC'),
_('Validate DNS replies and cache DNSSEC data, requires upstream to support DNSSEC.'));
o.optional = true;
- o = s.taboption('advanced', form.Flag, 'dnsseccheckunsigned',
+ o = s.taboption('dnssecopt', form.Flag, 'dnsseccheckunsigned',
_('DNSSEC check unsigned'),
_('Verify unsigned domain responses really come from unsigned domains.'));
o.default = o.enabled;
o.optional = true;
}
- s.taboption('advanced', form.Flag, 'expandhosts',
- _('Expand hosts'),
- _('Add local domain suffix to names served from hosts files.'));
-
- s.taboption('advanced', form.Flag, 'nonegcache',
+ s.taboption('filteropts', form.Flag, 'nonegcache',
_('No negative cache'),
_('Do not cache negative replies, e.g. for non-existent domains.'));
- o = s.taboption('advanced', form.Value, 'serversfile',
+ o = s.taboption('forward', form.Value, 'serversfile',
_('Additional servers file'),
- _('File listing upstream resolvers, optionally domain-specific, e.g. server=1.2.3.4
, server=/domain/1.2.3.4
.'));
+ customi18n(_('File listing upstream resolvers, optionally domain-specific, e.g. {servers_file_entry01}, {servers_file_entry02}.') )
+ );
o.placeholder = '/etc/dnsmasq.servers';
- o = s.taboption('advanced', form.Flag, 'strictorder',
- _('Strict order'),
- _('Upstream resolvers will be queried in the order of the resolv file.'));
+ o = s.taboption('forward', form.Value, 'addmac',
+ _('Add requestor MAC'),
+ _('Add the MAC address of the requestor to DNS queries which are forwarded upstream.') + ' ' + '
' +
+ _('%s uses the default MAC address format encoding').format('enabled
') + ' ' + '
' +
+ _('%s uses an alternative encoding of the MAC as base64').format('base64
') + ' ' + '
' +
+ _('%s uses a human-readable encoding of hex-and-colons').format('text
'));
+ o.optional = true;
+ o.value('', _('off'));
+ o.value('1', _('enabled (default)'));
+ o.value('base64');
+ o.value('text');
+
+ s.taboption('forward', form.Flag, 'stripmac',
+ _('Remove MAC address before forwarding query'),
+ _('Remove any MAC address information already in downstream queries before forwarding upstream.'));
+
+ o = s.taboption('forward', form.Value, 'addsubnet',
+ _('Add subnet address to forwards'),
+ _('Add a subnet address to the DNS queries which are forwarded upstream, leaving this value empty disables the feature.') + ' ' +
+ _('If an address is specified in the flag, it will be used, otherwise, the address of the requestor will be used.') + ' ' +
+ _('The amount of the address forwarded depends on the prefix length parameter: 32 (128 for IPv6) forwards the whole address, zero forwards none of it but still marks the request so that no upstream nameserver will add client address information either.') + ' ' + '
' +
+ _('The default (%s) is zero for both IPv4 and IPv6.').format('0,0
') + ' ' + '
' +
+ _('%s adds the /24 and /96 subnets of the requestor for IPv4 and IPv6 requestors, respectively.').format('24,96
') + ' ' + '
' +
+ _('%s adds 1.2.3.0/24 for IPv4 requestors and ::/0 for IPv6 requestors.').format('1.2.3.4/24
') + ' ' + '
' +
+ _('%s adds 1.2.3.0/24 for both IPv4 and IPv6 requestors.').format('1.2.3.4/24,1.2.3.4/24
'));
o.optional = true;
- o = s.taboption('advanced', form.Flag, 'allservers',
+ s.taboption('forward', form.Flag, 'stripsubnet',
+ _('Remove subnet address before forwarding query'),
+ _('Remove any subnet address already present in a downstream query before forwarding it upstream.'));
+
+ o = s.taboption('general', form.Flag, 'allservers',
_('All servers'),
- _('Query all available upstream resolvers.'));
+ _('Query all available upstream resolvers.') + ' ' + _('First answer wins.'));
o.optional = true;
- o = s.taboption('advanced', form.DynamicList, 'bogusnxdomain',
- _('IPs to override with NXDOMAIN'),
- _('List of IP addresses to convert into NXDOMAIN responses.'));
+ o = s.taboption('filteropts', form.DynamicList, 'bogusnxdomain',
+ customi18n(_('IPs to override with {nxdomain}') ),
+ customi18n(_('Transform replies which contain the specified addresses or subnets into {nxdomain} responses.') )
+ );
o.optional = true;
o.placeholder = '64.94.110.11';
- o = s.taboption('advanced', form.Value, 'port',
+ o = s.taboption('devices', form.Value, 'port',
_('DNS server port'),
_('Listening port for inbound DNS queries.'));
o.optional = true;
o.datatype = 'port';
o.placeholder = 53;
- o = s.taboption('advanced', form.Value, 'queryport',
+ o = s.taboption('devices', form.Value, 'queryport',
_('DNS query port'),
_('Fixed source port for outbound DNS queries.'));
o.optional = true;
o.datatype = 'port';
o.placeholder = _('any');
- o = s.taboption('advanced', form.Value, 'dhcpleasemax',
+ o = s.taboption('devices', form.Value, 'minport',
+ _('Minimum source port #'),
+ _('Min valid value %s.').format('1024
') + ' ' + _('Useful for systems behind firewalls.'));
+ o.optional = true;
+ o.datatype = 'port';
+ o.placeholder = 1024;
+ o.depends('queryport', '');
+
+ o = s.taboption('devices', form.Value, 'maxport',
+ _('Maximum source port #'),
+ _('Max valid value %s.').format('65535
') + ' ' + _('Useful for systems behind firewalls.'));
+ o.optional = true;
+ o.datatype = 'port';
+ o.placeholder = 50000;
+ o.depends('queryport', '');
+
+ o = s.taboption('limits', form.Value, 'dhcpleasemax',
_('Max. DHCP leases'),
_('Maximum allowed number of active DHCP leases.'));
o.optional = true;
o.datatype = 'uinteger';
- o.placeholder = _('unlimited');
+ o.placeholder = 150;
- o = s.taboption('advanced', form.Value, 'ednspacket_max',
+ o = s.taboption('limits', form.Value, 'ednspacket_max',
_('Max. EDNS0 packet size'),
_('Maximum allowed size of EDNS0 UDP packets.'));
o.optional = true;
o.datatype = 'uinteger';
o.placeholder = 1280;
- o = s.taboption('advanced', form.Value, 'dnsforwardmax',
+ o = s.taboption('limits', form.Value, 'dnsforwardmax',
_('Max. concurrent queries'),
_('Maximum allowed number of concurrent DNS queries.'));
o.optional = true;
o.datatype = 'uinteger';
o.placeholder = 150;
- o = s.taboption('advanced', form.Value, 'cachesize',
+ o = s.taboption('limits', form.Value, 'cachesize',
_('Size of DNS query cache'),
_('Number of cached DNS entries, 10000 is maximum, 0 is no caching.'));
o.optional = true;
o.datatype = 'range(0,10000)';
o.placeholder = 1000;
+ o = s.taboption('limits', form.Value, 'min_cache_ttl',
+ _('Min cache TTL'),
+ _('Extend short TTL values to the seconds value given when caching them. Use with caution.') +
+ _(' (Max 1h == 3600)'));
+ o.optional = true;
+ o.placeholder = 60;
+
+ o = s.taboption('limits', form.Value, 'max_cache_ttl',
+ _('Max cache TTL'),
+ _('Set a maximum seconds TTL value for entries in the cache.'));
+ o.optional = true;
+ o.placeholder = 3600;
+
o = s.taboption('pxe_tftp', form.Flag, 'enable_tftp',
_('Enable TFTP server'),
_('Enable the built-in single-instance TFTP server.'));
@@ -591,6 +883,7 @@ return view.extend({
ss = o.subsection;
ss.addremove = true;
ss.anonymous = true;
+ ss.modaltitle = _('Edit PXE/TFTP/BOOTP Host');
ss.nodescriptions = true;
so = ss.option(form.Value, 'filename',
@@ -613,19 +906,20 @@ return view.extend({
so = ss.option(form.DynamicList, 'dhcp_option',
_('DHCP Options'),
- _('Options for the Network-ID. (Note: needs also Network-ID.) E.g. "42,192.168.1.4
" for NTP server, "3,192.168.4.4
" for default route. 0.0.0.0
means "the address of the system running dnsmasq".'));
+ _('Additional options to send to the below match tags.') + '
' +
+ _('%s means "the address of the system running dnsmasq".').format('0.0.0.0
'));
so.optional = true;
- so.placeholder = '42,192.168.1.4';
+ so.placeholder = 'option:root-path,192.168.1.2:/data/netboot/root';
- so = ss.option(widgets.DeviceSelect, 'networkid',
- _('Network-ID'),
- _('Apply DHCP Options to this net. (Empty = all clients).'));
+ so = ss.option(form.Value, 'networkid',
+ _('Match this Tag'),
+ _('Only DHCP Clients with this tag are sent this boot option.'));
so.optional = true;
so.noaliases = true;
so = ss.option(form.Flag, 'force',
_('Force'),
- _('Always send DHCP Options. Sometimes needed, with e.g. PXELinux.'));
+ _('Always send the chosen DHCP options. Sometimes needed, with e.g. PXELinux.'));
so.optional = true;
so = ss.option(form.Value, 'instance',
@@ -634,10 +928,24 @@ return view.extend({
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 || '?'));
+ var [name, display_str] = generateDnsmasqInstanceEntry(val);
+ so.value(name, display_str);
});
- o = s.taboption('srvhosts', form.SectionValue, '__srvhosts__', form.TableSection, 'srvhost', null,
+ o = s.taboption('dnsrecords', form.SectionValue, '__dnsrecords__', form.TypedSection, '__dnsrecords__');
+
+ dnss = o.subsection;
+
+ dnss.anonymous = true;
+ dnss.cfgsections = function() { return [ '__dnsrecords__' ] };
+
+ dnss.tab('hosts', _('Hostnames'));
+ dnss.tab('srvhosts', _('SRV'));
+ dnss.tab('mxhosts', _('MX'));
+ dnss.tab('cnamehosts', _('CNAME'));
+ dnss.tab('dnsrr', _('DNS-RR'));
+
+ o = dnss.taboption('srvhosts', form.SectionValue, '__srvhosts__', form.TableSection, 'srvhost', null,
_('Bind service records to a domain name: specify the location of services. See RFC2782.').format('https://datatracker.ietf.org/doc/html/rfc2782')
+ '
' + _('_service: _sip, _ldap, _imap, _stun, _xmpp-client, … . (Note: while _http is possible, no browsers support SRV records.)')
+ '
' + _('_proto: _tcp, _udp, _sctp, _quic, … .')
@@ -651,15 +959,15 @@ return view.extend({
ss.sortable = true;
ss.rowcolors = true;
- so = ss.option(form.Value, 'srv', _('SRV'), _('Syntax: _service._proto.example.com
.'));
+ so = ss.option(form.Value, 'srv', _('SRV'), _('Syntax:') + ' ' + '_service._proto.example.com.
');
so.rmempty = false;
so.datatype = 'hostname';
- so.placeholder = '_sip._tcp.example.com';
+ 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.placeholder = 'sip.example.com.';
so = ss.option(form.Value, 'port', _('Port'));
so.rmempty = false;
@@ -676,7 +984,7 @@ return view.extend({
so.datatype = 'range(0,65535)';
so.placeholder = '50';
- o = s.taboption('mxhosts', form.SectionValue, '__mxhosts__', form.TableSection, 'mxhost', null,
+ o = dnss.taboption('mxhosts', form.SectionValue, '__mxhosts__', form.TableSection, 'mxhost', null,
_('Bind service records to a domain name: specify the location of services.')
+ '
' + _('You may add multiple records for the same domain.'));
@@ -691,19 +999,19 @@ return view.extend({
so = ss.option(form.Value, 'domain', _('Domain'));
so.rmempty = false;
so.datatype = 'hostname';
- so.placeholder = 'example.com';
+ so.placeholder = 'example.com.';
so = ss.option(form.Value, 'relay', _('Relay'));
so.rmempty = false;
so.datatype = 'hostname';
- so.placeholder = 'relay.example.com';
+ 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,
+ o = dnss.taboption('cnamehosts', form.SectionValue, '__cname__', form.TableSection, 'cname', null,
_('Set an alias for a hostname.'));
ss = o.subsection;
@@ -716,15 +1024,15 @@ return view.extend({
so = ss.option(form.Value, 'cname', _('Domain'));
so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'www.example.com';
+ so.validate = validateHostname;
+ so.placeholder = 'www.example.com.';
so = ss.option(form.Value, 'target', _('Target'));
so.rmempty = false;
so.datatype = 'hostname';
- so.placeholder = 'example.com';
+ so.placeholder = 'example.com.';
- o = s.taboption('hosts', form.SectionValue, '__hosts__', form.GridSection, 'domain', null,
+ o = dnss.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.'));
ss = o.subsection;
@@ -739,7 +1047,7 @@ return view.extend({
so = ss.option(form.Value, 'ip', _('IP address'));
so.rmempty = false;
- so.datatype = 'ipaddr';
+ so.datatype = 'ipaddr("nomask")';
var ipaddrs = {};
@@ -754,26 +1062,114 @@ return view.extend({
so.value(ipv4, '%s (%s)'.format(ipv4, ipaddrs[ipv4]));
});
+ o = dnss.taboption('dnsrr', form.SectionValue, '__dnsrr__', form.TableSection, 'dnsrr', null,
+ _('Set an arbitrary resource record (RR) type.') + '
' +
+ _('Hexdata is automatically en/decoded on save and load'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+
+ function hexdecodeload(section_id) {
+ let value = uci.get('dhcp', section_id, this.option) || '';
+ // Remove any spaces or colons from the hex string - they're allowed
+ value = value.replace(/[\s:]/g, '');
+ // Hex-decode the string before displaying
+ let decodedString = '';
+ for (let i = 0; i < value.length; i += 2) {
+ decodedString += String.fromCharCode(parseInt(value.substr(i, 2), 16));
+ }
+ return decodedString;
+ }
+
+ function hexencodesave(section, value) {
+ if (!value || value.length === 0) {
+ uci.unset('dhcp', section, 'hexdata');
+ return;
+ }
+ // Hex-encode the string before saving
+ const encodedArr = value.split('').map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('');
+ uci.set('dhcp', section, this.option, encodedArr);
+ }
+
+ so = ss.option(form.Value, 'dnsrr', _('Resource Record Name'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'svcb.example.com.';
+
+ so = ss.option(form.Value, 'rrnumber', _('Resource Record Number'));
+ so.rmempty = false;
+ so.datatype = 'uinteger';
+ so.placeholder = '64';
+
+ so = ss.option(form.Value, 'hexdata', _('Raw Data'));
+ so.rmempty = true;
+ so.datatype = 'string';
+ so.placeholder = 'free-form string';
+ so.load = hexdecodeload;
+ so.write = hexencodesave;
+
+ so = ss.option(form.DummyValue, '_hexdata', _('Hex Data'));
+ so.width = '10%';
+ so.rawhtml = true;
+ so.load = function(section_id) {
+ let hexdata = uci.get('dhcp', section_id, 'hexdata') || '';
+ hexdata = hexdata.replace(/[:]/g, '');
+ if (hexdata) {
+ return hexdata.replace(/(.{20})/g, '$1
'); // Inserts
after every 2 characters (hex pair)
+ } else {
+ return '';
+ }
+ }
+
o = s.taboption('ipsets', form.SectionValue, '__ipsets__', form.GridSection, 'ipset', null,
- _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.'));
+ _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.') + '
' +
+ _('The netfilter components below are only regarded when running fw4.'));
ss = o.subsection;
ss.filter = function(section_id, name) {
return (section_id.startsWith('omr_') == false);
};
+
ss.addremove = true;
ss.anonymous = true;
ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+ ss.modaltitle = _('Edit IP set');
- so = ss.option(form.DynamicList, 'name', _('IP set'));
+ so = ss.option(form.DynamicList, 'name', _('Name of the set'));
+ uci.sections('firewall', 'ipset', function(s) {
+ if (typeof(s.name) == 'string')
+ so.value(s.name, s.comment ? '%s (%s)'.format(s.name, s.comment) : s.name);
+ });
so.rmempty = false;
+ so.editable = false;
so.datatype = 'string';
- so = ss.option(form.DynamicList, 'domain', _('Domain'));
+ so = ss.option(form.DynamicList, 'domain', _('FQDN'));
so.rmempty = false;
+ so.editable = false;
so.datatype = 'hostname';
+ so = ss.option(form.Value, 'table', _('Netfilter table name'), _('Defaults to fw4.'));
+ so.editable = false;
+ so.placeholder = 'fw4';
+ so.rmempty = true;
+
+ so = ss.option(form.ListValue, 'table_family', _('Table IP family'), _('Defaults to IPv4+6.') + ' ' + _('Can be hinted by adding 4 or 6 to the name.') + '
' +
+ _('Adding an IPv6 to an IPv4 set and vice-versa silently fails.'));
+ so.editable = false;
+ so.rmempty = true;
+ so.value('inet', _('IPv4+6'));
+ so.value('ip', _('IPv4'));
+ so.value('ip6', _('IPv6'));
+
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.') + '
' +
_('Use the Add Button to add a new lease entry. The MAC address identifies the host, the IPv4 address specifies the fixed address to use, and the Hostname is assigned as a symbolic name to the requesting host. The optional Lease time can be used to set non-standard host-specific lease time, e.g. 12h, 3d or infinite.') + '
' +
@@ -802,61 +1198,22 @@ return view.extend({
uci.unset('dhcp', section, 'dns');
};
- so = ss.option(form.Value, 'mac',
+ //this can be a .DynamicList or a .Value with a widget and dnsmasq handles multimac OK.
+ so = ss.option(form.DynamicList, 'mac',
_('MAC address(es)'),
- _('The hardware address(es) of this entry/host, separated by spaces.') + '
' +
+ _('The hardware address(es) of this entry/host.') + '
' +
_('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)) {
- 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)
- ];
-
- 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) {
- var node = form.Value.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]),
- ipopt = this.section.children.filter(function(o) { return o.option == 'ip' })[0];
-
- node.addEventListener('cbi-dropdown-change', L.bind(function(ipopt, section_id, ev) {
- var mac = ev.detail.value.value;
- if (mac == null || mac == '' || !hosts[mac])
- return;
-
- var iphint = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0];
- if (iphint == null)
- return;
-
- var ip = ipopt.formvalue(section_id);
- if (ip != null && ip != '')
- return;
-
- var node = ipopt.map.findElement('id', ipopt.cbid(section_id));
- if (node)
- dom.callClassMethod(node, 'setValue', iphint);
- }, this, ipopt, section_id));
-
- return node;
+ var macs = uci.get('dhcp', section, 'mac');
+ if(!Array.isArray(macs)){
+ return expandAndFormatMAC(L.toArray(macs));
+ } else {
+ return expandAndFormatMAC(macs);
+ }
};
+ //removed jows renderwidget function which hindered multi-mac entry
so.validate = validateMACAddr.bind(so, pools);
Object.keys(hosts).forEach(function(mac) {
var hint = hosts[mac].name || L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0];
@@ -870,10 +1227,10 @@ return view.extend({
var m = this.section.formvalue(section, 'mac'),
n = this.section.formvalue(section, 'name');
- if ((m == null || m == '') && (n == null || n == ''))
+ if ((m && !m.length > 0) && !n)
return _('One of hostname or MAC address must be specified!');
- if (value == null || value == '' || value == 'ignore')
+ if (!value || value == 'ignore')
return true;
var leases = uci.sections('dhcp', 'host');
@@ -925,9 +1282,9 @@ return view.extend({
so = ss.option(form.DynamicList, 'match_tag',
_('Match Tag'),
- _('When a host matches an entry then the special tag known is set. Use known to match all known hosts.') + '
' +
- _('Ignore requests from unknown machines using !known.') + '
' +
- _('If a host matches an entry which cannot be used because it specifies an address on a different subnet, the tag known-othernet is set.'));
+ _('When a host matches an entry then the special tag %s is set. Use %s to match all known hosts.').format('known
', 'known
') + '
' +
+ _('Ignore requests from unknown machines using %s.').format('!known
') + '
' +
+ _('If a host matches an entry which cannot be used because it specifies an address on a different subnet, the tag %s is set.').format('known-othernet
'));
so.value('known', _('known'));
so.value('!known', _('!known (not known)'));
so.value('known-othernet', _('known-othernet (on different subnet)'));
@@ -939,7 +1296,8 @@ return view.extend({
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 || '?'));
+ var [name, display_str] = generateDnsmasqInstanceEntry(val);
+ so.value(name, display_str);
});
@@ -1016,7 +1374,7 @@ return view.extend({
return [
host || '-',
- lease.ip6addrs ? lease.ip6addrs.join(' ') : lease.ip6addr,
+ lease.ip6addrs ? lease.ip6addrs.join('
') : lease.ip6addr,
lease.duid,
exp
];
diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js b/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
index 0c575c416..1f63de5c4 100644
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
+++ b/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
@@ -65,46 +65,32 @@ function render_status(node, ifc, with_device) {
null, E('em', _('Interface is marked for deletion'))
]);
- var i18n = ifc.getI18n();
- if (i18n)
- desc = desc ? '%s (%s)'.format(desc, i18n) : i18n;
+ desc = desc ? '%s (%s)'.format(desc, ifc.getI18n()) : ifc.getI18n();
- var changecount = with_device ? 0 : count_changes(ifc.getName()),
- ipaddrs = changecount ? [] : ifc.getIPAddrs(),
- ip6addrs = changecount ? [] : ifc.getIP6Addrs(),
- errors = ifc.getErrors(),
- maindev = ifc.getL3Device() || ifc.getDevice(),
- macaddr = maindev ? maindev.getMAC() : null;
+ const changecount = with_device ? 0 : count_changes(ifc.getName());
+ const maindev = ifc.getL3Device() || ifc.getDevice();
+ const macaddr = maindev ? maindev.getMAC() : null;
+ const cond00 = !changecount && !ifc.isDynamic() && !ifc.isAlias();
+ const cond01 = cond00 && macaddr;
+ const cond02 = cond00 && maindev;
+
+ function addEntries(label, array) {
+ return Array.isArray(array) ? array.flatMap((item) => [label, item]) : [label, null];
+ }
return L.itemlist(node, [
_('Protocol'), with_device ? null : (desc || '?'),
- _('Device'), with_device ? (maindev ? maindev.getShortName() : E('em', _('Not present'))) : null,
- _('Uptime'), (!changecount && ifc.isUp()) ? '%t'.format(ifc.getUptime()) : null,
- _('MAC'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && macaddr) ? macaddr : null,
- _('RX'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && maindev) ? '%.2mB (%d %s)'.format(maindev.getRXBytes(), maindev.getRXPackets(), _('Pkts.')) : null,
- _('TX'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && maindev) ? '%.2mB (%d %s)'.format(maindev.getTXBytes(), maindev.getTXPackets(), _('Pkts.')) : null,
- _('IPv4'), ipaddrs[0],
- _('IPv4'), ipaddrs[1],
- _('IPv4'), ipaddrs[2],
- _('IPv4'), ipaddrs[3],
- _('IPv4'), ipaddrs[4],
- _('IPv6'), ip6addrs[0],
- _('IPv6'), ip6addrs[1],
- _('IPv6'), ip6addrs[2],
- _('IPv6'), ip6addrs[3],
- _('IPv6'), ip6addrs[4],
- _('IPv6'), ip6addrs[5],
- _('IPv6'), ip6addrs[6],
- _('IPv6'), ip6addrs[7],
- _('IPv6'), ip6addrs[8],
- _('IPv6'), ip6addrs[9],
- _('IPv6-PD'), changecount ? null : ifc.getIP6Prefix(),
+ _('Device'), with_device ? (maindev ? maindev.getShortName() : E('em', _('Not present'))) : null,
+ _('Uptime'), (!changecount && ifc.isUp()) ? '%t'.format(ifc.getUptime()) : null,
+ _('MAC'), (cond01) ? macaddr : null,
+ _('RX'), (cond02) ? '%.2mB (%d %s)'.format(maindev.getRXBytes(), maindev.getRXPackets(), _('Pkts.')) : null,
+ _('TX'), (cond02) ? '%.2mB (%d %s)'.format(maindev.getTXBytes(), maindev.getTXPackets(), _('Pkts.')) : null,
+ ...addEntries(_('IPv4'), changecount ? [] : ifc.getIPAddrs()),
+ ...addEntries(_('IPv6'), changecount ? [] : ifc.getIP6Addrs()),
+ ...addEntries(_('IPv6-PD'), changecount ? null : ifc.getIP6Prefixes?.()),
+ _('Information'), with_device ? null : (ifc.get('disabled') != '1' ? null : _('Interface disabled')),
_('Information'), with_device ? null : (ifc.get('auto') != '0' ? null : _('Not started on boot')),
- _('Error'), errors ? errors[0] : null,
- _('Error'), errors ? errors[1] : null,
- _('Error'), errors ? errors[2] : null,
- _('Error'), errors ? errors[3] : null,
- _('Error'), errors ? errors[4] : null,
+ ...addEntries(_('Error'), ifc.getErrors()),
null, changecount ? E('a', {
href: '#',
click: L.bind(ui.changes.displayChanges, ui.changes)
@@ -175,9 +161,9 @@ function iface_updown(up, id, ev, force) {
ui.showModal(_('Confirm disconnect'), [
E('p', _('You appear to be currently connected to the device via the "%h" interface. Do you really want to shut down the interface?').format(id)),
- E('div', { 'class': 'right' }, [
+ E('div', { 'class': 'button-row' }, [
E('button', {
- 'class': 'cbi-button cbi-button-neutral',
+ 'class': 'btn cbi-button cbi-button-neutral',
'click': function(ev) {
btns[1].classList.remove('spinning');
btns[1].disabled = false;
@@ -188,7 +174,7 @@ function iface_updown(up, id, ev, force) {
}, _('Cancel')),
' ',
E('button', {
- 'class': 'cbi-button cbi-button-negative important',
+ 'class': 'btn cbi-button cbi-button-negative important',
'click': function(ev) {
dsc.setAttribute('disconnect', '');
dom.content(dsc, E('em', _('Interface is shutting down...')));
@@ -264,39 +250,6 @@ function has_sourcefilter(proto) {
return false;
}
-var cbiRichListValue = form.ListValue.extend({
- renderWidget: function(section_id, option_index, cfgvalue) {
- var choices = this.transformChoices();
- var widget = new ui.Dropdown((cfgvalue != null) ? cfgvalue : this.default, choices, {
- id: this.cbid(section_id),
- sort: this.keylist,
- optional: true,
- select_placeholder: this.select_placeholder || this.placeholder,
- custom_placeholder: this.custom_placeholder || this.placeholder,
- validate: L.bind(this.validate, this, section_id),
- disabled: (this.readonly != null) ? this.readonly : this.map.readonly
- });
-
- return widget.render();
- },
-
- value: function(value, title, description) {
- if (description) {
- form.ListValue.prototype.value.call(this, value, E([], [
- E('span', { 'class': 'hide-open' }, [ title ]),
- E('div', { 'class': 'hide-close', 'style': 'min-width:25vw' }, [
- E('strong', [ title ]),
- E('br'),
- E('span', { 'style': 'white-space:normal' }, description)
- ])
- ]));
- }
- else {
- form.ListValue.prototype.value.call(this, value, title);
- }
- }
-});
-
return view.extend({
poll_status: function(map, networks) {
var resolveZone = null;
@@ -470,6 +423,7 @@ return view.extend({
},
render: function(data) {
+
if (this.interfaceBridgeWithIfnameSections().length)
return this.renderBridgeMigration();
else if (this.deviceWithIfnameSections().length || this.interfaceWithIfnameSections().length)
@@ -498,7 +452,8 @@ return view.extend({
s.load = function() {
return Promise.all([
network.getNetworks(),
- firewall.getZones()
+ firewall.getZones(),
+ uci.load('system')
]).then(L.bind(function(data) {
this.networks = data[0];
this.zones = data[1];
@@ -560,13 +515,9 @@ return view.extend({
};
s.addModalOptions = function(s) {
- var protoval = uci.get('network', s.section, 'proto'),
- protoclass = protoval ? network.getProtocol(protoval) : null,
+ var protoval = uci.get('network', s.section, 'proto') || 'none',
o, proto_select, proto_switch, type, stp, igmp, ss, so;
- if (!protoval)
- return;
-
return network.getNetwork(s.section).then(L.bind(function(ifc) {
var protocols = network.getProtocols();
@@ -589,6 +540,7 @@ return view.extend({
proto_select = s.taboption('general', form.ListValue, 'proto', _('Protocol'));
proto_select.modalonly = true;
+ proto_select.default = 'none';
proto_switch = s.taboption('general', form.Button, '_switch_proto');
proto_switch.modalonly = true;
@@ -657,7 +609,7 @@ return view.extend({
for (var i = 0; i < protocols.length; i++) {
proto_select.value(protocols[i].getProtocol(), protocols[i].getI18n());
- if (protocols[i].getProtocol() != uci.get('network', s.section, 'proto'))
+ if (protocols[i].getProtocol() != protoval)
proto_switch.depends('proto', protocols[i].getProtocol());
}
@@ -706,7 +658,7 @@ return view.extend({
ss.taboption('general', form.Flag, 'ignore', _('Ignore interface'), _('Disable DHCP for this interface.'));
if (protoval == 'static') {
- so = ss.taboption('general', form.Value, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
+ so = ss.taboption('general', form.Value, 'start', _('Start', 'DHCP IP range start address'), _('Lowest leased address as offset from the network address.'));
so.optional = true;
so.datatype = 'or(uinteger,ip4addr("nomask"))';
so.default = '100';
@@ -719,6 +671,17 @@ return view.extend({
so = ss.taboption('general', form.Value, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (2m
).'));
so.optional = true;
so.default = '12h';
+ so.validate = function (section_id, value) {
+ if (value === "infinite" || value === "deprecated") {
+ return true;
+ }
+
+ const regex = new RegExp("^[0-9]+[smhdw]?$", "i");
+ if (regex.test(value)) {
+ return true;
+ }
+ return _("Invalid DHCP lease time format. Use integer values optionally followed by s, m, h, d, or w.");
+ }
so = ss.taboption('advanced', form.Flag, 'dynamicdhcp', _('Dynamic DHCP'), _('Dynamically allocate DHCP addresses for clients. If disabled, only clients having static leases will be served.'));
so.default = so.enabled;
@@ -771,9 +734,9 @@ return view.extend({
/* 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
+ The intent is only to allow RA serving for interface protocols doing
some kind of static IP config over something resembling a layer 2
- ethernet device. */
+ Ethernet device. */
switch (protoval) {
case 'dhcp':
case 'dhcpv6':
@@ -823,8 +786,9 @@ return view.extend({
};
- so = ss.taboption('ipv6', cbiRichListValue, 'ra', _('RA-Service'),
+ so = ss.taboption('ipv6', form.RichListValue, 'ra', _('RA-Service'),
_('Configures the operation mode of the RA service on this interface.'));
+ so.optional = true;
so.value('', _('disabled'),
_('Do not send any RA messages on this interface.'));
so.value('server', _('server mode'),
@@ -834,8 +798,9 @@ return view.extend({
so.value('hybrid', _('hybrid mode'), ' ');
- so = ss.taboption('ipv6-ra', cbiRichListValue, 'ra_default', _('Default router'),
+ so = ss.taboption('ipv6-ra', form.RichListValue, 'ra_default', _('Default router'),
_('Configures the default router advertisement in RA messages.'));
+ so.optional = true;
so.value('', _('automatic'),
_('Announce this device as default router if a local IPv6 default route is present.'));
so.value('1', _('on available prefix'),
@@ -851,8 +816,9 @@ return view.extend({
so.depends('ra', 'server');
so.depends({ ra: 'hybrid', master: '0' });
- so = ss.taboption('ipv6-ra', cbiRichListValue, 'ra_flags', _('RA Flags'),
+ so = ss.taboption('ipv6-ra', form.RichListValue, 'ra_flags', _('RA Flags'),
_('Specifies the flags sent in RA messages, for example to instruct clients to request further information via stateful DHCPv6.'));
+ so.optional = true;
so.value('managed-config', _('managed config (M)'),
_('The Managed address configuration (M) flag indicates that IPv6 addresses are available via DHCPv6.'));
so.value('other-config', _('other config (O)'),
@@ -945,8 +911,9 @@ return view.extend({
};
- so = ss.taboption('ipv6', cbiRichListValue, 'dhcpv6', _('DHCPv6-Service'),
+ so = ss.taboption('ipv6', form.RichListValue, 'dhcpv6', _('DHCPv6-Service'),
_('Configures the operation mode of the DHCPv6 service on this interface.'));
+ so.optional = true;
so.value('', _('disabled'),
_('Do not offer DHCPv6 service on this interface.'));
so.value('server', _('server mode'),
@@ -984,9 +951,35 @@ return view.extend({
so.depends('dhcpv6', 'server');
so.depends({ dhcpv6: 'hybrid', master: '0' });
+ //This is a DHCPv6 specific odhcpd setting
+ so = ss.taboption('ipv6', form.DynamicList, 'ntp', _('NTP Servers'),
+ _('DHCPv6 option 56. %s.', 'DHCPv6 option 56. RFC5908 link').format('RFC5908').format('https://www.rfc-editor.org/rfc/rfc5908#section-4'));
+ so.datatype = 'host(0)';
+ for(var x of uci.get('system', 'ntp', 'server') || '') {
+ so.value(x);
+ }
+ var local_nets = this.networks.filter(function(n) { return n.getName() != 'loopback' });
+ if(local_nets) {
+ // If ntpd is set up, suggest our IP(v6) also
+ if(uci.get('system', 'ntp', 'enable_server')) {
+ local_nets.forEach(function(n){
+ n.getIPAddrs().forEach(function(i4) {
+ so.value(i4.split('/')[0]);
+ });
+ n.getIP6Addrs().forEach(function(i6) {
+ so.value(i6.split('/')[0]);
+ });
+ });
+ }
+ }
+ so.optional = true;
+ so.rmempty = true;
+ so.depends('dhcpv6', 'server');
+ so.depends({ dhcpv6: 'hybrid', master: '0' });
- so = ss.taboption('ipv6', cbiRichListValue, 'ndp', _('NDP-Proxy'),
+ so = ss.taboption('ipv6', form.RichListValue, 'ndp', _('NDP-Proxy'),
_('Configures the operation mode of the NDP proxy service on this interface.'));
+ so.optional = true;
so.value('', _('disabled'),
_('Do not proxy any NDP packets.'));
so.value('relay', _('relay mode'),
@@ -1041,10 +1034,11 @@ return view.extend({
o.datatype = 'uinteger';
o.placeholder = '0';
- o = nettools.replaceOption(s, 'advanced', form.Value, 'metric', _('Use gateway metric'));
+ o = nettools.replaceOption(s, 'advanced', form.Value, 'metric', _('Use gateway metric'),
+ _('Metric is an ordinal, where a gateway with 1 is chosen 1st, 2 is chosen 2nd, 3 is chosen 3rd, etc'));
o.datatype = 'uinteger';
o.placeholder = '0';
-
+
o = nettools.replaceOption(s,'advanced', form.ListValue, 'multipath', _('Multipath setting'), _('Only one interface must be set as Master.'));
o.value('on',_('Enabled'));
o.value('off',_('Disabled'));
@@ -1216,7 +1210,10 @@ return view.extend({
};
proto = s2.option(form.ListValue, 'proto', _('Protocol'));
- proto.validate = name.validate;
+ proto.onchange = function(ev, section_id, value) {
+ var elem = name.getUIElement(section_id);
+ elem.triggerValidation();
+ };
device = s2.option(widgets.DeviceSelect, 'device', _('Device'));
device.noaliases = false;
@@ -1471,6 +1468,9 @@ return view.extend({
case '8021ad':
return '8021ad';
+ case 'bonding':
+ return 'bonding';
+
case 'bridge':
return 'bridge';
@@ -1503,6 +1503,9 @@ return view.extend({
case '8021ad':
return _('VLAN (802.1ad)');
+ case 'bonding':
+ return _('Aggregation device');
+
case 'bridge':
return _('Bridge device');
@@ -1570,20 +1573,24 @@ return view.extend({
s.addremove = false;
s.anonymous = true;
- o = s.option(form.Value, 'ula_prefix', _('IPv6 ULA-Prefix'), _('Unique Local Address - in the range fc00::/7
. Typically only within the ‘local’ half fd00::/8
. ULA for IPv6 is analogous to IPv4 private network addressing. This prefix is randomly generated at first install.'));
+ o = s.option(form.Value, 'ula_prefix', _('IPv6 ULA-Prefix'),
+ _('Unique Local Address (%s) - prefix fd00::/8
(the L bit is always 1).').format('RFC4193').format('https://datatracker.ietf.org/doc/html/rfc4193#section-3') + ' ' +
+ _('ULA for IPv6 is analogous to IPv4 private network addressing.') + ' ' +
+ _('This prefix is randomly generated at first install.'));
o.datatype = 'cidr6';
o = s.option(form.ListValue, 'packet_steering', _('Packet Steering'), _('Enable packet steering across CPUs. May help or hinder network speed.'));
- o.value('', _('Disabled'));
+ o.value('0', _('Disabled'));
o.value('1',_('Enabled'));
o.value('2',_('Enabled (all CPUs)'));
+ o.default = '1';
o.optional = true;
- var steer_flow = uci.get('network', 'globals', 'steering_flows');
+ var steer_flow = uci.get('network', 'globals', 'steering_flows');
o = s.option(form.Value, 'steering_flows', _('Steering flows (RPS)'),
- _('Directs packet flows to specific CPUs where the local socket owner listens (the local service).') + ' ' +
- _('Note: this setting is for local services on the device only (not for forwarding).'));
+ _('Directs packet flows to specific CPUs where the local socket owner listens (the local service).') + ' ' +
+ _('Note: this setting is for local services on the device only (not for forwarding).'));
o.value('', _('Standard: none'));
o.value('128', _('Suggested: 128'));
o.value('256', _('256'));
diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js b/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
index 4004be219..312b53007 100644
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
+++ b/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
@@ -5,6 +5,7 @@
'require form';
'require network';
'require tools.widgets as widgets';
+'require tools.network as tn';
return view.extend({
load: function() {
@@ -25,7 +26,9 @@ return view.extend({
return e && e[0] > 0;
});
- m = new form.Map('network', _('Routing'), _('Routing defines over which interface and gateway a certain host or network can be reached.'));
+ m = new form.Map('network', _('Routing'), _('Routing defines over which interface and gateway a certain host or network can be reached.') +
+ '
' + _('Routes go in routing tables and define the specific path to reach destinations.') +
+ '
' + _('Rules determine which routing table to use, based on conditions like source address or interface.'));
m.tabbed = true;
for (var family = 4; family <= 6; family += 2) {
@@ -33,6 +36,7 @@ return view.extend({
s.anonymous = true;
s.addremove = true;
s.sortable = true;
+ s.cloneable = true;
s.nodescriptions = true;
s.tab('general', _('General Settings'));
@@ -41,7 +45,7 @@ return view.extend({
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.rmempty = true;
o = s.taboption('general', form.ListValue, 'type', _('Route type'), _('Specifies the route type to be created'));
o.modalonly = true;
@@ -78,19 +82,21 @@ return view.extend({
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'), _('Specifies the route metric to use'));
+ o = s.taboption('advanced', form.Value, 'metric', _('Metric'), _('Ordinal: routes with the lowest metric match first'));
o.datatype = 'uinteger';
o.placeholder = 0;
o.textvalue = function(section_id) {
return this.cfgvalue(section_id) || E('em', _('auto'));
};
- o = s.taboption('advanced', form.Value, 'mtu', _('MTU'), _('Defines a specific MTU for this route'));
+ o = s.taboption('advanced', form.Value, 'mtu', _('MTU'), _('Packets exceeding this value may be fragmented'));
o.modalonly = true;
o.datatype = 'and(uinteger,range(64,9000))';
o.placeholder = 1500;
- 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 = s.taboption('advanced', form.Value, 'table', _('Table'), _('Routing table into which to insert this rule.') + '
' +
+ _('A numeric table index, or symbol alias declared in %s. Special aliases local (255), main (254) and default (253) are also valid').format('/etc/iproute2/rt_tables
')
+ + '
' + _('Only interfaces using this table (via override) will use this route.'));
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]));
@@ -98,7 +104,8 @@ return view.extend({
return this.cfgvalue(section_id) || E('em', _('auto'));
};
- o = s.taboption('advanced', form.Value, 'source', _('Source'), _('Specifies the preferred source address when sending to destinations covered by the target'));
+ o = s.taboption('advanced', form.Value, 'source', _('Source'), _('Specifies the preferred source address when sending to destinations covered by the target')
+ + '
' + _('This is only used if no default route matches the destination gateway'));
o.modalonly = true;
o.datatype = (family == 6) ? 'ip6addr' : 'ip4addr';
for (var i = 0; i < netDevs.length; i++) {
@@ -122,12 +129,13 @@ return view.extend({
s.anonymous = true;
s.addremove = true;
s.sortable = true;
+ s.cloneable = true;
s.nodescriptions = true;
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
- o = s.taboption('general', form.Value, 'priority', _('Priority'), _('Specifies the ordering of the IP rules'));
+ o = s.taboption('general', form.Value, 'priority', _('Priority'), _('Execution order of this IP rule: lower numbers go first'));
o.datatype = 'uinteger';
o.placeholder = 30000;
o.textvalue = function(section_id) {
@@ -142,34 +150,42 @@ return view.extend({
o.value('blackhole');
o.value('throw');
- o = s.taboption('general', widgets.NetworkSelect, 'in', _('Incoming interface'), _('Specifies the incoming logical interface name'));
+ o = s.taboption('general', widgets.NetworkSelect, 'in', _('Incoming interface'), _('Match traffic from this interface'));
o.loopback = true;
o.nocreate = true;
- o = s.taboption('general', form.Value, 'src', _('Source'), _('Specifies the source subnet to match (CIDR notation)'));
+ o = s.taboption('general', form.Value, 'src', _('Source'), _('Match traffic from this source subnet (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'), _('Specifies the outgoing logical interface name'));
+ o = s.taboption('general', form.Value, 'ipproto', _('IP Protocol'), _('Match traffic IP protocol type'));
+ o.datatype = 'range(0,255)';
+ tn.protocols.forEach(function(p) {
+ o.value(p.i, p.d);
+ });
+
+ o = s.taboption('general', widgets.NetworkSelect, 'out', _('Outgoing interface'), _('Match traffic destined to this interface'));
o.loopback = true;
o.nocreate = true;
- o = s.taboption('general', form.Value, 'dest', _('Destination'), _('Specifies the destination subnet to match (CIDR notation)'));
+ o = s.taboption('general', form.Value, 'dest', _('Destination'), _('Match traffic destined to this subnet (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'), _('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 = s.taboption('advanced', form.Value, 'lookup', _('Table'), _('Routing table to use for traffic matching this rule.') + '
' +
+ _('A numeric table index, or symbol alias declared in %s. Special aliases local (255), main (254) and default (253) are also valid').format('/etc/iproute2/rt_tables
')
+ + '
' + _('Matched traffic re-targets to an interface using this table.'));
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'), _('The rule target is a jump to another rule specified by its priority value'));
+ o = s.taboption('advanced', form.Value, 'goto', _('Jump to rule'), _('Jumps to another rule specified by its priority value'));
o.modalonly = true;
o.datatype = 'uinteger';
o.placeholder = 80000;
@@ -189,7 +205,8 @@ return view.extend({
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 = 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')
+ + '
' + _('Prevents overly broad routes being considered. Setting 16 would consider /17, /24, /28 or more specific routes yet ignore /16, /8, /0 (default) routes'));
o.modalonly = true;
o.datatype = (family == 6) ? 'ip6prefix' : 'ip4prefix';
o.placeholder = (family == 6) ? 64 : 24;
diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js b/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
index 46f2a7d30..e76b2b711 100644
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
+++ b/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
@@ -80,7 +80,7 @@ function render_signal_badge(signalPercent, signalValue, noiseValue, wrap, mode)
}
if (noiseValue != null && noiseValue != 0) {
- value = '---/%d\x0a%s'.format(noiseValue, _('dBm'));
+ value = '---/%d\xa0%s'.format(noiseValue, _('dBm'));
title = '%s / %s: %d %s'.format(title, _('Noise'), noiseValue, _('dBm'));
}
else {
@@ -201,7 +201,9 @@ function format_wifirate(rate) {
mhz = rate.mhz, nss = rate.nss,
mcs = rate.mcs, sgi = rate.short_gi,
he = rate.he, he_gi = rate.he_gi,
- he_dcm = rate.he_dcm;
+ he_dcm = rate.he_dcm,
+ eht = rate?.eht ?? false, eht_gi = rate?.eht_gi ?? 0,
+ eht_dcm = rate?.eht_dcm ?? 0;
if (ht || vht) {
if (vht) s += ', VHT-MCS\xa0%d'.format(mcs);
@@ -217,6 +219,13 @@ function format_wifirate(rate) {
if (he_dcm) s += ', HE-DCM\xa0%d'.format(he_dcm);
}
+ if (eht) {
+ s += ', EHT-MCS\xa0%d'.format(mcs);
+ if (nss) s += ', EHT-NSS\xa0%d'.format(nss);
+ if (eht_gi) s += ', EHT-GI\xa0%d'.format(eht_gi);
+ if (eht_dcm) s += ', EHT-DCM\xa0%d'.format(eht_dcm);
+ }
+
return s;
}
@@ -298,7 +307,9 @@ function add_dependency_permutations(o, deps) {
o.depends(res[i]);
}
+// Define a class CBIWifiFrequencyValue that extends form.Value
var CBIWifiFrequencyValue = form.Value.extend({
+ // Declare an RPC method to get the frequency list for a given device
callFrequencyList: rpc.declare({
object: 'iwinfo',
method: 'freqlist',
@@ -306,15 +317,16 @@ var CBIWifiFrequencyValue = form.Value.extend({
expect: { results: [] }
}),
+ // Load method to fetch WiFi device details and frequency list
load: function(section_id) {
return Promise.all([
network.getWifiDevice(section_id),
this.callFrequencyList(section_id)
]).then(L.bind(function(data) {
this.channels = {
- '2g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
- '5g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
- '6g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
+ '2g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', { available: true } ] : [],
+ '5g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', { available: true } ] : [],
+ '6g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', { available: true } ] : [],
'60g': []
};
@@ -322,70 +334,97 @@ var CBIWifiFrequencyValue = form.Value.extend({
if (!data[1][i].band)
continue;
- var band = '%dg'.format(data[1][i].band);
+ var band = '%dg'.format(data[1][i].band),
+ available = true;
+
+
+ if (data[1][i].restricted && data[1][i].no_ir)
+ available = false;
this.channels[band].push(
data[1][i].channel,
'%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
- !data[1][i].restricted
+ {
+ available: available,
+ no_outdoor: data[1][i].no_outdoor
+ }
+
);
}
var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
.reduce(function(o, v) { o[v] = true; return o }, {});
+ // Define supported modes
this.modes = [
- '', 'Legacy', hwmodelist.a || hwmodelist.b || hwmodelist.g,
- 'n', 'N', hwmodelist.n,
- 'ac', 'AC', L.hasSystemFeature('hostapd', '11ac') && hwmodelist.ac,
- 'ax', 'AX', L.hasSystemFeature('hostapd', '11ax') && hwmodelist.ax
+ '', 'Legacy', { available: hwmodelist.a || hwmodelist.b || hwmodelist.g },
+ 'n', 'N', { available: hwmodelist.n },
+ 'ac', 'AC', { available: L.hasSystemFeature('hostapd', '11ac') && hwmodelist.ac },
+ 'ax', 'AX', { available: L.hasSystemFeature('hostapd', '11ax') && hwmodelist.ax },
+ 'be', 'BE', { available: L.hasSystemFeature('hostapd', '11be') && hwmodelist.be }
];
+ // Create a list of HT modes based on device capabilities
var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
.reduce(function(o, v) { o[v] = true; return o }, {});
this.htmodes = {
- '': [ '', '-', true ],
+ '': [ '', '-', { available: true } ],
'n': [
- 'HT20', '20 MHz', htmodelist.HT20,
- 'HT40', '40 MHz', htmodelist.HT40
+ 'HT20', '20 MHz', { available: htmodelist.HT20 },
+ 'HT40', '40 MHz', { available: htmodelist.HT40 }
],
'ac': [
- 'VHT20', '20 MHz', htmodelist.VHT20,
- 'VHT40', '40 MHz', htmodelist.VHT40,
- 'VHT80', '80 MHz', htmodelist.VHT80,
- 'VHT160', '160 MHz', htmodelist.VHT160
+ 'VHT20', '20 MHz', { available: htmodelist.VHT20 },
+ 'VHT40', '40 MHz', { available: htmodelist.VHT40 },
+ 'VHT80', '80 MHz', { available: htmodelist.VHT80 },
+ 'VHT160', '160 MHz', { available: htmodelist.VHT160 }
],
'ax': [
- 'HE20', '20 MHz', htmodelist.HE20,
- 'HE40', '40 MHz', htmodelist.HE40,
- 'HE80', '80 MHz', htmodelist.HE80,
- 'HE160', '160 MHz', htmodelist.HE160
+ 'HE20', '20 MHz', { available: htmodelist.HE20 },
+ 'HE40', '40 MHz', { available: htmodelist.HE40 },
+ 'HE80', '80 MHz', { available: htmodelist.HE80 },
+ 'HE160', '160 MHz', { available: htmodelist.HE160 }
+ ],
+ 'be': [
+ 'EHT20', '20 MHz', { available: htmodelist.EHT20 },
+ 'EHT40', '40 MHz', { available: htmodelist.EHT40 },
+ 'EHT80', '80 MHz', { available: htmodelist.EHT80 },
+ 'EHT160', '160 MHz', { available: htmodelist.EHT160 },
+ 'EHT320', '320 MHz', { available: htmodelist.EHT320 }
]
};
+ // Define available bands for widget selection based on channel availability
+ // AX and BE are available on 2/5/6G bands
this.bands = {
'': [
- '2g', '2.4 GHz', this.channels['2g'].length > 3,
- '5g', '5 GHz', this.channels['5g'].length > 3,
- '60g', '60 GHz', this.channels['60g'].length > 0
+ '2g', '2.4 GHz', { available: this.channels['2g'].length > 3 },
+ '5g', '5 GHz', { available: this.channels['5g'].length > 3 },
+ '60g', '60 GHz', { available: this.channels['60g'].length > 0 }
],
'n': [
- '2g', '2.4 GHz', this.channels['2g'].length > 3,
- '5g', '5 GHz', this.channels['5g'].length > 3
+ '2g', '2.4 GHz', { available: this.channels['2g'].length > 3 },
+ '5g', '5 GHz', { available: this.channels['5g'].length > 3 }
],
'ac': [
- '5g', '5 GHz', true
+ '5g', '5 GHz', { available: true }
],
'ax': [
- '2g', '2.4 GHz', this.channels['2g'].length > 3,
- '5g', '5 GHz', this.channels['5g'].length > 3,
- '6g', '6 GHz', this.channels['6g'].length > 3
+ '2g', '2.4 GHz', { available: this.channels['2g'].length > 3 },
+ '5g', '5 GHz', { available: this.channels['5g'].length > 3 },
+ '6g', '6 GHz', { available: this.channels['6g'].length > 3 }
+ ],
+ 'be': [
+ '2g', '2.4 GHz', { available: this.channels['2g'].length > 3 },
+ '5g', '5 GHz', { available: this.channels['5g'].length > 3 },
+ '6g', '6 GHz', { available: this.channels['6g'].length > 3 }
]
};
}, this));
},
+ // Set values in the select element
setValues: function(sel, vals) {
if (sel.vals)
sel.vals.selected = sel.selectedIndex;
@@ -394,7 +433,7 @@ var CBIWifiFrequencyValue = form.Value.extend({
sel.remove(0);
for (var i = 0; vals && i < vals.length; i += 3)
- if (vals[i+2])
+ if (vals[i+2] && vals[i+2].available)
sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
if (vals && !isNaN(vals.selected))
@@ -426,11 +465,31 @@ var CBIWifiFrequencyValue = form.Value.extend({
this.map.checkDepends();
},
+ checkWifiChannelRestriction: function(elem) {
+ var band = elem.querySelector('.band'),
+ chan = elem.querySelector('.channel'),
+ restricted_chan = elem.querySelector('.restricted_channel'),
+ channels = this.channels[band.value],
+ no_outdoor;
+
+ if (chan.selectedIndex < 0)
+ return;
+
+ no_outdoor = channels[(chan.selectedIndex*3)+2].no_outdoor;
+ if (no_outdoor)
+ restricted_chan.style.display = '';
+ else
+ restricted_chan.style.display = 'none';
+ },
+
toggleWifiChannel: function(elem) {
var band = elem.querySelector('.band');
var chan = elem.querySelector('.channel');
this.setValues(chan, this.channels[band.value]);
+
+ this.map.checkDepends();
+ this.checkWifiChannelRestriction(elem);
},
setInitialValues: function(section_id, elem) {
@@ -445,7 +504,10 @@ var CBIWifiFrequencyValue = form.Value.extend({
this.setValues(mode, this.modes);
- if (/HE20|HE40|HE80|HE160/.test(htval))
+ // Determine mode based on htmode value
+ if (/EHT20|EHT40|EHT80|EHT160|EHT320/.test(htval))
+ mode.value = 'be';
+ else if (/HE20|HE40|HE80|HE160/.test(htval))
mode.value = 'ax';
else if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
mode.value = 'ac';
@@ -475,6 +537,8 @@ var CBIWifiFrequencyValue = form.Value.extend({
bwdt.value = htval;
chan.value = chval || (chan.options[0] ? chan.options[0].value : 'auto');
+ this.checkWifiChannelRestriction(elem);
+
return elem;
},
@@ -482,6 +546,9 @@ var CBIWifiFrequencyValue = form.Value.extend({
var elem = E('div');
dom.content(elem, [
+ E('div', { 'class' : 'restricted_channel', 'style': 'display:none'}, [
+ E('div', {'class': 'cbi-button alert-message warning disabled'}, _('Indoor Only Channel Selected'))
+ ]),
E('label', { 'style': 'float:left; margin-right:3px' }, [
_('Mode'), E('br'),
E('select', {
@@ -505,7 +572,7 @@ var CBIWifiFrequencyValue = form.Value.extend({
E('select', {
'class': 'channel',
'style': 'width:auto',
- 'change': L.bind(this.map.checkDepends, this.map),
+ 'change': L.bind(this.toggleWifiChannel, this, elem),
'disabled': (this.disabled != null) ? this.disabled : this.map.readonly
})
]),
@@ -698,6 +765,25 @@ return view.extend({
])
];
+ var zones = data[4];
+ if (bss.vlan) {
+ var desc = bss.vlan.getI18n();
+ var vlan_network = bss.vlan.getNetwork();
+ var vlan_zone;
+
+ if (vlan_network && zones)
+ for (let zone of zones)
+ if (zone.getNetworks().includes(vlan_network))
+ vlan_zone = zone;
+
+ row[0].insertBefore(
+ E('div', {
+ 'class' : 'zonebadge',
+ 'title' : desc,
+ 'style' : firewall.getZoneColorStyle(vlan_zone)
+ }, [ desc ]), row[0].firstChild);
+ }
+
if (bss.network.isClientDisconnectSupported()) {
if (table.firstElementChild.childNodes.length < 6)
table.firstElementChild.appendChild(E('th', { 'class': 'th cbi-section-actions'}));
@@ -736,7 +822,8 @@ return view.extend({
return Promise.all([
uci.changes(),
uci.load('wireless'),
- uci.load('system')
+ uci.load('system'),
+ firewall.getZones(),
]);
},
@@ -756,11 +843,11 @@ return view.extend({
params: [ 'config', 'section', 'name' ]
}),
- render: function() {
+ render: function(data) {
if (this.checkAnonymousSections())
return this.renderMigration();
else
- return this.renderOverview();
+ return this.renderOverview(data[3]);
},
handleMigration: function(ev) {
@@ -795,7 +882,7 @@ return view.extend({
]);
},
- renderOverview: function() {
+ renderOverview: function(zones) {
var m, s, o;
m = new form.Map('wireless');
@@ -931,7 +1018,7 @@ return view.extend({
o.inputtitle = isDisabled ? _('Enable') : _('Disable');
o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map);
- o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '
' + _('Operating frequency'));
+ o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '
' + _('Operating frequency'), _('Some channels may be restricted to Indoor Only use by your Regulatory Domain. Make sure to follow this advice if a channel is reported as such.'));
o.ucisection = s.section;
if (hwtype == 'mac80211') {
@@ -941,7 +1028,7 @@ return view.extend({
o = ss.taboption('general', CBIWifiTxPowerValue, 'txpower', _('Maximum transmit power'), _('Specifies the maximum transmit power the wireless radio may use. Depending on regulatory requirements and wireless usage, the actual transmit power may be reduced by the driver.'));
o.wifiNetwork = radioNet;
- o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
+ o = ss.taboption('general', CBIWifiCountryValue, 'country', _('Country Code'));
o.wifiNetwork = radioNet;
o = ss.taboption('advanced', form.ListValue, 'cell_density', _('Coverage cell density'), _('Configures data rates based on the coverage cell density. Normal configures basic rates to 6, 12, 24 Mbps if legacy 802.11b rates are not used else to 5.5, 11 Mbps. High configures basic rates to 12, 24 Mbps if legacy 802.11b rates are not used else to the 11 Mbps rate. Very High configures 24 Mbps as the basic rate. Supported rates lower than the minimum basic rate are not offered.'));
@@ -950,7 +1037,7 @@ return view.extend({
o.value('2', _('High'));
o.value('3', _('Very High'));
- o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
+ o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters. Set only for distances above one kilometer; otherwise it is harmful.'));
o.datatype = 'or(range(0,114750),"auto")';
o.placeholder = 'auto';
@@ -1150,6 +1237,8 @@ return view.extend({
o.depends('mode', 'ap');
o.depends('mode', 'ap-wds');
+ o = ss.taboption('advanced', form.Flag, 'bridge_isolate', _('Isolate Bridge Port'), _('Prevents communication only with targets on isolated bridge ports (while allowing it with targets on non-isolated ones). This also prevents client-to-client communication on the same interface when the WiFi device is in AP mode.'));
+
o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
o.optional = true;
o.datatype = 'netdevname';
@@ -1217,10 +1306,10 @@ return view.extend({
var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
- if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed')
+ if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed' || value == 'wpa3-192')
uci.unset('wireless', section_id, 'key');
- if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
+ if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'ccmp256' || c == 'gcmp' || c == 'gcmp256' || c == 'tkip+ccmp'))
e += '+' + c;
uci.set('wireless', section_id, 'encryption', e);
@@ -1231,6 +1320,7 @@ return view.extend({
o.depends('encryption', 'wpa2');
o.depends('encryption', 'wpa3');
o.depends('encryption', 'wpa3-mixed');
+ o.depends('encryption', 'wpa3-192');
o.depends('encryption', 'psk');
o.depends('encryption', 'psk2');
o.depends('encryption', 'wpa-mixed');
@@ -1306,6 +1396,7 @@ return view.extend({
if (has_ap_eap192 || has_sta_eap192) {
crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
+ crypto_modes.push(['wpa3-192', 'WPA3-EAP 192-bit Mode', 36]);
}
crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
@@ -1329,6 +1420,7 @@ return view.extend({
'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
+ 'wpa3-192': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
'owe': has_ap_owe || _('Requires hostapd with OWE support')
},
'sta': {
@@ -1343,6 +1435,7 @@ return view.extend({
'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
+ 'wpa3-192': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
},
'adhoc': {
@@ -1404,41 +1497,76 @@ return view.extend({
}
+ o = ss.taboption('encryption', form.Flag, 'ppsk', _('Enable Private PSK (PPSK)'), _('Private Pre-Shared Key (PPSK) allows the use of different Pre-Shared Key for each STA MAC address. Private MAC PSKs are stored on the RADIUS server.'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'] });
+
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'], ppsk: ['1'] });
o.rmempty = true;
o.datatype = 'host(0)';
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'], ppsk: ['1'] });
o.rmempty = true;
o.datatype = 'port';
o.placeholder = '1812';
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'], ppsk: ['1'] });
o.rmempty = true;
o.password = true;
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o.rmempty = true;
o.datatype = 'host(0)';
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o.rmempty = true;
o.datatype = 'port';
o.placeholder = '1813';
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o.rmempty = true;
o.password = true;
/* extra RADIUS settings start */
+ var attr_validate = function(section_id, value) {
+ if (!value)
+ return true;
+
+ if (!/^[0-9]+(:s:.+|:d:[0-9]+|:x:([0-9a-zA-Z]{2})+)?$/.test(value) )
+ return _('Must be in %s format.').format('[:]');
+
+ return true;
+ };
+
+ var req_attr_syntax = _('Format:') + '<attr_id>[:<syntax:value>]
' + '
' +
+ 'syntax: s = %s; '.format(_('string (UTF-8)')) + 'd = %s; '.format(_('integer')) + 'x = %s
'.format(_('octet string'))
+
+ /* https://w1.fi/cgit/hostap/commit/?id=af35e7af7f8bb1ca9f0905b4074fb56a264aa12b */
+ o = ss.taboption('encryption', form.DynamicList, 'radius_auth_req_attr', _('RADIUS Access-Request attributes'),
+ _('Attributes to add/replace in each request.') + '
' + req_attr_syntax );
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
+ o.rmempty = true;
+ o.validate = attr_validate;
+ o.placeholder = '126:s:Operator';
+
+ o = ss.taboption('encryption', form.DynamicList, 'radius_acct_req_attr', _('RADIUS Accounting-Request attributes'),
+ _('Attributes to add/replace in each request.') + '
' + req_attr_syntax );
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
+ o.rmempty = true;
+ o.validate = attr_validate;
+ o.placeholder = '77:x:74657374696e67';
+
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'], ppsk: ['1'] });
o.value('0', _('Disabled'));
o.value('1', _('Optional'));
o.value('2', _('Required'));
@@ -1447,14 +1575,17 @@ return view.extend({
}
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'], ppsk: ['1'] });
//hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off
o = ss.taboption('encryption', form.Flag, 'vlan_naming', _('RADIUS VLAN Naming'), _('Off: vlanXXX
, e.g., vlan1
. On: vlan_tagged_interface.XXX
, e.g. eth0.1
.'));
- add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'], ppsk: ['1'] });
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'], ppsk: ['1'] });
o.size = 1;
o.rmempty = true;
o.multiple = false;
@@ -1463,23 +1594,25 @@ return view.extend({
o.noinactive = true;
o = ss.taboption('encryption', form.Value, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. br-vlan
or brvlan
.'));
- add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'], ppsk: ['1'] });
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o.rmempty = true;
o.datatype = 'host(0)';
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o.rmempty = true;
o.datatype = 'port';
o.placeholder = '3799';
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o.rmempty = true;
o.password = true;
@@ -1489,10 +1622,8 @@ return view.extend({
o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
- o.depends('encryption', 'psk');
- o.depends('encryption', 'psk2');
- o.depends('encryption', 'psk+psk2');
- o.depends('encryption', 'psk-mixed');
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'], ppsk: ['0'] });
+ add_dependency_permutations(o, { mode: ['sta', 'adhoc', 'mesh', 'sta-wds'], encryption: ['psk', 'psk2', 'psk+psk2', 'psk-mixed'] });
o.depends('encryption', 'sae');
o.depends('encryption', 'sae-mixed');
o.datatype = 'wpakey';
@@ -1551,13 +1682,13 @@ return view.extend({
var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa2', 'wpa3', 'wpa3-mixed', , 'wpa3-192'] });
if (has_80211r)
- add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
o.rmempty = true;
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'] });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o.depends({ ieee80211r: '1' });
o.rmempty = true;
@@ -1580,7 +1711,7 @@ return view.extend({
o.rmempty = true;
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' });
+ add_dependency_permutations(o, { ieee80211r: ['1'], mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed'] });
o.default = o.enabled;
o.rmempty = false;
@@ -1612,8 +1743,8 @@ return view.extend({
// 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.'));
+ /* 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');
@@ -1666,13 +1797,13 @@ return view.extend({
o.value('ttls', 'TTLS');
o.value('peap', 'PEAP');
o.value('fast', 'FAST');
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o = ss.taboption('encryption', form.Flag, 'ca_cert_usesystem', _('Use system certificates'), _("Validate server certificate using built-in system CA bundle,
requires the \"ca-bundle\" package"));
o.enabled = '1';
o.disabled = '0';
o.default = o.disabled;
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o.validate = function(section_id, value) {
if (value == '1' && !L.hasSystemFeature('cabundle')) {
return _("This option cannot be used because the ca-bundle package is not installed.");
@@ -1681,28 +1812,28 @@ return view.extend({
};
o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem: ['0'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], ca_cert_usesystem: ['0'] });
o = ss.taboption('encryption', form.Value, 'subject_match', _('Certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com
See `logread -f` during handshake for actual values"));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o = ss.taboption('encryption', form.DynamicList, 'altsubject_match', _('Certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values
(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com"));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o = ss.taboption('encryption', form.DynamicList, 'domain_match', _('Certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (exact match)"));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match', _('Certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (suffix match)"));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['tls'] });
o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['tls'] });
o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['tls'] });
o.password = true;
o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
@@ -1714,7 +1845,7 @@ return view.extend({
o.value('EAP-MD5', 'EAP-MD5');
o.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
o.value('EAP-TLS', 'EAP-TLS');
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['fast', 'peap', 'ttls'] });
o.validate = function(section_id, value) {
var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
@@ -1730,7 +1861,7 @@ return view.extend({
o.enabled = '1';
o.disabled = '0';
o.default = o.disabled;
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
o.validate = function(section_id, value) {
if (value == '1' && !L.hasSystemFeature('cabundle')) {
return _("This option cannot be used because the ca-bundle package is not installed.");
@@ -1739,38 +1870,38 @@ return view.extend({
};
o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'], ca_cert2_usesystem: ['0'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'], ca_cert2_usesystem: ['0'] });
o = ss.taboption('encryption', form.Value, 'subject_match2', _('Inner certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com
See `logread -f` during handshake for actual values"));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
o = ss.taboption('encryption', form.DynamicList, 'altsubject_match2', _('Inner certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values
(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com"));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
o = ss.taboption('encryption', form.DynamicList, 'domain_match2', _('Inner certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (exact match)"));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match2', _('Inner certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (suffix match)"));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
o.password = true;
o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
o = ss.taboption('encryption', form.Value, 'password', _('Password'));
- add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['fast', 'peap', 'ttls'] });
o.password = true;
@@ -1809,27 +1940,24 @@ return view.extend({
o.placeholder = '201';
o.rmempty = true;
- var version_omr = uci.get('openmptcprouter', 'settings', 'version') || '';
- if (!version_omr.includes('5.4')) {
- if (L.hasSystemFeature('hostapd', 'ocv') || L.hasSystemFeature('wpasupplicant', 'ocv')) {
- o = ss.taboption('encryption', form.ListValue, 'ocv', _('Operating Channel Validation'), _("Note: Workaround mode allows a STA that claims OCV capability to connect even if the STA doesn't send OCI or negotiate PMF."));
- o.value('0', _('Disabled'));
- o.value('1', _('Enabled'));
- o.value('2', _('Enabled (workaround mode)'));
- o.default = '0';
- o.depends('ieee80211w', '1');
- o.depends('ieee80211w', '2');
+ if (L.hasSystemFeature('hostapd', 'ocv') || L.hasSystemFeature('wpasupplicant', 'ocv')) {
+ o = ss.taboption('encryption', form.ListValue, 'ocv', _('Operating Channel Validation'), _("Note: Workaround mode allows a STA that claims OCV capability to connect even if the STA doesn't send OCI or negotiate PMF."));
+ o.value('0', _('Disabled'));
+ o.value('1', _('Enabled'));
+ o.value('2', _('Enabled (workaround mode)'));
+ o.default = '0';
+ o.depends('ieee80211w', '1');
+ o.depends('ieee80211w', '2');
- o.validate = function(section_id, value) {
- var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
- modeval = modeopt.formvalue(section_id);
+ o.validate = function(section_id, value) {
+ var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
+ modeval = modeopt.formvalue(section_id);
- if ((value == '2') && ((modeval == 'sta') || (modeval == 'sta-wds'))) {
- return _('Workaround mode can only be used when acting as an access point.');
- }
-
- return true;
+ if ((value == '2') && ((modeval == 'sta') || (modeval == 'sta-wds'))) {
+ return _('Workaround mode can only be used when acting as an access point.');
}
+
+ return true;
}
}
@@ -2029,6 +2157,7 @@ return view.extend({
}
var htmodes = radioDev.getHTModes();
+
if (bss.vht_operation && htmodes && htmodes.indexOf('VHT20') !== -1) {
for (var w = bss.vht_operation.channel_width; w >= 20; w /= 2) {
if (htmodes.indexOf('VHT'+w) !== -1) {
@@ -2044,6 +2173,7 @@ return view.extend({
else {
uci.remove('wireless', radioDev.getName(), 'htmode');
}
+
uci.set('wireless', radioDev.getName(), 'channel', bss.channel);
section_id = next_free_sid(wifi_sections.length);
@@ -2145,7 +2275,9 @@ return view.extend({
replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
- name = s2.option(form.Value, 'name', _('Name of the new network'), _('The allowed characters are: A-Z
, a-z
, 0-9
and _
'));
+ name = s2.option(form.Value, 'name', _('Name of the new network'),
+ _('Name for OpenWrt network configuration. (No relation to wireless network name/SSID)') + '
' +
+ _('The allowed characters are: A-Z
, a-z
, 0-9
and _
'));
name.datatype = 'uciname';
name.default = 'wwan';
name.rmempty = false;
@@ -2292,6 +2424,10 @@ return view.extend({
return hosts_radios_wifis;
});
}, network))
+ .then(L.bind(function(zones, data) {
+ data.push(zones);
+ return data;
+ }, network, zones))
.then(L.bind(this.poll_status, this, nodes));
}, this), 5);
diff --git a/omr-tracker/files/bin/omr-tracker-ss b/omr-tracker/files/bin/omr-tracker-ss
index 298ed45c6..13a90ef22 100755
--- a/omr-tracker/files/bin/omr-tracker-ss
+++ b/omr-tracker/files/bin/omr-tracker-ss
@@ -159,7 +159,8 @@ while true; do
last=$((last + 1 ))
[ -z "$nocontact" ] && nocontact="$host" || nocontact="$nocontact, $host"
[ "${last}" -ge "${retry}" ] && {
- if [ "$OMR_TRACKER_PREV_STATUS" != "ERROR" ] && { [ -n "$($IPTABLES -w -t nat -L -n 2>/dev/null | grep ssr)" ] || [ -n "$(nft list ruleset 2>/dev/null | grep ss_r)" ] || [ -n "$(nft list ruleset 2>/dev/null | grep ssr_r)" ]; }; then
+ #if [ "$OMR_TRACKER_PREV_STATUS" != "ERROR" ] && { [ -n "$($IPTABLES -w -t nat -L -n 2>/dev/null | grep ssr)" ] || [ -n "$(nft list ruleset 2>/dev/null | grep ss_r)" ] || [ -n "$(nft list ruleset 2>/dev/null | grep ssr_r)" ]; }; then
+ if [ -n "$($IPTABLES -w -t nat -L -n 2>/dev/null | grep ssr)" ] || [ -n "$(nft list ruleset 2>/dev/null | grep ss_r)" ] || [ -n "$(nft list ruleset 2>/dev/null | grep ssr_r)" ]; then
_log "Shadowsocks $type ${server} is down (can't contact via http ${nocontact})"
OMR_TRACKER_STATUS_MSG="Shadowsocks $type ${server} is down (can't contact via http ${nocontact})"
uci -q set openmptcprouter.omr.ss_${server}="down"
diff --git a/openmptcprouter/files/etc/init.d/openmptcprouter-vps b/openmptcprouter/files/etc/init.d/openmptcprouter-vps
index d63ee38cb..a987b1ce3 100755
--- a/openmptcprouter/files/etc/init.d/openmptcprouter-vps
+++ b/openmptcprouter/files/etc/init.d/openmptcprouter-vps
@@ -42,8 +42,8 @@ _login() {
#[ -z "$server" ] && server="$(uci -q get openmptcprouter.${servername}.ip)"
if [ -z "$token" ]; then
login_on_server() {
- server=$1
[ -n "$token" ] && return
+ server=$1
#auth=`curl --max-time 10 -s -k -H "Content-Type: application/json" -X POST -d '{"username":"'$username'","password":"'$password'"}' https://$server:$serverport/login`
#resolve="$(resolveip -t 5 $server)"
valid_ip6=$(valid_subnet6 $server)
@@ -83,7 +83,7 @@ _get_json() {
route=$1
[ -z "$token" ] && _login
[ -n "$token" ] && {
- resolve="$(resolveip -t 5 $server)"
+ #resolve="$(resolveip -t 5 $server)"
valid_ip6=$(valid_subnet6 $server)
if [ "$valid_ip6" = "ok" ]; then
result=`curl --max-time 10 -s -k -H "accept: application/json" -H "Authorization: Bearer $token" https://[$server]:$serverport/$route`
@@ -115,7 +115,7 @@ _set_json() {
settings="$2"
[ -z "$token" ] && _login
[ -n "$token" ] && {
- resolve="$(resolveip -t 5 $server)"
+ #resolve="$(resolveip -t 5 $server)"
valid_ip6=$(valid_subnet6 $server)
if [ "$valid_ip6" = "ok" ]; then
result=`curl --max-time 10 -s -k -H "Authorization: Bearer $token" -H "Content-Type: application/json" -X POST -d "$settings" https://[$server]:$serverport/$route`