I wanted to be able to use DNS-based firewall rules like on pfsense, opnsense and Sophos UTM for certain use cases. OpenWRT does not have this functionality built in. I came up with the following solution.
This allows you to list DNS names, for example in /etc/ipset-dns/dst_host_search_engines.list
:
google.com
www.google.com
yahoo.com
www.yahoo.com
bing.com
www.bing.com
With a corresponding IP set entry in /etc/system/firewall
:
config ipset
option name 'dst_host_search_engines'
option match 'dst_ip'
option enabled '1'
option timeout '0'
And an example firewall rule you could use with OpenWRT in /etc/config/firewall
:
config rule
option name 'Allow-Search-Engines'
option family 'ipv4'
list proto 'all'
option ipset 'dst_host_search_engines'
option family 'ipv4'
option target 'ACCEPT'
option src 'lan'
And the entirety of the script, which will query the hosts listed based on their IP address family, and set a timeout based on the TTL of the DNS entries.
/etc/ipset-dns.sh
(don't forget to set executable bit):
#!/bin/sh
for filename in /etc/ipset-dns/*.list; do
ipset=$(basename $filename | cut -d '.' -f1)
stdout=$(nft list set inet fw4 $ipset 2>&1)
if [ $? -gt 0 ]; then
echo $stdout | sed 's/^Error/Warning/'
continue
fi
nft_type=$(echo $stdout | grep -oE 'type \w+' | awk '{ print $2 }')
type=
if [[ "$nft_type" == "ipv4_addr" ]]; then
type=A
elif [[ "$nft_type" == "ipv6_addr" ]]; then
type=AAAA
else
echo "Warning: invalid type $nft_type for $ipset"
continue
fi
dig $type -f $filename +noall +answer | while read _ ttl _ _ ip; do
ttl=$(( ttl + 500 ))
nft add element inet fw4 $ipset { ${ip} timeout ${ttl}s };
done
done
And the following crontab entry for the root user, in /etc/crontabs/root
so that the script runs at boot and every 5 minutes (modify to suit, but make sure to update the 500 second addition on the TTL):
@reboot /etc/ipset-dns.sh
5 * * * * /etc/ipset-dns.sh