Skip to content

coreyleavitt/technitium-content-filter

Repository files navigation

Technitium Content Filter

CI CodeQL C# coverage Python coverage mypy Docs License .NET 9 Python 3.12+

A content filtering DNS app plugin for Technitium DNS Server. Provides domain blocking, allowlisting, DNS rewrites, service-level filtering, and per-client profile assignment -- all managed through a web UI.

Features

  • Per-profile filtering -- Create profiles with independent blocklists, allowlists, custom rules, regex rules, and DNS rewrites. Assign profiles to clients by IP, CIDR, or DNS-over-TLS/HTTPS client ID.
  • Blocklist subscriptions -- Subscribe to remote blocklists (AdGuard, hosts, plain domain formats) and regex blocklists. Automatic refresh with HTTP conditional fetch (If-Modified-Since/ETag).
  • Blocked services -- Block entire services (YouTube, TikTok, etc.) with 72 built-in service definitions. Define custom services with your own domain sets.
  • Regex rules -- Block or allow domains by regex pattern, inline per-profile or via remote regex blocklists. Compiled with ReDoS timeout protection.
  • DNS rewrites -- Redirect domains to alternate IPs or hostnames (e.g., force SafeSearch via CNAME rewrite).
  • Custom blocking responses -- Return specific IP addresses or CNAME records instead of NXDOMAIN. Configurable globally or per-profile.
  • Blocking diagnostics -- TXT query reports and EDNS Extended DNS Error (EDE code 15) with profile name, matched rule, and source.
  • Base profile inheritance -- Designate a base profile whose filters merge into all other profiles. Profile-level allowlists override base-level blocks.
  • Time-based schedules -- Enable/disable filtering per day-of-week with timezone support.
  • Web management UI -- Dashboard with protection toggle, profile/client management, and a full Filters menu (blocklists, allowlists, services, custom rules, DNS rewrites).

Architecture

The system has two components that share a config file (dnsApp.config):

  • C# Plugin -- Runs inside Technitium DNS Server, intercepts DNS queries, and applies filtering rules. Compiles profiles into optimized in-memory structures for O(1) domain lookups.
  • Python Web UI -- Starlette app that reads/writes the shared config file and triggers the plugin to reload via the Technitium API.

Evaluation Order

When a DNS query arrives, the plugin evaluates in this order:

  1. Blocking disabled? -- Allow
  2. Resolve client to profile (DoT/DoH client ID, exact IP, CIDR longest prefix, default)
  3. No profile? -- Use base profile if configured
  4. DNS rewrite match? -- Return rewrite response (A/AAAA/CNAME)
  5. Domain in allowlist? -- Allow
  6. Domain matches regex allow rule? -- Allow
  7. Schedule inactive? -- Allow
  8. Domain in merged block set? -- Block (NXDOMAIN or custom blocking addresses)
  9. Domain matches regex block rule? -- Block
  10. Default -- Allow

Project Structure

├── src/ContentFilter/              # C# DNS app plugin
│   ├── App.cs                      # Plugin entry point (IDnsApplication)
│   ├── Models/                     # Config and compiled profile models
│   └── Services/                   # FilteringService, BlockListManager,
│                                   # ProfileCompiler, ClientResolver,
│                                   # ScheduleEvaluator, DomainMatcher, etc.
├── tests/
│   ├── ContentFilter.Tests/        # C# unit + property tests (~395)
│   ├── ContentFilter.IntegrationTests/
│   └── ContentFilter.Benchmarks/
├── web/                            # Python web management UI
│   ├── app.py                      # Starlette app factory
│   ├── config.py                   # Config I/O, validation, shared state
│   ├── filtering.py                # Pure DNS filtering logic
│   ├── middleware.py               # Auth, CSRF, rate limiting middleware
│   ├── routes.py                   # Page and API route handlers
│   ├── templates/                  # Mako HTML templates
│   ├── static/                     # Vanilla JS + Tailwind CSS
│   └── tests/                      # Python tests
│       ├── test_*.py               # Unit, API, property tests (~285)
│       └── e2e/                    # Playwright browser tests (~98)
├── Dockerfile.build                # Plugin build (outputs ZIP)
├── Dockerfile.test                 # C# test runner
└── Dockerfile.integration-test     # Integration test runner

Getting Started

Prerequisites

  • Technitium DNS Server v14.3+ (required because the IDnsRequestBlockingHandler interface used by this plugin was introduced in v14.3; older versions will not load the plugin)
  • Docker (for building and testing)
  • uv (for web UI development)

Build the Plugin

docker build -f Dockerfile.build -o dist .

This outputs dist/ContentFilter.zip -- the plugin archive.

Install the Plugin

Upload via the Technitium DNS Server API:

curl -s -X POST "https://your-dns-server/api/apps/install" \
  -F "token=YOUR_API_TOKEN" \
  -F "name=ContentFilter" \
  -F "appZip=@dist/ContentFilter.zip"

To update an existing installation:

curl -s -X POST "https://your-dns-server/api/apps/update" \
  -F "token=YOUR_API_TOKEN" \
  -F "name=ContentFilter" \
  -F "appZip=@dist/ContentFilter.zip"

Run the Web UI

cd web
uv sync
uv run hypercorn app:app --bind 0.0.0.0:8000

The web UI needs these environment variables:

Variable Description
CONFIG_PATH Path to the plugin's dnsApp.config
BLOCKED_SERVICES_PATH Path to blocked-services.json
TECHNITIUM_URL Technitium DNS Server URL
TECHNITIUM_API_TOKEN API token for config reload

Docker Compose

A complete Docker Compose example is available at docker-compose.example.yaml with both the Technitium DNS Server and the Content Filter Web UI pre-configured with a shared volume.

Testing

C# Tests

# Unit + property tests
docker build -f Dockerfile.test -t content-filter-tests .
docker run --rm content-filter-tests

# With coverage
docker run --rm content-filter-tests \
  dotnet test --no-restore --settings /src/app/tests/coverlet.runsettings

# Integration tests (requires Docker socket)
docker build -f Dockerfile.integration-test -t content-filter-integration .
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock content-filter-integration

Python Tests

cd web

# Unit, API, and property tests (~285 tests, 98% coverage)
uv sync --extra test
uv run pytest

# E2E browser tests (~98 tests)
uv run playwright install chromium
uv run pytest -m e2e --no-cov

# Everything
uv run pytest -m '' --no-cov

Mutation Testing

dotnet tool install -g dotnet-stryker
dotnet stryker -f stryker-config.json

Configuration

The plugin stores its configuration in a dnsApp.config file in Technitium's app folder. Both the C# plugin and the Python web UI read/write this file. Here's the structure:

{
  "enableBlocking": true,
  "baseProfile": "base",
  "defaultProfile": "kids",
  "timeZone": "America/Denver",
  "scheduleAllDay": true,
  "profiles": {
    "kids": {
      "description": "Restricted profile for children",
      "blockedServices": ["youtube", "tiktok"],
      "blockLists": ["https://example.com/hosts.txt"],
      "allowList": ["khanacademy.org", "school.edu"],
      "customRules": ["bad-site.com", "@@exception.com"],
      "dnsRewrites": [
        { "domain": "google.com", "answer": "forcesafesearch.google.com" }
      ],
      "schedule": {
        "mon": { "allDay": false, "start": "08:00", "end": "20:00" }
      }
    }
  },
  "clients": [
    {
      "ids": ["192.168.1.100", "laptop.dns.leavitt.info"],
      "profile": "kids"
    }
  ],
  "blockLists": [
    {
      "url": "https://example.com/hosts.txt",
      "name": "Ad List",
      "enabled": true,
      "refreshHours": 24
    }
  ],
  "customServices": {
    "my-streaming": {
      "name": "My Streaming",
      "domains": ["stream.example.com"]
    }
  }
}

Comparison with Advanced Blocking App

Content Filter is a replacement for Technitium's built-in Advanced Blocking App with full feature parity plus DNS rewrites, blocked services, schedules, base profile inheritance, and a web UI. See the full comparison.

License

Apache License 2.0

About

Content filtering DNS app plugin for Technitium DNS Server

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors