Using dnsmasq & nftables together to create DNS block lists
I recently(ish) discovered yet another amazing feature in dnsmasq – it can add resolved IP addresses to nftsets! This is useful if you want to create an allow/block list based on DNS records instead of IP addresses.
In my case, I wanted to limit HTTPS access to a few domains out on the internet for some machines in my home lab.
Overview
Conceptually, it works like this:
- Someone looks up
example.com
using our dnsmasq as its resolver - dnsmasq resolves
example.com
, adds it’s IP address to the nftset as specified (and replies to the client, of course!) - The client tries to connect to
example.com
- nftables contains a ruleset which decides if the connection is allowed or not, checking the nftset dnsmasq added the resolved IP to
- The client is allowed or denied access to
example.com
To make this work, we need a few things:
- Using a dnsmasq with support for netfilter1
- A list of domains to allow or block access to
- A nft set in your nftables configuration
nftables
First of all, we need to define a set to collect addresses in. The
nftables documentation for sets shows we can use either
ipv4_addr
or ipv6_addr
to collect IP addresses, so create one for
each family if needed. It’s also safe to omit the timeout!2
table inet firewall {
set mgmtUpdateIPv4 {
type ipv4_addr; timeout 4h;
}
# Remove if you don't use IPv6!
set mgmtUpdateIPv6 {
type ipv6_addr; timeout 4h;
}
# the rest of your ruleset
}
Then you can use this named set to allow or block access in rulesets:
# Allow HTTP/HTTPS if the destination address is in the mgmtUpdateIPv4/6 set
chain update_ok {
tcp dport {80,443} ip daddr @mgmtUpdateIPv4 ct state new accept
tcp dport {80,443} ip6 daddr @mgmtUpdateIPv6 ct state new accept
}
In my ruleset I include the above (jump update_ok
) if I want that
subnet to be able to reach the (update) servers in the named set.
Next step is to configure dnsmasq to add IP addresses to these sets.
dnsmasq
Lets say we want to allow access to deb.debian.org
&
security.debian.org
for all our Debian machines, add below to your
dnsmasq.conf
:
nftset=/deb.debian.org/security.debian.org/4#inet#firewall#mgmtUpdateIPv4
nftset=/deb.debian.org/security.debian.org/6#inet#firewall#mgmtUpdateIPv6
Note that you have to adjust the right hand side of the line to align
with your configuration. My firewall is inet
, is named firewall
and the last part is the named sets we created in the previous
section. The first digit signifies if it’s IPv4 or IPv6.
Bonus
You can check which IP addresses are in the named set using the nft
command:
# nft list set inet firewall mgmtUpdateIPv4
table inet firewall {
set mgmtUpdateIPv4 {
type ipv4_addr
timeout 4h
elements = { 18.192.94.96 expires 2h58m41s432ms, 52.58.254.253 expires 2h58m41s433ms }
}
}
You can run
dnsmasq --version
and check ifipset
andnftset
are included under compile time options ↩︎It makes some sense to use a timeout for this, as resources hidden in a CDN might change it’s address every now and then, and if you rarely restart your firewall this might end up allowing access to unintended resources. ↩︎