What triggers dnsmasq to populate/update nft set?

Hello!

I am quite a newbie in Nftables/Dnsmasq and have a question: when does Dnsmasq (Dnsmasq-full to be precise) fills Nft IP Sets and is there a way to rigger it to re-populate a set?

A bit more details. Let's assume I have a Firewall IP Set (Nft Set) which is used in a Firewall Rule to mark traffic. The marked traffic then will be re-routed to another interface using Routing Table + Routing Rule.
The IP Set looks like this (below is a part of the /etc/config/firewall file):

config ipset
        option name 'reroute_test'
        option family 'ipv4'
        list match 'dest_net'

And I have a Dnsmasq IP Set (Rule?) to populate the Nft Set with values (below is a part of the /etc/config/dhcp file):

config ipset
        list name 'reroute_test'
        option table_family 'inet'
        list domain 'ifconfig.io'
        list domain 'myip.com'

When I apply this config and run nft list set inet fw4 reroute_test command I see an empty Nft Set:

table inet fw4 {
        set reroute_test {
                type ipv4_addr
                flags interval
                auto-merge
        }
}

If the Nft Set is empty the traffic is routed via default wan interface (I can check this by visiting myip.com or ifconfig.io - they both show the default wan interface IP address).

After a while however it gets some values:

table inet fw4 {
        set reroute_test {
                type ipv4_addr
                flags interval
                auto-merge
                elements = { 104.21.92.106, 104.26.8.59,
                             104.26.9.59, 172.67.75.163,
                             172.67.191.233 }
        }
}

And of course now the traffic flows through another wan interface (both myip.com and ifconfig.io show another IP address).

If I flush the Nft Set: nft flush set inet fw4 reroute_test it will remain empty for a while.

So my question is what exactly triggers Dnsmasq to fill the Nft Set and how can I manually trigger to do it or at least set a shorter interval?
If I change the Dnsmasq IP Set (Rule) and delete/add a few hosts, I'd like them to be resolved into the Nft Set as soon as possible. As an alternative I can write a script: something like this:

nft flush set inet fw4 reroute_test
<command to trigger Dnsmasq to re-populate the Set>

And run it each time I change the Dnsmasq IP Set.

Thank you in advance for any help and advice!

UPD: Forgot to mention that the real host list will consist of hosts which change their IP frequently thus I need to flush and re-populate the Nft Set frequently. Also the list itself (i.e. host names) changes frequently: some hosts are being added, some are being removed.

Reboot the router

After that do an nslookup of the domains e.g. the use of the domains by clients will populate the set as that triggers the lookup

But you need to flush the DNS cache so rebooting of router and client is necessary or manually flushing dns cache on router and client

Thank you for the advice!

But rebooting is not an option: some host names in the future (real) Dnsmasq IP Set change their IP constantly. I cannot reboot the router every 15 minutes or so.
I know it is kinda crazy, but I cannot do anything about it except to flush and update Nft Sets frequently.

DNSMASQ will populate the ipset when it receives a DNS request for a domain in the list. So, stablished connections, and connections that use an IP address will not trigger DNSMASQ.

Have you tried to add a timeout to the ipset, so the addresses are deleted after a period if time after each triggering?

1 Like

You only have to do the flushing once, after that the Lookup by DNSmasq will populate the set, if the DNS is frequently changing you can consider setting the cache size to 0 (default is 150)

I did. If the timeout is small (e.x. 10 sec.) then I get an empty ipset almost all the time. I think because dnsmasq caches records and doesn't query them. If I set it to ~15 min. then I get ipset with new (added) host names IP's only.

Where should I change this setting (I mean where can I find it)?
Is there a command to flush the DNS cache? Something like ipconfig /flushdns in Windows?

DHCP and DNS configuration /etc/config/dhcp

Look here:

So far I came up with the following solution:

  1. Set dnsmasq cache size to zero: I have an upstream DNS anyway.
  2. Wrote a script (see below) based on the https://openwrt.org/docs/guide-user/advanced/ipset_extras I didn't like the original one because it creates a new ip set and fills it from a file. My script directly fills nft sets using nft add element command. It also gets all host names from /tmp/dnsmasq.d/* files, resolves their IPs and adds to nft sets. But I am eternally grateful to the author of the original script for the idea.
  3. Put the script into CRON to be executed every hour.

Everything seems to work OK for now.

The only disadvantage is: the script only resolves domains mentioned in a dnsmasq ip set or file, but not subdomains.

#!/bin/sh

DNSMASQ_CONF_DIR="/tmp/dnsmasq.d"

main() {
    . /lib/functions.sh
    config_load dhcp
    config_foreach set_variables ipset
    uci_commit firewall
    service firewall reload
}

set_variables() {
    local IPSET_ID="${1}"
    local FW_TABLE_FAMILY
    local FW_TABLE
    config_get FW_TABLE_FAMILY "${IPSET_ID}" table_family
    config_get FW_TABLE "${IPSET_ID}" family "fw4"
    process_ipset_"${ACTION}"
}

process_ipset_fill() {
    local IPSET_TEMP_FILE="$(mktemp -t ipset.XXXXXX)"
    {
        config_list_foreach "${IPSET_ID}" domain get_domain
        config_list_foreach "${IPSET_ID}" cidr get_cidr
        config_list_foreach "${IPSET_ID}" asn get_asn
        config_list_foreach "${IPSET_ID}" geoip get_geoip
    } | sort -u > "${IPSET_TEMP_FILE}"
    config_list_foreach "${IPSET_ID}" name nftset_fill
    rm -f "${IPSET_TEMP_FILE}"
}

process_ipset_erase() {
    config_list_foreach "${IPSET_ID}" name nftset_erase
}

nftset_fill() {
    local NFTSET_NAME="${1}"
    local NFTSET_TEMP_FILE="$(mktemp -t nftset.XXXXXX)"
    local IP_FAMILY

    case "${NFTSET_NAME}" in
        (*6) IP_FAMILY=6
            sed -e "/\./d" ;;
        (*) IP_FAMILY=4
            sed -e "/:/d" ;;
    esac < "${IPSET_TEMP_FILE}" > "${NFTSET_TEMP_FILE}"

    for file in `find ${DNSMASQ_CONF_DIR} -type f`; do
        for domain in $(cat "$file" | grep "${IP_FAMILY}#${FW_TABLE_FAMILY}#${FW_TABLE}#${NFTSET_NAME}" | sed "s/nftset=\///g;s/\/${IP_FAMILY}#${FW_TABLE_FAMILY}#${FW_TABLE}#${NFTSET_NAME}//g;s/\// /g"); do
            for resolved_ip in $(get_domain ${domain}); do
                if [ "${resolved_ip}" = "0.0.0.0" ]; then
                    echo "Found zero IP: ${domain}"
                else
                    echo "${resolved_ip}" >> "${NFTSET_TEMP_FILE}"
                fi
            done
        done
    done

    sed -i -e "s/0.0.0.0//g" "${NFTSET_TEMP_FILE}"

    while IFS= read -r ip; do
        nft add element "${FW_TABLE_FAMILY}" "${FW_TABLE}" "${NFTSET_NAME}" { "${ip}" }
    done < "${NFTSET_TEMP_FILE}"

    rm -f "${NFTSET_TEMP_FILE}"
}

nftset_erase() {
    nft flush set "${FW_TABLE_FAMILY}" "${FW_TABLE}" "${NFTSET_NAME}" "${1}"
}

get_domain() {
    resolveip "${1}"
}

get_cidr() {
    echo "{1}"
}

get_asn() {
    wget -O - "https://stat.ripe.net/data/\
        announced-prefixes/data.json?resource=${1}" \
        | jsonfilter -e "@['data']['prefixes'][*]['prefix']"
}

get_geoip() {
    wget -O - "https://www.ipdeny.com/ipblocks/data/\
        aggregated/${1}-aggregated.zone" \
        "https://www.ipdeny.com/ipv6/ipaddresses/\
        aggregated/${1}-aggregated.zone"
}

ACTION="${1}"
case "${ACTION}" in
        (fill|erase) main;;
        (*) echo "Usage: nftset.sh fill|erase";;
esac

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.