diff --git a/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua b/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua
index 8d07c1d95..ec876228c 100755
--- a/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua
+++ b/luci-app-openmptcprouter/luasrc/controller/openmptcprouter.lua
@@ -358,6 +358,7 @@ function wizard_add()
uci_device = uci_device_from_interface(intf)
if uci_device == "" then
uci_device = intf .. "_dev"
+ ucic:set("network",uci_device,"device")
end
ucic:set("network",uci_device,"ttl",ttl)
diff --git a/luci-app-openmptcprouter/luasrc/view/openmptcprouter/wizard.htm b/luci-app-openmptcprouter/luasrc/view/openmptcprouter/wizard.htm
index 472c09540..15e7b0f00 100755
--- a/luci-app-openmptcprouter/luasrc/view/openmptcprouter/wizard.htm
+++ b/luci-app-openmptcprouter/luasrc/view/openmptcprouter/wizard.htm
@@ -573,6 +573,7 @@
for _, iface in ipairs(net:get_networks()) do
local ifname = iface:name()
local firewall_wan = luci.util.trim(luci.sys.exec("uci -q get firewall.zone_wan.network | tr ' ' '\n' | grep \'^" .. ifname .. "$\'"))
+ local ttl = uci:get("network",ifname .. "_dev","ttl")
if firewall_wan ~= "" then
-- local multipath = uci:get("network",ifname,"multipath")
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 6f16713fe..dfe9796c4 100755
--- a/luci-mod-network/htdocs/luci-static/resources/tools/network.js
+++ b/luci-mod-network/htdocs/luci-static/resources/tools/network.js
@@ -1,4 +1,5 @@
'use strict';
+'require fs';
'require ui';
'require dom';
'require uci';
@@ -149,25 +150,77 @@ function updatePlaceholders(opt, section_id) {
}
}
+var cbiFlagTristate = form.ListValue.extend({
+ __init__: function(/* ... */) {
+ this.super('__init__', arguments);
+ this.keylist = [ '', '0!', '1!' ];
+ this.vallist = [ _('automatic'), _('disabled'), _('enabled') ];
+ },
+
+ load: function(section_id) {
+ var invert = false, sysfs = this.sysfs;
+
+ if (sysfs) {
+ if (sysfs.charAt(0) == '!') {
+ invert = true;
+ sysfs = sysfs.substring(1);
+ }
+
+ return L.resolveDefault(fs.read(sysfs), '').then(L.bind(function(res) {
+ res = (res || '').trim();
+
+ if (res == '0')
+ this.sysfs_default = invert;
+ else if (res == '1')
+ this.sysfs_default = !invert;
+
+ return this.super('load', [section_id]);
+ }, this));
+ }
+
+ return this.super('load', [section_id]);
+ },
+
+ write: function(section_id, formvalue) {
+ if (formvalue == '1!')
+ return this.super('write', [section_id, '1']);
+ else if (formvalue == '0!')
+ return this.super('write', [section_id, '0']);
+ else
+ return this.super('remove', [section_id]);
+ },
+
+ renderWidget: function(section_id, option_index, cfgvalue) {
+ var sysdef = this.sysfs_default;
+
+ if (this.sysfs_default !== null) {
+ this.keylist[0] = sysdef ? '1' : '0';
+ this.vallist[0] = sysdef ? _('automatic (enabled)') : _('automatic (disabled)');
+ }
+
+ return this.super('renderWidget', [section_id, option_index, cfgvalue]);
+ }
+});
+
var cbiTagValue = form.Value.extend({
renderWidget: function(section_id, option_index, cfgvalue) {
var widget = new ui.Dropdown(cfgvalue || ['-'], {
'-': E([], [
E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '—' ]),
- E('span', { 'class': 'hide-close' }, [ _('Do not participate', 'VLAN port state') ])
+ E('span', { 'class': 'hide-close' }, [ _('Not Member', 'VLAN port state') ])
]),
'u': E([], [
- E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'u' ]),
- E('span', { 'class': 'hide-close' }, [ _('Egress untagged', 'VLAN port state') ])
+ E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'U' ]),
+ E('span', { 'class': 'hide-close' }, [ _('Untagged', 'VLAN port state') ])
]),
't': E([], [
- E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 't' ]),
- E('span', { 'class': 'hide-close' }, [ _('Egress tagged', 'VLAN port state') ])
+ E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'T' ]),
+ E('span', { 'class': 'hide-close' }, [ _('Tagged', 'VLAN port state') ])
]),
'*': E([], [
E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '*' ]),
- E('span', { 'class': 'hide-close' }, [ _('Primary VLAN ID', 'VLAN port state') ])
+ E('span', { 'class': 'hide-close' }, [ _('Is Primary VLAN', 'VLAN port state') ])
])
}, {
id: this.cbid(section_id),
@@ -274,7 +327,7 @@ var cbiTagValue = form.Value.extend({
var t = /t/.test(s[1] || '') ? 't' : 'u';
- return /\*/.test(s[1] || '') ? [t, '*'] : [t];
+ return /\x2a/.test(s[1] || '') ? [t, '*'] : [t];
}
return ['-'];
@@ -304,7 +357,7 @@ var cbiTagValue = form.Value.extend({
}
}
- uci.set('network', section_id, 'ports', ports);
+ uci.set('network', section_id, 'ports', ports.length ? ports : null);
},
remove: function() {}
@@ -331,6 +384,7 @@ return baseclass.extend({
addDeviceOptions: function(s, dev, isNew) {
var parent_dev = dev ? dev.getParent() : null,
+ devname = dev ? dev.getName() : null,
o, ss;
s.tab('devgeneral', _('General device options'));
@@ -421,7 +475,7 @@ return baseclass.extend({
vid = this.section.formvalue(section_id, 'vid'),
name = this.section.getUIElement(section_id, 'name_complex');
- if (base && vid && name && !name.isChanged()) {
+ if (base && vid && name && !name.isChanged() && isNew) {
name.setValue('%s.%d'.format(base, vid));
name.triggerValidation();
}
@@ -659,8 +713,8 @@ return baseclass.extend({
o.value('', _('disabled'));
o.value('loose', _('Loose filtering'));
o.value('strict', _('Strict filtering'));
- o.cfgvalue = function(section_id) {
- var val = form.ListValue.prototype.cfgvalue.apply(this, [section_id]);
+ o.cfgvalue = function(/* ... */) {
+ var val = form.ListValue.prototype.cfgvalue.apply(this, arguments);
switch (val || '') {
case 'loose':
@@ -676,11 +730,17 @@ return baseclass.extend({
}
};
- o = this.replaceOption(s, 'devadvanced', form.Flag, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses'));
- o.default = o.disabled;
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses'));
+ o.sysfs = '/proc/sys/net/ipv4/conf/%s/accept_local'.format(devname || 'default');
- o = this.replaceOption(s, 'devadvanced', form.Flag, 'sendredirects', _('Send ICMP redirects'));
- o.default = o.enabled;
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'sendredirects', _('Send ICMP redirects'));
+ o.sysfs = '/proc/sys/net/ipv4/conf/%s/send_redirects'.format(devname || 'default');
+
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'arp_accept ', _('Honor gratuitous ARP'), _('When enabled, new ARP table entries are added from received gratuitous APR requests or replies, otherwise only preexisting table entries are updated, but no new hosts are learned.'));
+ o.sysfs = '/proc/sys/net/ipv4/conf/%s/arp_accept'.format(devname || 'default');
+
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'drop_gratuitous_arp', _('Drop gratuitous ARP'), _('Drop all gratuitous ARP frames, for example if there’s a known good ARP proxy on the network and such frames need not be used or in the case of 802.11, must not be used to prevent attacks.'));
+ o.sysfs = '/proc/sys/net/ipv4/conf/%s/drop_gratuitous_arp'.format(devname || 'default');
o = this.replaceOption(s, 'devadvanced', form.Value, 'neighreachabletime', _('Neighbour cache validity'), _('Time in milliseconds'));
o.placeholder = '30000';
@@ -698,59 +758,77 @@ return baseclass.extend({
o.placeholder = '65';
o.datatype = 'uinteger';
- o = this.replaceOption(s, 'devgeneral', form.Flag, 'ipv6', _('Enable IPv6'));
+ o = this.replaceOption(s, 'devgeneral', cbiFlagTristate, 'ipv6', _('Enable IPv6'));
+ o.sysfs = '!/proc/sys/net/ipv6/conf/%s/disable_ipv6'.format(devname || 'default');
o.migrate = false;
- o.default = o.enabled;
+ //o.default = o.enabled;
+
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'ip6segmentrouting', _('Enable IPv6 segment routing'));
+ o.sysfs = '/proc/sys/net/ipv6/conf/%s/seg6_enabled'.format(devname || 'default');
+ o.depends('ipv6', /1/);
+
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'drop_unsolicited_na', _('Drop unsolicited NA'), _('Drop all unsolicited neighbor advertisements, for example if there’s a known good NA proxy on the network and such frames need not be used or in the case of 802.11, must not be used to prevent attacks.'));
+ o.sysfs = '/proc/sys/net/ipv6/conf/%s/drop_unsolicited_na'.format(devname || 'default');
+ o.depends('ipv6', /1/);
o = this.replaceOption(s, 'devgeneral', form.Value, 'mtu6', _('IPv6 MTU'));
o.datatype = 'max(9200)';
- o.depends('ipv6', '1');
+ o.depends('ipv6', /1/);
o = this.replaceOption(s, 'devgeneral', form.Value, 'dadtransmits', _('DAD transmits'), _('Amount of Duplicate Address Detection probes to send'));
o.placeholder = '1';
o.datatype = 'uinteger';
- o.depends('ipv6', '1');
+ o.depends('ipv6', /1/);
- o = this.replaceOption(s, 'devadvanced', form.Flag, 'multicast', _('Enable multicast support'));
- o.default = o.enabled;
+ o = this.replaceOption(s, 'devadvanced', cbiFlagTristate, 'multicast', _('Enable multicast support'));
+ o.sysfs_default = (dev && dev.dev && dev.dev.flags) ? dev.dev.flags.multicast : null;
o = this.replaceOption(s, 'devadvanced', form.ListValue, 'igmpversion', _('Force IGMP version'));
o.value('', _('No enforcement'));
o.value('1', _('Enforce IGMPv1'));
o.value('2', _('Enforce IGMPv2'));
o.value('3', _('Enforce IGMPv3'));
- o.depends('multicast', '1');
+ o.depends('multicast', /1/);
o = this.replaceOption(s, 'devadvanced', form.ListValue, 'mldversion', _('Force MLD version'));
o.value('', _('No enforcement'));
o.value('1', _('Enforce MLD version 1'));
o.value('2', _('Enforce MLD version 2'));
- o.depends('multicast', '1');
+ o.depends('multicast', /1/);
if (isBridgePort(dev)) {
o = this.replaceOption(s, 'brport', form.Flag, 'learning', _('Enable MAC address learning'));
- o.default = o.enabled;
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'learning', _('Enable MAC address learning'));
+ o.sysfs = '/sys/class/net/%s/brport/learning'.format(devname || 'default');
- o = this.replaceOption(s, 'brport', form.Flag, 'unicast_flood', _('Enable unicast flooding'));
- o.default = o.enabled;
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'unicast_flood', _('Enable unicast flooding'));
+ o.sysfs = '/sys/class/net/%s/brport/unicast_flood'.format(devname || 'default');
- o = this.replaceOption(s, 'brport', form.Flag, 'isolated', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled'));
- o.default = o.disabled;
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'isolate', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled'));
+ o.sysfs = '/sys/class/net/%s/brport/isolated'.format(devname || 'default');
o = this.replaceOption(s, 'brport', form.ListValue, 'multicast_router', _('Multicast routing'));
o.value('', _('Never'));
o.value('1', _('Learn'));
o.value('2', _('Always'));
- o.depends('multicast', '1');
+ o.depends('multicast', /1/);
- o = this.replaceOption(s, 'brport', form.Flag, 'multicast_to_unicast', _('Multicast to unicast'), _('Forward multicast packets as unicast packets on this device.'));
- o.default = o.disabled;
- o.depends('multicast', '1');
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'multicast_to_unicast', _('Multicast to unicast'), _('Forward multicast packets as unicast packets on this device.'));
+ o.sysfs = '/sys/class/net/%s/brport/multicast_to_unicast'.format(devname || 'default');
+ o.depends('multicast', /1/);
- o = this.replaceOption(s, 'brport', form.Flag, 'multicast_fast_leave', _('Enable multicast fast leave'));
- o.default = o.disabled;
- o.depends('multicast', '1');
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'multicast_fast_leave', _('Enable multicast fast leave'));
+ o.sysfs = '/sys/class/net/%s/brport/multicast_fast_leave'.format(devname || 'default');
+ o.depends('multicast', /1/);
+
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'drop_v4_unicast_in_l2_multicast', _('Drop nested IPv4 unicast'), _('Drop layer 2 multicast frames containing IPv4 unicast packets.'));
+ o.sysfs = '/proc/sys/net/ipv4/conf/%s/drop_unicast_in_l2_multicast'.format(devname || 'default');
+ o.depends('multicast', /1/);
+
+ o = this.replaceOption(s, 'brport', cbiFlagTristate, 'drop_v6_unicast_in_l2_multicast', _('Drop nested IPv6 unicast'), _('Drop layer 2 multicast frames containing IPv6 unicast packets.'));
+ o.sysfs = '/proc/sys/net/ipv6/conf/%s/drop_unicast_in_l2_multicast'.format(devname || 'default');
+ o.depends('multicast', /1/);
}
o = this.replaceOption(s, 'bridgevlan', form.Flag, 'vlan_filtering', _('Enable VLAN filtering'));
@@ -816,6 +894,8 @@ return baseclass.extend({
return network.instantiateDevice(port)
}).filter(function(dev) {
return dev.getType() != 'wifi' || dev.isUp();
+ }).sort(function(a, b) {
+ return L.naturalCompare(a.getName(), b.getName());
});
this.children = this.children.filter(function(opt) { return !opt.option.match(/^port_/) });
@@ -929,18 +1009,6 @@ return baseclass.extend({
for (var port_name in seen_ports)
ports.push(port_name);
- ports.sort(function(a, b) {
- var m1 = a.match(/^(.+?)([0-9]*)$/),
- m2 = b.match(/^(.+?)([0-9]*)$/);
-
- if (m1[1] < m2[1])
- return -1;
- else if (m1[1] > m2[1])
- return 1;
- else
- return +(m1[2] || 0) - +(m2[2] || 0);
- });
-
ss.updatePorts(ports);
},
diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js b/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
index 6c6163c7c..da0eeabb5 100755
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
+++ b/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
@@ -5,7 +5,9 @@
'require rpc';
'require uci';
'require form';
+'require network';
'require validation';
+'require tools.widgets as widgets';
var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus, CBILease6Status;
@@ -65,6 +67,58 @@ CBILease6Status = form.DummyValue.extend({
}
});
+function calculateNetwork(addr, mask) {
+ addr = validation.parseIPv4(String(addr));
+
+ if (!isNaN(mask))
+ mask = validation.parseIPv4(network.prefixToMask(+mask));
+ else
+ mask = validation.parseIPv4(String(mask));
+
+ if (addr == null || mask == null)
+ return null;
+
+ return [
+ [
+ addr[0] & (mask[0] >>> 0 & 255),
+ addr[1] & (mask[1] >>> 0 & 255),
+ addr[2] & (mask[2] >>> 0 & 255),
+ addr[3] & (mask[3] >>> 0 & 255)
+ ].join('.'),
+ mask.join('.')
+ ];
+}
+
+function getDHCPPools() {
+ return uci.load('dhcp').then(function() {
+ let sections = uci.sections('dhcp', 'dhcp'),
+ tasks = [], pools = [];
+
+ for (var i = 0; i < sections.length; i++) {
+ if (sections[i].ignore == '1' || !sections[i].interface)
+ continue;
+
+ tasks.push(network.getNetwork(sections[i].interface).then(L.bind(function(section_id, net) {
+ var cidr = net ? (net.getIPAddrs()[0] || '').split('/') : null;
+
+ if (cidr && cidr.length == 2) {
+ var net_mask = calculateNetwork(cidr[0], cidr[1]);
+
+ pools.push({
+ section_id: section_id,
+ network: net_mask[0],
+ netmask: net_mask[1]
+ });
+ }
+ }, null, sections[i]['.name'])));
+ }
+
+ return Promise.all(tasks).then(function() {
+ return pools;
+ });
+ });
+}
+
function validateHostname(sid, s) {
if (s == null || s == '')
return true;
@@ -72,7 +126,7 @@ function validateHostname(sid, s) {
if (s.length > 256)
return _('Expecting: %s').format(_('valid hostname'));
- var labels = s.replace(/^\.+|\.$/g, '').split(/\./);
+ var labels = s.replace(/^\*?\.?|\.$/g, '').split(/\./);
for (var i = 0; i < labels.length; i++)
if (!labels[i].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
@@ -102,13 +156,15 @@ function validateServerSpec(sid, s) {
if (s == null || s == '')
return true;
- var m = s.match(/^(?:\/(.+)\/)?(.*)$/);
+ var m = s.match(/^(\/.*\/)?(.*)$/);
if (!m)
return _('Expecting: %s').format(_('valid hostname'));
- var res = validateAddressList(sid, m[1]);
- if (res !== true)
- return res;
+ if (m[1] != '//' && m[1] != '/#/') {
+ var res = validateAddressList(sid, m[1]);
+ if (res !== true)
+ return res;
+ }
if (m[2] == '' || m[2] == '#')
return true;
@@ -138,285 +194,596 @@ function validateServerSpec(sid, s) {
return true;
}
+function validateMACAddr(pools, sid, s) {
+ if (s == null || s == '')
+ return true;
+
+ var leases = uci.sections('dhcp', 'host'),
+ this_macs = L.toArray(s).map(function(m) { return m.toUpperCase() });
+
+ for (var i = 0; i < pools.length; i++) {
+ var this_net_mask = calculateNetwork(this.section.formvalue(sid, 'ip'), pools[i].netmask);
+
+ if (!this_net_mask)
+ continue;
+
+ for (var j = 0; j < leases.length; j++) {
+ if (leases[j]['.name'] == sid || !leases[j].ip)
+ continue;
+
+ var lease_net_mask = calculateNetwork(leases[j].ip, pools[i].netmask);
+
+ if (!lease_net_mask || this_net_mask[0] != lease_net_mask[0])
+ continue;
+
+ var lease_macs = L.toArray(leases[j].mac).map(function(m) { return m.toUpperCase() });
+
+ for (var k = 0; k < lease_macs.length; k++)
+ for (var l = 0; l < this_macs.length; l++)
+ if (lease_macs[k] == this_macs[l])
+ return _('The MAC address %h is already used by another static lease in the same DHCP pool').format(this_macs[l]);
+ }
+ }
+
+ return true;
+}
+
return view.extend({
load: function() {
return Promise.all([
callHostHints(),
- callDUIDHints()
+ callDUIDHints(),
+ getDHCPPools(),
+ network.getNetworks()
]);
},
- render: function(hosts_duids) {
+ render: function(hosts_duids_pools) {
var has_dhcpv6 = L.hasSystemFeature('dnsmasq', 'dhcpv6') || L.hasSystemFeature('odhcpd'),
- hosts = hosts_duids[0],
- duids = hosts_duids[1],
+ hosts = hosts_duids_pools[0],
+ duids = hosts_duids_pools[1],
+ pools = hosts_duids_pools[2],
+ networks = hosts_duids_pools[3],
m, s, o, ss, so;
- m = new form.Map('dhcp', _('DHCP and DNS'), _('Dnsmasq is a combined DHCP-Server and DNS-Forwarder for NAT firewalls'));
+ m = new form.Map('dhcp', _('DHCP and DNS'),
+ _('Dnsmasq is a lightweight DHCP server and DNS forwarder.'));
- s = m.section(form.TypedSection, 'dnsmasq', _('Server Settings'));
+ s = m.section(form.TypedSection, 'dnsmasq');
s.anonymous = true;
s.addremove = false;
s.tab('general', _('General Settings'));
- s.tab('files', _('Resolv and Hosts Files'));
- s.tab('tftp', _('TFTP Settings'));
s.tab('advanced', _('Advanced Settings'));
s.tab('leases', _('Static Leases'));
+ s.tab('files', _('Resolv and Hosts Files'));
+ s.tab('hosts', _('Hostnames'));
+ s.tab('ipsets', _('IP Sets'));
+ s.tab('relay', _('Relay'));
+ s.tab('srvhosts', _('SRV'));
+ s.tab('mxhosts', _('MX'));
+ s.tab('cnamehosts', _('CNAME'));
+ s.tab('pxe_tftp', _('PXE/TFTP Settings'));
s.taboption('general', form.Flag, 'domainneeded',
_('Domain required'),
- _('Don\'t forward DNS-Requests without DNS-Name'));
+ _('Do not forward DNS queries without dots or domain parts.'));
s.taboption('general', form.Flag, 'authoritative',
_('Authoritative'),
- _('This is the only DHCP in the local network'));
-
-
- s.taboption('files', form.Flag, 'readethers',
- _('Use /etc/ethers
'),
- _('Read /etc/ethers
to configure the DHCP-Server'));
-
- s.taboption('files', form.Value, 'leasefile',
- _('Leasefile'),
- _('file where given DHCP-leases will be stored'));
-
- s.taboption('files', form.Flag, 'noresolv',
- _('Ignore resolve file')).optional = true;
-
- o = s.taboption('files', form.Value, 'resolvfile',
- _('Resolve file'),
- _('local DNS file'));
-
- o.depends('noresolv', '0');
- o.placeholder = '/tmp/resolv.conf.d/resolv.conf.auto';
- o.optional = true;
-
-
- s.taboption('files', form.Flag, 'nohosts',
- _('Ignore /etc/hosts
')).optional = true;
-
- s.taboption('files', form.DynamicList, 'addnhosts',
- _('Additional Hosts files')).optional = true;
-
- o = s.taboption('advanced', form.Flag, 'quietdhcp',
- _('Suppress logging'),
- _('Suppress logging of the routine operation of these protocols'));
- o.optional = true;
-
- o = s.taboption('advanced', form.Flag, 'sequential_ip',
- _('Allocate IP sequentially'),
- _('Allocate IP addresses sequentially, starting from the lowest available address'));
- o.optional = true;
-
- o = s.taboption('advanced', form.Flag, 'boguspriv',
- _('Filter private'),
- _('Do not forward reverse lookups for local networks'));
- o.default = o.enabled;
-
- s.taboption('advanced', form.Flag, 'filterwin2k',
- _('Filter useless'),
- _('Do not forward requests that cannot be answered by public name servers'));
-
-
- s.taboption('advanced', form.Flag, 'localise_queries',
- _('Localise queries'),
- _('Localise hostname depending on the requesting subnet if multiple IPs are available'));
-
- if (L.hasSystemFeature('dnsmasq', 'dnssec')) {
- o = s.taboption('advanced', form.Flag, 'dnssec',
- _('DNSSEC'));
- o.optional = true;
-
- o = s.taboption('advanced', form.Flag, 'dnsseccheckunsigned',
- _('DNSSEC check unsigned'),
- _('Requires upstream supports DNSSEC; verify unsigned domain responses really come from unsigned domains'));
- o.default = o.enabled;
- o.optional = true;
- }
+ _('This is the only DHCP server in the local network.'));
s.taboption('general', form.Value, 'local',
_('Local server'),
- _('Local domain specification. Names matching this domain are never forwarded and are resolved from DHCP or hosts files only'));
+ _('Never forward matching domains and subdomains, resolve from DHCP or hosts files only.'));
s.taboption('general', form.Value, 'domain',
_('Local domain'),
- _('Local domain suffix appended to DHCP names and hosts file entries'));
+ _('Local domain suffix appended to DHCP names and hosts file entries.'));
- s.taboption('advanced', form.Flag, 'expandhosts',
- _('Expand hosts'),
- _('Add local domain suffix to names served from hosts files'));
-
- s.taboption('advanced', form.Flag, 'nonegcache',
- _('No negative cache'),
- _('Do not cache negative replies, e.g. for not existing domains'));
-
- s.taboption('advanced', form.Value, 'serversfile',
- _('Additional servers file'),
- _('This file may contain lines like \'server=/domain/1.2.3.4\' or \'server=1.2.3.4\' for domain-specific or full upstream DNS servers.'));
-
- s.taboption('advanced', form.Flag, 'strictorder',
- _('Strict order'),
- _('DNS servers will be queried in the order of the resolvfile')).optional = true;
-
- s.taboption('advanced', form.Flag, 'allservers',
- _('All Servers'),
- _('Query all available upstream DNS servers')).optional = true;
-
- o = s.taboption('advanced', form.DynamicList, 'bogusnxdomain', _('Bogus NX Domain Override'),
- _('List of hosts that supply bogus NX domain results'));
-
- o.optional = true;
- o.placeholder = '67.215.65.132';
-
-
- s.taboption('general', form.Flag, 'logqueries',
+ o = s.taboption('general', form.Flag, 'logqueries',
_('Log queries'),
- _('Write received DNS requests to syslog')).optional = true;
-
- o = s.taboption('general', form.DynamicList, 'server', _('DNS forwardings'),
- _('List of DNS servers to forward requests to'));
+ _('Write received DNS queries to syslog.'));
+ o.optional = true;
+ 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.validate = validateServerSpec;
-
- o = s.taboption('general', form.DynamicList, 'address', _('Addresses'),
- _('List of domains to force to an IP address.'));
-
+ 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.'));
o.optional = true;
- o.placeholder = '/router.local/192.168.0.1';
+ o.placeholder = '/router.local/router.lan/192.168.0.1';
+ o = s.taboption('general', form.DynamicList, 'ipset',
+ _('IP sets'),
+ _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.'));
+ o.optional = true;
+ o.placeholder = '/example.org/ipset,ipset6';
o = s.taboption('general', form.Flag, 'rebind_protection',
_('Rebind protection'),
- _('Discard upstream RFC1918 responses'));
-
+ _('Discard upstream responses containing RFC1918 addresses.').format('https://datatracker.ietf.org/doc/html/rfc1918'));
o.rmempty = false;
-
o = s.taboption('general', form.Flag, 'rebind_localhost',
_('Allow localhost'),
- _('Allow upstream responses in the 127.0.0.0/8 range, e.g. for RBL services'));
-
+ _('Exempt 127.0.0.0/8
and ::1
from rebinding checks, e.g. for RBL services.'));
o.depends('rebind_protection', '1');
-
o = s.taboption('general', form.DynamicList, 'rebind_domain',
_('Domain whitelist'),
- _('List of domains to allow RFC1918 responses for'));
- o.optional = true;
-
+ _('List of domains to allow RFC1918 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',
+ _('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',
+ _('Non-wildcard'),
+ _('Bind dynamically to interfaces rather than wildcard address.'));
+ o.default = o.enabled;
+ o.optional = false;
+ o.rmempty = true;
+
+ o = s.taboption('general', form.DynamicList, 'interface',
+ _('Listen interfaces'),
+ _('Listen only on the specified interfaces, and loopback if not excluded explicitly.'));
+ o.optional = true;
+ o.placeholder = 'lan';
+
+ o = s.taboption('general', form.DynamicList, 'notinterface',
+ _('Exclude interfaces'),
+ _('Do not listen on the specified interfaces.'));
+ o.optional = true;
+ o.placeholder = 'loopback';
+
+ o = s.taboption('relay', form.SectionValue, '__relays__', form.TableSection, 'relay', null,
+ _('Relay DHCP requests elsewhere. OK: v4↔v4, v6↔v6. Not OK: v4↔v6, v6↔v4.')
+ + '
' + _('Note: you may also need a DHCP Proxy (currently unavailable) when specifying a non-standard Relay To port(addr#port
).')
+ + '
' + _('You may add multiple unique Relay To on the same Listen addr.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+
+ so = ss.option(form.Value, 'local_addr', _('Relay from'));
+ so.rmempty = false;
+ so.datatype = 'ipaddr';
+
+ for (var family = 4; family <= 6; family += 2) {
+ for (var i = 0; i < networks.length; i++) {
+ if (networks[i].getName() != 'loopback') {
+ var addrs = (family == 6) ? networks[i].getIP6Addrs() : networks[i].getIPAddrs();
+ for (var j = 0; j < addrs.length; j++) {
+ var addr = addrs[j].split('/')[0];
+ so.value(addr, E([], [
+ addr, ' (',
+ widgets.NetworkSelect.prototype.renderIfaceBadge(networks[i]),
+ ')'
+ ]));
+ }
+ }
+ }
+ }
+
+ so = ss.option(form.Value, 'server_addr', _('Relay to address'));
+ so.rmempty = false;
+ so.optional = false;
+ so.placeholder = '192.168.10.1#535';
+
+ so.validate = function(section, value) {
+ var m = this.section.formvalue(section, 'local_addr'),
+ n = this.section.formvalue(section, 'server_addr'),
+ p;
+ if (n != null && n != '')
+ p = n.split('#');
+ if (p.length > 1 && !/^[0-9]+$/.test(p[1]))
+ return _('Expected port number.');
+ else
+ n = p[0];
+
+ if ((m == null || m == '') && (n == null || n == ''))
+ return _('Both "Relay from" and "Relay to address" must be specified.');
+
+ if ((validation.parseIPv6(m) && validation.parseIPv6(n)) ||
+ validation.parseIPv4(m) && validation.parseIPv4(n))
+ return true;
+ else
+ return _('Address families of "Relay from" and "Relay to address" must match.')
+ };
+
+ so = ss.option(widgets.NetworkSelect, 'interface', _('Only accept replies via'));
+ so.optional = true;
+ so.rmempty = false;
+ so.placeholder = 'lan';
+
+ s.taboption('files', form.Flag, 'readethers',
+ _('Use /etc/ethers
'),
+ _('Read /etc/ethers
to configure the DHCP server.'));
+
+ s.taboption('files', form.Value, 'leasefile',
+ _('Lease file'),
+ _('File to store DHCP lease information.'));
+
+ o = s.taboption('files', form.Flag, 'noresolv',
+ _('Ignore resolv file'));
+ o.optional = true;
+
+ o = s.taboption('files', form.Value, 'resolvfile',
+ _('Resolv file'),
+ _('File with upstream resolvers.'));
+ o.depends('noresolv', '0');
+ o.placeholder = '/tmp/resolv.conf.d/resolv.conf.auto';
+ o.optional = true;
+
+ o = s.taboption('files', form.Flag, 'nohosts',
+ _('Ignore /etc/hosts
'));
+ o.optional = true;
+
+ o = s.taboption('files', form.DynamicList, 'addnhosts',
+ _('Additional hosts files'));
+ o.optional = true;
+ o.placeholder = '/etc/dnsmasq.hosts';
+
+ o = s.taboption('advanced', form.Flag, 'quietdhcp',
+ _('Suppress logging'),
+ _('Suppress logging of the routine operation for the DHCP protocol.'));
+ o.optional = true;
+
+ o = s.taboption('advanced', 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',
+ _('Filter private'),
+ _('Do not forward reverse lookups for local networks.'));
+ o.default = o.enabled;
+
+ s.taboption('advanced', 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',
+ _('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',
+ _('Filter IPv4 A records'),
+ _('Remove IPv4 addresses from the results and only return IPv6 addresses.'));
+ o.optional = true;
+
+ s.taboption('advanced', form.Flag, 'localise_queries',
+ _('Localise queries'),
+ _('Return answers to DNS queries matching the subnet from which the query was received if multiple IPs are available.'));
+
+ if (L.hasSystemFeature('dnsmasq', 'dnssec')) {
+ o = s.taboption('advanced', 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',
+ _('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',
+ _('No negative cache'),
+ _('Do not cache negative replies, e.g. for non-existent domains.'));
+
+ o = s.taboption('advanced', 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
.'));
+ 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.optional = true;
+
+ o = s.taboption('advanced', form.Flag, 'allservers',
+ _('All servers'),
+ _('Query all available upstream resolvers.'));
+ 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.optional = true;
+ o.placeholder = '64.94.110.11';
o = s.taboption('advanced', form.Value, 'port',
- _('DNS server port'),
- _('Listening port for inbound DNS queries'));
-
+ _('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',
- _('DNS query port'),
- _('Fixed source port for outbound DNS queries'));
-
+ _('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',
- _('Max. DHCP leases'),
- _('Maximum allowed number of active DHCP leases'));
-
+ _('Max. DHCP leases'),
+ _('Maximum allowed number of active DHCP leases.'));
o.optional = true;
o.datatype = 'uinteger';
o.placeholder = _('unlimited');
-
o = s.taboption('advanced', form.Value, 'ednspacket_max',
- _('Max. EDNS0 packet size'),
- _('Maximum allowed size of EDNS.0 UDP packets'));
-
+ _('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',
- _('Max. concurrent queries'),
- _('Maximum allowed number of concurrent DNS queries'));
-
+ _('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',
_('Size of DNS query cache'),
- _('Number of cached DNS entries (max is 10000, 0 is no caching)'));
+ _('Number of cached DNS entries, 10000 is maximum, 0 is no caching.'));
o.optional = true;
o.datatype = 'range(0,10000)';
- o.placeholder = 150;
-
- s.taboption('tftp', form.Flag, 'enable_tftp',
- _('Enable TFTP server')).optional = true;
-
- o = s.taboption('tftp', form.Value, 'tftp_root',
- _('TFTP server root'),
- _('Root directory for files served via TFTP'));
+ o.placeholder = 1000;
+ o = s.taboption('pxe_tftp', form.Flag, 'enable_tftp',
+ _('Enable TFTP server'),
+ _('Enable the built-in single-instance TFTP server.'));
o.optional = true;
+
+ o = s.taboption('pxe_tftp', form.Value, 'tftp_root',
+ _('TFTP server root'),
+ _('Root directory for files served via TFTP. Enable TFTP server and TFTP server root turn on the TFTP server and serve files from TFTP server root.'));
o.depends('enable_tftp', '1');
+ o.optional = true;
o.placeholder = '/';
-
- o = s.taboption('tftp', form.Value, 'dhcp_boot',
+ o = s.taboption('pxe_tftp', form.Value, 'dhcp_boot',
_('Network boot image'),
- _('Filename of the boot image advertised to clients'));
-
- o.optional = true;
+ _('Filename of the boot image advertised to clients.'));
o.depends('enable_tftp', '1');
+ o.optional = true;
o.placeholder = 'pxelinux.0';
- o = s.taboption('general', form.Flag, 'localservice',
- _('Local Service Only'),
- _('Limit DNS service to subnets interfaces on which we are serving DNS.'));
- o.optional = false;
- o.rmempty = false;
+ /* PXE - https://openwrt.org/docs/guide-user/base-system/dhcp#booting_options */
+ o = s.taboption('pxe_tftp', form.SectionValue, '__pxe__', form.GridSection, 'boot', null,
+ _('Special PXE boot options for Dnsmasq.'));
+ ss = o.subsection;
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.nodescriptions = true;
- o = s.taboption('general', form.Flag, 'nonwildcard',
- _('Non-wildcard'),
- _('Bind dynamically to interfaces rather than wildcard address (recommended as linux default)'));
- o.default = o.enabled;
- o.optional = false;
- o.rmempty = true;
+ so = ss.option(form.Value, 'filename',
+ _('Filename'),
+ _('Host requests this filename from the boot server.'));
+ so.optional = false;
+ so.placeholder = 'pxelinux.0';
- o = s.taboption('general', form.DynamicList, 'interface',
- _('Listen Interfaces'),
- _('Limit listening to these interfaces, and loopback.'));
- o.optional = true;
+ so = ss.option(form.Value, 'servername',
+ _('Server name'),
+ _('The hostname of the boot server'));
+ so.optional = false;
+ so.placeholder = 'myNAS';
- o = s.taboption('general', form.DynamicList, 'notinterface',
- _('Exclude interfaces'),
- _('Prevent listening on these interfaces.'));
- o.optional = true;
+ so = ss.option(form.Value, 'serveraddress',
+ _('Server address'),
+ _('The IP address of the boot server'));
+ so.optional = false;
+ so.placeholder = '192.168.1.2';
- 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.'));
+ 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".'));
+ so.optional = true;
+ so.placeholder = '42,192.168.1.4';
+
+ so = ss.option(widgets.DeviceSelect, 'networkid',
+ _('Network-ID'),
+ _('Apply DHCP Options to this net. (Empty = all clients).'));
+ so.optional = true;
+ so.noaliases = true;
+
+ so = ss.option(form.Flag, 'force',
+ _('Force'),
+ _('Always send DHCP Options. Sometimes needed, with e.g. PXELinux.'));
+ so.optional = true;
+
+ so = ss.option(form.Value, 'instance',
+ _('Instance'),
+ _('Dnsmasq instance to which this boot section is bound. If unspecified, the section is valid for all dnsmasq instances.'));
+ so.optional = true;
+
+ Object.values(L.uci.sections('dhcp', 'dnsmasq')).forEach(function(val, index) {
+ so.value(index, '%s (Domain: %s, Local: %s)'.format(index, val.domain || '?', val.local || '?'));
+ });
+
+ o = s.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, … .')
+ + '
' + _('You may add multiple records for the same Target.')
+ + '
' + _('Larger weights (of the same prio) are given a proportionately higher probability of being selected.'));
ss = o.subsection;
ss.addremove = true;
ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+
+ so = ss.option(form.Value, 'srv', _('SRV'), _('Syntax: _service._proto.example.com
.'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = '_sip._tcp.example.com';
+
+ so = ss.option(form.Value, 'target', _('Target'), _('CNAME or fqdn'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'sip.example.com';
+
+ so = ss.option(form.Value, 'port', _('Port'));
+ so.rmempty = false;
+ so.datatype = 'port';
+ so.placeholder = '5060';
+
+ so = ss.option(form.Value, 'class', _('Priority'), _('Ordinal: lower comes first.'));
+ so.rmempty = true;
+ so.datatype = 'range(0,65535)';
+ so.placeholder = '10';
+
+ so = ss.option(form.Value, 'weight', _('Weight'));
+ so.rmempty = true;
+ so.datatype = 'range(0,65535)';
+ so.placeholder = '50';
+
+ o = s.taboption('mxhosts', form.SectionValue, '__mxhosts__', form.TableSection, 'mxhost', null,
+ _('Bind service records to a domain name: specify the location of services.')
+ + '
' + _('You may add multiple records for the same domain.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+
+ so = ss.option(form.Value, 'domain', _('Domain'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'example.com';
+
+ so = ss.option(form.Value, 'relay', _('Relay'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'relay.example.com';
+
+ so = ss.option(form.Value, 'pref', _('Priority'), _('Ordinal: lower comes first.'));
+ so.rmempty = true;
+ so.datatype = 'range(0,65535)';
+ so.placeholder = '0';
+
+ o = s.taboption('cnamehosts', form.SectionValue, '__cname__', form.TableSection, 'cname', null,
+ _('Set an alias for a hostname.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+
+ so = ss.option(form.Value, 'cname', _('Domain'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'www.example.com';
+
+ so = ss.option(form.Value, 'target', _('Target'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'example.com';
+
+ o = s.taboption('hosts', form.SectionValue, '__hosts__', form.GridSection, 'domain', null,
+ _('Hostnames are used to bind a domain name to an IP address. This setting is redundant for hostnames already configured with static leases, but it can be useful to rebind an FQDN.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
so = ss.option(form.Value, 'name', _('Hostname'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+
+ so = ss.option(form.Value, 'ip', _('IP address'));
+ so.rmempty = false;
+ so.datatype = 'ipaddr';
+
+ var ipaddrs = {};
+
+ Object.keys(hosts).forEach(function(mac) {
+ var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4);
+
+ for (var i = 0; i < addrs.length; i++)
+ ipaddrs[addrs[i]] = hosts[mac].name || mac;
+ });
+
+ L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
+ so.value(ipv4, '%s (%s)'.format(ipv4, ipaddrs[ipv4]));
+ });
+
+ 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.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+
+ so = ss.option(form.DynamicList, 'name', _('IP set'));
+ so.rmempty = false;
+ so.datatype = 'string';
+
+ so = ss.option(form.DynamicList, 'domain', _('Domain'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+
+ o = s.taboption('leases', form.SectionValue, '__leases__', form.GridSection, 'host', null,
+ _('Static leases are used to assign fixed IP addresses and symbolic hostnames to DHCP clients. They are also required for non-dynamic interface configurations where only hosts with a corresponding lease are served.') + '
' +
+ _('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.') + '
' +
+ _('The tag construct filters which host directives are used; more than one tag can be provided, in this case the request must match all of them. Tagged directives are used in preference to untagged ones. Note that one of mac, duid or hostname still needs to be specified (can be a wildcard).'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.nodescriptions = true;
+ ss.max_cols = 8;
+ ss.modaltitle = _('Edit static lease');
+
+ so = ss.option(form.Value, 'name',
+ _('Hostname'),
+ _('Optional hostname to assign'));
so.validate = validateHostname;
so.rmempty = true;
so.write = function(section, value) {
@@ -428,20 +795,35 @@ return view.extend({
uci.unset('dhcp', section, 'dns');
};
- so = ss.option(form.Value, 'mac', _('MAC-Address'));
- so.datatype = 'list(unique(macaddr))';
+ so = ss.option(form.Value, 'mac',
+ _('MAC address(es)'),
+ _('The hardware address(es) of this entry/host, separated by spaces.') + '
' +
+ _('In DHCPv4, it is possible to include more than one mac address. This allows an IP address to be associated with multiple macaddrs, and dnsmasq abandons a DHCP lease to one of the macaddrs when another asks for a lease. It only works reliably if only one of the macaddrs is active at any time.'));
+ //As a special case, in DHCPv4, it is possible to include more than one hardware address. eg: --dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.2 This allows an IP address to be associated with multiple hardware addresses, and gives dnsmasq permission to abandon a DHCP lease to one of the hardware addresses when another one asks for a lease
+ so.validate = function(section_id, value) {
+ var macaddrs = L.toArray(value);
+
+ for (var i = 0; i < macaddrs.length; i++)
+ if (!macaddrs[i].match(/^([a-fA-F0-9]{2}|\*):([a-fA-F0-9]{2}:|\*:){4}(?:[a-fA-F0-9]{2}|\*)$/))
+ return _('Expecting a valid MAC address, optionally including wildcards');
+
+ return true;
+ };
so.rmempty = true;
so.cfgvalue = function(section) {
var macs = L.toArray(uci.get('dhcp', section, 'mac')),
result = [];
for (var i = 0, mac; (mac = macs[i]) != null; i++)
- if (/^([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2})$/.test(mac))
- result.push('%02X:%02X:%02X:%02X:%02X:%02X'.format(
+ if (/^([0-9a-fA-F]{1,2}|\*):([0-9a-fA-F]{1,2}|\*):([0-9a-fA-F]{1,2}|\*):([0-9a-fA-F]{1,2}|\*):([0-9a-fA-F]{1,2}|\*):([0-9a-fA-F]{1,2}|\*)$/.test(mac)) {
+ var m = [
parseInt(RegExp.$1, 16), parseInt(RegExp.$2, 16),
parseInt(RegExp.$3, 16), parseInt(RegExp.$4, 16),
- parseInt(RegExp.$5, 16), parseInt(RegExp.$6, 16)));
+ parseInt(RegExp.$5, 16), parseInt(RegExp.$6, 16)
+ ];
+ result.push(m.map(function(n) { return isNaN(n) ? '*' : '%02X'.format(n) }).join(':'));
+ }
return result.length ? result.join(' ') : null;
};
so.renderWidget = function(section_id, option_index, cfgvalue) {
@@ -468,75 +850,99 @@ return view.extend({
return node;
};
+ 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];
so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
});
- so.write = function(section, value) {
- var ip = this.map.lookupOption('ip', section)[0].formvalue(section);
- var hosts = uci.sections('dhcp', 'host');
- var section_removed = false;
-
- for (var i = 0; i < hosts.length; i++) {
- if (ip == hosts[i].ip) {
- uci.set('dhcp', hosts[i]['.name'], 'mac', [hosts[i].mac, value].join(' '));
- uci.remove('dhcp', section);
- section_removed = true;
- break;
- }
- }
-
- if (!section_removed) {
- uci.set('dhcp', section, 'mac', value);
- }
- }
-
- so = ss.option(form.Value, 'ip', _('IPv4-Address'));
+ so = ss.option(form.Value, 'ip', _('IPv4 address'), _('The IP address to be used for this host, or ignore to ignore any DHCP request from this host.'));
+ so.value('ignore', _('Ignore'));
so.datatype = 'or(ip4addr,"ignore")';
so.validate = function(section, value) {
- var mac = this.map.lookupOption('mac', section),
- name = this.map.lookupOption('name', section),
- m = mac ? mac[0].formvalue(section) : null,
- n = name ? name[0].formvalue(section) : null;
+ var m = this.section.formvalue(section, 'mac'),
+ n = this.section.formvalue(section, 'name');
if ((m == null || m == '') && (n == null || n == ''))
- return _('One of hostname or mac address must be specified!');
+ return _('One of hostname or MAC address must be specified!');
- return true;
+ if (value == null || value == '' || value == 'ignore')
+ return true;
+
+ var leases = uci.sections('dhcp', 'host');
+
+ for (var i = 0; i < leases.length; i++)
+ if (leases[i]['.name'] != section && leases[i].ip == value)
+ return _('The IP address %h is already used by another static lease').format(value);
+
+ for (var i = 0; i < pools.length; i++) {
+ var net_mask = calculateNetwork(value, pools[i].netmask);
+
+ if (net_mask && net_mask[0] == pools[i].network)
+ return true;
+ }
+
+ return _('The IP address is outside of any DHCP pool address range');
};
- var ipaddrs = {};
-
- Object.keys(hosts).forEach(function(mac) {
- var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4);
-
- for (var i = 0; i < addrs.length; i++)
- ipaddrs[addrs[i]] = hosts[mac].name;
- });
-
L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
so.value(ipv4, ipaddrs[ipv4] ? '%s (%s)'.format(ipv4, ipaddrs[ipv4]) : ipv4);
});
- so = ss.option(form.Value, 'gw', _('Gateway IPv4 Address'));
+ so = ss.option(form.Value, 'leasetime',
+ _('Lease time'),
+ _('Host-specific lease time, e.g. 5m
, 3h
, 7d
.'));
so.rmempty = true;
- so.datatype = 'or(ip4addr,"ignore")';
- Object.keys(hosts).forEach(function(mac) {
- if (hosts[mac].ipv4)
- so.value(hosts[mac].ipv4);
- });
+ so.value('5m', _('5m (5 minutes)'));
+ so.value('3h', _('3h (3 hours)'));
+ so.value('12h', _('12h (12 hours - default)'));
+ so.value('7d', _('7d (7 days)'));
+ so.value('infinite', _('infinite (lease does not expire)'));
- so = ss.option(form.Value, 'leasetime', _('Lease time'));
- so.rmempty = true;
-
- so = ss.option(form.Value, 'duid', _('DUID'));
+ so = ss.option(form.Value, 'duid',
+ _('DUID'),
+ _('The DHCPv6-DUID (DHCP unique identifier) of this host.'));
so.datatype = 'and(rangelength(20,36),hexstring)';
Object.keys(duids).forEach(function(duid) {
so.value(duid, '%s (%s)'.format(duid, duids[duid].hostname || duids[duid].macaddr || duids[duid].ip6addr || '?'));
});
- so = ss.option(form.Value, 'hostid', _('IPv6-Suffix (hex)'));
+ so = ss.option(form.Value, 'hostid',
+ _('IPv6-Suffix (hex)'),
+ _('The IPv6 interface identifier (address suffix) as hexadecimal number (max. 16 chars).'));
+ so.datatype = 'and(rangelength(0,16),hexstring)';
+
+ so = ss.option(form.DynamicList, 'tag',
+ _('Tag'),
+ _('Assign new, freeform tags to this entry.'));
+
+ so = ss.option(form.DynamicList, 'match_tag',
+ _('Match Tag'),
+ _('When a host matches an entry then the special tag 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.'));
+ so.value('known', _('known'));
+ so.value('!known', _('!known (not known)'));
+ so.value('known-othernet', _('known-othernet (on different subnet)'));
+ so.optional = true;
+
+ so = ss.option(form.Value, 'instance',
+ _('Instance'),
+ _('Dnsmasq instance to which this DHCP host section is bound. If unspecified, the section is valid for all dnsmasq instances.'));
+ so.optional = true;
+
+ Object.values(L.uci.sections('dhcp', 'dnsmasq')).forEach(function(val, index) {
+ so.value(index, '%s (Domain: %s, Local: %s)'.format(index, val.domain || '?', val.local || '?'));
+ });
+
+
+ so = ss.option(form.Flag, 'broadcast',
+ _('Broadcast'),
+ _('Force broadcast DHCP response.'));
+
+ so = ss.option(form.Flag, 'dns',
+ _('Forward/reverse DNS'),
+ _('Add static forward and reverse DNS entries for this host.'));
o = s.taboption('leases', CBILeaseStatus, '__status__');
@@ -560,8 +966,17 @@ return view.extend({
else
exp = '%t'.format(lease.expires);
+ var hint = lease.macaddr ? hosts[lease.macaddr] : null,
+ name = hint ? hint.name : null,
+ host = null;
+
+ if (name && lease.hostname && lease.hostname != name)
+ host = '%s (%s)'.format(lease.hostname, name);
+ else if (lease.hostname)
+ host = lease.hostname;
+
return [
- lease.hostname || '?',
+ host || '-',
lease.ipaddr,
lease.macaddr,
exp
diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js b/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js
index 5d6bd4765..1bfa95501 100755
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js
+++ b/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js
@@ -4,6 +4,7 @@
'require fs';
'require ui';
'require uci';
+'require network';
return view.extend({
handleCommand: function(exec, args) {
@@ -13,8 +14,7 @@ return view.extend({
buttons[i].setAttribute('disabled', 'true');
return fs.exec(exec, args).then(function(res) {
- var out = document.querySelector('.command-output');
- out.style.display = '';
+ var out = document.querySelector('textarea');
dom.content(out, [ res.stdout || '', res.stderr || '' ]);
}).catch(function(err) {
@@ -36,7 +36,7 @@ return view.extend({
handleTraceroute: function(ev, cmd) {
var exec = cmd || 'traceroute',
addr = ev.currentTarget.parentNode.previousSibling.value,
- args = (exec == 'traceroute') ? [ '-q', '1', '-w', '1', '-n', addr ] : [ '-q', '1', '-w', '2', '-n', addr ];
+ args = (exec == 'traceroute') ? [ '-4', '-q', '1', '-w', '1', '-n', '-m', String(L.env.rpctimeout || 20), addr ] : [ '-q', '1', '-w', '2', '-n', addr ];
return this.handleCommand(exec, args);
},
@@ -47,12 +47,20 @@ return view.extend({
return this.handleCommand('nslookup', [ addr ]);
},
+ handleArpScan: function(ev, cmd) {
+ var addr = ev.currentTarget.parentNode.previousSibling.value;
+
+ return this.handleCommand('arp-scan', [ '-l', '-I', addr ]);
+ },
+
load: function() {
return Promise.all([
L.resolveDefault(fs.stat('/bin/ping6'), {}),
L.resolveDefault(fs.stat('/usr/bin/ping6'), {}),
L.resolveDefault(fs.stat('/bin/traceroute6'), {}),
L.resolveDefault(fs.stat('/usr/bin/traceroute6'), {}),
+ L.resolveDefault(fs.stat('/usr/bin/arp-scan'), {}),
+ network.getDevices(),
uci.load('luci')
]);
},
@@ -60,15 +68,15 @@ return view.extend({
render: function(res) {
var has_ping6 = res[0].path || res[1].path,
has_traceroute6 = res[2].path || res[3].path,
+ has_arpscan = res[4].path,
+ devices = res[5],
dns_host = uci.get('luci', 'diag', 'dns') || 'openwrt.org',
ping_host = uci.get('luci', 'diag', 'ping') || 'openwrt.org',
route_host = uci.get('luci', 'diag', 'route') || 'openwrt.org';
- return E([], [
- E('h2', {}, [ _('Network Utilities') ]),
- E('table', { 'class': 'table' }, [
+ var table = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr' }, [
- E('td', { 'class': 'td left' }, [
+ E('td', { 'class': 'td left', 'style': 'overflow:initial' }, [
E('input', {
'style': 'margin:5px 0',
'type': 'text',
@@ -91,7 +99,7 @@ return view.extend({
])
]),
- E('td', { 'class': 'td left' }, [
+ E('td', { 'class': 'td left', 'style': 'overflow:initial' }, [
E('input', {
'style': 'margin:5px 0',
'type': 'text',
@@ -126,11 +134,45 @@ return view.extend({
'click': ui.createHandlerFn(this, 'handleNslookup')
}, [ _('Nslookup') ])
])
- ])
+ ]),
+
+ has_arpscan ? E('td', { 'class': 'td left' }, [
+ E('select', {
+ 'style': 'margin:5px 0'
+ }, devices.map(function(device) {
+ if (!device.isUp())
+ return E([]);
+
+ return E('option', { 'value': device.getName() }, [ device.getI18n() ]);
+ })),
+ E('span', { 'class': 'diag-action' }, [
+ E('button', {
+ 'class': 'cbi-button cbi-button-action',
+ 'click': ui.createHandlerFn(this, 'handleArpScan')
+ }, [ _('Arp-scan') ])
+ ])
+ ]) : E([]),
])
- ]),
- E('pre', { 'class': 'command-output', 'style': 'display:none' })
+ ]);
+
+ var view = E('div', { 'class': 'cbi-map'}, [
+ E('h2', {}, [ _('Diagnostics') ]),
+ E('div', { 'class': 'cbi-map-descr'}, _('Execution of various network commands to check the connection and name resolution to other systems.')),
+ table,
+ E('div', {'class': 'cbi-section'}, [
+ E('div', { 'id' : 'command-output'},
+ E('textarea', {
+ 'id': 'widget.command-output',
+ 'style': 'width: 100%; font-family:monospace; white-space:pre',
+ 'readonly': true,
+ 'wrap': 'off',
+ 'rows': '20'
+ })
+ )
+ ])
]);
+
+ return view;
},
handleSaveApply: null,
diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js b/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js
deleted file mode 100755
index 93ebf5ba6..000000000
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js
+++ /dev/null
@@ -1,50 +0,0 @@
-'use strict';
-'require view';
-'require rpc';
-'require form';
-
-return view.extend({
- callHostHints: rpc.declare({
- object: 'luci-rpc',
- method: 'getHostHints',
- expect: { '': {} }
- }),
-
- load: function() {
- return this.callHostHints();
- },
-
- render: function(hosts) {
- var m, s, o;
-
- m = new form.Map('dhcp', _('Hostnames'));
-
- s = m.section(form.GridSection, 'domain', _('Host entries'));
- s.addremove = true;
- s.anonymous = true;
- s.sortable = true;
-
- o = s.option(form.Value, 'name', _('Hostname'));
- o.datatype = 'hostname';
- o.rmempty = true;
-
- o = s.option(form.Value, 'ip', _('IP address'));
- o.datatype = 'ipaddr';
- o.rmempty = true;
-
- var ipaddrs = {};
-
- Object.keys(hosts).forEach(function(mac) {
- var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4);
-
- for (var i = 0; i < addrs.length; i++)
- ipaddrs[addrs[i]] = hosts[mac].name || mac;
- });
-
- L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
- o.value(ipv4, '%s (%s)'.format(ipv4, ipaddrs[ipv4]));
- });
-
- return m.render();
- }
-});
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 ff179d404..2a952f263 100755
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
+++ b/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
@@ -228,6 +228,23 @@ function get_netmask(s, use_cfgvalue) {
return subnetmask;
}
+function has_peerdns(proto) {
+ switch (proto) {
+ case 'dhcp':
+ case 'dhcpv6':
+ case 'qmi':
+ case 'ppp':
+ case 'pppoe':
+ case 'pppoa':
+ case 'pptp':
+ case 'openvpn':
+ case 'sstp':
+ return true;
+ }
+
+ return false;
+}
+
var cbiRichListValue = form.ListValue.extend({
renderWidget: function(section_id, option_index, cfgvalue) {
var choices = this.transformChoices();
@@ -488,7 +505,7 @@ return view.extend({
};
s.modaltitle = function(section_id) {
- return _('Interfaces') + ' » ' + section_id.toUpperCase();
+ return _('Interfaces') + ' » ' + section_id;
};
s.renderRowActions = function(section_id) {
@@ -535,7 +552,7 @@ return view.extend({
var protocols = network.getProtocols();
protocols.sort(function(a, b) {
- return a.getProtocol() > b.getProtocol();
+ return L.naturalCompare(a.getProtocol(), b.getProtocol());
});
o = s.taboption('general', form.DummyValue, '_ifacestat_modal', _('Status'));
@@ -643,7 +660,7 @@ return view.extend({
E('p', _('No DHCP Server configured for this interface') + ' '),
E('button', {
'class': 'cbi-button cbi-button-add',
- 'title': _('Setup DHCP Server'),
+ 'title': _('Set up DHCP Server'),
'click': ui.createHandlerFn(this, function(section_id, ev) {
this.map.save(function() {
uci.add('dhcp', 'dhcp', section_id);
@@ -659,7 +676,7 @@ return view.extend({
}
});
}, ifc.getName())
- }, _('Setup DHCP Server'))
+ }, _('Set up DHCP Server'))
]);
};
@@ -723,12 +740,35 @@ return view.extend({
var hybrid_downstream_desc = _('Operate in relay mode if a designated master interface is configured and active, otherwise fall back to server mode.'),
ndp_downstream_desc = _('Operate in relay mode if a designated master interface is configured and active, otherwise disable NDP proxying.'),
hybrid_master_desc = _('Operate in relay mode if an upstream IPv6 prefix is present, otherwise disable service.'),
+ ra_server_allowed = true,
checked = this.formvalue(section_id),
dhcpv6 = this.section.getOption('dhcpv6').getUIElement(section_id),
ndp = this.section.getOption('ndp').getUIElement(section_id),
ra = this.section.getOption('ra').getUIElement(section_id);
- if (checked == '1' || protoval != 'static') {
+ /* Assume that serving RAs by default is fine, but disallow it for certain
+ interface protocols such as DHCP, DHCPv6 or the various PPP flavors.
+ The intent is to only allow RA serving for interface protocols doing
+ some kind of static IP config over something resembling a layer 2
+ ethernet device. */
+ switch (protoval) {
+ case 'dhcp':
+ case 'dhcpv6':
+ case '3g':
+ case 'l2tp':
+ case 'ppp':
+ case 'pppoa':
+ case 'pppoe':
+ case 'pptp':
+ case 'pppossh':
+ case 'ipip':
+ case 'gre':
+ case 'grev6':
+ ra_server_allowed = false;
+ break;
+ }
+
+ if (checked == '1' || !ra_server_allowed) {
dhcpv6.node.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
if (dhcpv6.getValue() == 'server')
@@ -746,7 +786,7 @@ return view.extend({
ndp.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_master_desc;
}
else {
- if (protoval == 'static') {
+ if (ra_server_allowed) {
dhcpv6.node.querySelector('li[data-value="server"]').removeAttribute('unselectable');
ra.node.querySelector('li[data-value="server"]').removeAttribute('unselectable');
}
@@ -805,9 +845,23 @@ return view.extend({
return flags.length ? flags : [ 'other-config' ];
};
so.remove = function(section_id) {
- uci.set('dhcp', section_id, 'ra_flags', [ 'none' ]);
+ var existing = L.toArray(uci.get('dhcp', section_id, 'ra_flags'));
+ if (this.isActive(section_id)) {
+ if (existing.length != 1 || existing[0] != 'none')
+ uci.set('dhcp', section_id, 'ra_flags', [ 'none' ]);
+ }
+ else if (existing.length) {
+ uci.unset('dhcp', section_id, 'ra_flags');
+ }
};
+ so = ss.taboption('ipv6-ra', form.Value, 'ra_pref64', _('NAT64 prefix'), _('Announce NAT64 prefix in RA messages.'));
+ so.optional = true;
+ so.datatype = 'cidr6';
+ so.placeholder = '64:ff9b::/96';
+ so.depends('ra', 'server');
+ so.depends({ ra: 'hybrid', master: '0' });
+
so = ss.taboption('ipv6-ra', form.Value, 'ra_maxinterval', _('Max RA interval'), _('Maximum time allowed between sending unsolicited RA. Default is 600 seconds.'));
so.optional = true;
so.datatype = 'uinteger';
@@ -835,15 +889,17 @@ return view.extend({
so.depends('ra', 'server');
so.depends({ ra: 'hybrid', master: '0' });
so.load = function(section_id) {
- var dev = ifc.getL3Device();
+ var dev = ifc.getL3Device(),
+ path = dev ? "/proc/sys/net/ipv6/conf/%s/mtu".format(dev.getName()) : null;
- if (dev) {
- var path = "/proc/sys/net/ipv6/conf/%s/mtu".format(dev.getName());
+ return Promise.all([
+ dev ? L.resolveDefault(fs.read(path), dev.getMTU()) : null,
+ this.super('load', [section_id])
+ ]).then(L.bind(function(res) {
+ this.placeholder = +res[0];
- return L.resolveDefault(fs.read(path), dev.getMTU()).then(L.bind(function(data) {
- this.placeholder = data;
- }, this));
- }
+ return res[1];
+ }, this));
};
so = ss.taboption('ipv6-ra', form.Value, 'ra_hoplimit', _('RA Hop Limit'), _('The maximum hops to be published in RA messages. Maximum is 255 hops.'));
@@ -852,15 +908,17 @@ return view.extend({
so.depends('ra', 'server');
so.depends({ ra: 'hybrid', master: '0' });
so.load = function(section_id) {
- var dev = ifc.getL3Device();
+ var dev = ifc.getL3Device(),
+ path = dev ? "/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev.getName()) : null;
- if (dev) {
- var path = "/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev.getName());
+ return Promise.all([
+ dev ? L.resolveDefault(fs.read(path), 64) : null,
+ this.super('load', [section_id])
+ ]).then(L.bind(function(res) {
+ this.placeholder = +res[0];
- return L.resolveDefault(fs.read(path), 64).then(L.bind(function(data) {
- this.placeholder = data;
- }, this));
- }
+ return res[1];
+ }, this));
};
@@ -874,22 +932,32 @@ return view.extend({
_('Forward DHCPv6 messages between the designated master interface and downstream interfaces.'));
so.value('hybrid', _('hybrid mode'), ' ');
+ so = ss.taboption('ipv6', form.Value, 'dhcpv6_pd_min_len', _('PD minimum length'),
+ _('Configures the minimum delegated prefix length assigned to a requesting downstream router, potentially overriding a requested prefix length. If left unspecified, the device will assign the smallest available prefix greater than or equal to the requested prefix.'));
+ so.datatype = 'range(1,62)';
+ so.depends('dhcpv6', 'server');
so = ss.taboption('ipv6', form.DynamicList, 'dns', _('Announced IPv6 DNS servers'),
_('Specifies a fixed list of IPv6 DNS server addresses to announce via DHCPv6. If left unspecified, the device will announce itself as IPv6 DNS server unless the Local IPv6 DNS server option is disabled.'));
so.datatype = 'ip6addr("nomask")'; /* restrict to IPv6 only for now since dnsmasq (DHCPv4) does not honour this option */
+ so.depends('ra', 'server');
+ so.depends({ ra: 'hybrid', master: '0' });
so.depends('dhcpv6', 'server');
so.depends({ dhcpv6: 'hybrid', master: '0' });
so = ss.taboption('ipv6', form.Flag, 'dns_service', _('Local IPv6 DNS server'),
_('Announce this device as IPv6 DNS server.'));
so.default = so.enabled;
+ so.depends({ ra: 'server', dns: /^$/ });
+ so.depends({ ra: 'hybrid', dns: /^$/, master: '0' });
so.depends({ dhcpv6: 'server', dns: /^$/ });
so.depends({ dhcpv6: 'hybrid', dns: /^$/, master: '0' });
so = ss.taboption('ipv6', form.DynamicList, 'domain', _('Announced DNS domains'),
_('Specifies a fixed list of DNS search domains to announce via DHCPv6. If left unspecified, the local device DNS search domain will be announced.'));
so.datatype = 'hostname';
+ so.depends('ra', 'server');
+ so.depends({ ra: 'hybrid', master: '0' });
so.depends('dhcpv6', 'server');
so.depends({ dhcpv6: 'hybrid', master: '0' });
@@ -903,7 +971,7 @@ return view.extend({
so.value('hybrid', _('hybrid mode'), ' ');
- so = ss.taboption('ipv6', form.Flag, 'ndproxy_routing', _('Learn routes'), _('Setup routes for proxied IPv6 neighbours.'));
+ so = ss.taboption('ipv6', form.Flag, 'ndproxy_routing', _('Learn routes'), _('Set up routes for proxied IPv6 neighbours.'));
so.default = so.enabled;
so.depends('ndp', 'relay');
so.depends('ndp', 'hybrid');
@@ -911,6 +979,18 @@ return view.extend({
so = ss.taboption('ipv6', form.Flag, 'ndproxy_slave', _('NDP-Proxy slave'), _('Set interface as NDP-Proxy external slave. Default is off.'));
so.depends({ ndp: 'relay', master: '0' });
so.depends({ ndp: 'hybrid', master: '0' });
+
+ so = ss.taboption('ipv6', form.Value, 'preferred_lifetime', _('IPv6 Prefix Lifetime'), _('Preferred lifetime for a prefix.'));
+ so.optional = true;
+ so.placeholder = '12h';
+ so.value('5m', _('5m (5 minutes)'));
+ so.value('3h', _('3h (3 hours)'));
+ so.value('12h', _('12h (12 hours - default)'));
+ so.value('7d', _('7d (7 days)'));
+
+ //This is a ra_* setting, but its placement is more logical/findable under IPv6 settings.
+ so = ss.taboption('ipv6', form.Flag, 'ra_useleasetime', _('Follow IPv4 Lifetime'), _('DHCPv4 leasetime
is used as limit and preferred lifetime of the IPv6 prefix.'));
+ so.optional = true;
}
ifc.renderFormOptions(s);
@@ -919,13 +999,13 @@ return view.extend({
o = nettools.replaceOption(s, 'advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
o.default = o.enabled;
- if (protoval != 'static') {
+ if (has_peerdns(protoval)) {
o = nettools.replaceOption(s, 'advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
o.default = o.enabled;
}
o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- if (protoval != 'static')
+ if (has_peerdns(protoval))
o.depends('peerdns', '0');
o.datatype = 'ipaddr';
@@ -961,7 +1041,12 @@ return view.extend({
o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6table', _('Override IPv6 routing table'));
o.datatype = 'or(uinteger, string)';
for (var i = 0; i < rtTables.length; i++)
- o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][0], rtTables[i][1]));
+ o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
+
+ if (protoval == 'dhcpv6') {
+ o = nettools.replaceOption(s, 'advanced', form.Flag, 'sourcefilter', _('IPv6 source routing'), _('Automatically handle multiple uplink interfaces using source-based policy routing.'));
+ o.default = o.enabled;
+ }
o = nettools.replaceOption(s, 'advanced', form.Flag, 'delegate', _('Delegate IPv6 prefixes'), _('Enable downstream delegation of IPv6 prefixes available on this interface'));
o.default = o.enabled;
@@ -1080,7 +1165,7 @@ return view.extend({
proto, name, device;
protocols.sort(function(a, b) {
- return a.getProtocol() > b.getProtocol();
+ return L.naturalCompare(a.getProtocol(), b.getProtocol());
});
s2.render = function() {
@@ -1154,6 +1239,9 @@ return view.extend({
protoclass.addDevice(device.formvalue('_new_'));
m.children[0].addedSection = section_id;
+
+ ui.hideModal();
+ ui.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
}).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval));
});
})
@@ -1183,9 +1271,9 @@ return view.extend({
var node = E('div', { 'class': 'ifacebox' }, [
E('div', {
'class': 'ifacebox-head',
- 'style': 'background-color:%s'.format(zone ? zone.getColor() : '#EEEEEE'),
+ 'style': firewall.getZoneColorStyle(zone),
'title': zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned')
- }, E('strong', net.getName().toUpperCase())),
+ }, E('strong', net.getName())),
E('div', {
'class': 'ifacebox-body',
'id': '%s-ifc-devices'.format(section_id),
@@ -1239,7 +1327,7 @@ return view.extend({
s.cfgsections = function() {
var sections = uci.sections('network', 'device'),
- section_ids = sections.sort(function(a, b) { return a.name > b.name }).map(function(s) { return s['.name'] });
+ section_ids = sections.sort(function(a, b) { return L.naturalCompare(a.name, b.name) }).map(function(s) { return s['.name'] });
for (var i = 0; i < netDevs.length; i++) {
if (sections.filter(function(s) { return s.name == netDevs[i].getName() }).length)
@@ -1284,7 +1372,7 @@ return view.extend({
var trEl = this.super('renderRowActions', [ section_id, _('Configure…') ]),
deleteBtn = trEl.querySelector('button:last-child');
- deleteBtn.firstChild.data = _('Reset');
+ deleteBtn.firstChild.data = _('Unconfigure');
deleteBtn.setAttribute('title', _('Remove related device settings from the configuration'));
deleteBtn.disabled = section_id.match(/^dev:/) ? true : null;
@@ -1317,9 +1405,26 @@ return view.extend({
for (var i = 0; i < map.addedVLANs.length; i++)
uci.remove('network', map.addedVLANs[i]);
+ if (this.addedSection)
+ uci.remove('network', this.addedSection);
+
return form.GridSection.prototype.handleModalCancel.apply(this, arguments);
};
+ s.handleRemove = function(section_id /*, ... */) {
+ var name = uci.get('network', section_id, 'name'),
+ type = uci.get('network', section_id, 'type');
+
+ if (name != null && type == 'bridge') {
+ uci.sections('network', 'bridge-vlan', function(bvs) {
+ if (bvs.device == name)
+ uci.remove('network', bvs['.name']);
+ });
+ }
+
+ return form.GridSection.prototype.handleRemove.apply(this, arguments);
+ };
+
function getDevice(section_id) {
var m = section_id.match(/^dev:(.+)$/),
name = m ? m[1] : uci.get('network', section_id, 'name');
@@ -1422,7 +1527,7 @@ return view.extend({
mac = dev ? dev.getMAC() : null;
return val ? E('strong', {
- 'data-tooltip': _('The value is overridden by configuration. Original: %s').format(mac || _('unknown'))
+ 'data-tooltip': _('The value is overridden by configuration.')
}, [ val.toUpperCase() ]) : (mac || '-');
};
@@ -1434,7 +1539,7 @@ return view.extend({
mtu = dev ? dev.getMTU() : null;
return val ? E('strong', {
- 'data-tooltip': _('The value is overridden by configuration. Original: %s').format(mtu || _('unknown'))
+ 'data-tooltip': _('The value is overridden by configuration.')
}, [ val ]) : (mtu || '-').toString();
};
@@ -1454,21 +1559,27 @@ return view.extend({
s.anonymous = true;
o = s.option(form.ListValue, 'annex', _('Annex'));
- o.value('a', _('Annex A + L + M (all)'));
- o.value('b', _('Annex B (all)'));
- o.value('j', _('Annex J (all)'));
- o.value('m', _('Annex M (all)'));
- o.value('bdmt', _('Annex B G.992.1'));
- o.value('b2', _('Annex B G.992.3'));
- o.value('b2p', _('Annex B G.992.5'));
+ if (dslModemType == 'vdsl') {
+ o.value('a', _('ADSL (all variants) Annex A/L/M + VDSL2 Annex A/B/C'));
+ o.value('b', _('ADSL (all variants) Annex B + VDSL2 Annex A/B/C'));
+ o.value('j', _('ADSL (all variants) Annex B/J + VDSL2 Annex A/B/C'));
+ } else {
+ o.value('a', _('ADSL (all variants) Annex A/L/M'));
+ o.value('b', _('ADSL (all variants) Annex B'));
+ o.value('j', _('ADSL (all variants) Annex B/J'));
+ }
+ o.value('m', _('ADSL (all variants) Annex M'));
o.value('at1', _('ANSI T1.413'));
- o.value('admt', _('Annex A G.992.1'));
- o.value('alite', _('Annex A G.992.2'));
- o.value('a2', _('Annex A G.992.3'));
- o.value('a2p', _('Annex A G.992.5'));
- o.value('l', _('Annex L G.992.3 POTS 1'));
- o.value('m2', _('Annex M G.992.3'));
- o.value('m2p', _('Annex M G.992.5'));
+ o.value('admt', _('ADSL (G.992.1) Annex A'));
+ o.value('bdmt', _('ADSL (G.992.1) Annex B'));
+ o.value('alite', _('Splitterless ADSL (G.992.2) Annex A'));
+ o.value('a2', _('ADSL2 (G.992.3) Annex A'));
+ o.value('b2', _('ADSL2 (G.992.3) Annex B'));
+ o.value('l', _('ADSL2 (G.992.3) Annex L'));
+ o.value('m2', _('ADSL2 (G.992.3) Annex M'));
+ o.value('a2p', _('ADSL2+ (G.992.5) Annex A'));
+ o.value('b2p', _('ADSL2+ (G.992.5) Annex B'));
+ o.value('m2p', _('ADSL2+ (G.992.5) Annex M'));
o = s.option(form.ListValue, 'tone', _('Tone'));
o.value('', _('auto'));
diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js b/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
index 7e11a3cb4..4004be219 100755
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
+++ b/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
@@ -1,22 +1,35 @@
'use strict';
'require view';
+'require fs';
+'require uci';
'require form';
'require network';
'require tools.widgets as widgets';
return view.extend({
load: function() {
- return network.getDevices();
+ return Promise.all([
+ network.getDevices(),
+ fs.lines('/etc/iproute2/rt_tables')
+ ]);
},
- render: function(netdevs) {
- var m, s, o;
+ render: function(data) {
+ var netDevs = data[0],
+ m, s, o;
- m = new form.Map('network', _('Routes'), _('Routes specify over which interface and gateway a certain host or network can be reached.'));
+ var rtTables = data[1].map(function(l) {
+ var m = l.trim().match(/^(\d+)\s+(\S+)$/);
+ return m ? [ +m[1], m[2] ] : null;
+ }).filter(function(e) {
+ 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.tabbed = true;
- for (var i = 4; i <= 6; i += 2) {
- s = m.section(form.GridSection, (i == 4) ? 'route' : 'route6', (i == 4) ? _('Static IPv4 Routes') : _('Static IPv6 Routes'));
+ for (var family = 4; family <= 6; family += 2) {
+ s = m.section(form.GridSection, (family == 6) ? 'route6' : 'route', (family == 6) ? _('Static IPv6 Routes') : _('Static IPv4 Routes'));
s.anonymous = true;
s.addremove = true;
s.sortable = true;
@@ -25,44 +38,13 @@ return view.extend({
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
- o = s.taboption('general', widgets.NetworkSelect, 'interface', _('Interface'));
- o.rmempty = false;
+ 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 = s.taboption('general', form.Flag, 'disabled', _('Disable'), _('Disable this route'));
- o.rmempty = true;
- o.default = o.disabled;
-
- o = s.taboption('general', form.Value, 'target', _('Target'), (i == 4) ? _('Host-IP or Network') : _('IPv6-Address or Network (CIDR)'));
- o.datatype = (i == 4) ? 'ip4addr' : 'ip6addr';
o.rmempty = false;
- if (i == 4) {
- o = s.taboption('general', form.Value, 'netmask', _('IPv4-Netmask'), _('if target is a network'));
- o.placeholder = '255.255.255.255';
- o.datatype = 'ip4addr';
- o.rmempty = true;
- }
-
- o = s.taboption('general', form.Value, 'gateway', (i == 4) ? _('IPv4-Gateway') : _('IPv6-Gateway'));
- o.datatype = (i == 4) ? 'ip4addr' : 'ip6addr';
- o.rmempty = true;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Metric'));
- o.placeholder = 0;
- o.datatype = (i == 4) ? 'range(0,255)' : 'range(0,65535)';
- o.rmempty = true;
- o.textvalue = function(section_id) {
- return this.cfgvalue(section_id) || 0;
- };
-
- o = s.taboption('advanced', form.Value, 'mtu', _('MTU'));
- o.placeholder = 1500;
- o.datatype = 'range(64,9000)';
- o.rmempty = true;
+ o = s.taboption('general', form.ListValue, 'type', _('Route type'), _('Specifies the route type to be created'));
o.modalonly = true;
-
- o = s.taboption('advanced', form.ListValue, 'type', _('Route type'));
o.value('', 'unicast');
o.value('local');
o.value('broadcast');
@@ -71,36 +53,155 @@ return view.extend({
o.value('prohibit');
o.value('blackhole');
o.value('anycast');
- o.default = '';
- o.rmempty = true;
- o.modalonly = true;
+ o.value('throw');
- o = s.taboption('advanced', form.Value, 'table', _('Route table'));
- o.value('local', 'local (255)');
- o.value('main', 'main (254)');
- o.value('default', 'default (253)');
- o.rmempty = true;
- o.modalonly = true;
+ o = s.taboption('general', form.Value, 'target', _('Target'), _('Network address'));
+ o.rmempty = false;
+ o.datatype = (family == 6) ? 'cidr6' : 'cidr4';
+ o.placeholder = (family == 6) ? '::/0' : '0.0.0.0/0';
o.cfgvalue = function(section_id) {
- var cfgvalue = this.map.data.get('network', section_id, 'table');
- return cfgvalue || 'main';
+ var section_type = uci.get('network', section_id, '.type'),
+ target = uci.get('network', section_id, 'target'),
+ mask = uci.get('network', section_id, 'netmask'),
+ v6 = (section_type == 'route6') ? true : false,
+ bits = mask ? network.maskToPrefix(mask, v6) : (v6 ? 128 : 32);
+ if (target) {
+ return target.split('/')[1] ? target : target + '/' + bits;
+ }
+ }
+ o.write = function(section_id, formvalue) {
+ uci.set('network', section_id, 'target', formvalue);
+ uci.unset('network', section_id, 'netmask');
+ }
+
+ o = s.taboption('general', form.Value, 'gateway', _('Gateway'), _('Specifies the network gateway. If omitted, the gateway from the parent interface is taken if any, otherwise creates a link scope route. If set to 0.0.0.0 no gateway will be specified for the route'));
+ o.datatype = (family == 6) ? 'ip6addr("nomask")' : 'ip4addr("nomask")';
+ o.placeholder = (family == 6) ? 'fe80::1' : '192.168.0.1';
+
+ o = s.taboption('advanced', form.Value, 'metric', _('Metric'), _('Specifies the route metric to use'));
+ 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, 'source', _('Source Address'));
- o.placeholder = E('em', _('automatic'));
- for (var j = 0; j < netdevs.length; j++) {
- var addrs = netdevs[j].getIPAddrs();
- for (var k = 0; k < addrs.length; k++)
- o.value(addrs[k].split('/')[0]);
- }
- o.datatype = (i == 4) ? 'ip4addr' : 'ip6addr';
- o.default = '';
- o.rmempty = true;
+ o = s.taboption('advanced', form.Value, 'mtu', _('MTU'), _('Defines a specific MTU for this route'));
o.modalonly = true;
+ o.datatype = 'and(uinteger,range(64,9000))';
+ o.placeholder = 1500;
- o = s.taboption('advanced', form.Flag, 'onlink', _('On-Link route'));
+ o = s.taboption('advanced', form.Value, 'table', _('Table'), _('The rule target is a table lookup ID: a numeric table index ranging from 0 to 65535 or symbol alias declared in /etc/iproute2/rt_tables. Special aliases local (255), main (254) and default (253) are also valid'));
+ o.datatype = 'or(uinteger, string)';
+ for (var i = 0; i < rtTables.length; i++)
+ o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
+ o.textvalue = function(section_id) {
+ return this.cfgvalue(section_id) || 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.modalonly = true;
+ o.datatype = (family == 6) ? 'ip6addr' : 'ip4addr';
+ for (var i = 0; i < netDevs.length; i++) {
+ var addrs = (family == 6) ? netDevs[i].getIP6Addrs() : netDevs[i].getIPAddrs();
+ for (var j = 0; j < addrs.length; j++)
+ o.value(addrs[j].split('/')[0]);
+ }
+
+ o = s.taboption('advanced', form.Flag, 'onlink', _('On-link'), _('When enabled, gateway is on-link even if the gateway does not match any interface prefix'));
+ o.modalonly = true;
+ o.default = o.disabled;
+
+ o = s.taboption('advanced', form.Flag, 'disabled', _('Disable'));
+ o.modalonly = false;
+ o.editable = true;
+ o.default = o.disabled;
+ }
+
+ for (var family = 4; family <= 6; family += 2) {
+ s = m.section(form.GridSection, (family == 6) ? 'rule6' : 'rule', (family == 6) ? _('IPv6 Rules') : _('IPv4 Rules'));
+ s.anonymous = true;
+ s.addremove = true;
+ s.sortable = 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.datatype = 'uinteger';
+ o.placeholder = 30000;
+ o.textvalue = function(section_id) {
+ return this.cfgvalue(section_id) || E('em', _('auto'));
+ };
+
+ o = s.taboption('general', form.ListValue, 'action', _('Rule type'), _('Specifies the rule target routing action'));
+ o.modalonly = true;
+ o.value('', 'unicast');
+ o.value('unreachable');
+ o.value('prohibit');
+ o.value('blackhole');
+ o.value('throw');
+
+ o = s.taboption('general', widgets.NetworkSelect, 'in', _('Incoming interface'), _('Specifies the incoming logical interface name'));
+ o.loopback = true;
+ o.nocreate = true;
+
+ o = s.taboption('general', form.Value, 'src', _('Source'), _('Specifies the source subnet to match (CIDR notation)'));
+ o.datatype = (family == 6) ? 'cidr6' : 'cidr4';
+ o.placeholder = (family == 6) ? '::/0' : '0.0.0.0/0';
+ o.textvalue = function(section_id) {
+ return this.cfgvalue(section_id) || E('em', _('any'));
+ };
+
+ o = s.taboption('general', widgets.NetworkSelect, 'out', _('Outgoing interface'), _('Specifies the outgoing logical interface name'));
+ o.loopback = true;
+ o.nocreate = true;
+
+ o = s.taboption('general', form.Value, 'dest', _('Destination'), _('Specifies the destination subnet to match (CIDR notation)'));
+ o.datatype = (family == 6) ? 'cidr6' : 'cidr4';
+ o.placeholder = (family == 6) ? '::/0' : '0.0.0.0/0';
+ o.textvalue = function(section_id) {
+ return this.cfgvalue(section_id) || E('em', _('any'));
+ };
+
+ o = s.taboption('general', form.Value, 'lookup', _('Table'), _('The rule target is a table lookup ID: a numeric table index ranging from 0 to 65535 or symbol alias declared in /etc/iproute2/rt_tables. Special aliases local (255), main (254) and default (253) are also valid'));
+ o.datatype = 'or(uinteger, string)';
+ for (var i = 0; i < rtTables.length; i++)
+ o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
+
+ o = s.taboption('advanced', form.Value, 'goto', _('Jump to rule'), _('The rule target is a jump to another rule specified by its priority value'));
+ o.modalonly = true;
+ o.datatype = 'uinteger';
+ o.placeholder = 80000;
+
+ o = s.taboption('advanced', form.Value, 'mark', _('Firewall mark'), _('Specifies the fwmark and optionally its mask to match, e.g. 0xFF to match mark 255 or 0x0/0x1 to match any even mark value'));
+ o.modalonly = true;
+ o.datatype = 'string';
+ o.placeholder = '0x1/0xf';
+
+ o = s.taboption('advanced', form.Value, 'tos', _('Type of service'), _('Specifies the TOS value to match in IP headers'));
+ o.modalonly = true;
+ o.datatype = 'uinteger';
+ o.placeholder = 10;
+
+ o = s.taboption('advanced', form.Value, 'uidrange', _('User identifier'), _('Specifies an individual UID or range of UIDs to match, e.g. 1000 to match corresponding UID or 1000-1005 to inclusively match all UIDs within the corresponding range'));
+ o.modalonly = true;
+ o.datatype = 'string';
+ o.placeholder = '1000-1005';
+
+ o = s.taboption('advanced', form.Value, 'suppress_prefixlength', _('Prefix suppressor'), _('Reject routing decisions that have a prefix length less than or equal to the specified value'));
+ o.modalonly = true;
+ o.datatype = (family == 6) ? 'ip6prefix' : 'ip4prefix';
+ o.placeholder = (family == 6) ? 64 : 24;
+
+ o = s.taboption('advanced', form.Flag, 'invert', _('Invert match'), _('If set, the meaning of the match options is inverted'));
+ o.modalonly = true;
+ o.default = o.disabled;
+
+ o = s.taboption('advanced', form.Flag, 'disabled', _('Disable'));
+ o.modalonly = false;
+ o.editable = true;
o.default = o.disabled;
- o.rmempty = true;
}
return m.render();
diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js b/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js
index 3133d2725..535a133e7 100755
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js
+++ b/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js
@@ -180,8 +180,10 @@ return view.extend({
s = m.section(form.NamedSection, sid, 'switch', switch_title);
s.addremove = false;
- if (feat.vlan_option)
- s.option(form.Flag, feat.vlan_option, _('Enable VLAN functionality'));
+ if (feat.vlan_option) {
+ o = s.option(form.Flag, feat.vlan_option, _('Enable VLAN functionality'));
+ o.rmempty = false;
+ }
if (feat.learning_option) {
o = s.option(form.Flag, feat.learning_option, _('Enable learning and aging'));
@@ -222,7 +224,7 @@ return view.extend({
s.filter = function(section_id) {
var device = uci.get('network', section_id, 'device');
- return (device == switch_name);
+ return (device == this.device);
};
s.cfgsections = function() {
@@ -246,7 +248,7 @@ return view.extend({
max_vid = 0;
for (var j = 0; j < sections.length; j++) {
- if (sections[j].device != s.device)
+ if (sections[j].device != this.device)
continue;
var vlan = +sections[j].vlan,
@@ -259,7 +261,7 @@ return view.extend({
max_vid = vid;
}
- uci.set('network', section_id, 'device', s.device);
+ uci.set('network', section_id, 'device', this.device);
uci.set('network', section_id, 'vlan', max_vlan + 1);
if (feat.vid_option)
@@ -268,8 +270,6 @@ return view.extend({
return this.map.save(null, true);
};
- var port_opts = [];
-
o = s.option(form.Value, feat.vid_option || 'vlan', 'VLAN ID');
o.rmempty = false;
o.forcewrite = true;
@@ -297,21 +297,23 @@ return view.extend({
return true;
};
+ var port_opts = o.port_opts = [];
+
o.write = function(section_id, value) {
var topology = this.section.topology,
values = [];
- for (var i = 0; i < port_opts.length; i++) {
- var tagging = port_opts[i].formvalue(section_id),
+ for (var i = 0; i < this.port_opts.length; i++) {
+ var tagging = this.port_opts[i].formvalue(section_id),
portspec = Array.isArray(topology.ports) ? topology.ports[i] : null;
if (tagging == 't')
- values.push(port_opts[i].option + tagging);
+ values.push(this.port_opts[i].option + tagging);
else if (tagging == 'u')
- values.push(port_opts[i].option);
+ values.push(this.port_opts[i].option);
if (portspec && portspec.device) {
- var old_tag = port_opts[i].cfgvalue(section_id),
+ var old_tag = this.port_opts[i].cfgvalue(section_id),
old_vid = this.cfgvalue(section_id);
if (old_tag != tagging || old_vid != value) {
diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js b/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
index 5115a69eb..4c2daf588 100755
--- a/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
+++ b/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
@@ -314,24 +314,16 @@ var CBIWifiFrequencyValue = form.Value.extend({
this.channels = {
'2g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
'5g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
- '6g': [],
+ '6g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
'60g': []
};
for (var i = 0; i < data[1].length; i++) {
- var band;
-
- if (data[1][i].mhz >= 2412 && data[1][i].mhz <= 2484)
- band = '2g';
- else if (data[1][i].mhz >= 5160 && data[1][i].mhz <= 5885)
- band = '5g';
- else if (data[1][i].mhz >= 5925 && data[1][i].mhz <= 7125)
- band = '6g';
- else if (data[1][i].mhz >= 58329 && data[1][i].mhz <= 69120)
- band = '60g';
- else
+ if (!data[1][i].band)
continue;
+ var band = '%dg'.format(data[1][i].band);
+
this.channels[band].push(
data[1][i].channel,
'%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
@@ -343,10 +335,10 @@ var CBIWifiFrequencyValue = form.Value.extend({
.reduce(function(o, v) { o[v] = true; return o }, {});
this.modes = [
- '', 'Legacy', true,
+ '', 'Legacy', hwmodelist.a || hwmodelist.b || hwmodelist.g,
'n', 'N', hwmodelist.n,
- 'ac', 'AC', hwmodelist.ac,
- 'ax', 'AX', hwmodelist.ax
+ 'ac', 'AC', L.hasSystemFeature('hostapd', '11ac') && hwmodelist.ac,
+ 'ax', 'AX', L.hasSystemFeature('hostapd', '11ax') && hwmodelist.ax
];
var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
@@ -375,7 +367,8 @@ var CBIWifiFrequencyValue = form.Value.extend({
this.bands = {
'': [
'2g', '2.4 GHz', this.channels['2g'].length > 3,
- '5g', '5 GHz', this.channels['5g'].length > 3
+ '5g', '5 GHz', this.channels['5g'].length > 3,
+ '60g', '60 GHz', this.channels['60g'].length > 0
],
'n': [
'2g', '2.4 GHz', this.channels['2g'].length > 3,
@@ -386,7 +379,8 @@ var CBIWifiFrequencyValue = form.Value.extend({
],
'ax': [
'2g', '2.4 GHz', this.channels['2g'].length > 3,
- '5g', '5 GHz', this.channels['5g'].length > 3
+ '5g', '5 GHz', this.channels['5g'].length > 3,
+ '6g', '6 GHz', this.channels['6g'].length > 3
]
};
}, this));
@@ -479,7 +473,7 @@ var CBIWifiFrequencyValue = form.Value.extend({
this.toggleWifiBand(elem);
bwdt.value = htval;
- chan.value = chval || chan.options[0].value;
+ chan.value = chval || (chan.options[0] ? chan.options[0].value : 'auto');
return elem;
},
@@ -741,7 +735,8 @@ return view.extend({
load: function() {
return Promise.all([
uci.changes(),
- uci.load('wireless')
+ uci.load('wireless'),
+ uci.load('system')
]);
},
@@ -941,7 +936,7 @@ return view.extend({
if (hwtype == 'mac80211') {
o = ss.taboption('general', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'), _('Legacy or badly behaving devices may require legacy 802.11b rates to interoperate. Airtime efficiency may be significantly reduced where these are used. It is recommended to not allow 802.11b rates where possible.'));
- o.depends({'_freq': '11g', '!contains': true});
+ o.depends({'_freq': '2g', '!contains': true});
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;
@@ -985,6 +980,7 @@ return view.extend({
ss.tab('encryption', _('Wireless Security'));
ss.tab('macfilter', _('MAC-Filter'));
ss.tab('advanced', _('Advanced Settings'));
+ ss.tab('roaming', _('WLAN roaming'), _('Settings for assisting wireless clients in roaming between multiple APs: 802.11r, 802.11k and 802.11v'));
o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
o.value('ap', _('Access Point'));
@@ -1089,6 +1085,7 @@ return view.extend({
o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
o.datatype = 'macaddr';
+ o.retain = true;
o.depends('macfilter', 'allow');
o.depends('macfilter', 'deny');
o.load = function(section_id) {
@@ -1144,16 +1141,28 @@ return view.extend({
o.depends('mode', 'ap-wds');
o.default = o.enabled;
+ /* https://w1.fi/cgit/hostap/commit/?id=34f7c699a6bcb5c45f82ceb6743354ad79296078 */
+ /* multicast_to_unicast https://github.com/openwrt/openwrt/commit/7babb978ad9d7fc29acb1ff86afb1eb343af303a */
+ o = ss.taboption('advanced', form.Flag, 'multicast_to_unicast_all', _('Multi To Unicast'), _('ARP, IPv4 and IPv6 (even 802.1Q) with multicast destination MACs are unicast to the STA MAC address. Note: This is not Directed Multicast Service (DMS) in 802.11v. Note: might break receiver STA multicast expectations.'));
+ o.rmempty = true;
+
o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
o.depends('mode', 'ap');
o.depends('mode', 'ap-wds');
o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
o.optional = true;
+ o.datatype = 'netdevname';
o.placeholder = radioNet.getIfname();
if (/^radio\d+\.network/.test(o.placeholder))
o.placeholder = '';
+ var macaddr = uci.get('wireless', radioNet.getName(), 'macaddr');
+ o = ss.taboption('advanced', form.Value, 'macaddr', _('MAC address'), _('Override default MAC address - the range of usable addresses might be limited by the driver'));
+ o.value('', _('driver default (%s)').format(!macaddr ? radioNet.getActiveBSSID() : _('no override')));
+ o.value('random', _('randomly generated'));
+ o.datatype = "or('random',macaddr)";
+
o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
o.default = o.enabled;
@@ -1171,7 +1180,7 @@ return view.extend({
o.optional = true;
o.datatype = 'uinteger';
- o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
+ o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('802.11v: BSS Max Idle. Units: seconds.'));
o.optional = true;
o.placeholder = 300;
o.datatype = 'uinteger';
@@ -1274,7 +1283,7 @@ return view.extend({
if (has_hostapd || has_supplicant) {
crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
- crypto_modes.push(['psk', 'WPA-PSK', 21]);
+ crypto_modes.push(['psk', 'WPA-PSK', 12]);
}
else {
encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
@@ -1374,7 +1383,7 @@ return view.extend({
else if (hwtype == 'broadcom') {
crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
- crypto_modes.push(['psk', 'WPA-PSK', 21]);
+ crypto_modes.push(['psk', 'WPA-PSK', 12]);
crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
}
@@ -1392,51 +1401,89 @@ return view.extend({
}
- o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
+ o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'host(0)';
- o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
+ o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'port';
+ o.placeholder = '1812';
- o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
+ o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.password = true;
- o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
+ o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'host(0)';
- o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
+ o = ss.taboption('encryption', form.Value, 'acct_port', _('RADIUS Accounting Port'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'port';
+ o.placeholder = '1813';
- o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
+ o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.password = true;
- o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
+ /* extra RADIUS settings start */
+ o = ss.taboption('encryption', form.ListValue, 'dynamic_vlan', _('RADIUS Dynamic VLAN Assignment'), _('Required: Rejects auth if RADIUS server does not provide appropriate VLAN attributes.'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ o.value('0', _('Disabled'));
+ o.value('1', _('Optional'));
+ o.value('2', _('Required'));
+ o.write = function (section_id, value) {
+ return this.super('write', [section_id, (value == 0) ? null: value]);
+ }
+
+ o = ss.taboption('encryption', form.Flag, 'per_sta_vif', _('RADIUS Per STA VLAN'), _('Each STA is assigned its own AP_VLAN interface.'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+
+ //hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off
+ o = ss.taboption('encryption', form.Flag, 'vlan_naming', _('RADIUS VLAN Naming'), _('Off: 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'] });
+
+ o = ss.taboption('encryption', widgets.DeviceSelect, 'vlan_tagged_interface', _('RADIUS VLAN Tagged Interface'), _('E.g. eth0, eth1'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ o.size = 1;
+ o.rmempty = true;
+ o.multiple = false;
+ o.noaliases = true;
+ o.nocreate = true;
+ o.noinactive = true;
+
+ o = ss.taboption('encryption', form.Value, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. br-vlan
or brvlan
.'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ o.rmempty = true;
+ /* extra RADIUS settings end */
+
+ o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'host(0)';
- o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
+ o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'port';
+ o.placeholder = '3799';
- o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
+ o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.password = true;
+ //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames.
+ o = ss.taboption('encryption', form.Flag, 'rsn_preauth', _('RSN Preauth'), _('Robust Security Network (RSN): Allow roaming preauth for WPA2-EAP networks (and advertise it in WLAN beacons). Only works if the specified network interface is a bridge. Shortens the time-critical reassociation process.'));
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa2', 'wpa3', 'wpa3-mixed'] });
+
o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
o.depends('encryption', 'psk');
@@ -1500,66 +1547,117 @@ return view.extend({
// Probe 802.11r support (and EAP support as a proxy for Openwrt)
var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
- o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
+ o = ss.taboption('roaming', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
if (has_80211r)
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
o.rmempty = true;
- o = ss.taboption('encryption', form.Value, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.'));
+ o = ss.taboption('roaming', form.Value, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.depends({ ieee80211r: '1' });
o.rmempty = true;
- o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
+ o = ss.taboption('roaming', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
o.depends({ ieee80211r: '1' });
o.placeholder = '4f57';
o.datatype = 'and(hexstring,length(4))';
o.rmempty = true;
- o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
+ o = ss.taboption('roaming', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
o.depends({ ieee80211r: '1' });
o.placeholder = '1000';
o.datatype = 'range(1000,65535)';
o.rmempty = true;
- o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
+ o = ss.taboption('roaming', form.ListValue, 'ft_over_ds', _('FT protocol'));
o.depends({ ieee80211r: '1' });
- o.value('1', _('FT over DS'));
o.value('0', _('FT over the Air'));
+ o.value('1', _('FT over DS'));
o.rmempty = true;
- o = ss.taboption('encryption', form.Flag, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.'));
+ o = ss.taboption('roaming', form.Flag, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.'));
o.depends({ ieee80211r: '1' });
o.default = o.enabled;
o.rmempty = false;
- o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
+ o = ss.taboption('roaming', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
o.depends({ ieee80211r: '1' });
o.placeholder = '10000';
o.datatype = 'uinteger';
o.rmempty = true;
- o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
+ o = ss.taboption('roaming', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
o.depends({ ieee80211r: '1' });
o.placeholder = '00004f577274';
o.datatype = 'and(hexstring,length(12))';
o.rmempty = true;
- o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
+ o = ss.taboption('roaming', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
o.depends({ ieee80211r: '1' });
o.placeholder = '0';
o.rmempty = true;
- o = ss.taboption('encryption', form.DynamicList, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain.
Format: MAC-address,NAS-Identifier,128-bit key as hex string.
This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.'));
+ o = ss.taboption('roaming', form.DynamicList, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain.
Format: MAC-address,NAS-Identifier,256-bit key as hex string.
This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.'));
o.depends({ ieee80211r: '1' });
o.rmempty = true;
- o = ss.taboption('encryption', form.DynamicList, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain.
Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string.
This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.'));
+ o = ss.taboption('roaming', form.DynamicList, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain.
Format: MAC-address,R1KH-ID as 6 octets with colons,256-bit key as hex string.
This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.'));
o.depends({ ieee80211r: '1' });
o.rmempty = true;
// End of 802.11r options
+ // Probe 802.11k and 802.11v support via EAP support (full hostapd has EAP)
+ if (L.hasSystemFeature('hostapd', 'eap')) {
+ /* 802.11k settings start */ o =
+ ss.taboption('roaming', form.Flag, 'ieee80211k', _('802.11k RRM'), _('Radio Resource Measurement - Sends beacons to assist roaming. Not all clients support this.'));
+ // add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
+ o.depends('mode', 'ap');
+ o.depends('mode', 'ap-wds');
+
+ o = ss.taboption('roaming', form.Flag, 'rrm_neighbor_report', _('Neighbour Report'), _('802.11k: Enable neighbor report via radio measurements.'));
+ o.depends({ ieee80211k: '1' });
+ o.default = o.enabled;
+
+ o = ss.taboption('roaming', form.Flag, 'rrm_beacon_report', _('Beacon Report'), _('802.11k: Enable beacon report via radio measurements.'));
+ o.depends({ ieee80211k: '1' });
+ o.default = o.enabled;
+ /* 802.11k settings end */
+
+ /* 802.11v settings start */
+ o = ss.taboption('roaming', form.ListValue, 'time_advertisement', _('Time advertisement'), _('802.11v: Time Advertisement in management frames.'));
+ o.value('0', _('Disabled'));
+ o.value('2', _('Enabled'));
+ o.write = function (section_id, value) {
+ return this.super('write', [section_id, (value == 2) ? value: null]);
+ }
+
+ //Pull current System TZ setting
+ var tz = uci.get('system', '@system[0]', 'timezone');
+ o = ss.taboption('roaming', form.Value, 'time_zone', _('Time zone'), _('802.11v: Local Time Zone Advertisement in management frames.'));
+ o.value(tz);
+ o.rmempty = true;
+
+ o = ss.taboption('roaming', form.Flag, 'wnm_sleep_mode', _('WNM Sleep Mode'), _('802.11v: Wireless Network Management (WNM) Sleep Mode (extended sleep mode for stations).'));
+ o.rmempty = true;
+
+ /* wnm_sleep_mode_no_keys: https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=bf98faaac8ed24cf7d3d93dd4fcd7304d109363b */
+ o = ss.taboption('roaming', form.Flag, 'wnm_sleep_mode_no_keys', _('WNM Sleep Mode Fixes'), _('802.11v: Wireless Network Management (WNM) Sleep Mode Fixes: Prevents reinstallation attacks.'));
+ o.rmempty = true;
+
+ o = ss.taboption('roaming', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.'));
+ o.rmempty = true;
+
+ /* in master, but not 21.02.1: proxy_arp */
+ o = ss.taboption('roaming', form.Flag, 'proxy_arp', _('ProxyARP'), _('802.11v: Proxy ARP enables non-AP STA to remain in power-save for longer.'));
+ o.rmempty = true;
+
+ /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */
+ }
+ /* 802.11v settings end */
+ }
+
+ if (hwtype == 'mac80211') {
o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
o.value('tls', 'TLS');
o.value('ttls', 'TTLS');
@@ -1676,7 +1774,7 @@ return view.extend({
if (hwtype == 'mac80211') {
// ieee802.11w options
o = ss.taboption('encryption', form.ListValue, 'ieee80211w', _('802.11w Management Frame Protection'), _("Note: Some wireless drivers do not fully support 802.11w. E.g. mwlwifi may have problems"));
- o.value('', _('Disabled'));
+ o.value('0', _('Disabled'));
o.value('1', _('Optional'));
o.value('2', _('Required'));
add_dependency_permutations(o, { mode: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
@@ -1684,7 +1782,14 @@ return view.extend({
o.defaults = {
'2': [{ encryption: 'sae' }, { encryption: 'owe' }, { encryption: 'wpa3' }, { encryption: 'wpa3-mixed' }],
'1': [{ encryption: 'sae-mixed'}],
- '': []
+ '0': []
+ };
+
+ o.write = function(section_id, value) {
+ if (value != this.default)
+ return form.ListValue.prototype.write.call(this, section_id, value);
+ else
+ return form.ListValue.prototype.remove.call(this, section_id);
};
o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
@@ -1956,6 +2061,8 @@ return view.extend({
});
});
}).then(L.bind(function() {
+ ui.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
+
return this.renderMoreOptionsModal(section_id);
}, this));
};
@@ -2157,5 +2264,7 @@ return view.extend({
return E([ nodes, E('h3', _('Associated Stations')), table ]);
}, this, m));
- }
+ },
+
+ handleReset: null
});
diff --git a/luci-mod-network/root/usr/share/luci/menu.d/luci-mod-network.json b/luci-mod-network/root/usr/share/luci/menu.d/luci-mod-network.json
index 188c695f3..2fa3cf6ab 100755
--- a/luci-mod-network/root/usr/share/luci/menu.d/luci-mod-network.json
+++ b/luci-mod-network/root/usr/share/luci/menu.d/luci-mod-network.json
@@ -46,35 +46,9 @@
}
},
- "admin/network/dhcp": {
- "title": "DHCP and DNS",
- "order": 30,
- "action": {
- "type": "view",
- "path": "network/dhcp"
- },
- "depends": {
- "acl": [ "luci-mod-network-dhcp" ],
- "uci": { "dhcp": true }
- }
- },
-
- "admin/network/hosts": {
- "title": "Hostnames",
- "order": 40,
- "action": {
- "type": "view",
- "path": "network/hosts"
- },
- "depends": {
- "acl": [ "luci-mod-network-dhcp" ],
- "uci": { "dhcp": true }
- }
- },
-
"admin/network/routes": {
- "title": "Static Routes",
- "order": 50,
+ "title": "Routing",
+ "order": 30,
"action": {
"type": "view",
"path": "network/routes"
@@ -84,9 +58,23 @@
}
},
+ "admin/network/dhcp": {
+ "title": "DHCP and DNS",
+ "order": 40,
+ "action": {
+ "type": "view",
+ "path": "network/dhcp"
+ },
+ "depends": {
+ "acl": [ "luci-mod-network-dhcp" ],
+ "fs": { "/usr/sbin/dnsmasq": "executable" },
+ "uci": { "dhcp": true }
+ }
+ },
+
"admin/network/diagnostics": {
"title": "Diagnostics",
- "order": 60,
+ "order": 50,
"action": {
"type": "view",
"path": "network/diagnostics"
diff --git a/luci-mod-network/root/usr/share/rpcd/acl.d/luci-mod-network.json b/luci-mod-network/root/usr/share/rpcd/acl.d/luci-mod-network.json
index 6943d9563..b377f395f 100755
--- a/luci-mod-network/root/usr/share/rpcd/acl.d/luci-mod-network.json
+++ b/luci-mod-network/root/usr/share/rpcd/acl.d/luci-mod-network.json
@@ -8,7 +8,9 @@
"/proc/sys/net/ipv6/conf/*/mtu": [ "read" ],
"/proc/sys/net/ipv6/conf/*/hop_limit": [ "read" ],
"/usr/libexec/luci-peeraddr": [ "exec" ],
- "/usr/lib/opkg/info/netifd.control": [ "read" ]
+ "/usr/lib/opkg/info/netifd.control": [ "read" ],
+ "/proc/sys/net/ipv[46]/conf/*": [ "read" ],
+ "/sys/class/net/*/brport/*": [ "read" ]
},
"ubus": {
"file": [ "exec" ],
@@ -58,7 +60,8 @@
"/usr/bin/ping": [ "exec" ],
"/usr/bin/ping6": [ "exec", "list" ],
"/usr/bin/traceroute": [ "exec" ],
- "/usr/bin/traceroute6": [ "exec", "list" ]
+ "/usr/bin/traceroute6": [ "exec", "list" ],
+ "/usr/bin/arp-scan": [ "exec", "list" ]
},
"ubus": {
"file": [ "exec", "stat" ]
diff --git a/netifd/Makefile b/netifd/Makefile
index 826889aca..2a9d4cc1b 100755
--- a/netifd/Makefile
+++ b/netifd/Makefile
@@ -5,9 +5,12 @@ PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL=$(PROJECT_GIT)/project/netifd.git
-PKG_SOURCE_DATE:=2023-11-10
-PKG_SOURCE_VERSION:=35facc8306f590a7330789ab6d5785c0d43073ef
-PKG_MIRROR_HASH:=4f73591ae1873e18df235349e478f2196ca0d3123c313a04149dc9d5e2bfb403
+#PKG_SOURCE_DATE:=2023-11-20
+#PKG_SOURCE_VERSION:=f3e06e81b347bbdec1c6c71603328b6e442728d4
+#PKG_MIRROR_HASH:=f16dd61aede5597fd7b5ee8e7752a916494281bc981b35c16e788ddb7409584a
+PKG_SOURCE_DATE:=2023-11-14
+PKG_SOURCE_VERSION:=8587c074f1eb2064c42adb0a6aa5073f695ab89d
+PKG_MIRROR_HASH:=f5ceb771badd7a23cceb53537299580d4b483e2b3ec5de09b9c3c54692893dd9
PKG_MAINTAINER:=Felix Fietkau
PKG_LICENSE:=GPL-2.0
@@ -21,6 +24,7 @@ include $(INCLUDE_DIR)/cmake.mk
define Package/netifd
SECTION:=base
CATEGORY:=Base system
+# DEPENDS:=+libuci +libnl-tiny +libubus +ubus +ubusd +jshn +libubox +libudebug
DEPENDS:=+libuci +libnl-tiny +libubus +ubus +ubusd +jshn +libubox
TITLE:=OpenWrt Network Interface Configuration Daemon
endef
diff --git a/netifd/patches/001-defin-RTN_POLICY_FAILED.patch b/netifd/patches/001-defin-RTN_POLICY_FAILED.patch
new file mode 100644
index 000000000..d7b530393
--- /dev/null
+++ b/netifd/patches/001-defin-RTN_POLICY_FAILED.patch
@@ -0,0 +1,12 @@
+--- a/system-linux.c 2023-11-24 16:49:37.715537192 +0100
++++ b/system-linux.c 2023-11-24 16:50:23.002742488 +0100
+@@ -53,6 +53,9 @@
+ #ifndef RTN_FAILED_POLICY
+ #define RTN_FAILED_POLICY 12
+ #endif
++#ifndef RTN_POLICY_FAILED
++#define RTN_POLICY_FAILED 12
++#endif
+
+ #ifndef IFA_F_NOPREFIXROUTE
+ #define IFA_F_NOPREFIXROUTE 0x200
diff --git a/r8152/patches/020-6.1-support.patch b/r8152/patches/020-6.1-support.patch
deleted file mode 100755
index 756aba51f..000000000
--- a/r8152/patches/020-6.1-support.patch
+++ /dev/null
@@ -1,38 +0,0 @@
---- a/compatibility.h
-+++ b/compatibility.h
-@@ -237,9 +237,15 @@
- #define napi_disable(napi_ptr) netif_poll_disable(container_of(napi_ptr, struct r8152, napi)->netdev)
- #define napi_schedule(napi_ptr) netif_rx_schedule(container_of(napi_ptr, struct r8152, napi)->netdev)
- #define napi_complete(napi_ptr) netif_rx_complete(container_of(napi_ptr, struct r8152, napi)->netdev)
-+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
-+ #define netif_napi_add_weight(ndev, napi_ptr, function, weight_t) \
-+ ndev->poll = function; \
-+ ndev->weight = weight_t;
-+#else
- #define netif_napi_add(ndev, napi_ptr, function, weight_t) \
- ndev->poll = function; \
- ndev->weight = weight_t;
-+#endif
- typedef unsigned long uintptr_t;
- #define DMA_BIT_MASK(value) \
- (value < 64 ? ((1ULL << value) - 1) : 0xFFFFFFFFFFFFFFFFULL)
---- a/r8152.c
-+++ b/r8152.c
-@@ -20718,10 +20718,17 @@
-
- usb_set_intfdata(intf, tp);
-
-+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
-+ if (tp->support_2500full)
-+ netif_napi_add_weight(netdev, &tp->napi, r8152_poll, 256);
-+ else
-+ netif_napi_add_weight(netdev, &tp->napi, r8152_poll, 64);
-+#else
- if (tp->support_2500full)
- netif_napi_add(netdev, &tp->napi, r8152_poll, 256);
- else
- netif_napi_add(netdev, &tp->napi, r8152_poll, 64);
-+#endif
-
- ret = register_netdev(netdev);
- if (ret != 0) {