DIY Linux Router Part 4: Firewall and Port Forwards
The following is the fourth part of a multipart series describing how I build (software not hardware) my own Linux router from scratch, based on Debian 11.
- Part 1: Hardware
- Part 2: Interfaces, DHCP and VLAN
- Part 3: PPPOE and Routing
- Part 5: DNS with Unbound
- Part 6: WireGuard VPN
- Part 7: WiFi
- Part 8: NetFlow / IPFIX
By now we have a basic working router. We can connect new Clients/PCs via the switch, they get an IP address from the DHCP server and they can access the internet.
Next up we will add an iptables based firewall. Linux is currently in a transition from iptables to nf_tables, but it has been like this for years and iptables is still the default, so that is what I will be using. In the last few weeks nf_tables 1.0 and firewalld 1.0 have come out, so maybe with Ubuntu 22.04 I will switch do a newer alternative.
We should first talk about what we will need the firewall for. The first thought many people probable have is, to protect our clients, but this is unnecessary since our clients are not accessible from the internet. Some people will yell at me for this, but when using NAT our clients are as protected from connections from the internet as they would be with a firewall. But this doesn't mean I don't use my firewall as an additional layer of protection. When I add IPv6 later I definitely need the firewall since there is no NAT with IPv6 and our clients would be directly accessible from the Internet.
Where iptables is needed for, is to stop connections to our router itself since the router is directly accessible from the internet. Also we might not want all services that our router provides to the clients to be accessible for guests. I also use iptables for port forwards, to expose services that one of my Linux clients provides to the internet. Lastly I use it to redirect all outgoing DNS request to my unbound DNS server that we will setup in a later blog post.
Base Script
The basics for my iptables setup can be found in my GitHub repo: https://github.com/stephan13360/systemd-services/tree/master/iptables
I have one base script that sets up some defaults, like allowing established connections, localhost connections and ICMP. It also creates additional chains all starting with MAIN- that other scripts can then insert them self into. This is what it looks like:
#!/bin/bash
if test -z "$1"; then
echo "Please specify 'on', 'off', or 'reload'"
exit 1
elif test "$1" == 'on'; then
# Default Policy
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -F
iptables -X
iptables -Z
iptables -t nat -F
iptables -t nat -X
iptables -t nat -Z
iptables -t mangle -F
iptables -t mangle -X
iptables -t mangle -Z
ip6tables -P INPUT ACCEPT
ip6tables -P OUTPUT ACCEPT
ip6tables -P FORWARD ACCEPT
ip6tables -F
ip6tables -X
ip6tables -Z
ip6tables -t nat -F
ip6tables -t nat -X
ip6tables -t nat -Z
# Allow localhost
iptables -A INPUT -i lo -j ACCEPT
ip6tables -A INPUT -i lo -j ACCEPT
# Allow established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
ip6tables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Block invalid packets
iptables -A INPUT -p tcp ! --tcp-flags SYN,FIN,ACK SYN -m conntrack --ctstate NEW -j REJECT
iptables -A INPUT -m conntrack --ctstate INVALID -j REJECT
iptables -A FORWARD -m conntrack --ctstate INVALID -j REJECT
ip6tables -A INPUT -p tcp ! --tcp-flags SYN,FIN,ACK SYN -m conntrack --ctstate NEW -j REJECT
ip6tables -A INPUT -m conntrack --ctstate INVALID -j REJECT
ip6tables -A FORWARD -m conntrack --ctstate INVALID -j REJECT
# Allow ICMP
iptables -A INPUT -p icmp -j ACCEPT
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
# Create service chains
iptables -N MAIN-INPUT
iptables -N MAIN-FORWARD
iptables -t nat -N MAIN-PREROUTING
iptables -t nat -N MAIN-POSTROUTING
iptables -t mangle -N MAIN-FORWARD
ip6tables -N MAIN-INPUT
ip6tables -N MAIN-FORWARD
ip6tables -t nat -N MAIN-PREROUTING
ip6tables -t nat -N MAIN-POSTROUTING
iptables -A INPUT -j MAIN-INPUT
iptables -A FORWARD -j MAIN-FORWARD
iptables -t nat -A PREROUTING -j MAIN-PREROUTING
iptables -t nat -A POSTROUTING -j MAIN-POSTROUTING
iptables -t mangle -A FORWARD -j MAIN-FORWARD
ip6tables -A INPUT -j MAIN-INPUT
ip6tables -A FORWARD -j MAIN-FORWARD
ip6tables -t nat -A PREROUTING -j MAIN-PREROUTING
ip6tables -t nat -A POSTROUTING -j MAIN-POSTROUTING
run-parts --regex '\.fw$' --arg='on' /usr/local/etc/firewall
# Deny everything else
iptables -A INPUT -j LOG --log-prefix "iptables reject: " --log-level 7
iptables -A INPUT -j REJECT
ip6tables -A INPUT -j LOG --log-prefix "iptables reject: " --log-level 7
ip6tables -A INPUT -j REJECT
iptables -A FORWARD -j LOG --log-prefix "iptables reject: " --log-level 7
iptables -A FORWARD -j REJECT
ip6tables -A FORWARD -j LOG --log-prefix "iptables reject: " --log-level 7
ip6tables -A FORWARD -j REJECT
elif test "$1" == 'off'; then
# Set Default Policy and clear chains
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -F
iptables -X
iptables -Z
iptables -t nat -F
iptables -t nat -X
iptables -t nat -Z
iptables -t mangle -F
iptables -t mangle -X
iptables -t mangle -Z
ip6tables -P INPUT ACCEPT
ip6tables -P OUTPUT ACCEPT
ip6tables -P FORWARD ACCEPT
ip6tables -F
ip6tables -X
ip6tables -Z
ip6tables -t nat -F
ip6tables -t nat -X
ip6tables -t nat -Z
elif test "$1" == 'reload'; then
iptables -F MAIN-INPUT
iptables -F MAIN-FORWARD
iptables -t nat -F MAIN-PREROUTING
iptables -t nat -F MAIN-POSTROUTING
iptables -t mangle -F MAIN-FORWARD
ip6tables -F MAIN-INPUT
ip6tables -F MAIN-FORWARD
ip6tables -t nat -F MAIN-PREROUTING
ip6tables -t nat -F MAIN-POSTROUTING
run-parts --regex '\.fw$' --arg='reload' /usr/local/etc/firewall
else
echo "Please specify 'on', 'off', or 'reload'"
exit 1
fi
Since there is no default policy to reject packets only to drop packets, I use accept as the default policy and the reject everything at the end of the script. Dropping can be really annoying to debug when all connections time out, but is fine when you have active malicious traffic you want to block. The script also logs everything that is rejected to journald which makes debugging easier and it can be quite interesting to see what ports some random IPs from the internet try to reach.
My firewall script has three options: on, off and reload.
ON creates some basic rules like allowing ICMP traffic and already established connections. It also creates an additional chain for each of the four chains INPUT, FORWARD, PREROUTING, and POSTROUTING. These additional chains allow me to add and remove rules and then reload the firewall without flushing the default chains which would also flush the REJECT at the end. This would ideally only disable the firewall protection for a brief moment, but if I had an error somewhere in my ON section it could leave me without any rules. This way, when the reload fails, I at least have the default REJECT in place and can fix the problem over my still established ssh connection or through the console.
RELOAD flushes my additional base chains and then executes all scripts inside /usr/local/etc/firewall.
OFF just flushes and deletes all chains.
Inside /usr/local/etc/firewall I then have multiple scripts for different purposes. These are all run by the run-parts command inside the base script.
These scripts all create their own chains, append these chains to the MAIN- chain and then add rules to their own chain.
SSH
#!/bin/bash
iptables -F SSH-INPUT
iptables -X SSH-INPUT
ip6tables -F SSH-INPUT
ip6tables -X SSH-INPUT
iptables -N SSH-INPUT
iptables -A SSH-INPUT -p tcp --dport 22 -m limit --limit 10/m --limit-burst 50 -j ACCEPT
iptables -A MAIN-INPUT -j SSH-INPUT
ip6tables -N SSH-INPUT
ip6tables -A SSH-INPUT -p tcp --dport 22 -m limit --limit 10/m --limit-burst 50 -j ACCEPT
ip6tables -A MAIN-INPUT -j SSH-INPUT
SSH is allowed from anywhere, but I rate limit the connections so attackers can not try to break in as fast as they like. Not that it really matters since no passwords are allowed, only public keys.
DHCP
#!/bin/bash
iptables -F DHCP-INPUT
iptables -X DHCP-INPUT
ip6tables -F DHCP-INPUT
ip6tables -X DHCP-INPUT
iptables -N DHCP-INPUT
iptables -A DHCP-INPUT -i br0 -p udp --dport 67 -j ACCEPT
iptables -A DHCP-INPUT -i vlan222 -p udp --dport 67 -j ACCEPT
iptables -A MAIN-INPUT -j DHCP-INPUT
ip6tables -N DHCP-INPUT
ip6tables -A DHCP-INPUT -i br0 -p udp --dport 547 -j ACCEPT
ip6tables -A DHCP-INPUT -i vlan222 -p udp --dport 547 -j ACCEPT
ip6tables -A MAIN-INPUT -j DHCP-INPUT
Allow DHCP connections which use Port 67 UDP from inside my two networks. Without this, DHCP would not work.
Notice that we use the virtual br0
and vlan222
interface for iptables and not the physical LAN or OPT1 interfaces.
Routing
Since the base script has a default reject target in the forwarding chain, we need to allow forwarding for our clients again. This is what I mentioned at the beginning and would not be necessary, when using a NAT, to protect the clients.
#!/bin/bash
iptables -F ROUTING-FORWARD
iptables -X ROUTING-FORWARD
ip6tables -F ROUTING-FORWARD
ip6tables -X ROUTING-FORWARD
iptables -t nat -F ROUTING-POSTROUTING
iptables -t nat -X ROUTING-POSTROUTING
iptables -t mangle -F ROUTING-FORWARD
iptables -t mangle -X ROUTING-FORWARD
iptables -N ROUTING-FORWARD
iptables -A ROUTING-FORWARD -i br0 -j ACCEPT
iptables -A ROUTING-FORWARD -i vlan222 -j ACCEPT
iptables -A MAIN-FORWARD -j ROUTING-FORWARD
iptables -t nat -N ROUTING-POSTROUTING
iptables -t nat -A ROUTING-POSTROUTING -o ppp0 -j MASQUERADE
iptables -t nat -A MAIN-POSTROUTING -j ROUTING-POSTROUTING
ip6tables -N ROUTING-FORWARD
ip6tables -A ROUTING-FORWARD -i br0 -j ACCEPT
ip6tables -A ROUTING-FORWARD -i vlan222 -o ppp0 -j ACCEPT
ip6tables -A MAIN-FORWARD -j ROUTING-FORWARD
iptables -t mangle -N ROUTING-FORWARD
iptables -t mangle -o ppp0 -I ROUTING-FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
iptables -t mangle -A MAIN-FORWARD -j ROUTING-FORWARD
Here I allow my two networks to forwards packets to the internet, but do not allow connections from the internet to reach my clients.
This is also where I put the MSS Clamping and NAT rules from Part 3.
Port Forwards
I have a small Linux server behind my router that offers some services to the internet. To be reachable I need to forward some ports that reach the routers public IP to my server.
#!/bin/bash
iptables -F PORT-FORWARDS-FORWARD
iptables -X PORT-FORWARDS-FORWARD
iptables -t nat -F PORT-FORWARDS-PREROUTING
iptables -t nat -X PORT-FORWARDS-PREROUTING
iptables -N PORT-FORWARDS-FORWARD
iptables -A PORT-FORWARDS-FORWARD -d 192.168.144.10 -j ACCEPT
iptables -A MAIN-FORWARD -j PORT-FORWARDS-FORWARD
iptables -t nat -N PORT-FORWARDS-PREROUTING
iptables -t nat -A PORT-FORWARDS-PREROUTING -i ppp0 -p tcp --dport 443 -j DNAT --to-destination 192.168.144.10 # https
iptables -t nat -A PORT-FORWARDS-PREROUTING -i ppp0 -p tcp --dport 80 -j DNAT --to-destination 192.168.144.10 # http
iptables -t nat -A MAIN-PREROUTING -j PORT-FORWARDS-PREROUTING
Here we need to do two things. First we need to allow packets to be forwarded to my server with:
iptables -A PORT-FORWARDS-FORWARD -d 192.168.144.10 -j ACCEPT
And the we can add port to the PREROUTUNG chain using the DNAT target to exchange the destination IP address from the public router IP the the internal server IP.
Running the firewall at boot
To run the base script at boot and allow easy reload, stopping and starting of the rules I created a systemd service. The service uses as much sandboxing as possible, since it is running as root even though it is only running scripts I wrote myself. For a more detailed description and reasoning behind this, look here: https://github.com/stephan13360/systemd-services
[Unit]
Description=Firewall
Wants=network.target
After=network.target
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/local/sbin/firewall.sh on
ExecReload=/usr/local/sbin/firewall.sh reload
ExecStop=/usr/local/sbin/firewall.sh off
# filesystem access
TemporaryFileSystem=/:ro
BindReadOnlyPaths=/lib/ -/lib64/ /usr/lib/ -/usr/lib64/ /etc/ld.so.cache /etc/ld.so.conf /etc/ld.so.conf.d/ /etc/bindresvport.blacklist /usr/share/zoneinfo/ /usr/share/locale/ /etc/localtime /usr/share/common-licenses/ /etc/ssl/certs/ /usr/lib/ssl/certs/ /usr/share/ca-certificates/ /etc/alternatives/ /etc/resolv.conf
BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout /run/systemd/notify
BindReadOnlyPaths=/bin/bash /usr/local/sbin/firewall.sh /usr/local/etc/firewall/ /usr/bin/run-parts /usr/sbin/iptables /usr/sbin/ip6tables /usr/sbin/ipset /sbin/modprobe
BindPaths=/run/
PrivateTmp=true
PrivateDevices=true
ProtectControlGroups=true
ProtectKernelTunables=true
# misc
NoNewPrivileges=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
ProtectKernelLogs=true
LockPersonality=true
ProtectHostname=true
RemoveIPC=true
RestrictSUIDSGID=true
# capabilities
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_SYS_MODULE
[Install]
WantedBy=multi-user.target
We can now enable and start the firewall with:
systemctl enable --now firewall.service
This is what my chains look like:
Chain INPUT (policy ACCEPT 18 packets, 10593 bytes)
pkts bytes target prot opt in out source destination
1775K 779M NETFLOW all -- * * 0.0.0.0/0 0.0.0.0/0 NETFLOW
479K 161M ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
1264K 615M ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
112 37280 REJECT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp flags:!0x13/0x02 ctstate NEW reject-with icmp-port-unreachable
1062 48673 REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate INVALID reject-with icmp-port-unreachable
22 1042 ACCEPT icmp -- * * 0.0.0.0/0 0.0.0.0/0
30680 3117K MAIN-INPUT all -- * * 0.0.0.0/0 0.0.0.0/0
13904 1967K LOG all -- * * 0.0.0.0/0 0.0.0.0/0 LOG flags 0 level 7 prefix "iptables reject: "
13904 1967K REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-port-unreachable
Chain FORWARD (policy ACCEPT 5 packets, 325 bytes)
pkts bytes target prot opt in out source destination
19M 16G NETFLOW all -- * * 0.0.0.0/0 0.0.0.0/0 NETFLOW
18M 16G ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
1090 53213 REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate INVALID reject-with icmp-port-unreachable
60691 6730K MAIN-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0
1 77 LOG all -- * * 0.0.0.0/0 0.0.0.0/0 LOG flags 0 level 7 prefix "iptables reject: "
1 77 REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-port-unreachable
Chain OUTPUT (policy ACCEPT 1664K packets, 1760M bytes)
pkts bytes target prot opt in out source destination
1664K 1760M NETFLOW all -- * * 0.0.0.0/0 0.0.0.0/0 NETFLOW
Chain DHCP-INPUT (1 references)
pkts bytes target prot opt in out source destination
20 6548 ACCEPT udp -- br0 * 0.0.0.0/0 0.0.0.0/0 udp dpt:67
0 0 ACCEPT udp -- vlan222 * 0.0.0.0/0 0.0.0.0/0 udp dpt:67
Chain MAIN-FORWARD (1 references)
pkts bytes target prot opt in out source destination
4625 476K PORT-FORWARDS-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0
3716 423K ROUTING-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0
0 0 WIREGUARD-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0
Chain MAIN-INPUT (1 references)
pkts bytes target prot opt in out source destination
2331 163K DHCP-INPUT all -- * * 0.0.0.0/0 0.0.0.0/0
2311 156K SSH-INPUT all -- * * 0.0.0.0/0 0.0.0.0/0
2310 156K UNBOUND-INPUT all -- * * 0.0.0.0/0 0.0.0.0/0
593 40686 WIREGUARD-INPUT all -- * * 0.0.0.0/0 0.0.0.0/0
Chain PORT-FORWARDS-FORWARD (1 references)
pkts bytes target prot opt in out source destination
909 53332 ACCEPT all -- * * 0.0.0.0/0 192.168.144.10
Chain ROUTING-FORWARD (1 references)
pkts bytes target prot opt in out source destination
3716 423K ACCEPT all -- br0 * 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- vlan222 * 0.0.0.0/0 0.0.0.0/0
Chain SSH-INPUT (1 references)
pkts bytes target prot opt in out source destination
1 60 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:54345 limit: avg 10/min burst 50
Chain UNBOUND-INPUT (1 references)
pkts bytes target prot opt in out source destination
1647 111K ACCEPT udp -- br0 * 0.0.0.0/0 0.0.0.0/0 udp dpt:53
0 0 ACCEPT tcp -- br0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
70 4480 ACCEPT tcp -- br0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:853
0 0 ACCEPT udp -- vlan222 * 0.0.0.0/0 0.0.0.0/0 udp dpt:53
0 0 ACCEPT tcp -- vlan222 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
0 0 ACCEPT tcp -- vlan222 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:853
0 0 ACCEPT udp -- wg0 * 0.0.0.0/0 0.0.0.0/0 udp dpt:53
0 0 ACCEPT tcp -- wg0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
0 0 ACCEPT tcp -- wg0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:853
Chain WIREGUARD-FORWARD (1 references)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- * * 10.10.10.0/24 0.0.0.0/0
Chain WIREGUARD-INPUT (1 references)
pkts bytes target prot opt in out source destination
0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:51820
Chain PREROUTING (policy ACCEPT 108K packets, 17M bytes)
pkts bytes target prot opt in out source destination
137K 18M MAIN-PREROUTING all -- * * 0.0.0.0/0 0.0.0.0/0
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 53767 packets, 3274K bytes)
pkts bytes target prot opt in out source destination
101K 9051K MAIN-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0
Chain DNS-REDIRECT-PREROUTING (1 references)
pkts bytes target prot opt in out source destination
1390 93330 REDIRECT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:53
0 0 REDIRECT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
Chain MAIN-POSTROUTING (1 references)
pkts bytes target prot opt in out source destination
7485 644K ROUTING-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0
3837 237K WIREGUARD-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0
Chain MAIN-PREROUTING (1 references)
pkts bytes target prot opt in out source destination
10002 1245K DNS-REDIRECT-PREROUTING all -- * * 0.0.0.0/0 0.0.0.0/0
8541 1148K PORT-FORWARDS-PREROUTING all -- * * 0.0.0.0/0 0.0.0.0/0
Chain PORT-FORWARDS-PREROUTING (1 references)
pkts bytes target prot opt in out source destination
32 1628 DNAT tcp -- ppp0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 to:192.168.144.10
3 124 DNAT tcp -- ppp0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:192.168.144.10
Chain ROUTING-POSTROUTING (1 references)
pkts bytes target prot opt in out source destination
3648 408K MASQUERADE all -- * ppp0 0.0.0.0/0 0.0.0.0/0
Chain WIREGUARD-POSTROUTING (1 references)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * ppp0 10.10.10.0/24 !192.168.144.0/24
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 19M packets, 16G bytes)
pkts bytes target prot opt in out source destination
19M 16G MAIN-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain MAIN-FORWARD (1 references)
pkts bytes target prot opt in out source destination
1494K 2098M ROUTING-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0
Chain ROUTING-FORWARD (1 references)
pkts bytes target prot opt in out source destination
3886 226K TCPMSS tcp -- * ppp0 0.0.0.0/0 0.0.0.0/0 tcp flags:0x06/0x02 TCPMSS clamp to PMTU
We will get to the UNBOUND, WIREGUARD and NETFLOW chains/targets later.
Up next: DNS