Network Ad Blocking with Pi-hole and OpenWrt
6 min read

Network Ad Blocking with Pi-hole and OpenWrt

Network Ad Blocking with Pi-hole and OpenWrt

Pi-hole is a self-proclaimed "black hole" for internet advertisements. A simple bash one-liner allows you to set up a network-wide ad blocking appliance, on hardware as basic as a Raspberry Pi. A typical configuration would take no more than ten minutes to set up, assuming some prior experience with Linux. It's really quite easy: install Pi-hole, then set your DNS server to the local IP address of your Pi. This basic installation will do just fine for the majority of users. However, there is still some room for improvement. Some applications and devices use hard-coded DNS servers specifically to get around this type of ad blocking. The main offender is Google, who forces most Android clients to ignore the DNS server being broadcasted by the router. Using OpenWrt, we can tighten things up a bit. Here are the three goals that I was looking to achieve in my setup, and the steps I took to achieve them:

1.) Block advertisement traffic on devices that use hard-coded DNS.

Several of my housemates have Android smartphones. After setting up Pi-hole, they noticed that they were still receiving the same amount of advertisements as before. By looking at the Pi-hole interface I could see that only a small portion of their DNS requests were going through the Pi-hole. Naturally, I wanted the Pi-hole to filter all traffic originating from these devices as well as other devices that often use hard-coded DNS servers such as IoT devices and Smart TVs.

This problem is solved by using some custom firewall rules in OpenWrt. Choose Firewall from the Network menu, then click on the Custom Rules tab. Here you can directly input iptables commands which offer more flexibility than the LuCI interface. The following rules redirect port 53 traffic from all devices to the Pi-hole.

iptables -t nat -A POSTROUTING -j MASQUERADE
iptables -t nat -I PREROUTING -i br-lan -p tcp --dport 53 -j DNAT --to 192.168.X.X:53
iptables -t nat -I PREROUTING -i br-lan -p udp --dport 53 -j DNAT --to 192.168.X.X:53

A nice side effect of these rules is that any client that attempts to manually use a DNS server other than the Pi-hole will fail to resolve any requests. A second set of rules allows the Pi-hole to forward unblocked requests to the upstream DNS server.

iptables -t nat -I PREROUTING -i br-lan -p tcp -s 192.168.X.X --dport 53 -j ACCEPT
iptables -t nat -I PREROUTING -i br-lan -p udp -s 192.168.X.X --dport 53 -j ACCEPT

Don't forget to substitute your Pi-hole's local IP address. Keep in mind that depending on your router model, interface names such as eth0 and br-lan may differ. You can view the names of your router's interfaces in OpenWrt by choosing Interfaces from the Network menu. After inputting the rules, click the Save button and reboot the router.

2.) Keep the DHCP server on the router, while still showing discrete clients in the Pi-hole interface.

This is perhaps a contentious one. It would be a lot easier to run the DHCP server on the Pi, but that would create a single point of failure for both DHCP and DNS. If the Pi goes down, new clients wouldn't be able connect to the network. Additionally I found OpenWrt's interface for DHCP related tasks to be much nicer, since it is designed for use on routers.

Some Pi-hole setup guides recommend changing your router's DNS server to the Pi. If you are running a stock router firmware, often times this is the only solution if you don't want to use the Pi-hole's DHCP server. The downside to this method is that all requests in the Pi-hole interface will be shown as originating from your router, regardless of the client that initiated the request. This was unacceptable to me as I wanted to take advantage of the detailed statistics offered by Pi-hole. There are also features such as Group Management that are essentially useless without discrete clients.

The solution to this problem is to advertise the Pi-hole's DNS server to clients using dnsmasq. In OpenWrt, navigate to the Interfaces page under the Network menu. Then click the blue Edit button on your LAN interface. Next, go to the DHCP Server tab and then the Advanced Settings tab underneath. In the DHCP-Options field, type 6,192.168.X.X substituting your Pi-hole's local IP address. Click the Save button and restart the LAN interface. If you want more information on other cool things you can do with DHCP-Options in dnsmasq, this is a good resource.

3.) Retain the ability to resolve local hostnames to IP addresses.

I find this particularly useful since it allows me to easily identify devices on my network. It's much more pleasant to SSH into openwrt/ or raspberry/ than a string of numbers when configuring devices. Also in the Pi-hole interface it is much more convenient to see a list of names such as maxs-iphone and Apple-TV when managing clients or examining query logs. Again, I concede that running the DHCP server on the Pi-hole would eliminate this problem. But I'm stubborn.

Luckily the Pi-hole has a feature for us stubborn folks, called Conditional Forwarding. In the Pi-hole Admin Interface, navigate to the DNS Settings page. Scroll all the way to the bottom and fill out the Conditional Forwarding section with your OpenWrt router's local IP address. Although the Local Domain Name field is marked as optional, I found that it is necessary if you are using a local domain name other than .lan. In either case, make sure you match everything with the OpenWrt settings.

Some OpenWrt Idiosyncracies

During my troubleshooting of Android circumventing the Pi-hole DNS server, I changed some other settings in OpenWrt. If you still have issues with ad blocking on Android clients after following the above steps, you may need to do some or all of these things to get it working smoothly. First, make sure that "Use DNS servers advertised by peer" is unchecked in the Advanced Settings of both the WAN and WAN6 interfaces. With this setting enabled, the Android clients were still receiving advertisements.

I also made sure that there were no DNS forwarding servers set in the DHCP and DNS settings page in the Networking menu. This had a side effect of making the Raspberry Pi itself not able to resolve queries to the outside world, but editing the  nameserver entry in the Pi's /etc/resolv.conf to point to Google's DNS instead of the router took care of that issue.

Another thing that may be getting in the way is Android's Private DNS feature. According to Google's knowledge base article, it is enabled by default on all networks that support it. It can only be manually disabled by the user on a per-device basis, and I can't figure out a way to prevent it at the router firewall level.

Closing Comments

Overall Pi-hole has done an excellent job at reducing the amount of advertising and tracking traffic on my network. At the time of writing, Pi-hole blocks on average around 20.4% of the queries it receives. It is quite alarming to look through the query logs and see how often devices on your network are "phoning home" with analytics data.

I encourage anyone to give Pi-hole a shot, whether it be a basic installation or a setup as convoluted as my own. If you run into any issues, both the Pi-hole Userspace and the subreddit have many resources and knowledgable individuals who would be more than happy to help get you up and running.