DNS-based firewall rules on OpenWrt 22.03 using IP sets and fw4

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