Support A and AAAA DNS answer rewriting, CNAME alias handling, and temporary IPv4/IPv6 NAT mappings backed by nftables sets/maps. Add example nftables rules and expand the README with usage, behavior, and setup notes.
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
- A client sends a DNS query (UDP/53) to
dotp. dotpreceives the packet, allocates a per-requestclient_ctx, and opens a temporary UDP socket to the upstream DNS server.- 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.
- 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 <table> nat_addr/nat_addr6sets. - Add a mapping from fake to real in
inet <table> nat_map/nat_map6sets.
- Add the real address to
- The modified DNS response is sent back to the client.
- When the NAT timer expires,
dotpremoves 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_natis 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 afterNAT_TTLseconds.
- 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:
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_HOSTIP address to bind the local UDP DNS listener to (IPv4 only in current implementation). Required. -
-p, --port LISTEN_PORTLocal UDP port to listen on. Defaults to 53. -
-d, --domain DOMAINDomain 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_PREFIXIPv4 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_PREFIXIPv6 prefix (e.g.fd00::/64) from which fake IPv6 addresses will be allocated. Prefix length must be ≤ 64. Required. -
--upstream-host UPSTREAM_HOSTIPv4 address of the upstream DNS server to which queries are forwarded. Required. -
--upstream-port UPSTREAM_PORTUDP port of the upstream DNS server. Defaults to 53. -
--daemonizeRun the process in the background usingdaemon(3).
dotp exits with error status if required options are missing or invalid.
Runtime behavior
- Uses
libevfor event-driven I/O:- One main
server_ctxfor the listening socket. - A short-lived
client_ctxfor each in-flight upstream query with a 5-second timeout.
- One main
- On
SIGINTorSIGTERM, 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) libevlibnftables
On Debian/Ubuntu-like systems, packages may be named:
libev-devlibnftables-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 dotpset up according tosrc/rules.nft. - You want to redirect traffic for
example.comandfoo.example.com. - You have fake address ranges:
- IPv4:
100.64.0.0/24 - IPv6:
fd00::/64
- IPv4:
- Your upstream resolver is
1.1.1.1.
You might run:
./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.