From 7e562895380d6bbe89ed88fe7576272c078f3b59 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sat, 3 Jul 2021 18:54:14 +0200 Subject: [PATCH] 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 --- .../resources/view/network/dhcp.js | 121 +++++++++++++++++- 1 file 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 DHCP-Server and DNS-Forwarder for NAT firewalls')); s = m.section(form.TypedSection, 'dnsmasq', _('Server Settings')); @@ -429,7 +520,7 @@ return view.extend({ }; so = ss.option(form.Value, 'mac', _('MAC-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 = {}; -- 2.30.2