Wai Hon's Blog

Local Network DNS Rewrite

2023-05-10 #hosting

Major Updates

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 RecordTypeData
service.example.comAHome 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 RecordTypeData
service.example.comAServer 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.