Skip to content

sleaper/dnswall

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dnswall — A Filtering DNS Forwarder

From-scratch DNS filtering server written in C++20. It listens for DNS queries, drops (or rejects) queries for domains listed in a supplied blocklist, and forwards everything else — unchanged — to an upstream recursive resolver, then relays the resolver's answer back to the original client.

The DNS wire protocol (message parsing, header/question/record handling, name compression) is implemented by hand using only raw UDP sockets and the standard C/C++ libraries — no third-party DNS or networking libraries.

This was developed as a university project for the Computer Networks and Communications course at the Faculty of Information Technology, Brno University of Technology (FIT VUT), and is published here as a portfolio piece.

Features

  • DNS forwarding proxy over UDP, for A record queries.
  • Domain blocklist with subdomain matching — example.com also blocks www.example.com, ads.tracker.example.com, etc.
  • IPv4 and IPv6 listening sockets, so clients can query either 127.0.0.1 or ::1.
  • Transparent relaying of upstream responses — they are forwarded untouched, preserving DNS name compression present in the reply.
  • Hand-written DNS protocol layer — header, question, and record parsing; support for domain-name label compression per RFC 1035.
  • Retry and timeout handling — queries that receive no answer from the upstream resolver within a configurable delay are retransmitted up to a configurable number of times (-r / -t extensions).
  • Graceful failure modes — malformed messages, unsupported query types, and refused domains produce appropriate DNS RCODE responses (FORMERR, SERVFAIL, NOTIMP, REFUSED).
  • Verbose mode (-v) that prints per-query tracing information.
  • Inline ID remapping — each upstream query is issued with a locally allocated 16-bit ID so the proxy can multiplex many concurrent clients.
  • Unit and integration tests, wired up to run via make test, plus a CI workflow on GitHub Actions.

Getting started

Prerequisites

  • A C++20 compiler (e.g. g++ 12+)
  • GNU make
  • dig (for the integration test)

Build

make dns        # build the server binary ./dns
make debug      # build with -g, extra warnings, and -Werror
make test        # build and run unit tests + integration tests
make clean       # remove build artifacts and the binary

Usage

Usage: dns -s server [-p port] -f filter_file [-v] [-r max_retries] [-t retry_delay_ms]
Flag Description
-s server IP address or hostname of the upstream DNS resolver (required).
-f filter_file Path to the blocklist file (required).
-p port Local UDP port to listen on. Defaults to 53.
-v Verbose — log every query/answer to stdout.
-r max_retries Number of retransmissions to the upstream before giving up.
-t retry_delay_ms Milliseconds to wait for an upstream answer before retrying.
-h Show help.

Flags may be passed in any order.

Example

Run on a non-privileged port, forwarding to Cloudflare's resolver and filtering blacklist.txt:

./dns -s 1.1.1.1 -p 5400 -f blacklist.txt -v

Then, in another terminal:

dig @127.0.0.1 -p 5400 example.com A      # allowed  -> forwarded
dig @127.0.0.1 -p 5400 badexample.com A   # blocked  -> refused

Blocklist file format

One domain per line. Blank lines and lines starting with # are ignored. The file may use Unix, Windows, or classic Mac OS line endings. Surrounding whitespace on a line is tolerated. Malformed lines are skipped with a warning on stderr so the operator knows the file needs fixing.

# ads and trackers
doubleclick.net
ads.example.com

Project layout

.
├── Makefile
├── include/            # public headers
│   ├── arg_parser.h
│   ├── dns_header.h
│   ├── dns_message.h
│   ├── dns_question.h
│   ├── dns_record.h
│   ├── dns_server.h
│   ├── filter_domains.h
│   ├── udp_socket.h
│   └── utilz.h
├── src/                # implementation (one translation unit per module)
│   ├── arg_parser.cpp
│   ├── dns_header.cpp
│   ├── dns_message.cpp
│   ├── dns_question.cpp
│   ├── dns_record.cpp
│   ├── dns_server.cpp
│   ├── filter_domains.cpp
│   ├── main.cpp
│   ├── udp_socket.cpp
│   └── utilz.cpp
└── tests/              # unit tests (C++) and integration test (shell + python)

Module overview

  • main — argument parsing, wiring up the filter and the server.
  • arg_parsergetopt-based CLI parsing with validation.
  • filter_domains — loads and stores the blocklist; answers "is this domain blocked?" including subdomain checks.
  • dns_server — the event loop: receives client queries, decides block/forward, tracks in-flight upstream queries, relays answers, handles retransmissions and timeouts.
  • dns_message / dns_header / dns_question / dns_record — the hand-written DNS parser/serializer (RFC 1035).
  • udp_socket — thin RAII wrapper around socket(2) / bind(2) / recvfrom(2) / sendto(2) for IPv4, IPv6, and the upstream socket.
  • utilz — small helpers (domain utilities, splitting, subdomain check).

Testing

make test runs:

  1. Unit tests (in tests/test_*.cpp) covering domain validation, subdomain matching, label encode/decode, the blocklist predicate, and DNS name-to-string conversion.
  2. Integration tests (tests/integration_test.sh) — starts the server against a real upstream and digs it from multiple address families, with both an unreachable and a reachable resolver to exercise retransmission.
  3. A Python/scapy-based black-box test (tests/test_dns_python.py) that crafts raw DNS packets.

CI (.github/workflows/test.yml) runs the same pipeline on push to main.

References

  • [RFC 1035] Domain names — implementation and specification
  • [RFC 1123] §2.1 — relaxation of the "domain names start with a letter" requirement
  • [RFC 3696] §2 — further guidance on domain-name syntax

License

This project was created for academic coursework. Feel free to read and learn from it; please don't submit it as your own work.

About

Filtering DNS resolver in C++

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors