# DNS-TProxy DNS-based transparent proxy using fake IPs and nftables to redirect and NAT selected domain traffic. `dotp` could stand for Domain Over Transparent Proxy. ## Overview `dotp` is a small DNS proxy that selectively rewrites A and AAAA records for specified domains to "fake" IP addresses and maintains a temporary one-to-one mapping between fake and real client addresses using nftables. This allows traffic to be transparently redirected and NAT-ed while keeping per-connection state in userspace and in kernel nftables sets. The program: - Listens for DNS queries on a UDP socket. - Forwards queries to an upstream DNS server. - Inspects the DNS responses and, for configured domains, replaces the returned IPv4/IPv6 addresses with fake addresses from configured prefixes. - Programs nftables sets to map fake addresses back to the corresponding real addresses for a short TTL. ## How it works ### DNS path 1. A client sends a DNS query (UDP/53) to `dotp`. 2. `dotp` receives the packet, allocates a per-request `client_ctx`, and opens a temporary UDP socket to the upstream DNS server. 3. The upstream response is read and parsed: - DNS header and question section are copied as-is. - Answer section is scanned record by record. - For each name that matches a configured domain: - CNAME records are copied over, preserving compression and structure. - A/AAAA records are intercepted: their RDATA (IP address) is replaced with a fake IP allocated from the configured IPv4/IPv6 prefix pools. 4. For every substituted address: - A NAT entry is created or looked up in an in-memory hash table. - A libev timer is armed for 120 seconds (`NAT_TTL`) for the mapping. - nftables commands are issued (via libnftables) to: - Add the real address to `inet nat_addr` / `nat_addr6` sets. - Add a mapping from fake to real in `inet
nat_map` / `nat_map6` sets. 5. The modified DNS response is sent back to the client. 6. When the NAT timer expires, `dotp` removes the corresponding entries from the nftables sets and frees the mapping. If the incoming DNS packet is larger than the internal buffer (`MAX_MESSAGE_SIZE`), the response is truncated and the TC (truncation) flag is set in the DNS header. ### Domain matching Domains are supplied via the `-d` option and stored in a tree of labels. During DNS parsing, domain names in questions and answers are decoded, including compression pointers, and matched against this tree: - Only names under one of the configured domains are subject to rewriting. - CNAME chains are followed so that subsequent A/AAAA answers for the aliased name are also rewritten. ### NAT pool `dotp` manages two address pools (IPv4 and IPv6) defined by prefixes passed on the command line. It uses a simple hash (`city_hash_mix`) to allocate unique fake addresses within the prefix ranges: - For each real address, a `struct ip_nat` is created containing: - real address and fake address - family (AF_INET / AF_INET6) - links for two separate hash chains (by real and by fake address) - a libev timer (`expire`) that removes the mapping after `NAT_TTL` seconds. - Lookups by real address refresh the timer. ## nftables integration The code assumes an existing nftables table named `inet dotp` with appropriate sets/rules. It manipulates the following sets: - `nat_addr` / `nat_addr6`: containers for real client addresses. - `nat_map` / `nat_map6`: maps from fake to real addresses. It uses commands like: - `add element inet dotp nat_addr { REAL }` - `add element inet dotp nat_map { FAKE:REAL }` - `add element inet dotp nat_addr6 { [REAL6] }` - `add element inet dotp nat_map6 { [FAKE6]:[REAL6] }` and their corresponding `delete element` variants on expiry. The nftables rules themselves (e.g. for DNAT/SNAT using these sets) are not set up by the program; an example configuration is provided in `src/rules.nft`. ## Command-line usage From the `main` function, the expected usage is: ```text Usage: dotp -H LISTEN_HOST -p LISTEN_PORT -4 FAKE_IP_PREFIX -6 FAKE_IP6_PREFIX --upstream-host UPSTREAM_HOST --upstream-port UPSTREAM_PORT [ -d DOMAIN ] [ --daemonize ] ``` ### Options - `-H, --host LISTEN_HOST` IP address to bind the local UDP DNS listener to (IPv4 only in current implementation). Required. - `-p, --port LISTEN_PORT` Local UDP port to listen on. Defaults to 53. - `-d, --domain DOMAIN` Domain to subject to address rewriting (may be specified multiple times). Domain syntax is validated (alphanumeric plus `-`, no leading/trailing `-`, each label up to 63 chars). - `-4, --ipv4-prefix FAKE_IP_PREFIX` IPv4 prefix (e.g. `100.64.0.0/24`) from which fake IPv4 addresses will be allocated. Prefix length must be ≤ 30. Required. - `-6, --ipv6-prefix FAKE_IP6_PREFIX` IPv6 prefix (e.g. `fd00::/64`) from which fake IPv6 addresses will be allocated. Prefix length must be ≤ 64. Required. - `--upstream-host UPSTREAM_HOST` IPv4 address of the upstream DNS server to which queries are forwarded. Required. - `--upstream-port UPSTREAM_PORT` UDP port of the upstream DNS server. Defaults to 53. - `--daemonize` Run the process in the background using `daemon(3)`. `dotp` exits with error status if required options are missing or invalid. ## Runtime behavior - Uses `libev` for event-driven I/O: - One main `server_ctx` for the listening socket. - A short-lived `client_ctx` for each in-flight upstream query with a 5-second timeout. - On `SIGINT` or `SIGTERM`, the event loop is broken and the server exits cleanly, freeing domain/NAT structures and nftables context. ## Dependencies Build-time and runtime dependencies inferred from `src/main.c`: - POSIX sockets (`AF_INET`, `SOCK_DGRAM`, `recvfrom`, `sendto`, `bind`) - `libev` - `libnftables` On Debian/Ubuntu-like systems, packages may be named: - `libev-dev` - `libnftables-dev` ## Limitations - Only UDP DNS is supported. - Listener and upstream are currently IPv4-only. - DNS payload size is limited to `MAX_MESSAGE_SIZE` (0x200 bytes). - nftables table/sets must be created externally (see `src/rules.nft`). ## Example Assuming: - You have an nftables table `inet dotp` set up according to `src/rules.nft`. - You want to redirect traffic for `example.com` and `foo.example.com`. - You have fake address ranges: - IPv4: `100.64.0.0/24` - IPv6: `fd00::/64` - Your upstream resolver is `1.1.1.1`. You might run: ```sh ./dotp \ -H 0.0.0.0 -p 53 \ -4 100.64.0.0/24 \ -6 fd00::/64 \ --upstream-host 1.1.1.1 \ --upstream-port 53 \ -d example.com ``` Point your clients' DNS to the host running `dotp`. For matching domains, clients will see fake IPs, while nftables rules will map those fake addresses back to the real ones for the lifetime of the mapping.