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. ↩︎