<p>
A firewall filters network traffic, which is transmitted in packets.<br />
The network packet filter is configured with rules.<br />
The basics of these filter rules are explained here.
</p>

<h3>Contents</h3>
<ul>
<li><a href="#iptables">iptables</a>
<ul>
<li><a href="#iptables_boot">Load iptables rules on boot</a></li>
<li><a href="#iptables_ipv4">IPv4 rules</a>
<ul>
<li><a href="#iptables_ipv4_nat">Network Address Translation (NAT)</a>
</ul>
</li>
<li><a href="#iptables_ipv6">IPv6 rules</a></li>
</ul>
</li>
<li><a href="#nftables">nftables</a>
<ul>
<li><a href="#nftables_boot">Load nftables ruleset on boot</a></li>
</ul>
</li>
<li><a href="#links">External Links</a></li>
</ul>

<h3 id="iptables">iptables</h3>

<h4 id="iptables_boot">Load iptables rules on boot</h4>
<p>
On Debian, <code>iptables-restore</code> is called on boot by installing the following package:
</p>
<pre><code class="language-bash">apt install iptables-persistent</code></pre>

<h4 id="iptables_ipv4">IPv4 rules</h4>
<p>
We set the default policy of the <code>INPUT</code> chain to <code>DROP</code> and<br />
specify some rules, which are applied from top to bottom:
</p>
<ol>
<li>Incoming packets on the loopback interface <code>lo</code> are accepted.</li>
<li><code>ESTABLISHED</code> connections or <code>RELATED</code> packets are accepted.</li>
<li>ICMP traffic (e.g. incoming <code>ping</code> requests) is allowed.</li>
<li>The number of SSH connection attempts from each source IP address is limited<br />
to 8 in 60 seconds. This slows down brute-force password attacks significantly.</li>
<li>A web server needs incoming packets on port 80 (http) and port 443 (https)<br />
to be accepted.</li>
</ol>
<dl class="file">
<dt><code class="filename">/etc/iptables/rules.v4</code></dt>
<dd>
<pre class="file"><code class="language-plaintext">*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]

-A INPUT -i lo -j ACCEPT

-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

-A INPUT -p icmp -j ACCEPT

-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 8 --rttl --name SSH -j DROP
-A INPUT -p tcp --dport 22 -j ACCEPT

-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

COMMIT
</code></pre>
</dd>
</dl>

<p>
Load the IPv4 rules from file:
</p>
<pre><code class="language-bash">iptables-restore &lt; /etc/iptables/rules.v4</code></pre>
<p>
Verify the loaded IPv4 rules:
</p>
<pre><code class="language-bash">iptables -L</code></pre>

<h5 id="iptables_ipv4_nat">Network Address Translation (NAT)</h5>
<p>
The <code>PREROUTING</code> chain is used for port forwarding.
</p>
<p>
The rule in the <code>POSTROUTING</code> chain translates the source address of outgoing packets.<br />
The reply will be forwarded by translating the destination address of incoming packets.<br />
This is what ordinary IPv4 routers do.
</p>
<p>
Rules for the <code>nat</code> table are part of the iptables rules:
</p>
<dl class="file">
<dt><code class="filename">/etc/iptables/rules.v4</code></dt>
<dd>
<pre class="file"><code class="language-plaintext">*filter
#...
COMMIT

*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.0.100:80
-A POSTROUTING -s 192.168.0.0/255.255.255.0 -o ppp0 -j MASQUERADE
COMMIT
</code></pre>
</dd>
</dl>
<p>
Forwarding of IPv4 packets needs to be enabled in the kernel.<br />
An interface for modifying kernel parameters is the proc file system.
</p>
<pre><code class="language-bash">echo 1 > /proc/sys/net/ipv4/ip_forward</code></pre>
<p>
A simplified command line tool for the proc file system is <code>sysctl</code>.<br />
To make the change persistent over reboots, edit the following file:
</p>
<dl class="file">
<dt><code class="filename">/etc/sysctl.conf</code></dt>
<dd>
<pre class="file"><code class="language-plaintext">#...

# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1

#...
</code></pre>
</dd>
</dl>

<h4 id="iptables_ipv6">IPv6 rules</h4>
<p>
For IPv6, the following rules need to be added:
</p>
<ul>
<li>ICMPv6 is required for IPv6 to work correctly.</li>
<li>DHCPv6 needs incoming UDP packets on port 546 to be accepted.</li>
</ul>
<dl class="file">
<dt><code class="filename">/etc/iptables/rules.v6</code></dt>
<dd>
<pre class="file"><code class="language-plaintext">*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]

-A INPUT -i lo -j ACCEPT

-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

-A INPUT -p icmpv6 -j ACCEPT

-A INPUT -p udp -s fe80::/10 --dport 546 -j ACCEPT

-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 8 --rttl --name SSH -j DROP
-A INPUT -p tcp --dport 22 -j ACCEPT

-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

COMMIT
</code></pre>
</dd>
</dl>

<p>
Load the IPv6 rules from file:
</p>
<pre><code class="language-bash">ip6tables-restore &lt; /etc/iptables/rules.v6</code></pre>
<p>
Verify the loaded IPv6 rules:
</p>
<pre><code class="language-bash">ip6tables -L</code></pre>

<h3 id="nftables">nftables</h3>
<p>
nftables is the successor of {ip,ip6,arp,eb}tables.<br />
There is only one ruleset needed to cover IPv4 and IPv6.
</p>
<p>
The table <code>ip</code> contains chains with rules for IPv4.<br />
The contained chain <code>input</code> filters incoming packets.<br />
All accepted IPv4 packets are marked with <code>0x34414343</code> ("4ACC"):
</p>
<ol>
<li>All ICMP packets are accepted.</li>
<li>For new SSH connections, rules are created that limit the rate<br />
to 12 connections per minute for each source IP address.<br />
This slows down brute-force password attacks significantly.</li>
</ol>
<p>
The table <code>ip6</code> contains chains with rules for IPv6.<br />
The contained chain <code>input</code> filters incoming packets.<br />
All accepted IPv6 packets are marked with <code>0x36414343</code> ("6ACC"):
</p>
<ol>
<li>All ICMPv6 packets are accepted. This is mandatory for IPv6.</li>
<li>All DHCPv6 packets are accepted from link-local IPv6 addresses.<br />
This is mandatory for IPv6.</li>
<li>For new SSH connections, rules are created that limit the rate<br />
to 12 connections per minute for each source IP address.<br />
This slows down brute-force password attacks significantly.</li>
</ol>
<p>
The table <code>inet</code> contains chains with rules for IPv4 and IPv6.<br />
The contained chain <code>input</code> accepts the following incoming packets:
</p>
<ol>
<li>All packets on the loopback interface (lo) are accepted.</li>
<li>Packets from established connections or related packets are accepted.</li>
<li>Packets marked with <code>0x34414343</code> (from table <code>ip</code>) are accepted.</li>
<li>Packets marked with <code>0x36414343</code> (from table <code>ip6</code>) are accepted.</li>
<li>For a web server, packets on port 80 (http) are accepted.</li>
<li>Packets on port 443 (https) are accepted.</li>
</ol>
<dl class="file">
<dt><code class="filename">/etc/nftables.conf</code></dt>
<dd>
<pre class="file"><code class="language-plaintext">#!/usr/sbin/nft -f

flush ruleset

table ip filter {
    set ipv4_ssh_meter {
        type ipv4_addr
        flags dynamic,timeout
        timeout 10m
    }
    chain input {
        type filter hook input priority -10;
        meta l4proto icmp counter meta mark set 0x34414343 accept
        tcp dport 22 ct state new update @ipv4_ssh_meter { ip saddr limit rate 12/minute } counter meta mark set 0x34414343 accept
    }
}

table ip6 filter {
    set ipv6_ssh_meter {
        type ipv6_addr
        flags dynamic,timeout
        timeout 10m
    }
    chain input {
        type filter hook input priority -10;
        meta l4proto ipv6-icmp counter meta mark set 0x36414343 accept
        ip6 saddr fe80::/10 udp dport 546 counter meta mark set 0x36414343 accept
        tcp dport 22 ct state new update @ipv6_ssh_meter { ip6 saddr limit rate 12/minute } counter meta mark set 0x36414343 accept
    }
}

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        iifname "lo" counter accept
        ct state established,related counter accept
        meta mark 0x34414343 counter accept
        meta mark 0x36414343 counter accept
        tcp dport 80 counter accept
        tcp dport 443 counter accept
    }
}
</code></pre>
</dd>
</dl>

<p>
Load the ruleset from file:
</p>
<pre><code class="language-bash">nft -f /etc/nftables.conf</code></pre>
<p>
Verify the loaded ruleset:
</p>
<pre><code class="language-bash">nft list ruleset</code></pre>

<h4 id="nftables_boot">Load nftables ruleset on boot</h4>
<pre><code class="language-bash">systemctl enable nftables.service</code></pre>

<h3 id="links">External Links</h3>
<ul>
<li><a href="https://www.netfilter.org/" target="_blank">
https://www.netfilter.org/</a></li>
<li><a href="https://ipset.netfilter.org/iptables.man.html" target="_blank">
https://ipset.netfilter.org/iptables.man.html</a></li>
<li><a href="https://www.netfilter.org/projects/nftables/manpage.html" target="_blank">
https://www.netfilter.org/projects/nftables/manpage.html</a></li>
<li><a href="https://tldp.org/HOWTO/Linux+IPv6-HOWTO/ch18s05.html" target="_blank">
https://tldp.org/HOWTO/Linux+IPv6-HOWTO/ch18s05.html</a></li>
<li><a href="https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes" target="_blank">
https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes</a></li>
<li><a href="https://wiki.nftables.org/wiki-nftables/index.php/Meters" target="_blank">
https://wiki.nftables.org/wiki-nftables/index.php/Meters</a></li>
</ul>