diff --git a/APIDOCS.md b/APIDOCS.md index 6ac32958..bbb3dbfb 100644 --- a/APIDOCS.md +++ b/APIDOCS.md @@ -5898,6 +5898,13 @@ RESPONSE: "comments": "comments" } ], + "hostNameOverrides": [ + { + "hostName": "my-host", + "hardwareAddress": "00-00-00-00-00-01", + "comments": "comments" + } + ], "allowOnlyReservedLeases": false, "blockLocallyAdministeredMacAddresses": true, "ignoreClientIdentifierOption": true @@ -5957,6 +5964,7 @@ WHERE: - `genericOptions` (optional): This feature allows you to define DHCP options that are not yet directly supported. Use a `|` separated list of DHCP option code defined for it and the value in either a colon (:) separated hex string or a normal hex string in format `{option-code}|{hex-string-value}`. - `exclusions` (optional): A `|` separated list of IP address range in format `{starting address}|{ending address}` that must be excluded or not assigned dynamically to any client by the DHCP server. - `reservedLeases` (optional): A `|` separated list of reserved IP addresses in format `{host name}|{MAC address}|{reserved IP address}|{comments}` to be assigned to specific clients based on their MAC address. +- `hostNameOverrides` (optional): A `|` separated list of host name overrides in format `{host name}|{MAC address}|{comments}` to override the DNS hostname for specific clients based on their MAC address without reserving a static IP address for them. - `allowOnlyReservedLeases` (optional): Set this parameter to `true` to stop dynamic IP address allocation and allocate only reserved IP addresses. - `blockLocallyAdministeredMacAddresses` (optional): Set this parameter to `true` to stop dynamic IP address allocation for clients with locally administered MAC addresses. MAC address with 0x02 bit set in the first octet indicate a locally administered MAC address which usually means that the device is not using its original MAC address. - `ignoreClientIdentifierOption` (optional): Set this parameter to `true` to always use the client's MAC address as the identifier to allocate lease instead of the Client Identifier (Option 61) provided by the client in the request. Changing this option may cause the existing clients to get a different IP lease on renewal. @@ -6024,6 +6032,60 @@ RESPONSE: } ``` +### Add Host Name Override + +Adds a host name override entry to the specified scope. Unlike a reserved lease, this only overrides the DNS hostname for the client based on its MAC address; the client still receives a normal dynamically allocated IP address lease. + +URL:\ +`http://localhost:5380/api/dhcp/scopes/addHostNameOverride?name=Default&hardwareAddress=00:00:00:00:00:00&hostName=my-host` + +PERMISSIONS:\ +DhcpServer: Modify + +HEADERS: +- Authorization: Bearer + +WHERE: +- `token`: The session token generated by the `login` or the `createToken` call. +- `name`: The name of the DHCP scope. +- `hardwareAddress`: The MAC address of the client. +- `hostName`: The hostname of the client to override. +- `comments` (optional): Comments for the host name override entry. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### Remove Host Name Override + +Removes a host name override entry from the specified scope. + +URL:\ +`http://localhost:5380/api/dhcp/scopes/removeHostNameOverride?name=Default&hardwareAddress=00:00:00:00:00:00` + +PERMISSIONS:\ +DhcpServer: Modify + +HEADERS: +- Authorization: Bearer + +WHERE: +- `token`: The session token generated by the `login` or the `createToken` call. +- `name`: The name of the DHCP scope. +- `hardwareAddress`: The MAC address of the client. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + ### Enable DHCP Scope Enables the DHCP scope allowing the server to allocate leases. diff --git a/DnsServerCore/Dhcp/DhcpServer.cs b/DnsServerCore/Dhcp/DhcpServer.cs index f7806d22..9a0dbff3 100644 --- a/DnsServerCore/Dhcp/DhcpServer.cs +++ b/DnsServerCore/Dhcp/DhcpServer.cs @@ -312,10 +312,8 @@ private async Task ProcessDhcpMessageAsync(DhcpMessage request, IPE if (!string.IsNullOrWhiteSpace(scope.DomainName)) { - //get override host name from reserved lease - Lease reservedLease = scope.GetReservedLease(request); - if (reservedLease is not null) - reservedLeaseHostName = reservedLease.HostName; + //get override host name from reserved lease or host name override + reservedLeaseHostName = GetOverrideHostName(scope, request); } List options = await scope.GetOptionsAsync(request, serverIdentifierAddress, reservedLeaseHostName, _dnsServer); @@ -443,10 +441,8 @@ private async Task ProcessDhcpMessageAsync(DhcpMessage request, IPE if (!string.IsNullOrWhiteSpace(scope.DomainName)) { - //get override host name from reserved lease - Lease reservedLease = scope.GetReservedLease(request); - if (reservedLease is not null) - reservedLeaseHostName = reservedLease.HostName; + //get override host name from reserved lease or host name override + reservedLeaseHostName = GetOverrideHostName(scope, request); } List options = await scope.GetOptionsAsync(request, serverIdentifierAddress, reservedLeaseHostName, _dnsServer); @@ -713,6 +709,19 @@ internal static string GetSanitizedHostName(string hostname) return sb.ToString(); } + private static string GetOverrideHostName(Scope scope, DhcpMessage request) + { + Lease reservedLease = scope.GetReservedLease(request); + if (reservedLease is not null) + return reservedLease.HostName; + + HostNameOverride hostNameOverride = scope.GetHostNameOverride(request); + if (hostNameOverride is not null) + return hostNameOverride.HostName; + + return null; + } + internal void AddDnsEntries(Scope scope, Lease lease) { if (lease.Type == LeaseType.Reserved) diff --git a/DnsServerCore/Dhcp/HostNameOverride.cs b/DnsServerCore/Dhcp/HostNameOverride.cs new file mode 100644 index 00000000..3dcc5939 --- /dev/null +++ b/DnsServerCore/Dhcp/HostNameOverride.cs @@ -0,0 +1,121 @@ +/* +Technitium DNS Server +Copyright (C) 2026 Shreyas Zare (shreyas@technitium.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +using DnsServerCore.Dhcp.Options; +using System.IO; +using TechnitiumLibrary.IO; + +namespace DnsServerCore.Dhcp +{ + public class HostNameOverride + { + #region variables + + readonly ClientIdentifierOption _clientIdentifier; + string _hostName; + readonly byte[] _hardwareAddress; + string _comments; + + #endregion + + #region constructor + + internal HostNameOverride(string hostName, DhcpMessageHardwareAddressType hardwareAddressType, byte[] hardwareAddress, string comments) + { + _clientIdentifier = new ClientIdentifierOption((byte)hardwareAddressType, hardwareAddress); + _hostName = hostName; + _hardwareAddress = hardwareAddress; + _comments = comments; + } + + internal HostNameOverride(string hostName, DhcpMessageHardwareAddressType hardwareAddressType, string hardwareAddress, string comments) + : this(hostName, hardwareAddressType, Lease.ParseHardwareAddress(hardwareAddress), comments) + { } + + internal HostNameOverride(BinaryReader bR) + { + byte version = bR.ReadByte(); + switch (version) + { + case 1: + _clientIdentifier = DhcpOption.Parse(bR.BaseStream) as ClientIdentifierOption; + _clientIdentifier.ParseOptionValue(); + + _hostName = bR.BaseStream.ReadShortString(); + if (string.IsNullOrWhiteSpace(_hostName)) + _hostName = null; + + _hardwareAddress = bR.ReadBuffer(); + + _comments = bR.BaseStream.ReadShortString(); + if (string.IsNullOrWhiteSpace(_comments)) + _comments = null; + + break; + + default: + throw new InvalidDataException("HostNameOverride data format version not supported."); + } + } + + #endregion + + #region public + + public void WriteTo(BinaryWriter bW) + { + bW.Write((byte)1); //version + + _clientIdentifier.WriteTo(bW.BaseStream); + + if (string.IsNullOrWhiteSpace(_hostName)) + bW.Write((byte)0); + else + bW.BaseStream.WriteShortString(_hostName); + + bW.WriteBuffer(_hardwareAddress); + + if (string.IsNullOrWhiteSpace(_comments)) + bW.Write((byte)0); + else + bW.BaseStream.WriteShortString(_comments); + } + + #endregion + + #region properties + + internal ClientIdentifierOption ClientIdentifier + { get { return _clientIdentifier; } } + + public string HostName + { get { return _hostName; } } + + public byte[] HardwareAddress + { get { return _hardwareAddress; } } + + public string Comments + { + get { return _comments; } + set { _comments = value; } + } + + #endregion + } +} diff --git a/DnsServerCore/Dhcp/Scope.cs b/DnsServerCore/Dhcp/Scope.cs index 4486dd56..739d9501 100644 --- a/DnsServerCore/Dhcp/Scope.cs +++ b/DnsServerCore/Dhcp/Scope.cs @@ -84,6 +84,7 @@ public sealed class Scope : IComparable, IDisposable IReadOnlyCollection _genericOptions; IReadOnlyCollection _exclusions; readonly ConcurrentDictionary _reservedLeases = new ConcurrentDictionary(); + readonly ConcurrentDictionary _hostNameOverrides = new ConcurrentDictionary(); bool _allowOnlyReservedLeases; bool _blockLocallyAdministeredMacAddresses; bool _ignoreClientIdentifierOption; @@ -144,6 +145,7 @@ public Scope(Stream s, LogManager log, DhcpServer dhcpServer) case 8: case 9: case 10: + case 11: _name = s.ReadShortString(); _enabled = bR.ReadBoolean(); @@ -400,6 +402,19 @@ public Scope(Stream s, LogManager log, DhcpServer dhcpServer) } } + if (version >= 11) + { + int count = bR.ReadInt32(); + if (count > 0) + { + for (int i = 0; i < count; i++) + { + HostNameOverride hostNameOverride = new HostNameOverride(bR); + _hostNameOverrides.TryAdd(hostNameOverride.ClientIdentifier, hostNameOverride); + } + } + } + break; default: @@ -917,6 +932,16 @@ internal Lease GetReservedLease(DhcpMessage request) return GetReservedLease(new ClientIdentifierOption((byte)request.HardwareAddressType, request.ClientHardwareAddress), request.GetClientIdentifier(_ignoreClientIdentifierOption)); } + internal HostNameOverride GetHostNameOverride(DhcpMessage request) + { + ClientIdentifierOption hostNameOverridesClientIdentifier = new ClientIdentifierOption((byte)request.HardwareAddressType, request.ClientHardwareAddress); + + if (_hostNameOverrides.TryGetValue(hostNameOverridesClientIdentifier, out HostNameOverride hostNameOverride)) + return hostNameOverride; + + return null; + } + private Lease GetReservedLease(ClientIdentifierOption reservedLeasesClientIdentifier, ClientIdentifierOption clientIdentifier) { if (_reservedLeases.TryGetValue(reservedLeasesClientIdentifier, out Lease reservedLease)) @@ -1502,6 +1527,19 @@ public bool TryRemoveReservedLease(string hardwareAddress) return false; } + public bool TryAddHostNameOverride(HostNameOverride hostNameOverride) + { + return _hostNameOverrides.TryAdd(hostNameOverride.ClientIdentifier, hostNameOverride); + } + + public bool TryRemoveHostNameOverride(string hardwareAddress) + { + byte[] hardwareAddressBytes = Lease.ParseHardwareAddress(hardwareAddress); + ClientIdentifierOption hostNameOverrideClientIdentifier = new ClientIdentifierOption((byte)DhcpMessageHardwareAddressType.Ethernet, hardwareAddressBytes); + + return _hostNameOverrides.TryRemove(hostNameOverrideClientIdentifier, out _); + } + public Lease RemoveLease(string hardwareAddress) { byte[] hardwareAddressBytes = Lease.ParseHardwareAddress(hardwareAddress); @@ -1603,7 +1641,7 @@ public void WriteTo(Stream s) BinaryWriter bW = new BinaryWriter(s); bW.Write(Encoding.ASCII.GetBytes("SC")); - bW.Write((byte)10); //version + bW.Write((byte)11); //version s.WriteShortString(_name); bW.Write(_enabled); @@ -1809,6 +1847,13 @@ public void WriteTo(Stream s) foreach (KeyValuePair lease in _leases) lease.Value.WriteTo(bW); } + + { + bW.Write(_hostNameOverrides.Count); + + foreach (KeyValuePair hostNameOverride in _hostNameOverrides) + hostNameOverride.Value.WriteTo(bW); + } } public override bool Equals(object obj) @@ -2209,6 +2254,30 @@ public IReadOnlyCollection ReservedLeases } } + public IReadOnlyCollection HostNameOverrides + { + get + { + List hostNameOverrides = new List(_hostNameOverrides.Count); + + foreach (KeyValuePair entry in _hostNameOverrides) + hostNameOverrides.Add(entry.Value); + + hostNameOverrides.Sort((a, b) => string.Compare(a.HostName, b.HostName, StringComparison.OrdinalIgnoreCase)); + return hostNameOverrides; + } + set + { + _hostNameOverrides.Clear(); + + if ((value is not null) && (value.Count > 0)) + { + foreach (HostNameOverride hostNameOverride in value) + _hostNameOverrides.TryAdd(hostNameOverride.ClientIdentifier, hostNameOverride); + } + } + } + public bool AllowOnlyReservedLeases { get { return _allowOnlyReservedLeases; } diff --git a/DnsServerCore/DnsWebService.cs b/DnsServerCore/DnsWebService.cs index 5833dbef..5c6de6f8 100644 --- a/DnsServerCore/DnsWebService.cs +++ b/DnsServerCore/DnsWebService.cs @@ -2144,6 +2144,8 @@ private void ConfigureWebServiceRoutes() _webService.MapGetAndPost("/api/dhcp/scopes/set", _dhcpApi.SetDhcpScopeAsync); _webService.MapGetAndPost("/api/dhcp/scopes/addReservedLease", _dhcpApi.AddReservedLease); _webService.MapGetAndPost("/api/dhcp/scopes/removeReservedLease", _dhcpApi.RemoveReservedLease); + _webService.MapGetAndPost("/api/dhcp/scopes/addHostNameOverride", _dhcpApi.AddHostNameOverride); + _webService.MapGetAndPost("/api/dhcp/scopes/removeHostNameOverride", _dhcpApi.RemoveHostNameOverride); _webService.MapGetAndPost("/api/dhcp/scopes/enable", _dhcpApi.EnableDhcpScopeAsync); _webService.MapGetAndPost("/api/dhcp/scopes/disable", _dhcpApi.DisableDhcpScope); _webService.MapGetAndPost("/api/dhcp/scopes/delete", _dhcpApi.DeleteDhcpScope); diff --git a/DnsServerCore/WebServiceDhcpApi.cs b/DnsServerCore/WebServiceDhcpApi.cs index 5d228cb6..13b49a54 100644 --- a/DnsServerCore/WebServiceDhcpApi.cs +++ b/DnsServerCore/WebServiceDhcpApi.cs @@ -367,6 +367,22 @@ public void GetDhcpScope(HttpContext context) jsonWriter.WriteEndArray(); + jsonWriter.WritePropertyName("hostNameOverrides"); + jsonWriter.WriteStartArray(); + + foreach (HostNameOverride hostNameOverride in scope.HostNameOverrides) + { + jsonWriter.WriteStartObject(); + + jsonWriter.WriteString("hostName", hostNameOverride.HostName); + jsonWriter.WriteString("hardwareAddress", BitConverter.ToString(hostNameOverride.HardwareAddress)); + jsonWriter.WriteString("comments", hostNameOverride.Comments); + + jsonWriter.WriteEndObject(); + } + + jsonWriter.WriteEndArray(); + jsonWriter.WriteBoolean("allowOnlyReservedLeases", scope.AllowOnlyReservedLeases); jsonWriter.WriteBoolean("blockLocallyAdministeredMacAddresses", scope.BlockLocallyAdministeredMacAddresses); jsonWriter.WriteBoolean("ignoreClientIdentifierOption", scope.IgnoreClientIdentifierOption); @@ -641,6 +657,25 @@ public async Task SetDhcpScopeAsync(HttpContext context) } } + string strHostNameOverrides = request.QueryOrForm("hostNameOverrides"); + if (strHostNameOverrides is not null) + { + if (strHostNameOverrides.Length == 0) + { + scope.HostNameOverrides = null; + } + else + { + string[] strHostNameOverrideParts = strHostNameOverrides.Split('|'); + List hostNameOverrides = new List(); + + for (int i = 0; i < strHostNameOverrideParts.Length; i += 3) + hostNameOverrides.Add(new HostNameOverride(strHostNameOverrideParts[i + 0], DhcpMessageHardwareAddressType.Ethernet, strHostNameOverrideParts[i + 1], strHostNameOverrideParts[i + 2])); + + scope.HostNameOverrides = hostNameOverrides; + } + } + if (request.TryGetQueryOrForm("allowOnlyReservedLeases", bool.Parse, out bool allowOnlyReservedLeases)) scope.AllowOnlyReservedLeases = allowOnlyReservedLeases; @@ -726,6 +761,60 @@ public void RemoveReservedLease(HttpContext context) _dnsWebService._log.Write(_dnsWebService.GetRemoteEndPoint(context), "[" + sessionUser.Username + "] DHCP scope reserved lease was removed successfully: " + scopeName); } + public void AddHostNameOverride(HttpContext context) + { + User sessionUser = _dnsWebService.GetSessionUser(context); + + if (!_dnsWebService._authManager.IsPermitted(PermissionSection.DhcpServer, sessionUser, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + HttpRequest request = context.Request; + + string scopeName = request.GetQueryOrForm("name"); + + Scope scope = _dnsWebService._dhcpServer.GetScope(scopeName); + if (scope is null) + throw new DnsWebServiceException("No such scope exists: " + scopeName); + + string hostName = request.GetQueryOrForm("hostName"); + string hardwareAddress = request.GetQueryOrForm("hardwareAddress"); + string comments = request.QueryOrForm("comments"); + + HostNameOverride hostNameOverride = new HostNameOverride(hostName, DhcpMessageHardwareAddressType.Ethernet, hardwareAddress, comments); + + if (!scope.TryAddHostNameOverride(hostNameOverride)) + throw new DnsWebServiceException("A host name override with same hardware address already exists in scope: " + scopeName); + + _dnsWebService._dhcpServer.SaveScope(scopeName); + + _dnsWebService._log.Write(_dnsWebService.GetRemoteEndPoint(context), "[" + sessionUser.Username + "] DHCP scope host name override was added successfully: " + scopeName); + } + + public void RemoveHostNameOverride(HttpContext context) + { + User sessionUser = _dnsWebService.GetSessionUser(context); + + if (!_dnsWebService._authManager.IsPermitted(PermissionSection.DhcpServer, sessionUser, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + HttpRequest request = context.Request; + + string scopeName = request.GetQueryOrForm("name"); + + Scope scope = _dnsWebService._dhcpServer.GetScope(scopeName); + if (scope is null) + throw new DnsWebServiceException("No such scope exists: " + scopeName); + + string hardwareAddress = request.GetQueryOrForm("hardwareAddress"); + + if (!scope.TryRemoveHostNameOverride(hardwareAddress)) + throw new DnsWebServiceException("No such host name override was found in scope: " + scopeName); + + _dnsWebService._dhcpServer.SaveScope(scopeName); + + _dnsWebService._log.Write(_dnsWebService.GetRemoteEndPoint(context), "[" + sessionUser.Username + "] DHCP scope host name override was removed successfully: " + scopeName); + } + public async Task EnableDhcpScopeAsync(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); diff --git a/DnsServerCore/www/index.html b/DnsServerCore/www/index.html index 479a25c9..35cc8b6a 100644 --- a/DnsServerCore/www/index.html +++ b/DnsServerCore/www/index.html @@ -2893,6 +2893,26 @@

Edit Scope

+
+
+ +
+ + + + + + + + + + +
Host NameMAC AddressComments
+
+
Override the DNS hostname for a specific client based on their MAC address without reserving a static IP address; the client still receives a normal dynamically allocated lease.
+
+
+
diff --git a/DnsServerCore/www/js/dhcp.js b/DnsServerCore/www/js/dhcp.js index 2419f752..3f3e5bb1 100644 --- a/DnsServerCore/www/js/dhcp.js +++ b/DnsServerCore/www/js/dhcp.js @@ -307,6 +307,18 @@ function addDhcpScopeReservedLeaseRow(hostName, hardwareAddress, address, commen $("#tableDhcpScopeReservedLeases").append(tableHtmlRows); } +function addDhcpScopeHostNameOverrideRow(hostName, hardwareAddress, comments) { + var id = Math.floor(Math.random() * 10000); + + var tableHtmlRows = ""; + tableHtmlRows += ""; + tableHtmlRows += ""; + tableHtmlRows += ""; + tableHtmlRows += ""; + + $("#tableDhcpScopeHostNameOverrides").append(tableHtmlRows); +} + function clearDhcpScopeForm() { $("#txtDhcpScopeName").attr("data-name", ""); $("#txtDhcpScopeName").val(""); @@ -343,6 +355,7 @@ function clearDhcpScopeForm() { $("#tableDhcpScopeGenericOptions").html(""); $("#tableDhcpScopeExclusions").html(""); $("#tableDhcpScopeReservedLeases").html(""); + $("#tableDhcpScopeHostNameOverrides").html(""); $("#chkAllowOnlyReservedLeases").prop("checked", false); $("#chkBlockLocallyAdministeredMacAddresses").prop("checked", false); $("#chkIgnoreClientIdentifierOption").prop("checked", true); @@ -466,6 +479,12 @@ function showEditDhcpScope(scopeName) { } } + if (responseJSON.response.hostNameOverrides != null) { + for (var i = 0; i < responseJSON.response.hostNameOverrides.length; i++) { + addDhcpScopeHostNameOverrideRow(responseJSON.response.hostNameOverrides[i].hostName, responseJSON.response.hostNameOverrides[i].hardwareAddress, responseJSON.response.hostNameOverrides[i].comments); + } + } + $("#chkAllowOnlyReservedLeases").prop("checked", responseJSON.response.allowOnlyReservedLeases); $("#chkBlockLocallyAdministeredMacAddresses").prop("checked", responseJSON.response.blockLocallyAdministeredMacAddresses); $("#chkIgnoreClientIdentifierOption").prop("checked", responseJSON.response.ignoreClientIdentifierOption); @@ -546,6 +565,10 @@ function saveDhcpScope() { if (reservedLeases === false) return; + var hostNameOverrides = serializeTableData($("#tableDhcpScopeHostNameOverrides"), 3); + if (hostNameOverrides === false) + return; + var allowOnlyReservedLeases = $("#chkAllowOnlyReservedLeases").prop('checked'); var blockLocallyAdministeredMacAddresses = $("#chkBlockLocallyAdministeredMacAddresses").prop('checked'); var ignoreClientIdentifierOption = $("#chkIgnoreClientIdentifierOption").prop('checked'); @@ -563,7 +586,7 @@ function saveDhcpScope() { "&leaseTimeDays=" + leaseTimeDays + "&leaseTimeHours=" + leaseTimeHours + "&leaseTimeMinutes=" + leaseTimeMinutes + "&offerDelayTime=" + offerDelayTime + "&pingCheckEnabled=" + pingCheckEnabled + "&pingCheckTimeout=" + pingCheckTimeout + "&pingCheckRetries=" + pingCheckRetries + "&domainName=" + encodeURIComponent(domainName) + "&domainSearchList=" + encodeURIComponent(domainSearchList) + "&dnsUpdates=" + dnsUpdates + "&dnsOverwriteForDynamicLease=" + dnsOverwriteForDynamicLease + "&dnsTtl=" + dnsTtl + "&serverAddress=" + encodeURIComponent(serverAddress) + "&serverHostName=" + encodeURIComponent(serverHostName) + "&bootFileName=" + encodeURIComponent(bootFileName) + "&routerAddress=" + encodeURIComponent(routerAddress) + "&useThisDnsServer=" + useThisDnsServer + (useThisDnsServer ? "" : "&dnsServers=" + encodeURIComponent(dnsServers)) + "&winsServers=" + encodeURIComponent(winsServers) + "&ntpServers=" + encodeURIComponent(ntpServers) + "&ntpServerDomainNames=" + encodeURIComponent(ntpServerDomainNames) + - "&staticRoutes=" + encodeURIComponent(staticRoutes) + "&vendorInfo=" + encodeURIComponent(vendorInfo) + "&capwapAcIpAddresses=" + encodeURIComponent(capwapAcIpAddresses) + "&tftpServerAddresses=" + encodeURIComponent(tftpServerAddresses) + "&genericOptions=" + encodeURIComponent(genericOptions) + "&exclusions=" + encodeURIComponent(exclusions) + "&reservedLeases=" + encodeURIComponent(reservedLeases) + "&allowOnlyReservedLeases=" + allowOnlyReservedLeases + "&blockLocallyAdministeredMacAddresses=" + blockLocallyAdministeredMacAddresses + "&ignoreClientIdentifierOption=" + ignoreClientIdentifierOption, + "&staticRoutes=" + encodeURIComponent(staticRoutes) + "&vendorInfo=" + encodeURIComponent(vendorInfo) + "&capwapAcIpAddresses=" + encodeURIComponent(capwapAcIpAddresses) + "&tftpServerAddresses=" + encodeURIComponent(tftpServerAddresses) + "&genericOptions=" + encodeURIComponent(genericOptions) + "&exclusions=" + encodeURIComponent(exclusions) + "&reservedLeases=" + encodeURIComponent(reservedLeases) + "&hostNameOverrides=" + encodeURIComponent(hostNameOverrides) + "&allowOnlyReservedLeases=" + allowOnlyReservedLeases + "&blockLocallyAdministeredMacAddresses=" + blockLocallyAdministeredMacAddresses + "&ignoreClientIdentifierOption=" + ignoreClientIdentifierOption, processData: false, success: function (responseJSON) { refreshDhcpScopes();