/*
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
}
}