/* Technitium DNS Server Copyright (C) 2025 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.Auth; using DnsServerCore.Cluster; using Microsoft.AspNetCore.Http; using System; using System.Buffers.Text; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace DnsServerCore { public partial class DnsWebService { sealed class WebServiceClusterApi { #region variables readonly DnsWebService _dnsWebService; #endregion #region constructor public WebServiceClusterApi(DnsWebService dnsWebService) { _dnsWebService = dnsWebService; } #endregion #region private private void WriteClusterState(Utf8JsonWriter jsonWriter, bool includeServerIpAddresses = false) { jsonWriter.WriteString("version", _dnsWebService.GetServerVersion()); jsonWriter.WriteString("dnsServerDomain", _dnsWebService._dnsServer.ServerDomain); jsonWriter.WriteBoolean("clusterInitialized", _dnsWebService._clusterManager.ClusterInitialized); if (_dnsWebService._clusterManager.ClusterInitialized) { jsonWriter.WriteString("clusterDomain", _dnsWebService._clusterManager.ClusterDomain); jsonWriter.WriteNumber("heartbeatRefreshIntervalSeconds", _dnsWebService._clusterManager.HeartbeatRefreshIntervalSeconds); jsonWriter.WriteNumber("heartbeatRetryIntervalSeconds", _dnsWebService._clusterManager.HeartBeatRetryIntervalSeconds); jsonWriter.WriteNumber("configRefreshIntervalSeconds", _dnsWebService._clusterManager.ConfigRefreshIntervalSeconds); jsonWriter.WriteNumber("configRetryIntervalSeconds", _dnsWebService._clusterManager.ConfigRetryIntervalSeconds); WriteClusterNodes(jsonWriter); } if (includeServerIpAddresses) { jsonWriter.WriteStartArray("serverIpAddresses"); foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces()) { if (networkInterface.OperationalStatus != OperationalStatus.Up) continue; foreach (UnicastIPAddressInformation ip in networkInterface.GetIPProperties().UnicastAddresses) { if (IPAddress.IsLoopback(ip.Address)) continue; switch (ip.Address.AddressFamily) { case AddressFamily.InterNetwork: jsonWriter.WriteStringValue(ip.Address.ToString()); break; case AddressFamily.InterNetworkV6: if (ip.Address.IsIPv6LinkLocal || ip.Address.IsIPv6Teredo) continue; jsonWriter.WriteStringValue(ip.Address.ToString()); break; } } } jsonWriter.WriteEndArray(); } } internal void WriteClusterNodes(Utf8JsonWriter jsonWriter) { List sortedClusterNodes = [.. _dnsWebService._clusterManager.ClusterNodes.Values]; sortedClusterNodes.Sort(); jsonWriter.WriteStartArray("clusterNodes"); foreach (ClusterNode clusterNode in sortedClusterNodes) { jsonWriter.WriteStartObject(); jsonWriter.WriteNumber("id", clusterNode.Id); jsonWriter.WriteString("name", clusterNode.Name); jsonWriter.WriteString("url", clusterNode.Url.OriginalString); jsonWriter.WriteStartArray("ipAddresses"); foreach (IPAddress ipAddress in clusterNode.IPAddresses) jsonWriter.WriteStringValue(ipAddress.ToString()); jsonWriter.WriteEndArray(); jsonWriter.WriteString("type", clusterNode.Type.ToString()); jsonWriter.WriteString("state", clusterNode.State.ToString()); if (clusterNode.State == ClusterNodeState.Self) { jsonWriter.WriteString("upSince", clusterNode.UpSince); if (clusterNode.Type == ClusterNodeType.Secondary) { if (_dnsWebService._clusterManager.ConfigLastSynced != default) jsonWriter.WriteString("configLastSynced", _dnsWebService._clusterManager.ConfigLastSynced); } } else { if (clusterNode.UpSince != default) jsonWriter.WriteString("upSince", clusterNode.UpSince); if (clusterNode.LastSeen != default) jsonWriter.WriteString("lastSeen", clusterNode.LastSeen); } jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } private void EnableWebServiceTlsWithSelfSignedCertificate() { _dnsWebService._webServiceEnableTls = true; _dnsWebService._webServiceUseSelfSignedTlsCertificate = true; _dnsWebService._webServiceTlsCertificatePath = null; _dnsWebService._webServiceTlsCertificatePassword = null; _dnsWebService.CheckAndLoadSelfSignedCertificate(false, true); _dnsWebService.SaveConfigFile(); } private void RestartWebService() { ThreadPool.QueueUserWorkItem(async delegate (object state) { try { await Task.Delay(2000); //wait for the current HTTP response to be delivered before restarting web server _dnsWebService._log.Write("Attempting to restart web service."); await _dnsWebService.StopWebServiceAsync(); await _dnsWebService.StartWebServiceAsync(false); _dnsWebService._log.Write("Web service was restarted successfully."); } catch (Exception ex) { _dnsWebService._log.Write("Failed to restart web service.\r\n" + ex.ToString()); _dnsWebService._log.Write("Attempting to restart web service in HTTP only mode."); try { await _dnsWebService.StopWebServiceAsync(); await _dnsWebService.StartWebServiceAsync(true); } catch (Exception ex2) { _dnsWebService._log.Write("Failed to restart web service in HTTP only mode.\r\n" + ex2.ToString()); } } }); } #endregion #region public public void GetClusterState(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.View)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; bool includeServerIpAddresses = request.GetQueryOrForm("includeServerIpAddresses", bool.Parse, false); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter, includeServerIpAddresses); } public void InitializeCluster(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; string clusterDomain = request.GetQueryOrForm("clusterDomain").TrimEnd('.'); if (!request.TryGetQueryOrFormArray("primaryNodeIpAddresses", IPAddress.Parse, out IPAddress[] primaryNodeIpAddresses)) throw new DnsWebServiceException("Parameter 'primaryNodeIpAddresses' missing."); bool restartWebService = false; //enable TLS web service if not already enabled if (!_dnsWebService.IsWebServiceTlsEnabled) { EnableWebServiceTlsWithSelfSignedCertificate(); restartWebService = true; } try { _dnsWebService._clusterManager.InitializeCluster(clusterDomain, primaryNodeIpAddresses, context.GetCurrentSession()); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Cluster (" + _dnsWebService._clusterManager.ClusterDomain + ") was initialized successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } finally { //restart TLS web service to apply HTTPS changes if (restartWebService) RestartWebService(); } } public void DeleteCluster(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; bool forceDelete = request.GetQueryOrForm("forceDelete", bool.Parse, false); string clusterDomain = _dnsWebService._clusterManager.ClusterDomain; _dnsWebService._clusterManager.DeleteCluster(forceDelete); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Cluster (" + clusterDomain + ") was deleted successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } public void JoinCluster(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; int secondaryNodeId = request.GetQueryOrForm("secondaryNodeId", int.Parse); Uri secondaryNodeUrl = new Uri(request.GetQueryOrForm("secondaryNodeUrl")); if (!request.TryGetQueryOrFormArray("secondaryNodeIpAddresses", IPAddress.Parse, out IPAddress[] secondaryNodeIpAddresses)) throw new DnsWebServiceException("Parameter 'secondaryNodeIpAddresses' missing."); X509Certificate2 secondaryNodeCertificate = X509CertificateLoader.LoadCertificate(Base64Url.DecodeFromChars(request.GetQueryOrForm("secondaryNodeCertificate"))); ClusterNode secondaryNode = _dnsWebService._clusterManager.JoinCluster(secondaryNodeId, secondaryNodeUrl, secondaryNodeIpAddresses, secondaryNodeCertificate); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Secondary node '" + secondaryNode.ToString() + "' joined the Cluster (" + _dnsWebService._clusterManager.ClusterDomain + ") successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } public async Task RemoveSecondaryNodeAsync(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; int secondaryNodeId = request.GetQueryOrForm("secondaryNodeId", int.Parse); ClusterNode secondaryNode = await _dnsWebService._clusterManager.AskSecondaryNodeToLeaveClusterAsync(secondaryNodeId); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Secondary node '" + secondaryNode.ToString() + "' was asked to leave the Cluster (" + _dnsWebService._clusterManager.ClusterDomain + ") successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } public void DeleteSecondaryNode(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; int secondaryNodeId = request.GetQueryOrForm("secondaryNodeId", int.Parse); ClusterNode secondaryNode = _dnsWebService._clusterManager.DeleteSecondaryNode(secondaryNodeId); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Secondary node '" + secondaryNode.ToString() + "' was deleted from the Cluster (" + _dnsWebService._clusterManager.ClusterDomain + ") successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } public void UpdateSecondaryNode(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; int secondaryNodeId = request.GetQueryOrForm("secondaryNodeId", int.Parse); Uri secondaryNodeUrl = new Uri(request.GetQueryOrForm("secondaryNodeUrl")); if (!request.TryGetQueryOrFormArray("secondaryNodeIpAddresses", IPAddress.Parse, out IPAddress[] secondaryNodeIpAddresses)) throw new DnsWebServiceException("Parameter 'secondaryNodeIpAddresses' missing."); X509Certificate2 secondaryNodeCertificate = X509CertificateLoader.LoadCertificate(Base64Url.DecodeFromChars(request.GetQueryOrForm("secondaryNodeCertificate"))); ClusterNode secondaryNode = _dnsWebService._clusterManager.UpdateSecondaryNode(secondaryNodeId, secondaryNodeUrl, secondaryNodeIpAddresses, secondaryNodeCertificate); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Secondary node '" + secondaryNode.ToString() + "' details were updated successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } public async Task TransferConfigAsync(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; string ifModifiedSinceValue = request.Headers.IfModifiedSince; string includeZonesValue = request.QueryOrForm("includeZones"); DateTime ifModifiedSince = string.IsNullOrEmpty(ifModifiedSinceValue) ? DateTime.UnixEpoch : DateTime.ParseExact(ifModifiedSinceValue, "R", CultureInfo.InvariantCulture); string[] includeZones = string.IsNullOrEmpty(includeZonesValue) ? null : includeZonesValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); string tmpFile = Path.GetTempFileName(); try { await using (FileStream configZipStream = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite)) { //create config zip file await _dnsWebService._clusterManager.TransferConfigAsync(configZipStream, ifModifiedSince, includeZones); //send config zip file configZipStream.Position = 0; HttpResponse response = context.Response; response.ContentType = "application/zip"; response.ContentLength = configZipStream.Length; response.Headers.LastModified = DateTime.UtcNow.ToString("R"); response.Headers.Append("Content-Disposition", "attachment; filename=\"config.zip\""); await using (Stream output = response.Body) { await configZipStream.CopyToAsync(output); } } } finally { try { File.Delete(tmpFile); } catch (Exception ex) { _dnsWebService._log.Write(ex); } } _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Server configuration was transferred successfully."); } public void SetClusterOptions(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Modify)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; ushort heartbeatRefreshIntervalSeconds = request.GetQueryOrForm("heartbeatRefreshIntervalSeconds", ushort.Parse, _dnsWebService._clusterManager.HeartbeatRefreshIntervalSeconds); ushort heartbeatRetryIntervalSeconds = request.GetQueryOrForm("heartbeatRetryIntervalSeconds", ushort.Parse, _dnsWebService._clusterManager.HeartBeatRetryIntervalSeconds); ushort configRefreshIntervalSeconds = request.GetQueryOrForm("configRefreshIntervalSeconds", ushort.Parse, _dnsWebService._clusterManager.ConfigRefreshIntervalSeconds); ushort configRetryIntervalSeconds = request.GetQueryOrForm("configRetryIntervalSeconds", ushort.Parse, _dnsWebService._clusterManager.ConfigRetryIntervalSeconds); _dnsWebService._clusterManager.UpdateClusterOptions(heartbeatRefreshIntervalSeconds, heartbeatRetryIntervalSeconds, configRefreshIntervalSeconds, configRetryIntervalSeconds); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Cluster (" + _dnsWebService._clusterManager.ClusterDomain + ") options were updated successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } public async Task InitializeAndJoinClusterAsync(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; if (!request.TryGetQueryOrFormArray("secondaryNodeIpAddresses", IPAddress.Parse, out IPAddress[] secondaryNodeIpAddresses)) throw new DnsWebServiceException("Parameter 'secondaryNodeIpAddresses' missing."); Uri primaryNodeUrl = new Uri(request.GetQueryOrForm("primaryNodeUrl")); IPAddress primaryNodeIpAddress = request.GetQueryOrForm("primaryNodeIpAddress", IPAddress.Parse, null); string primaryNodeUsername = request.GetQueryOrForm("primaryNodeUsername"); string primaryNodePassword = request.GetQueryOrForm("primaryNodePassword"); string primaryNodeTotp = request.GetQueryOrForm("primaryNodeTotp", null); bool ignoreCertificateErrors = request.GetQueryOrForm("ignoreCertificateErrors", bool.Parse, false); bool restartWebService = false; //enable TLS web service if not already enabled if (!_dnsWebService.IsWebServiceTlsEnabled) { EnableWebServiceTlsWithSelfSignedCertificate(); restartWebService = true; } try { await _dnsWebService._clusterManager.InitializeAndJoinClusterAsync(secondaryNodeIpAddresses, primaryNodeUrl, primaryNodeUsername, primaryNodePassword, primaryNodeTotp, primaryNodeIpAddress is null ? null : [primaryNodeIpAddress], ignoreCertificateErrors); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Joined the Cluster (" + _dnsWebService._clusterManager.ClusterDomain + ") as a Secondary node successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } finally { //restart TLS web service to apply HTTPS changes if (restartWebService) RestartWebService(); } } public async Task LeaveClusterAsync(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; bool forceLeave = request.GetQueryOrForm("forceLeave", bool.Parse, false); string clusterDomain = _dnsWebService._clusterManager.ClusterDomain; await _dnsWebService._clusterManager.LeaveClusterAsync(forceLeave); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Left the Cluster (" + clusterDomain + ") successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } public async Task ConfigUpdateNotificationAsync(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; int primaryNodeId = request.GetQueryOrForm("primaryNodeId", int.Parse); Uri primaryNodeUrl = new Uri(request.GetQueryOrForm("primaryNodeUrl")); if (!request.TryGetQueryOrFormArray("primaryNodeIpAddresses", IPAddress.Parse, out IPAddress[] primaryNodeIpAddresses)) throw new DnsWebServiceException("Parameter 'primaryNodeIpAddresses' missing."); //update primary node ClusterNode primaryNode = await _dnsWebService._clusterManager.UpdatePrimaryNodeAsync(primaryNodeUrl, primaryNodeIpAddresses, primaryNodeId); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Notification for configuration update was received. Primary node '" + primaryNode.ToString() + "' details were updated successfully."); } public void ResyncCluster(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Modify)) throw new DnsWebServiceException("Access was denied."); _dnsWebService._clusterManager.TriggerResyncForConfig(); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Resync for configuration and Cluster Secondary zones was triggered successfully."); } public async Task UpdatePrimaryNodeAsync(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Modify)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; Uri primaryNodeUrl = new Uri(request.GetQueryOrForm("primaryNodeUrl")); if (!request.TryGetQueryOrFormArray("primaryNodeIpAddresses", IPAddress.Parse, out IPAddress[] primaryNodeIpAddresses)) primaryNodeIpAddresses = null; //update primary node ClusterNode primaryNode = await _dnsWebService._clusterManager.UpdatePrimaryNodeAsync(primaryNodeUrl, primaryNodeIpAddresses); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] Primary node '" + primaryNode.ToString() + "' details were updated successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } public async Task PromoteToPrimaryNodeAsync(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; bool forceDeletePrimary = request.GetQueryOrForm("forceDeletePrimary", bool.Parse, false); //promote to primary node await _dnsWebService._clusterManager.PromoteToPrimaryNodeAsync(forceDeletePrimary); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] This Secondary node was promoted to be a Primary node for the Cluster successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } public void UpdateSelfNodeIPAddress(HttpContext context) { User sessionUser = _dnsWebService.GetSessionUser(context); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Administration, sessionUser, PermissionFlag.Modify)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; if (!request.TryGetQueryOrFormArray("ipAddresses", IPAddress.Parse, out IPAddress[] ipAddresses)) throw new DnsWebServiceException("Parameter 'ipAddresses' missing."); //update self node IP address ClusterNode selfNode = _dnsWebService._clusterManager.UpdateSelfNodeIPAddresses(ipAddresses); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + sessionUser.Username + "] " + selfNode.Type.ToString() + " node '" + selfNode.ToString() + "' IP address was updated successfully."); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); WriteClusterState(jsonWriter); } #endregion } } }