/* Technitium DNS Server Copyright (C) 2024 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.ApplicationCommon; using LogExporter.Strategy; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; using TechnitiumLibrary.Net.Dns; using TechnitiumLibrary.Net.Dns.ResourceRecords; namespace LogExporter { public sealed class App : IDnsApplication, IDnsQueryLogger { #region variables private const int BULK_INSERT_COUNT = 1000; private const int DEFAULT_QUEUE_CAPACITY = 1000; private const int QUEUE_TIMER_INTERVAL = 10000; private readonly IReadOnlyList _emptyList = []; private readonly ExportManager _exportManager = new ExportManager(); private BufferManagementConfig? _config; private IDnsServer _dnsServer; private BlockingCollection _logBuffer; private Timer _queueTimer; private bool disposedValue; #endregion variables #region constructor public App() { } #endregion constructor #region IDisposable public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { _queueTimer?.Dispose(); ExportLogs(); //flush any pending logs _logBuffer.Dispose(); } disposedValue = true; } } #endregion IDisposable #region public public Task InitializeAsync(IDnsServer dnsServer, string config) { _dnsServer = dnsServer; _config = BufferManagementConfig.Deserialize(config); if (_config == null) { throw new DnsClientException("Invalid application configuration."); } if (_config.MaxLogEntries != null) { _logBuffer = new BlockingCollection(_config.MaxLogEntries.Value); } else { _logBuffer = new BlockingCollection(DEFAULT_QUEUE_CAPACITY); } RegisterExportTargets(); if (_exportManager.HasStrategy()) { _queueTimer = new Timer(HandleExportLogCallback, state: null, QUEUE_TIMER_INTERVAL, Timeout.Infinite); } return Task.CompletedTask; } public Task InsertLogAsync(DateTime timestamp, DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, DnsDatagram response) { _logBuffer.Add(new LogEntry(timestamp, remoteEP, protocol, request, response)); return Task.CompletedTask; } public Task QueryLogsAsync(long pageNumber, int entriesPerPage, bool descendingOrder, DateTime? start, DateTime? end, IPAddress clientIpAddress, DnsTransportProtocol? protocol, DnsServerResponseType? responseType, DnsResponseCode? rcode, string qname, DnsResourceRecordType? qtype, DnsClass? qclass) { return Task.FromResult(new DnsLogPage(0, 0, 0, _emptyList)); } #endregion public #region private private void ExportLogs() { var logs = new List(BULK_INSERT_COUNT); // Process logs within the timer interval, then let the timer reschedule while (logs.Count <= BULK_INSERT_COUNT && _logBuffer.TryTake(out var log)) { logs.Add(log); } // If we have any logs to process, export them if (logs.Count > 0) { _exportManager.ImplementStrategy(logs); } } private void HandleExportLogCallback(object? state) { try { ExportLogs(); } catch (Exception ex) { _dnsServer?.WriteLog(ex); } finally { try { _queueTimer.Change(QUEUE_TIMER_INTERVAL, Timeout.Infinite); } catch (ObjectDisposedException) { } } } private void RegisterExportTargets() { // Helper function to register an export strategy if the target is enabled void RegisterIfEnabled(TTarget target, Func strategyFactory) where TTarget : TargetBase where TStrategy : IExportStrategy { if (target?.Enabled == true) { var strategy = strategyFactory(target); _exportManager.AddOrReplaceStrategy(strategy); } } // Register the different strategies using the helper RegisterIfEnabled(_config!.FileTarget!, target => new FileExportStrategy(target.Path)); RegisterIfEnabled(_config!.HttpTarget!, target => new HttpExportStrategy(target.Endpoint, target.Headers)); RegisterIfEnabled(_config!.SyslogTarget!, target => new SyslogExportStrategy(target.Address, target.Port, target.Protocol)); } #endregion private #region properties public string Description { get { return "The app allows exporting logs to a third party sink using an internal buffer."; } } #endregion properties } }