luci-mod-network: improve static DHCP lease validation
- Ensure that MAC addresses are unique within the same pool
- Ensure that IP addresses are globally unique
- Ensure that IP addresses are within any DHCP pool range
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
(cherry picked from commit 7e56289538
)
This commit is contained in:
parent
e3ef035260
commit
e8b8a9be64
1 changed files with 115 additions and 6 deletions
|
@ -5,6 +5,7 @@
|
||||||
'require rpc';
|
'require rpc';
|
||||||
'require uci';
|
'require uci';
|
||||||
'require form';
|
'require form';
|
||||||
|
'require network';
|
||||||
'require validation';
|
'require validation';
|
||||||
|
|
||||||
var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus, CBILease6Status;
|
var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus, CBILease6Status;
|
||||||
|
@ -65,6 +66,58 @@ CBILease6Status = form.DummyValue.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function calculateNetwork(addr, mask) {
|
||||||
|
addr = validation.parseIPv4(addr);
|
||||||
|
|
||||||
|
if (!isNaN(mask))
|
||||||
|
mask = validation.parseIPv4(network.prefixToMask(+mask));
|
||||||
|
else
|
||||||
|
mask = validation.parseIPv4(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.getIPAddrs()[0] || '').split('/');
|
||||||
|
|
||||||
|
if (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) {
|
function validateHostname(sid, s) {
|
||||||
if (s == null || s == '')
|
if (s == null || s == '')
|
||||||
return true;
|
return true;
|
||||||
|
@ -138,20 +191,58 @@ function validateServerSpec(sid, s) {
|
||||||
return true;
|
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(uci.get('dhcp', 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({
|
return view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
callHostHints(),
|
callHostHints(),
|
||||||
callDUIDHints()
|
callDUIDHints(),
|
||||||
|
getDHCPPools()
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(hosts_duids) {
|
render: function(hosts_duids_pools) {
|
||||||
var has_dhcpv6 = L.hasSystemFeature('dnsmasq', 'dhcpv6') || L.hasSystemFeature('odhcpd'),
|
var has_dhcpv6 = L.hasSystemFeature('dnsmasq', 'dhcpv6') || L.hasSystemFeature('odhcpd'),
|
||||||
hosts = hosts_duids[0],
|
hosts = hosts_duids_pools[0],
|
||||||
duids = hosts_duids[1],
|
duids = hosts_duids_pools[1],
|
||||||
|
pools = hosts_duids_pools[2],
|
||||||
m, s, o, ss, so;
|
m, s, o, ss, so;
|
||||||
|
|
||||||
|
console.debug(pools);
|
||||||
|
|
||||||
m = new form.Map('dhcp', _('DHCP and DNS'), _('Dnsmasq is a combined <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-Server and <abbr title="Domain Name System">DNS</abbr>-Forwarder for <abbr title="Network Address Translation">NAT</abbr> firewalls'));
|
m = new form.Map('dhcp', _('DHCP and DNS'), _('Dnsmasq is a combined <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-Server and <abbr title="Domain Name System">DNS</abbr>-Forwarder for <abbr title="Network Address Translation">NAT</abbr> firewalls'));
|
||||||
|
|
||||||
s = m.section(form.TypedSection, 'dnsmasq', _('Server Settings'));
|
s = m.section(form.TypedSection, 'dnsmasq', _('Server Settings'));
|
||||||
|
@ -429,7 +520,7 @@ return view.extend({
|
||||||
};
|
};
|
||||||
|
|
||||||
so = ss.option(form.Value, 'mac', _('<abbr title="Media Access Control">MAC</abbr>-Address'));
|
so = ss.option(form.Value, 'mac', _('<abbr title="Media Access Control">MAC</abbr>-Address'));
|
||||||
so.datatype = 'list(unique(macaddr))';
|
so.datatype = 'list(macaddr)';
|
||||||
so.rmempty = true;
|
so.rmempty = true;
|
||||||
so.cfgvalue = function(section) {
|
so.cfgvalue = function(section) {
|
||||||
var macs = L.toArray(uci.get('dhcp', section, 'mac')),
|
var macs = L.toArray(uci.get('dhcp', section, 'mac')),
|
||||||
|
@ -468,6 +559,7 @@ return view.extend({
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
so.validate = validateMACAddr.bind(so, pools);
|
||||||
Object.keys(hosts).forEach(function(mac) {
|
Object.keys(hosts).forEach(function(mac) {
|
||||||
var hint = hosts[mac].name || L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0];
|
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.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
|
||||||
|
@ -484,7 +576,24 @@ return view.extend({
|
||||||
if ((m == null || m == '') && (n == null || n == ''))
|
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 = {};
|
var ipaddrs = {};
|
||||||
|
|
Loading…
Reference in a new issue