/* Technitium DNS Server Copyright (C) 2022 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.Dns; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Globalization; using System.Net; using System.Threading.Tasks; using TechnitiumLibrary.Net.Dns; using TechnitiumLibrary.Net.Dns.ResourceRecords; namespace DnsServerCore { class WebServiceDashboardApi { #region variables readonly DnsWebService _dnsWebService; #endregion #region constructor public WebServiceDashboardApi(DnsWebService dnsWebService) { _dnsWebService = dnsWebService; } #endregion #region private private static void WriteChartDataSet(JsonTextWriter jsonWriter, string label, string backgroundColor, string borderColor, List> statsPerInterval) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("label"); jsonWriter.WriteValue(label); jsonWriter.WritePropertyName("backgroundColor"); jsonWriter.WriteValue(backgroundColor); jsonWriter.WritePropertyName("borderColor"); jsonWriter.WriteValue(borderColor); jsonWriter.WritePropertyName("borderWidth"); jsonWriter.WriteValue(2); jsonWriter.WritePropertyName("fill"); jsonWriter.WriteValue(true); jsonWriter.WritePropertyName("data"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in statsPerInterval) jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndArray(); jsonWriter.WriteEndObject(); } private async Task> ResolvePtrTopClientsAsync(List> topClients) { IDictionary dhcpClientIpMap = _dnsWebService.DhcpServer.GetAddressHostNameMap(); async Task> ResolvePtrAsync(string ip) { if (dhcpClientIpMap.TryGetValue(ip, out string dhcpDomain)) return new KeyValuePair(ip, dhcpDomain); IPAddress address = IPAddress.Parse(ip); if (IPAddress.IsLoopback(address)) return new KeyValuePair(ip, "localhost"); DnsDatagram ptrResponse = await _dnsWebService.DnsServer.DirectQueryAsync(new DnsQuestionRecord(address, DnsClass.IN), 500); if (ptrResponse.Answer.Count > 0) { IReadOnlyList ptrDomains = DnsClient.ParseResponsePTR(ptrResponse); if (ptrDomains.Count > 0) return new KeyValuePair(ip, ptrDomains[0]); } return new KeyValuePair(ip, null); } List>> resolverTasks = new List>>(); foreach (KeyValuePair item in topClients) { resolverTasks.Add(ResolvePtrAsync(item.Key)); } Dictionary result = new Dictionary(); foreach (Task> resolverTask in resolverTasks) { try { KeyValuePair ptrResult = await resolverTask; result[ptrResult.Key] = ptrResult.Value; } catch { } } return result; } #endregion #region public public async Task GetStats(HttpListenerRequest request, JsonTextWriter jsonWriter) { string strType = request.QueryString["type"]; if (string.IsNullOrEmpty(strType)) strType = "lastHour"; Dictionary>> data; switch (strType) { case "lastHour": data = _dnsWebService.DnsServer.StatsManager.GetLastHourMinuteWiseStats(); break; case "lastDay": data = _dnsWebService.DnsServer.StatsManager.GetLastDayHourWiseStats(); break; case "lastWeek": data = _dnsWebService.DnsServer.StatsManager.GetLastWeekDayWiseStats(); break; case "lastMonth": data = _dnsWebService.DnsServer.StatsManager.GetLastMonthDayWiseStats(); break; case "lastYear": data = _dnsWebService.DnsServer.StatsManager.GetLastYearMonthWiseStats(); break; case "custom": string strStartDate = request.QueryString["start"]; if (string.IsNullOrEmpty(strStartDate)) throw new DnsWebServiceException("Parameter 'start' missing."); string strEndDate = request.QueryString["end"]; if (string.IsNullOrEmpty(strEndDate)) throw new DnsWebServiceException("Parameter 'end' missing."); if (!DateTime.TryParseExact(strStartDate, "yyyy-M-d", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal, out DateTime startDate)) throw new DnsWebServiceException("Invalid start date format."); if (!DateTime.TryParseExact(strEndDate, "yyyy-M-d", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal, out DateTime endDate)) throw new DnsWebServiceException("Invalid end date format."); if (startDate > endDate) throw new DnsWebServiceException("Start date must be less than or equal to end date."); if ((Convert.ToInt32((endDate - startDate).TotalDays) + 1) > 7) data = _dnsWebService.DnsServer.StatsManager.GetDayWiseStats(startDate, endDate); else data = _dnsWebService.DnsServer.StatsManager.GetHourWiseStats(startDate, endDate); break; default: throw new DnsWebServiceException("Unknown stats type requested: " + strType); } //stats { List> stats = data["stats"]; jsonWriter.WritePropertyName("stats"); jsonWriter.WriteStartObject(); foreach (KeyValuePair item in stats) { jsonWriter.WritePropertyName(item.Key); jsonWriter.WriteValue(item.Value); } jsonWriter.WritePropertyName("zones"); jsonWriter.WriteValue(_dnsWebService.DnsServer.AuthZoneManager.TotalZones); jsonWriter.WritePropertyName("allowedZones"); jsonWriter.WriteValue(_dnsWebService.DnsServer.AllowedZoneManager.TotalZonesAllowed); jsonWriter.WritePropertyName("blockedZones"); jsonWriter.WriteValue(_dnsWebService.DnsServer.BlockedZoneManager.TotalZonesBlocked); jsonWriter.WritePropertyName("blockListZones"); jsonWriter.WriteValue(_dnsWebService.DnsServer.BlockListZoneManager.TotalZonesBlocked); jsonWriter.WriteEndObject(); } //main chart { jsonWriter.WritePropertyName("mainChartData"); jsonWriter.WriteStartObject(); //label { List> statsPerInterval = data["totalQueriesPerInterval"]; jsonWriter.WritePropertyName("labels"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in statsPerInterval) jsonWriter.WriteValue(item.Key); jsonWriter.WriteEndArray(); } //datasets { jsonWriter.WritePropertyName("datasets"); jsonWriter.WriteStartArray(); WriteChartDataSet(jsonWriter, "Total", "rgba(102, 153, 255, 0.1)", "rgb(102, 153, 255)", data["totalQueriesPerInterval"]); WriteChartDataSet(jsonWriter, "No Error", "rgba(92, 184, 92, 0.1)", "rgb(92, 184, 92)", data["totalNoErrorPerInterval"]); WriteChartDataSet(jsonWriter, "Server Failure", "rgba(217, 83, 79, 0.1)", "rgb(217, 83, 79)", data["totalServerFailurePerInterval"]); WriteChartDataSet(jsonWriter, "NX Domain", "rgba(7, 7, 7, 0.1)", "rgb(7, 7, 7)", data["totalNxDomainPerInterval"]); WriteChartDataSet(jsonWriter, "Refused", "rgba(91, 192, 222, 0.1)", "rgb(91, 192, 222)", data["totalRefusedPerInterval"]); WriteChartDataSet(jsonWriter, "Authoritative", "rgba(150, 150, 0, 0.1)", "rgb(150, 150, 0)", data["totalAuthHitPerInterval"]); WriteChartDataSet(jsonWriter, "Recursive", "rgba(23, 162, 184, 0.1)", "rgb(23, 162, 184)", data["totalRecursionsPerInterval"]); WriteChartDataSet(jsonWriter, "Cached", "rgba(111, 84, 153, 0.1)", "rgb(111, 84, 153)", data["totalCacheHitPerInterval"]); WriteChartDataSet(jsonWriter, "Blocked", "rgba(255, 165, 0, 0.1)", "rgb(255, 165, 0)", data["totalBlockedPerInterval"]); WriteChartDataSet(jsonWriter, "Clients", "rgba(51, 122, 183, 0.1)", "rgb(51, 122, 183)", data["totalClientsPerInterval"]); jsonWriter.WriteEndArray(); } jsonWriter.WriteEndObject(); } //query response chart { jsonWriter.WritePropertyName("queryResponseChartData"); jsonWriter.WriteStartObject(); List> stats = data["stats"]; //labels { jsonWriter.WritePropertyName("labels"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in stats) { switch (item.Key) { case "totalAuthoritative": jsonWriter.WriteValue("Authoritative"); break; case "totalRecursive": jsonWriter.WriteValue("Recursive"); break; case "totalCached": jsonWriter.WriteValue("Cached"); break; case "totalBlocked": jsonWriter.WriteValue("Blocked"); break; } } jsonWriter.WriteEndArray(); } //datasets { jsonWriter.WritePropertyName("datasets"); jsonWriter.WriteStartArray(); jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("data"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in stats) { switch (item.Key) { case "totalAuthoritative": case "totalRecursive": case "totalCached": case "totalBlocked": jsonWriter.WriteValue(item.Value); break; } } jsonWriter.WriteEndArray(); jsonWriter.WritePropertyName("backgroundColor"); jsonWriter.WriteStartArray(); jsonWriter.WriteValue("rgba(150, 150, 0, 0.5)"); jsonWriter.WriteValue("rgba(23, 162, 184, 0.5)"); jsonWriter.WriteValue("rgba(111, 84, 153, 0.5)"); jsonWriter.WriteValue("rgba(255, 165, 0, 0.5)"); jsonWriter.WriteEndArray(); jsonWriter.WriteEndObject(); jsonWriter.WriteEndArray(); } jsonWriter.WriteEndObject(); } //query type chart { jsonWriter.WritePropertyName("queryTypeChartData"); jsonWriter.WriteStartObject(); List> queryTypes = data["queryTypes"]; //labels { jsonWriter.WritePropertyName("labels"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in queryTypes) jsonWriter.WriteValue(item.Key); jsonWriter.WriteEndArray(); } //datasets { jsonWriter.WritePropertyName("datasets"); jsonWriter.WriteStartArray(); jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("data"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in queryTypes) jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndArray(); jsonWriter.WritePropertyName("backgroundColor"); jsonWriter.WriteStartArray(); jsonWriter.WriteValue("rgba(102, 153, 255, 0.5)"); jsonWriter.WriteValue("rgba(92, 184, 92, 0.5)"); jsonWriter.WriteValue("rgba(7, 7, 7, 0.5)"); jsonWriter.WriteValue("rgba(91, 192, 222, 0.5)"); jsonWriter.WriteValue("rgba(150, 150, 0, 0.5)"); jsonWriter.WriteValue("rgba(23, 162, 184, 0.5)"); jsonWriter.WriteValue("rgba(111, 84, 153, 0.5)"); jsonWriter.WriteValue("rgba(255, 165, 0, 0.5)"); jsonWriter.WriteValue("rgba(51, 122, 183, 0.5)"); jsonWriter.WriteValue("rgba(150, 150, 150, 0.5)"); jsonWriter.WriteEndArray(); jsonWriter.WriteEndObject(); jsonWriter.WriteEndArray(); } jsonWriter.WriteEndObject(); } //top clients { List> topClients = data["topClients"]; IDictionary clientIpMap = await ResolvePtrTopClientsAsync(topClients); jsonWriter.WritePropertyName("topClients"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topClients) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); if (clientIpMap.TryGetValue(item.Key, out string clientDomain) && !string.IsNullOrEmpty(clientDomain)) { jsonWriter.WritePropertyName("domain"); jsonWriter.WriteValue(clientDomain); } jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } //top domains { List> topDomains = data["topDomains"]; jsonWriter.WritePropertyName("topDomains"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topDomains) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } //top blocked domains { List> topBlockedDomains = data["topBlockedDomains"]; jsonWriter.WritePropertyName("topBlockedDomains"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topBlockedDomains) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } } public async Task GetTopStats(HttpListenerRequest request, JsonTextWriter jsonWriter) { string strType = request.QueryString["type"]; if (string.IsNullOrEmpty(strType)) strType = "lastHour"; string strStatsType = request.QueryString["statsType"]; if (string.IsNullOrEmpty(strStatsType)) throw new DnsWebServiceException("Parameter 'statsType' missing."); string strLimit = request.QueryString["limit"]; if (string.IsNullOrEmpty(strLimit)) strLimit = "1000"; TopStatsType statsType = Enum.Parse(strStatsType, true); int limit = int.Parse(strLimit); List> topStatsData; switch (strType) { case "lastHour": topStatsData = _dnsWebService.DnsServer.StatsManager.GetLastHourTopStats(statsType, limit); break; case "lastDay": topStatsData = _dnsWebService.DnsServer.StatsManager.GetLastDayTopStats(statsType, limit); break; case "lastWeek": topStatsData = _dnsWebService.DnsServer.StatsManager.GetLastWeekTopStats(statsType, limit); break; case "lastMonth": topStatsData = _dnsWebService.DnsServer.StatsManager.GetLastMonthTopStats(statsType, limit); break; case "lastYear": topStatsData = _dnsWebService.DnsServer.StatsManager.GetLastYearTopStats(statsType, limit); break; case "custom": string strStartDate = request.QueryString["start"]; if (string.IsNullOrEmpty(strStartDate)) throw new DnsWebServiceException("Parameter 'start' missing."); string strEndDate = request.QueryString["end"]; if (string.IsNullOrEmpty(strEndDate)) throw new DnsWebServiceException("Parameter 'end' missing."); if (!DateTime.TryParseExact(strStartDate, "yyyy-M-d", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime startDate)) throw new DnsWebServiceException("Invalid start date format."); if (!DateTime.TryParseExact(strEndDate, "yyyy-M-d", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime endDate)) throw new DnsWebServiceException("Invalid end date format."); if (startDate > endDate) throw new DnsWebServiceException("Start date must be less than or equal to end date."); if ((Convert.ToInt32((endDate - startDate).TotalDays) + 1) > 7) topStatsData = _dnsWebService.DnsServer.StatsManager.GetDayWiseTopStats(startDate, endDate, statsType, limit); else topStatsData = _dnsWebService.DnsServer.StatsManager.GetHourWiseTopStats(startDate, endDate, statsType, limit); break; default: throw new DnsWebServiceException("Unknown stats type requested: " + strType); } switch (statsType) { case TopStatsType.TopClients: { IDictionary clientIpMap = await ResolvePtrTopClientsAsync(topStatsData); jsonWriter.WritePropertyName("topClients"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topStatsData) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); if (clientIpMap.TryGetValue(item.Key, out string clientDomain) && !string.IsNullOrEmpty(clientDomain)) { jsonWriter.WritePropertyName("domain"); jsonWriter.WriteValue(clientDomain); } jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } break; case TopStatsType.TopDomains: { jsonWriter.WritePropertyName("topDomains"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topStatsData) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } break; case TopStatsType.TopBlockedDomains: { jsonWriter.WritePropertyName("topBlockedDomains"); jsonWriter.WriteStartArray(); foreach (KeyValuePair item in topStatsData) { jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(item.Key); jsonWriter.WritePropertyName("hits"); jsonWriter.WriteValue(item.Value); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } break; default: throw new NotSupportedException(); } } #endregion } }