Pi-hole on Raspberry Pi with IPv6

Setting up Pi-hole with Unbound on Raspberry Pi 4 B with IPv6 support

I’ve had a Raspberry Pi 4B sitting in my cabinet for a few months now. I dusted it off and realized that the SD card was busted. Got a replacement 64GB U3 A2 card and got it up and running with Ubuntu server. The primary intended use was to run docker with DB containers that I use for my side projects such as Postgres/MySQL/MongoDB.

My current home network consists of several routers for WiFi reachability. I try to ensure 5Ghz coverage to all my devices. Due to the concrete walls a single router does not suffice, hence a home made mesh.

The earlier ISP was a IPv4 only provider. Easy to setup. Worked out of the box. To safeguard other users of the network, I use a privacy sensitive DNS provider, Adguard. The issue with Adguard DNS was no geo proximity. It caused large ping times, as the closest CDN was never picked.

When I moved to my new ISP which supports IPv6, I decided to also deploy Pi-hole alongwith my docker containers.

./network-layout.png
Home Network Layout

Installing Pi-hole

Pi-hole is quite easy to deploy, especially with the auto install script.

curl -sSL https://install.pi-hole.net | bash

Once I followed the steps I got Pi-hole running out of the box. As suggested you have to change your router settings to send the IP address of your Pi-hole server as your local DNS. Done and done.

./pi-mob-home.png
Pi-hole home

The Problem

Ads still show!

To ensure that I wasn’t fudging up DHCP/DNS discovery I used scutil:

scutil --dns

resolver #1
  search domain[0] : lan
  nameserver[0] : 2405:201:xxxx:xxxx:xxx:xxxx:4209:2091
  nameserver[1] : fe80::a204:60ff:fe43:3005%en0
  nameserver[2] : 192.168.2.50
  if_index : 6 (en0)
  flags    : Scoped, Request A records, Request AAAA records
  reach    : 0x00020002 (Reachable,Directly Reachable Address)

IPv4 looked good. 192.168.2.50 is the IP of my pi-hole server.

However, the IPv6 didn’t belong to pi-hole. The default ISP setting was to use stateless IPv6 config and DNS was being advertised by the router. It was using my upstream DNS (ISP) server.

To fix this, I made the following changes:

IPv6 on pi-hole

  • Get your Ipv6 address for pi-hole
ip addr show
  • Enable IPv6 support on pi-hole
./ph-slaac.png
IPv6 Slaac
  • Set the IPV6_ADDRESS add /etc/pihole/setupVars.conf

This should’ve already been done during the installation of pi-hole but if isn’t, you can manually set it up using the address above

IPV4_ADDRESS=192.168.2.50/24
IPV6_ADDRESS=2405:xxx:xxxx:xxxx::50/64

A neat trick I learnt while assigning static IPv6 addresses is to the use the same suffix as IPv4. Easy to remember and deal with.

Restart FTL:

systemctl restart pihole-FTL
  • Enable AAAA query analysis for Pi-hole

Pi-hole by default will only analyse A queries, so we need to add support for AAAA.

DBINTERVAL=60
MAXDBDAYS=7
AAAA_QUERY_ANALYSIS=yes

PRIVACYLEVEL=0

I also added a throttling parameters to not wear out the SD Card.

  • Refresh Pi-hole

Reboot the server. Fetch gravity lists again

pihole -g
  • Stateful DHCPv6
./jio-dhcpv6.png
ISP Stateful DHCPv6

My attempts at trying to get DHCP v4 and v6 from pi-hole failed. This is probably due to wrong assumptions of SLAAC + RA.

  • Debug Pi-hole

At the end I made sure everything is kosher by running diagnosis on Pi-hole

pihole -d

Setting up Unbound

I do not like using any of the open DNS servers. They have their place and provide a lot of value but it’s not for me. I prefer a combo of ad-block and recursive DNS. Unbound works perfectly with Pi-hole. Setting it up is again very simple.

apt install unbound

Update the unbound config to support Pi-hole

/etc/unbound/unbound.conf.d/pi-hole.conf

Make a note of the port, the default port 53 is used by Pi-hole and must be changed.

server:
    interface: 0.0.0.0
    interface: ::0
    port: 5335

    access-control: 192.168.2.0/24 allow
    access-control: 127.0.0.0 allow
    access-control: 2001:db8:dead:beef::/48 allow

    # unbound optimisation
    num-threads: 4
    msg-cache-slabs: 16
    rrset-cache-slabs: 16
    infra-cache-slabs: 16
    key-cache-slabs: 16
    outgoing-range: 206
    so-rcvbuf: 4m
    so-sndbuf: 4m
    so-reuseport: yes
    rrset-cache-size: 100m
    msg-cache-size: 50m

    # unbound security
    do-ip4: yes
    do-ip6: yes
    do-udp: yes
    do-tcp: yes
    cache-max-ttl: 86400
    cache-min-ttl: 3600
    hide-identity: yes
    hide-version: yes
    minimal-responses: yes
    prefetch: yes
    use-caps-for-id: yes
    verbosity: 1
    harden-glue: yes
    harden-dnssec-stripped: yes

    # download from ftp://ftp.internic.net/domain/named.cache
    # root-hints: "/var/lib/unbound/root.hints"

    # Ensure privacy of local IP ranges
    private-domain: "lan"
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: 172.16.0.0/12
    # private-address: 10.0.0.0/8
    private-address: fd00::/8
    private-address: fe80::/10

Tweak it as per your needs and restart Unbound

systemctl restart unbound

Check unbound

Will pass:

root@pi-desktop:~# dig sigok.verteiltesysteme.net @127.0.0.1 -p 5335
; <<>> DiG 9.16.6-Ubuntu <<>> sigok.verteiltesysteme.net @127.0.0.1 -p 5335
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48863
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;sigok.verteiltesysteme.net.	IN	A

;; ANSWER SECTION:
sigok.verteiltesysteme.net. 3600 IN	A	134.91.78.139

;; Query time: 499 msec
;; SERVER: 127.0.0.1#5335(127.0.0.1)
;; WHEN: Tue Feb 23 08:53:17 IST 2021
;; MSG SIZE  rcvd: 71

Will fail:

root@pi-desktop:~# dig sigfail.verteiltesysteme.net @127.0.0.1 -p 5335
; <<>> DiG 9.16.6-Ubuntu <<>> sigfail.verteiltesysteme.net @127.0.0.1 -p 5335
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 56203
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;sigfail.verteiltesysteme.net.	IN	A

;; Query time: 1147 msec
;; SERVER: 127.0.0.1#5335(127.0.0.1)
;; WHEN: Tue Feb 23 08:53:43 IST 2021
;; MSG SIZE  rcvd: 57

DHCP Confirmation

At least for IPv4, you can run the debug tool to make sure DHCP is correct.

root@pi-desktop:~# pihole-FTL dhcp-discover
Scanning all your interfaces for DHCP servers
Timeout: 10 seconds

* Received 301 bytes from eth0:192.168.2.1
  Offered IP address: 192.168.2.113
  Server IP address: 192.168.2.1
  Relay-agent IP address: N/A
  BOOTP server: (empty)
  BOOTP file: (empty)
  DHCP options:
   Message type: DHCPOFFER (2)
   server-identifier: 192.168.2.1
   lease-time: 3600 ( 1h )
   renewal-time: 1800 ( 30m )
   rebinding-time: 3150 ( 52m 30s )
   netmask: 255.255.255.0
   broadcast: 192.168.2.255
   router: 192.168.2.1
   dns-server: 192.168.2.50
   dns-server: 192.168.2.50
   domain-name: "lan"
   --- end of options ---

Switching Pi-hole to use unbound

Disable all Upstream DNS servers and add custom DNS that you setup for Unbound. Use the loopback addresses for Unbound:

IPv4  127.0.0.1#5335

IPv6  ::1#5335

./pi-unbound.png
Unbound with Pi-hole

That should be it! Hope you enjoyed reading the article.

Join my newsletter
I'll send you my latest blog posts by email. Also, you'll be the first to hear about new things I'm working on.