If you’re using PiHole on your network to block ads and prevent your various smart devices from sending tracking information to their manufacturers, you might be surprised to find out that some of these devices are using a sneaky tactic to bypass your PiHole entirely.
Smart devices manufacturers often “hard-code” in a public DNS server, like Google’s 8.8.8.8, and their devices ignore whatever DNS server is assigned by your router – such as your PiHole.
Nearly 70% of smart TVs and 46% of game consoles were found to contain hardcoded DNS settings – allowing them to simply ignore your local network’s DNS server entirely. On average, Smart TVs generate an average of 60 megabytes of outgoing Internet traffic per day, all the while bypassing tools like PiHole.
Force all DNS queries through PiHole
Fortunately, with a few simple firewall rules, you can intercept these hardcoded DNS queries and redirect them to your PiHole. These instructions are for pfSense, however you should be able to adapt them for Sophos XG, Ubiquiti EdgeRouter, etc.
Create NAT Rules
Log in to your pfSense admin interface, and navigate to Firewall > NAT > Port Forward.
We’re going to create two Port Forward NAT rules – one to redirect any DNS queries originating from devices on the LAN to PiHole, and another to allow PiHole to commmunicate with external DNS servers. We will also create an additional outbound NAT rule that will make this process invisible to any clients on the network with hardcoded DNS.
NAT Rule 1: Redirect DNS queries to PiHole
Click the Add button to create your first new NAT Port Forward rule.
- Interface: LAN
- Protcol: TCP/UDP
- Source: LAN net (you may need to click the blue show advanced button to see this option)
- Destination – Invert match: Checked
- Destination – Type: Single host or alias
- Destination – Address/mask: Your PiHole’s IP address
- Destination Port Range – From: DNS
- Destination Port Range – To: DNS
- Redirect Target IP: Your PiHole’s IP address
- Redirect Target Port: DNS
- Description: Intercept any outgoing DNS queries and redirect them to PiHole.
NAT Rule 2: Exempt PiHole from DNS query redirects
Click the Add button to create your second new NAT Port Forward rule.
- No RDR (NOT): Checked
- Interface: LAN
- Protcol: TCP/UDP
- Source – Type: Single host or alias
- Source – Address/Mask: Your PiHole’s IP address
- Destination: Any
- Destination Port Range – From: DNS
- Destination Port Range – From: DNS
- Description: Allow PiHole to reach external DNS servers
Note: pfSense (and most other firewalls) process rules from top to bottom. Make sure you drag the second rule exempting PiHole from DNS query redirects above the first rule we created – otherwise PiHole will not be able to contact external DNS servers.
NAT Rule 3: Prevent clients from giving unexpected source errors
Finally, we need to create an outbound NAT rule. Navigate to Firewall > NAT > Outbound.
- Interface: LAN
- Address Family: IPv4+IPv6
- Protocol: any
- Source – Type: Network
- Source – Network for the outbound NAT mapping: Your internal LAN network
- Destination – Type: Network
- Destination – Network for the outbound NAT Mappings: Your PiHole’s IP Address
- Destination Port Range: 53
- Translation: Interface Address
- Description: Prevents hardcoded DNS clients from giving unexpected source error after DNS redirected to PiHole.
Test it out
You can easily test to make sure your DNS redirection is working properly.
- Create a new, temporary internal DNS entry on your network (“piholetest.example.com”), and point it to 10.0.1.1. You can do this right from PiHole under Local DNS Records.
- Manually set your computer’s DNS server to 1.1.1.1.
- Open a terminal window (or command promt on Windows), and run nslookup piholetest.example.com
- If you set this up correctly, nslookup should return 10.0.1.1. Your computer thinks it’s receiving DNS records from 1.1.1.1, while in reality they are coming from your PiHole.
macbookpro:~ labzilla$ nslookup piholetest.example.com Server: 1.1.1.1 Address: 1.1.1.1#53 Name: piholetest.example.com Address: 10.0.1.1
- You can further demonstate this by temporarily disabling the first NAT rule we created, and running the same nslookup piholetest.example.com command:
macbookpro:~ labzilla$ nslookup piholetest.example.com Server: 1.1.1.1 Address: 1.1.1.1#53 ** server can't find piholetest.example.com: NXDOMAIN
As “piholetest.example.com” doesn’t exist on the public Internet, the real 1.1.1.1 server has no record to provide – resulting in your nslookup request returning a NXDOMAIN error.
Don’t forget to revert your computer’s DNS settings back to their original value, and reenable any firewall rules you temporary disabled while testing.
DNS over TLS/HTTPS
Andrew Healey brought up a great point in a tweet responding to this article:
He’s right. DNS over HTTPS travels over port 443, and would sail right through these firewall rules (as would DNS overt TLS). Blocking HTTPS/443 is not an option for obvious reasons. Instead, we can mitigate this threat by blocking all outbound traffic to known public DNS servers (except for PiHole).
This option isn’t perfect – the firewall rule is only as good as the source list behind it.
Create IP List in pfSense
You can aliases in pfSense that will automatically pull down (and update) a list of IP addresses. Public-DNS.info maintains a list of publicly accessible DNS servers that we can have pfSense copy for firewall rules.
From the pfSense dashboard, click Firewall, then click Aliases. Click Add in the bottom right corner. Configure the alias with the following uptions:
- Name: IP_PublicDNS
- Description: Public DNS Server List
- Type: URL Table (IPs)
- URL Table (IPs): http://public-dns.info/nameservers-all.txt
- Update Frequency: 7 (this is the drop down menu after the “/” in the above field)
Create Firewall Rules using IP List
Now that we have our alias list of public DNS servers configured in pfSense, we can make rules to block outgoing traffic (1) destined for IP addresses that are on the list (2) that didn’t come from PiHole.
I prefer to use firewall rules that are as narrowly tailored as possible, so I’m opting to block only DNS over HTTPS (port 443) and DNS over TLS (port 853) traffic. You could also just block everything.
Navigate to Firewall > Rules, and click the LAN tab. Click the Add button in the bottom right to make a new rule.
- Action: Block
- Interface: LAN
- Address Family: IPv4+IPv6
- Protocol: TCP/UDP
- Source – Invert match: Checked
- Source: Your PiHole’s internal IP address
- Destination: Single host/alias – IP_PublicDNS (or whatever you called your alias in the previous section).
- Destination Port Range: HTTPS
- Log: Checked (optional, but helpful for troubleshooting)
- Description: Block HTTPS – PublicDNS list
Save the rule, and then click the Copy icon to duplicate the rule to also block DNS over TLS. Change the following:
- Destination Port Range: DNS over TLS (from/to)
- Description: Block DNS over TLS – PublicDNS list
Save the rule, and then click Apply Changes.
Test it out
You can test to make sure this rule is working properly by opening a terminal window and running the following command telnet 1.1.1.1 443. The connection should eventually fail:
macbookpro:~ labzilla$ telnet 1.1.1.1 443 Trying 1.1.1.1...
If you disable the firewall rule, the connection will succeed:
macbookpro:~ labzilla$ telnet 1.1.1.1 443 Trying 1.1.1.1... Connected to one.one.one.one.
Don’t forget to reenable your firewall rules!
Hacker News
This post hit the front page of Hacker News on Saturday December 5th, 2020. Thank you @boramalper for submitting it, and I hope you found the information useful!
- If you’re curious about what the Hacker News bump looks like – this blog normally sees about 100 hits per day. Between December 5th-6th, this post had over 70,000 views.
- The original comment thread is full of insightful comments from individuals who work on IoT hardware and other embedded devices, and is well worth a read.
- I added an additional section incorporating some of the suggestion that @healyio made in this Twitter thread regarding DNS over TLS/HTTPS.