Skip to content

Conversation

zbalkan
Copy link
Contributor

@zbalkan zbalkan commented Jul 24, 2025

1. Problem Statement

Technitium DNS Server requires a direct, scalable method to ingest domain-based Indicators of Compromise (IOCs) from a MISP instance for network-level blocking. Any such integration must handle potentially massive datasets (>1m domains) and operate as a well-behaved background service, ensuring its resource consumption (CPU, memory) during IOC updates does not degrade the low-latency performance of the core DNS server.

2. Proposed Solution

This PR introduces a new Technitium DNS App called MispConnectorApp. It operates as a resilient background service that periodically fetches domain IOCs from a MISP instance and loads them into an optimized, in-memory blocklist.

3. Architectural Decisions and Technical Implementation

This application was designed with the following key principles and patterns:

Configuration Model: POCO with Data Annotations

Configuration is managed via a strongly-typed Config POCO class, deserialized from dnsApp.config. Validation is handled declaratively using System.ComponentModel.DataAnnotations.

This provides compile-time type safety and centralized validation, making the app safer and more maintainable than manual parsing. It fails fast on invalid configuration.

Data Fetching: Paginated and Resilient API Client

The FetchDomainsFromMispAsync method fetches data in configurable pages. Each page request includes a local retry policy with exponential backoff and jitter to handle transient network errors.

This ensures the entire update process is atomic (all-or-nothing) and prevents a single network blip from failing the entire multi-hour update cycle. It also ensures a low, stable resource footprint, protecting live DNS server performance.

Blocking Storage: FrozenSet

The blocklist is stored in a FrozenSet<string>, which is created during the update process.

This is the ideal data structure for a "create-once, read-many" workload. It is immutable, inherently thread-safe for reads, and highly optimized for the fastest possible lookup performance, which is critical for the DNS query path.

Concurrency Model: Lock-Free Atomic Updates

The _globalBlocklist reference is updated using Interlocked.Exchange(). The read path in IsDomainBlocked accesses the FrozenSet directly without locks.

This is the highest-performance, lock-free pattern for this scenario. It provides an instantaneous, atomic swap of the blocklist with zero blocking or contention on the read path, guaranteeing maximum query throughput.

Background Task Management: PeriodicTimer

The background update loop is managed by a System.Threading.PeriodicTimer within a long-running async Task.

This is the modern, correct, and safe way to handle periodic asynchronous work in .NET. It prevents reentrancy (overlapping updates) and integrates seamlessly with CancellationToken for graceful shutdown.

Query Processing Performance: Span on the Hot Path

The IsDomainBlocked method uses ReadOnlySpan<char> to perform parent domain lookups.

This avoids heap allocations for intermediate substrings within the lookup loop, significantly reducing GC pressure and improving latency under heavy query load. However, we still create strings due to FrozenSet data structure. We get minor performance improvement here.

Diagnostics: EDE and TXT Reporting

The connector provides both Extended DNS Errors (EDE, RFC 8914) and optional TXT record reports for blocked domains.

These features are complementary. EDE provides a standards-compliant, machine-readable error code for modern resolvers, while TXT records offer a universally accessible, human-friendly diagnostic tool for administrators.

@ShreyasZare
Copy link
Member

Thanks @zbalkan for the PR.

@zbalkan
Copy link
Contributor Author

zbalkan commented Jul 24, 2025

Hi @ShreyasZare,

I have a question for you. I followed the steps of Advanced Blocking App adnd returned the SOA but is it actually needed when the request gets blocked?

I followed the handling of TXT queries as well. However, to me we can avoid that with the use of EDR. But it is up to you.

@ShreyasZare
Copy link
Member

ShreyasZare commented Jul 24, 2025

Thanks for asking. The SOA in authority section is actually required as per the standards. The client will be useing the SOA record's TTL and MINIMUM value to do negative caching so by sending SOA, you get to control how much time the client will cache this blocked response.

The TXT support is useful since many DNS clients do not support Extended DNS Errors so its useful when you need to test with tools like nslookup for example.

@zbalkan
Copy link
Contributor Author

zbalkan commented Jul 24, 2025

Thanks for the clarification. I'll keep it as is then.

@zbalkan zbalkan marked this pull request as ready for review July 24, 2025 14:10
@zbalkan
Copy link
Contributor Author

zbalkan commented Aug 3, 2025

I'll do some minor changes. I'll mark it as a draft and try to finish until next Friday.

@zbalkan zbalkan marked this pull request as draft August 3, 2025 14:14
@zbalkan
Copy link
Contributor Author

zbalkan commented Aug 3, 2025

Found an edge case during tests. It may be related to my test environment though. I'll create a clean test environment and retest to reproduce, then finalize the PR.

@zbalkan zbalkan marked this pull request as ready for review August 4, 2025 09:09
…task

Signed-off-by: Zafer Balkan <zafer@zaferbalkan.com>
Signed-off-by: Zafer Balkan <zafer@zaferbalkan.com>
@zbalkan
Copy link
Contributor Author

zbalkan commented Aug 4, 2025

Finally it is complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants