Files
DnsServer/Apps/LogExporterApp/LogEntry.cs

138 lines
5.6 KiB
C#

/*
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 <http://www.gnu.org/licenses/>.
*/
using DnsServerCore.ApplicationCommon;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using TechnitiumLibrary.Net.Dns;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
namespace LogExporter
{
public class LogEntry
{
public DateTime Timestamp { get; private set; }
public string ClientIp { get; private set; }
public DnsTransportProtocol Protocol { get; private set; }
public DnsServerResponseType ResponseType { get; private set; }
public double? ResponseRtt { get; private set; }
public DnsResponseCode ResponseCode { get; private set; }
public DnsQuestion? Question { get; private set; }
public List<DnsResourceRecord> Answers { get; private set; }
public LogEntry(DateTime timestamp, IPEndPoint remoteEP, DnsTransportProtocol protocol, DnsDatagram request, DnsDatagram response)
{
// Assign timestamp and ensure it's in UTC
Timestamp = timestamp.Kind == DateTimeKind.Utc ? timestamp : timestamp.ToUniversalTime();
// Extract client information
ClientIp = remoteEP.Address.ToString();
Protocol = protocol;
ResponseType = response.Tag == null ? DnsServerResponseType.Recursive : (DnsServerResponseType)response.Tag;
if ((ResponseType == DnsServerResponseType.Recursive) && (response.Metadata is not null))
ResponseRtt = response.Metadata.RoundTripTime;
ResponseCode = response.RCODE;
// Extract request information
if (request.Question.Count > 0)
{
DnsQuestionRecord query = request.Question[0];
Question = new DnsQuestion
{
QuestionName = query.Name,
QuestionType = query.Type,
QuestionClass = query.Class,
};
}
// Convert answer section into a simple string summary (comma-separated for multiple answers)
Answers = new List<DnsResourceRecord>(response.Answer.Count);
if (response.Answer.Count > 0)
{
Answers.AddRange(response.Answer.Select(record => new DnsResourceRecord
{
Name = record.Name,
RecordType = record.Type,
RecordClass = record.Class,
RecordTtl = record.TTL,
RecordData = record.RDATA.ToString(),
DnssecStatus = record.DnssecStatus,
}));
}
}
public class DnsQuestion
{
public string QuestionName { get; set; }
public DnsResourceRecordType QuestionType { get; set; }
public DnsClass QuestionClass { get; set; }
}
public class DnsResourceRecord
{
public string Name { get; set; }
public DnsResourceRecordType RecordType { get; set; }
public DnsClass RecordClass { get; set; }
public uint RecordTtl { get; set; }
public string RecordData { get; set; }
public DnssecStatus DnssecStatus { get; set; }
}
public override string ToString()
{
return JsonSerializer.Serialize(this, DnsLogSerializerOptions.Default);
}
// Custom DateTime converter to handle UTC serialization in ISO 8601 format
public class JsonDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string? dts = reader.GetString();
return dts == null ? DateTime.MinValue : DateTime.Parse(dts);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
}
}
// Setup reusable options with a single instance
public static class DnsLogSerializerOptions
{
public static readonly JsonSerializerOptions Default = new JsonSerializerOptions
{
WriteIndented = false, // Newline delimited logs should not be multiline
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // Convert properties to camelCase
Converters = { new JsonStringEnumConverter(), new JsonDateTimeConverter() }, // Handle enums and DateTime conversion
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // For safe encoding
NumberHandling = JsonNumberHandling.Strict,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull // Ignore null values
};
}
}
}