summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-mod-network
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2021-07-03 18:54:14 +0200
committerJo-Philipp Wich <jo@mein.io>2021-07-03 18:54:14 +0200
commit7e562895380d6bbe89ed88fe7576272c078f3b59 (patch)
tree7fe8c17e7d75a76ed950e4ccbd25a1f1c6aaef56 /modules/luci-mod-network
parent91f46d765b36825fcfe63e2b7d4863de00cdc69d (diff)
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>
Diffstat (limited to 'modules/luci-mod-network')
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js121
1 files changed, 115 insertions, 6 deletions
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
index c5700a822c..a9a5ec85ee 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
@@ -5,6 +5,7 @@
'require rpc';
'require uci';
'require form';
+'require network';
'require validation';
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) {
if (s == null || s == '')
return true;
@@ -138,20 +191,58 @@ 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(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({
load: function() {
return Promise.all([
callHostHints(),
- callDUIDHints()
+ callDUIDHints(),
+ getDHCPPools()
]);
},
- 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],
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'));
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.datatype = 'list(unique(macaddr))';
+ so.datatype = 'list(macaddr)';
so.rmempty = true;
so.cfgvalue = function(section) {
var macs = L.toArray(uci.get('dhcp', section, 'mac')),
@@ -468,6 +559,7 @@ 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);
@@ -484,7 +576,24 @@ return view.extend({
if ((m == null || m == '') && (n == null || n == ''))
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 = {};