Local Network DNS Rewrite
2023-05-10 #hosting
Major Updates
- 2024-12-05: Add the lightweight solution using bind9/named response policy zone (RPZ).
- 2023-05-10: Thanks to @RogerBW for telling me this problem is known as “Split-horizon DNS”. As a hindsight, I was solving a known problem from scratch. Here is how:/
Problem: Slow Throughput at Home Network
I host some services at home by mapping public domain names to my home IP. It is a typical self-hosting setup that allows me to access those services anywhere, even outside of my home. For example, I use a WebDAV service to sychronize with mobile.
However, I noticed the network throughput is very low (10~20 Mbps) when I am at home, connecting to the same local network. I would expect at least a few hundred Mbps.
Cause: Requests Routed Outside
The requests to my home server are routed outside my home’s network and come back because my service’s domain is mapped to a public IP.
Public DNS Record | Type | Data |
---|---|---|
service.example.com | A | Home IP (e.g., 123.123.123.123) |
While my ISP gives me 500M+ Mbits/sec of download, it is stint on the upload bandwidth! My local network throughout is throttled by the ISP upload speed.
I benchmark the throughput with iperf3
. With a public IP, I get 21 Mbps. It matches the upload bandwidth from the ISP.
$ iperf3 -c <PUBLIC IP>
...
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 25.6 MBytes 21.5 Mbits/sec 380 sender
[ 5] 0.00-10.01 sec 25.0 MBytes 21.0 Mbits/sec receiver
With a private IP, I get 29 Gbps, a local throughout that looks right!
$ iperf3 -c <PRIVATE IP>
...
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 33.5 GBytes 28.8 Gbits/sec 0 sender
[ 5] 0.00-10.00 sec 33.5 GBytes 28.8 Gbits/sec receiver
These results match my hypothesis! The question becomes – Can I resolve the domain to a private IP when I am home?
Solution: Local DNS Rewrite with AdGuard Home
Yes! One easy solution is to rewrite the DNS record of my domain to a private IP with a local DNS (AdGuard Home).
Local DNS Record | Type | Data |
---|---|---|
service.example.com | A | Server Private IP (e.g., 192.168.1.2) |
It works! I can get a much higher local throughput at home using the public domain name! Also. it works seamlessly when leaving or arriving home because my phone flush its system’s DNS cache when switching network.
Solution: Response Policy Zone (RPZ) with Bind9
Another way is to set up response policy zone (RPZ) with bind9/named.
// /etc/named.conf
options {
...
// Add the subnet for recursion
allow-recursion { 127.0.0.1; 192.168.1.0/24; };
// Add external DNS to forward requests
forwarders {
8.8.8.8;
8.8.4.4;
}
// Set up the response policy zone
response-policy {zone "rpz"; };
...
};
zone "rpz" {
type master;
file "rpz.zone";
};
;; /var/named/rpz.zone
$TTL 1h
;; (Serial, Refresh, Retry, Expire, Negative Cache TTL)
@ SOA localhost. root.localhost. (2024120462 8h 30m 1w 1h)
@ NS localhost.
;; List of overriding domains
service.example.com A 192.168.1.2
service2.example.com A 192.168.1.2
# Test if it works. It should return 192.168.1.2
nslookup service.example.com localhost
Alternative: Hairpin NAT
Thanks to this reply, it turns out this problem could be solved by routers with “Hairpin NAT” support (a.k.a. “NAT Loopback”). Unfortunately, mine (Google Wifi) doesn’t seem to support Hairpin NAT and I rely on a local DNS rewrite.