Nftables - Filtering traffic at IP addresses level based on relevant domain name

Hi,

I got an nftables filtering of traffic based on sets of IP address filled via DNS queries, that is similar to the fw3 ipset implementation https://openwrt.org/docs/guide-user/firewall/fw3_configurations/dns_ipset but with fw4 in 22.03.

Here the configuration, I would like to get some comment and then add a page in the wiki.

Goal : Filter traffic in fw4 based on the destination IP address of the packets, getting the list of addresses from their domain names.

Prerequisites :

  1. You need a firewall zone without forwarding to wan, so that no traffic to the internet is allowed by default.
  2. Have dig and grep installed

Overall approach : We will add a rule to the forwarding chain of the firewall zone mentioned in the prerequisite, the rule will allow traffic to specified IP addresses associated to their domains.

In /etc/rc.local add the below code to create the nft set in which we will save the IP addresses, the proposed code is ipv4 only but can be extended to cover ipv6

# Filter wildlan by IP addresses
## Create a set for "inet fw4" table with name "blackhole" that can include "ipv4_addr"
nft add set inet fw4 blackhole { type ipv4_addr \;}

## Add element to "blackhole" from file urls.txt
for address in $(dig a -f /etc/sets-ipdns/wildlan-urls.list +short | grep -v '\.$'); do
	nft add element inet fw4 blackhole {$address}
done

## Allow packes in "blackhole" (all others are denied by default)
nft insert rule inet fw4 forward_wildlan position 424 ip daddr @blackhole accept

The list of domains to which traffic will be allowed shall be included in a the custom file /etc/sets-ipdns/wildlan-urls.list, to create the file

cd /etc
mkdir sets-ipdns
cd sets-ipdns
vim wildlan-urls.list

List inside vim the domain names that shall be allowed.

At this point it has to be verified the handle to which insert the new firewall rule included in rc.local, in the above example is added before the handle 424.

To verify your handle, assuming you are using the standard table inet fw4

nft -n -a list table inet fw4

This will list the chains inside inet fw4, within those you need to identify the forward chain of the firewall zone mentioned in the prerequisites. In the below example the zone is called wildlan.

chain forward_wildlan { # handle 20
     ct status 0x20 accept comment "!fw4: Accept port forwards" # handle 423
     jump reject_to_wildlan # handle 424
}

The command nft insert rule inet fw4 forward_wildlan position 424 ip daddr @blackhole accept will result in

chain forward_wildlan { # handle 20
     ct status 0x20 accept comment "!fw4: Accept port forwards" # handle 423
     ip daddr @blackhole accept # handle 450
     jump reject_to_wildlan # handle 424
}

Based on this forward chain only the traffic with destination to the IP addresses included in @blackhole will be allowed. At this stage the @blackhole sets is still empty.

The rc.local is executed at boot time, so that @blackhole will be filled with IP addresses. That set shall be periodically updated for two reasons:

  1. The IP addresses may change
  2. In case of DNS Load Balancing, the same DNS query will result in different IP addresses (all valid) based on time of request.

In the Scheduled Task in Luci or in /etc/crontabs/root we execute every 15 minutes a script to update the sets

15 * * * * /etc/sets-ipdns/update-sets.sh

In the /etc/sets-ipdns/update-sets.sh include the update of sets code from /etc/rc.local

## Add element to "blackhole" from file urls.txt
for address in $(dig a -f /etc/sets-ipdns/wildlan-urls.list +short | grep -v '\.$'); do
	nft add element inet fw4 blackhole {$address}
done

Enable the script and reboot

chmod +x /etc/sets-ipdns/update-sets.sh
reboot

After the reboot, verify the content of the @blackhole nft list set inet fw4 blackhole and the result should be a list of ipv4 addresses.

The final crosscheck is to verify that addresses listed in /etc/sets-ipdns/wildlan-urls.list can be accessed, no other domains should be accessible unless the same IP address is shared between multiple domains (that happen with CDNs).

1 Like

In case anyone reading this topic in future will wonder, here the wiki page https://openwrt.org/docs/guide-user/firewall/filtering_traffic_at_ip_addresses_by_dns

well isn't this far harder than before?
this is exactly the reason why i'm not adopting FW4. With FW3 i have a long list of ipsets to filter web access to my iot devices, why are we trying to make things harder?
i don't even know if the fw4-compliant dnsmasq-full is out and if it manages ipsets..

It depends on what you are referring, the wiki example referenced in this new wiki page share exactly the same approach, with a list of domains translated into list of IPs.

The fw4 equivalent of ipset are called sets, with dnsmasq 2.87 will be introduced --nftset as equivalent of --ipset (https://github.com/openwrt/openwrt/pull/4977).

So I would say that is not harder than before, is just moving to something different.

i was referring exactly to the equivalent of ipset that - please correct me if i'm wrong - today is still not available and we don't even have an expected availability.
I was planning to start from scratch to abandon 21.02 and go back to master, but it seems it's not the case..

There won't be a standalone ipset replacement, the successor is nftables' builtin set support. See https://wiki.nftables.org/wiki-nftables/index.php/Ipset for some further information.

The equivalent of ipset is available and is called sets, is no longer an external package but is bundled with nftables. What is missing is the equivalent of dnsmasq --ipset option, that will be avaiable in 2.8.7 as --nftset option.

So, if you are filling your ipset from a list of domains, the functionalities are already there. If you are filling your ipset from a subset of queries resolved by dnsmasq you still have to wait.

Q.Could this Work on OpenWrt 21.02.3, how to add fw4 feature on 21.02.3?
Q.HowTo Create a new interface like wildlan . Is it virtual ?
Q.I try this method for whitelist feature, Is there any more detailed references or examples?
O.Does it support wildcard domain name?

Seems that you have fw3, so use the link for fw3 (in the first paragraph of the first topic).

Hi, thank you for the neat tutorial! It worked perfectly on an Archer C6v3 with a custom image of OpenWRT 22.03.0. I will use this to replace a former ipset configuration that directed traffic to streaming sites like Netflix over WAN directly instead of using the wireguard tunnel. Therefor, my aim was not to only allow traffic to the IPs from the list but to reroute the traffic over a different interface, which lead to a slight alteration. However, the core steps were the same.

Something to add to your tutorial could be how to remove the rules again:

nft delete rule inet fw4 forward_wildlan handle 450
nft delete set inet fw4 handle 449

The handle number may change depending on other nft configurations.

Hi,

glad it helped, how are you managing the multiple urls that netflix has?

Regarding the handles, in the wiki page the handle are no longer referenced directly to avoid that issue, here for your reference https://openwrt.org/docs/guide-user/firewall/filtering_traffic_at_ip_addresses_by_dns.

Good adjustment. I accidently deleted a wrong handle while testing the deletion, which wasn't an issue due to persistent configuration in /etc/config/firewall but for other circumstances it might cause one.

The urls Netflix uses for streaming and which are necessary to reroute traffic to aren't many. They also have expanded only once or twice in the past two years. As of now adding these domains is sufficient to be able to watch all content in Germany while having everything else routed over a wireguard tunnel:

netflix.com
netflix.net
nflxext.com
nflximg.com
nflximg.net
nflxvideo.net
nflxso.net

Further tests will extend to DAZN, Sky and other streaming services that use geoblocking and seem to also block whole address ranges from serverfarms that allow anyone to host a server.

Those urls unfortunately didn't worked as allow-list, likely netflix is trying to access some subdomain.netflix.something that results in a different IP than the base domain itself.

Once dnsmasq 2.8.7 will be available, using the nft-sets it will be possible to allow the full set of subdomains without have to list them all.

Hmm, I need to check it out again then. Interestingly enough, Netflix and DAZN work without any filter rules in place, which wasn't the case with previous builds for Archer C7, C59 and older TP-Link models.

Thanks again though, I'll let you know whether I succeed in the future. :slight_smile:

For anyone searching for a way to implement the old DNS firewall with ipsets in new snapshot releases or everyone with dnsmasq 2.87 in 22.03 i posted a howto in devel section

Nice, then we should use the "list domain" in the dhcp configuration to create an allowed list of domains to be served.

If you don't mind, you could update this page to reflect this better option with the new dnsmasq.

I haven't applied for a wiki account yet. But if anyone wants to use the post and its content its information its free. have at it :wink:

Hi @kdw,

I've tried to configure the nft set according to your guide, but I wasn't able to get it working. For sure I was missing some piece, but got the firewall to be not able to recognize properly the nftset.

For anyone else that could find himself in my same condition, to get it working I've skipped UCI and configured directly as follow:

The nftset is created via command line as in previous cases, but the timeout is added on the set instead of being applied at time of adding IPs:

nft add set inet fw4 blackhole { type ipv4_addr \; timeout 24h \;}

In the setup the wildlan has no forward to wan, so no communication will happen unless it points to an IP address included in the set.

nft insert rule inet fw4 forward_wildlan ip daddr @blackhole accept

Those first two commands are same as in the previous example, but the set is now filled directly dy dnsmasq. To do so, update /etc/dnsmasq.conf

nftset=/#/4#inet#fw4#blackhole

The above instruction will have dnsmasq tp update any URL into blackhole. To allow only a subset of URLs it can be used instead

nftset=/google.com/4#inet#fw4#blackhole

I will update also the wiki.

Please don't add wiki instructions using manual nft commands, document the proper uci solution instead.

Syntax should be:

config ipset
  option family ipv4
  option name blackhole
  option timeout 86400
  list match ip

config rule
  option family ipv4
  option proto all
  option src wildlan
  option dest *
  option ipset 'blackhole dest' 
  option target accept

Sorry for that, but haven't been able to get it working with UCI. The wiki page I'm referring was already created (before dnsmasq 2.87) with nft commands and now I've updated to cover also the case of latest dnsmasq.

Hope that the wiki page will not be removed.