User:Pietinger/Tutorials/Comprehensive Explanation of a Linux FireWall

From Gentoo Wiki
Jump to:navigation Jump to:search

I wrote this article so that I can link to it in the Gentoo forums if users have questions about it.

Comprehensive Explanation of a Linux FireWall

This tutorial aims to explain everything about a Linux FireWall so that you truly understand what you can and/or should do. If you are brand new to Gentoo and you found this article first I suggest you read this as well: User:Pietinger/New_at_Gentoo

Big request: First read this article completely before doing anything. Then start from the beginning and look for all the links. Then read these linked articles. Only then really start with the configuration.

I am very lazy and do not want to repeat what has already been documented elsewhere. This means that I only want to guide you so that you can build up the necessary knowledge. I will start at the very beginning - if you are already a network expert, you will probably find the first few chapters boring ... or you will skip them. However, I will start with a reader who only knows that there is such a thing as IP version 4 and IP version 6, and who also knows that an IP address has a network mask, but otherwise has no idea about networks.

Why am I talking about networks when this article is "only" intended to explain a FireWall?

Because you really need to know a little more about networks before you try to understand a FireWall. I spent a long time looking for a good introduction to networks that explains everything about networks as briefly as possible and only as long as necessary. But before I give you the link, I want to say a few things that are essential to get started:

Basics

In computing, a FireWall is a network security system that monitors and controls incoming and outgoing network traffic based on configurable security rules. A firewall typically establishes a barrier between a trusted network and an untrusted network, such as the Internet or between several [LANs /] VLANs [Virtual Local Area Network]. Firewalls can be categorized as network-based or host-based [1].

1. Host-based firewalls are also called a "Personal FireWall". A Personal FireWall is one that runs on a machine that does not exchange data between different interfaces, e.g. your computer that "only" has a connection to your DSL modem. Even if you also have a WLAN connection, it is a Personal FireWall as long as you do no Routing. Normal FireWalls not only protect a single computer but entire networks. Take a look at the diagram here: [[1]] Yes, several FireWalls are usually used in large companies. In this article we will only discuss Personal FireWalls and a FireWall for small networks.

2. There is only one FireWall in Linux! It is the kernel ... using various kernel modules that perform different tasks. All the programs you may have read about are not firewalls but only "utility programs". Let me start at the very beginning:

2a) A Personal FireWall filters data packets that are addressed to your computer (i.e., that reach your Ethernet connection or your wireless antenna) and are sent from your computer. A normal/network-based firewall also filters all data packets that are forwarded from one interface to another interface.

2b) Your kernel performs this filtering ... but to do so, it needs to know WHAT to filter. These are the filter rules, also known as firewall rules (or security rules).

2c) As a Linux user, you already know that you can reconfigure your kernel at runtime using the "sysctl" program. However, these settings are lost when you reboot. That's why OpenRC systems have the sysctl-RC-script (in the "boot" runlevel), which automatically sets certain values for the kernel when the system starts up.

2d) The same applies to filter rules. You have to transfer them to the kernel using a specific program and repeat this every time the system starts up, because these rules are lost when the system is shut down.

2e) There is not just ONE special program for passing the rules to the kernel, but TWO: the old "iptables" [2] and the new "nftables" [3]. No more – just these two? What is the difference?

2f) A long time ago, there was only IP version 4. Yes, "iptables" can only be used for IPv4. When IPv6 was invented later, another program had to be written to handle it: "ip6tables". That's cumbersome. The new nftables can do both. For you, this means that if you have a dual-stack machine, the new nftables is more practical. Only if you have an IPv4-only machine (like me) can you safely use the old iptables. I will explain both here. Why? Because it is easier to start with iptables for learning purposes.

2g) But there are other firewall programs, such as "ufw" and "shorewall", aren't there? No! These are programs that "only" generate rules. Yes, generators for rules that are then passed to the kernel via iptables/ip6tables or nftables. Why do such programs exist? Because the aim was to ensure that no one would have to learn the syntax for iptables or nftables anymore. The problem with this is that if something doesn't work the way you want it to, you're stuck. In addition, your system may not be as secure as you think if you simply unlock something with ufw. Furthermore, the most important feature of a firewall is usually omitted: the LOGGING of warnings ->

3. A Personal FireWall cannot always protect you! Sometimes it can only warn you that something bad has happened. To do this, you need a message in the system log. Now you ask why this is the case? To understand this, you need to know that there are two types of computer connections: incoming and outgoing connections. The difference is who sent the first data packet: your computer or a foreign computer.

3a) When you start your browser to read something in our Gentoo Wiki, your browser instructs the kernel to establish a connection to another computer. The kernel sends the first packet and waits for a response. This is an outgoing connection.

3b) If you have the SSH daemon running on your mainframe at home and you want to establish an SSH connection to your mainframe from your notebook, your notebook sends the first packet to your mainframe, which then sends a response. For your mainframe, this is an incoming connection. Almost always, a distinction is made between an incoming connection from the local (trusted) network (LAN) or the (malicious) Internet. However, the local network can also be dangerous (this is called an in-house attack).

4. The two purposes of a Personal FireWall

4a) The kernel can protect you perfectly against all incoming connections. You tell it – by rule – that it should simply reject all incoming connection requests. A data packet addressed to your SSH daemon will then never reach the "sshd"; the kernel has simply discarded the data packet. However, if you need an SSH connection from your notebook, there is a simple and secure method: you tell the kernel to allow an SSH connection coming FROM YOUR notebook and to discard everything else. Otherwise, your kernel will only allow responses from existing connections = connections that your computer has initiated (this also requires a rule; details later).

4b) But what can you do if you have a malicious program that wants to transfer data from your computer to a criminal server on the Internet? Yes, you can block all outgoing connections... but then you'll be offline. You want to connect to our Gentoo Wiki via HTTPS, right? Then you have to allow your kernel to accept all data packets destined for gentoo.org. But you also want to access other servers on the Internet via your browser. Then you have to allow ALL HTTPS connections. And then an evil program can also establish an HTTPS connection. Your firewall = kernel cannot protect you here. At least not on its own. The kernel needs help. From a programm through which all HTTPS traffic runs: a web proxy (e.g. "squid" or "privoxy"). Ask yourself why this Wiki article: Security_Handbook/Firewalls_and_Network_Security contains a chapter on "squid" ... I will go into more detail on this later.

4c) As you certainly already suspect, protection according to 4b is a little more complex than 4a. If you think now, 4a is enough for me, then you should know something: As a private person, you probably have a DSL-router-modem that has already installed a firewall. This is usually already activated by default and works according to the same principle of 4a. Too frankly, if you want to "only" protect yourself from incoming connections from the internet (because you don't have a Local Area Network), then you don't necessarily need an extra personal firewall on your computer. Unfortunately, this is no longer true with IPv6. Here, you should always filter at least incoming traffic (see later in the chapter on nftables). If you have a LAN and are concerned about an in-house attack, then it is still advisable to use at least also a personal firewall that only filters incoming connections (better than nothing). But once again: My recommendation is quite clearly to use a personal firewall that filters incoming and outgoing connections.

5. The kernel can do more than just filter. The kernel can also manipulate data packets. This is needed if you want to use NAT Network Address Translation [4]. Don't read the links yet ... wait. Don't worry, you don't need this for a personal firewall - only for a network-based firewall. That's why I explain it very late in this article. The use of tunneling or VPNs is not covered in this tutorial (and, in my opinion, is only marginally related to the topic of firewalls, even though some netfilter kernel modules are also used).

6. What is a stateless and a stateful Firewall? [5] You have to understand this difference for iptables as well as for nftables, although it is only mentioned in our Wiki article for Iptables [6], while in our wiki article for nftables [7] it is no longer explicitly mentioned. But it is also used for nftables. So what is that?

I cant remember how long ago it is. Yes there was a time (I think until kernel 2.0; No it was 2.2) you had to explicitly allow an outgoing ping-request and the incoming ping-response. And the same for every protocol or target host, or target net. But then we had a great improvement with a new kernel (2.2 I believe; No it was 2.4; My thanks go to the user of this talk page.): the STATEFUL inspection. What does this mean? Every communication between two computers begins with sending out the FIRST packet to the target, e.g. saying "hello, its me, I want to talk with your web-server". The answer from this web-server and all other packets related to this session, must be allowed also in the firewall, because the kernel filters every packet. With the new kernel you was able to allow all RELATED packets for this session automatically (with a special rule). So if we configure a firewall today, we just allow the initiating of a session (or only the first packet if a session-less protocol is used, like UDP or ICMP) and the kernel checks itself whether a packet only belongs to this session. Then you can allow this packet automatically. (This is rule number 2 in next chapter).

  • Example of a stateless configuration:
    • Allow outgoing PING (If you ping another machine)
    • Allow incoming PING response
    • Allow outgoing HTTPS
    • Allow incoming HTTPS response
    • Allow outgoing DNS
    • Allow incoming DNS response
    • Allow outgoing SSH (If you do ssh to another machine)
    • Allow incoming SSH response
    • Allow incoming PING (If someone pings your machine)
    • Allow outgoing PING response
    • Allow incoming SSH (If someone wants to connect to your running sshd)
    • Allow outgoing SSH response
  • The same example for statefull:
    • Allow outgoing PING
    • Allow outgoing HTTPS
    • Allow outgoing DNS
    • Allow outgoing SSH
    • Allow ALL incoming packets that are ONLY responses to already established connections.
    • Allow incoming PING
    • Allow incoming SSH
    • Allow ALL outgoing packets that are ONLY responses to already established connections.


7. The two possible working methods of a firewall:

7a) Prohibit dangerous connections and allow everyone else.

7b) Allow only safe connections and prohibit everything else.

Even if there are very special use cases for 7a, it should be clear that only 7b makes sense for us. This leads directly to the general skeleton for the structure of the rules:

  • Allow all packets for the Loopback Interface (yes, the kernel really filters everything)
  • Allow all packets that belong to an existing connection (see number 6 above)
  • Allow all packets that I really need (I'll help you here)
  • Allow all packets that I need, but which could also be dangerous; that is why these packets also logged.
  • Drop all packets that are not needed but are harmless; I don't want to burden my system log with them.
  • Log all other packets ... before they are going to die ->
  • All remaining packets are automatically handled as specified by the default policy. This should, of course, be "DROP".


8) Whether iptables or nftables, both work in exactly the same way: they compare the contents of a data packet with the characteristics specified in a rule. If this rule applies to the packet, a target is jumped to (in iptables, this is -j for jump). The four most important targets are: ACCEPT, DROP, REJECT, and LOG (*). The first three targets are "end station" targets, which means that the kernel executes this action immediately and does not check any further rules. Only with LOG does the kernel log something and then check the next rule. *) Here is a deep link explaining these targets (don't worry, you'll get it again later): [[2]]

8a) Here you can already see the first difference between iptables and nftables: With iptables, you have to create two different rules, while nftables can perform logging in one rule together with another target. Example: You want to log AND allow an incoming SSH connection coming from a station on the local network (Here: 192.168.1.0). In iptables, you need two rules for this (the first one logs the connection; the second one allows the packet), while in nftables, both can be done in one rule:

iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport ssh -j LOG --log-prefix "ACCEPT IN SSH GUEST "
iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport ssh -j ACCEPT

ip saddr 192.168.1.0/24 tcp dport ssh log prefix "ACCEPT IN SSH GUEST " accept

8b) A second "major" difference between iptables and nftables that often causes confusion is that in iptables, the chains INPUT and OUTPUT (and others such as FORWARD) are predefined, whereas in nftables, you first have to create them. In iptables, you can define the default policy immediately, while in nftables you first define a chain (and then specify the default policy):

iptables -P INPUT DROP
iptables -A INPUT .... # rules 

table filter {
  chain input {
    type filter hook input priority 0; policy drop;

    # rules

  }
}

The chain names are firmly specified in iptables; With nftables you can choose the table and chain name yourself; instead of "table filter" you could also call it "table firewall"; instead of "chain input" also "chain incoming" - this also often leads to confusion when you see regular examples from the Internet.

Attention: Many examples on the Internet are misleading or completely wrong. Just rely on your self-learned knowledge (even if you read our own wiki articles).

[1] https://en.wikipedia.org/wiki/Firewall_(computing)

[2] https://en.wikipedia.org/wiki/Iptables

[3] https://en.wikipedia.org/wiki/Nftables

[4] https://en.wikipedia.org/wiki/Network_address_translation

[5] https://en.wikipedia.org/wiki/Stateful_firewall

[6] Iptables

[7] Nftables

Network Knowledge

You need basic network knowledge. What is a protocol? What is TCP, UDP and ICMP? In my opinion, the fastest way to get this is to learn with these 5 YouTube videos:

https://www.youtube.com/playlist?list=PL0iJrrpaWpyWTmLQxeRy64LKGzLbRLDfD

Another way is to start reading this article and then read also all linked articles:

https://en.wikipedia.org/wiki/OSI_model

Read also: https://en.wikipedia.org/wiki/Port_(computer_networking) and then look at the contents of the two files: /etc/services and /etc/protocols, and you will see what they are.

Finally read this article: https://en.wikipedia.org/wiki/Netfilter

Instead of looking in your /etc/services, you will probably use this Wiki article in the future (because it is more comprehensive): https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers

This is a great "link list" ;-) https://en.wikipedia.org/wiki/List_of_network_protocols_(OSI_model)

Secure and insecure holes in the FireWall

After reading (or watching the YouTube videos) in the previous chapter, you now know everything about ports. A time server (NTP) listens on port number 123 and a DNS server on port 53. Since you need both, you must of course allow this in your firewall.You have two options:

1. Allow all programs to establish an outgoing connection via port 53 and 123, or

2. Allow all programs to establish an outgoing connection via port 53 to a specific DNS server and via port 123 to a specific NTP-Time-Sever.

If you have a malicious program that wants to connect to a malicious server, this program will NOT be able to reach the malicious server with a hole numbered 2... but a hole numbered 1 would allow it. Don't forget that there is no guarantee that a DNS server is actually active on port 53 on a server; it could also be a netcat listening there. Or – in the case of a criminal server – a program that controls a bot network or delivers malware.

So you see: A real problem is allowing a protocol to ALL servers of the internet. A bad programm can use this kind of holes to communicate with a bad server (listening to all ports). If you allow traceroute with

iptables -A OUTPUT      -p udp --dport 33434:33524 -j ACCEPT

a bad programm can misuse these open ports and connect to a bad server. One possible solution is to simply not allow it. Then you won't be able to do a traceroute anymore :-D Unfortunately, there are some things you can't do without:

Now you're probably thinking: I have Gentoo and need to do an emerge --sync ... but I don't know which server I'll be connected to. I need to allow a hole for port 873 (RSYNC) without specifying a specific target server.

There is an elegant solution for this (don't laugh, this is actually how it's done in companies; why do you think libnftables supports JSON?):

Only allow it when you need it and then immediately close the hole (=delete the rule). I actually do it this way myself (I use eix-sync instead of emerge --sync):

FILE /root/mysync.sh
#!/bin/sh
iptables -I OUTPUT 3 -p tcp --dport 873 -j LOG --log-prefix "!!! ACCEPT OUT RSYNC "
iptables -I OUTPUT 4 -p tcp --dport 873 -j ACCEPT
eix-sync
iptables -D OUTPUT 4
iptables -D OUTPUT 3
emerge -uUDvp @world

(-I makes an insert and -D deletes it again)

Now you say that this is not possible for port 443 ... that's HTTPS ... and your browser (and wget and others) need it so that you can surf the Internet. This is where the web proxy mentioned earlier comes into play. Yes, you should really install one and ONLY allow outgoing connections via port 443 (and 80) to the web proxy. No program should be allowed to connect to the internet directly through these ports. This proxy can then perform further filtering or at least log everything. (I use Privoxy because, in my opinion, squid is a monster; you need a degree to configure it).

Long story short: Any hole in your firewall that allows connections to all servers on the Internet is insecure. Only holes that allow connections to a specific server (or to a small, secure subnet) are secure, as they cannot be exploited. Every hole that allows connections to all servers on the Internet must therefore be logged and evaluated with a log check.

Why is filtering outgoing connections so important?

In response to this question, I will simply give you an excerpt from my system log after I caught a criminal server on the Internet (which claimed to offer security software for Linux) with my browser (which had JavaScript enabled; Yes, JavaScript is really powerful...):

# dmesg | grep !
[...]
[ 3200.351757] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP1 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=10212 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.351788] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP1 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=10213 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.351837] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP2 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=47502 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.351852] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP2 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=47503 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.351893] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP3 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=27510 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.351905] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP3 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=27511 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.359446] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=224.0.0.251 LEN=157 TOS=0x00 PREC=0x00 TTL=1 ID=10109 DF PROTO=UDP SPT=5353 DPT=5353 LEN=137
[ 3200.601869] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP1 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=10220 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.601914] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP1 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=10221 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.602048] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP2 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=47574 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.602098] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP2 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=47575 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.602147] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP3 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=27557 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3200.602160] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=IP3 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=27558 DF PROTO=UDP SPT=54229 DPT=3478 LEN=28
[ 3201.362067] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=224.0.0.251 LEN=157 TOS=0x00 PREC=0x00 TTL=1 ID=10332 DF PROTO=UDP SPT=5353 DPT=5353 LEN=137
[ 3202.360963] !!! REJECT IN= OUT=enp5s0 SRC=192.168.2.4 DST=224.0.0.251 LEN=157 TOS=0x00 PREC=0x00 TTL=1 ID=10345 DF PROTO=UDP SPT=5353 DPT=5353 LEN=137

I deleted the three destination addresses here. With your knowledge of ports, you can now figure out for yourself what this website was trying to do. At the time, I googled all three IP addresses, and the first hit was always a link to: https://www.abuseipdb.com/ Yes, all 3 addresses were already known.

Iptables for a Personal Firewall

Take this script for initialization of the iptables-rules and find out with this link what the script does:

https://lewestech.com/mirrors/www.iptables.info/en/iptables-contents.html

- And/Or - use this really short pocket reference:

https://linuxbg.eu/books/Linux%20Iptables%20Pocket%20Reference.pdf

#!/bin/sh
set -eu

# 2025-08-16: Initial FireWall-Sript for: Mainframe/Host at home ; this Wiki article

### Defines ###

# define general logging
logit="-j LOG --log-prefix"

# define addresses, networks and ports
ip_notebook="192.168.1.2"       # local notebook with a static IP address when working in my home LAN (using DHCP only if I am elsewhere)
net_local="192.168.1.0/24"      # local network

### Basic Settings ###

# First, flush the rules.
iptables -F
# Delete any user defined chain (if there would be any; not really necessary in this example)
iptables -X
# Set the default policy to throw away any uninvited packets on input
iptables -P INPUT       DROP
# Accept all output packets unless there is a rule defined to stop them.
iptables -P OUTPUT      ACCEPT
# We are not a router so if something gets into the FORWARD table
# throw it away. May want a logging rule as that would be a bug somewhere.
iptables -P FORWARD     DROP

### Firewall In ###

iptables -A INPUT   -i lo -j ACCEPT
iptables -A INPUT   -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# accept some ICMPs
iptables -A INPUT   -p icmp --icmp-type 3 -j ACCEPT
iptables -A INPUT   -p icmp --icmp-type 11 -j ACCEPT
iptables -A INPUT   -p icmp --icmp-type 12 -j ACCEPT

# accept SSH IN from notebook without logging
iptables -A INPUT   -s ${ip_notebook} -p tcp --dport ssh -j ACCEPT

# accept and log SSH IN from guests
iptables -A INPUT   -s ${net_local} -p tcp --dport ssh ${logit} "!!! ACCEPT IN SSH "
iptables -A INPUT   -s ${net_local} -p tcp --dport ssh -j ACCEPT

# drop some stuff (if you have WINDOWS in your network) without logging
iptables -A INPUT   -p udp --dport 137 -j DROP      # netbios-ns
iptables -A INPUT   -p udp --dport 138 -j DROP      # netbios-dgm
iptables -A INPUT   -p udp --dport 67 -j DROP
iptables -A INPUT   -p udp --dport 68 -j DROP
iptables -A INPUT   -p 2 -j DROP                    # IGMP stuff from router

# log all other
iptables -A INPUT   ${logit} "!!! DROP IN "

Why do I use such stupid prefixes for logging? Because I want to quickly use grep to see either all messages from iptables (dmesg | grep !) or just all rejected outgoing messages (dmesg | grep REJECT), or all permitted messages (dmesg | grep ACCEPT), or ... (And why three exclamation marks? Because it reminds me of the good old days of mainframes. There, the log entries also had a 3-byte prefix. Yes, it's completely pointless. But you'll change it to suit your preferences anyway.)

Yes, this is a very simple solution which protects you only as explained in 4a == filtering only incoming traffic.

Protection as explained in 4b == filtering incoming and outgoing traffic, is to be found in this (old) forum post (yes, I did not want to further bloze this article and therefore did not copy it here): [[3]]

After running this script do a iptables -L -vn to check it.

Read these articles to learn about the difference between the two targets DROP and REJECT. My recommendation for incoming connections is to use DROP (almost all companies do not want to send ICMP responses because this would only put additional strain on the firewall). Only for outgoing connections (which you do not allow) is it better to use REJECT (although many programs do not evaluate and observe this at all).

Why we use a script?

This has two reasons - one is historical. So, today it is only because of one reason.

Perhaps you already know, what you do when you type in an iptables command: You configure your kernel at runtime, like you configure your kernel at runtime with the command "sysctl". But the kernel doesnt store these settings. After a reboot they are all gone. So you have to load them again when you startup your computer. All settings you want to set with sysctl is done from the init-script: "/etc/init.d/sysctl" (you know this already).

- A long time ago we had no script for setting the filtering settings (with iptables) at startup. Therefore you had to do it by yourself. An old example of such a script you find here: Security_Handbook/Firewalls (-> /etc/init.d/firewall). Please dont use it - its outdated (and insecure). Today we have our own script for saving and restoring the settings: /etc/init.d/iptables. Use only this one!

- So, today we use a script only for one reason: If you want to change or add some rules for an existant setting. Theoretical you can use "iptables -D ..." or "-I" or -R" to delete, insert or replace some lines of your rules, but nobody does this. It is too complicated. It is easier to delete all existing rules and send all (with the new rule(s)) again to the kernel. So we use only "iptables -A" (for append) in our script. And we run the script only one time and then do an initial /etc/init.d/iptables save (this RC-script must be added to the runlevel "default" also). Or a second time, if you change something ...

Nftables for a Personal Firewall

The most important link at all:

https://wiki.nftables.org/wiki-nftables/index.php/Main_Page

Most important command: nft list ruleset

If you install the iptables package with the Use-flag "nftables", you get a great program: iptables-translate ... guess what it does ;-)

My personal recommendations and opinions on IPv6

I don't like IPv6 (because of privacy problems *) and (fortunately) don't need it; that means I only have IPv4 machines in operation and use good old iptables.

  • (*) Yes, I know that RFC4941 [[4]] "Privacy Extensions for Stateless Address Autoconfiguration in IPv6" exists (See also: [[5]]). The problem is that the prefix alone is enough to uniquely identify your connection. As long as this connection does not provide access to a network with many users, but only to you, you are uniquely identifiable. Some ISPs therefore rotate the prefix, but under certain conditions this does not help either. So if you have a "smart" TV, it can render this prefix rotation useless -> [[6]].


This chapter is therefore dedicated to everyone who absolutely needs IPv6. (However, please also read the previous chapters on iptables, because everything said there also applies to nftables.) Since such machines usually still have IPv4, we are talking about dual-stack configurations here. That's why I will only use the "inet" family in the following for nftables. The advantage is obvious: I can allow outgoing HTTPS (and HTTP) for IPv4 AND IPv6 with a single rule.

First, I would like to point out one of the most important things to keep in mind when operating IPv6 (and which you probably already know): While you have set a private IP address for your Ethernet card in IPv4 (e.g., 192.168.x.y -> [[7]]), which is not accessible from the Internet, your Ethernet interface (also) has an IPv6 address that is accessible worldwide.

Please keep also in mind that in private installations, your DSL router already has an active firewall and does not accept incoming connection requests for IPv4 - unless you have explicitly configured "port forwarding" in the DSL router configuration (Perhaps there are routers in the SoHo sector that do this, but I don't know of any). So if you allow your personal firewall to respond to IPv4-pings, this means that only pings from other stations in your LAN will be answered.

This no longer applies to IPv6. And that is precisely why I would like to take this opportunity to disagree with the statement:

There is absolutely nothing wrong with allowing all ICMP, in fact, it's probably best. (with a link to: [[8]])

In my opinion, a distinction must be made between these machines having a firewall:

  1. Personal firewall on a client in a local network (e.g., your work machine)
  2. Personal firewall on a server in a LAN for internal purposes (e.g., File server, personal NAS system)
  3. Personal firewall on a server in a DMZ (*) that should, of course, be accessible from the internet (e.g., Web server)
  4. Private network-based firewall on a machine that is intended to separate a small network from a DMZ and the internet.
  5. Core and internal network-based firewall(s) in larger companies
  6. Edge (boundary) firewall(s) in larger companies
  7. (I dont talk about network providers and ISPs here)

(* Yes, if you have more than one server in the DMZ, you should also install a personal firewall on each server so that they are protected from each other.)

Yes, larger companies sometimes allow you to ping individual (publicly accessible) servers or perform a traceroute on them. That is up to the respective company. But don't think that you can ping internal systems of them. In my opinion, the above statement only applies to points 3 and 6. In my opinion, point 4 is borderline; I would not allow my small private firewall to respond to pings (don't forget that every response ties up the machine's CPU resources, because an ICMP response packet has to be created and sent). For all others, I believe the above statement is incorrect ... because it is very questionable from a security standpoint.

Do you have multiple machines on your private LAN and want to ping them? Do you really want to use an IPv6 address, or is a good old IPv4-ping sufficient? In my opinion, allowing an IPv4-ping response is sufficient. If you only have one computer connected directly to the DSL router, you don't need some of the rules at all.

Simple personal firewall for IP dual-stack

Secondly, I would like to repeat what I said earlier: Work with logging. Without it, you are flying blind and don't know exactly what happened. This means that you really do log every DROP. I cannot recommend this rule, which is cited as an example on the Internet, for two reasons:

  chain base_filter {
    ct state vmap {
      established: accept,
      related: accept,
      new: continue,
      invalid: drop
    }

1. Here, the DROP is not logged, and

2. it is unnecessarily complicated.

This brings me to my third recommendation: Keep your configuration as simple as possible. The good old KISS principle.

There is no need to explicitly reject invalid packets in a Personal FireWall if they are dropped anyway by the default policy at the end of the chain - and, of course, logged beforehand! You also don't need VMAP in this example if you simply allow what you need; it's better to use this:

       ct state { established, related } counter accept

(By the way: ufw generates exactly the same rule, but without the counter.) Here, I have already done something that you will have to decide later: Do you want a counter for your rules (which is already active by default in iptables)? If not, you can delete "counter" everywhere. Do you want a very accurate count of the different packet types? Then you have to allow certain packets individually and cannot, for example, combine several packet types in a single rule. So there is a difference between this rule and the individual ones:

        icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } counter accept

        icmpv6 type nd-router-advert counter accept
        icmpv6 type nd-neighbor-solicit counter accept
        icmpv6 type nd-neighbor-advert counter accept

In the following example, I will therefore only use a counter for really important rules (in terms of monitoring). You can reset the counter with the command nft reset counters (needs kernel version 6.2 or higher if you want reset anonymous counter; which we use here).

As already mentioned, you can choose any table and chain names you like. I have written them in CAPITAL LETTERS so that you can easily recognize them (and change them to suit your taste).

In our example of a Personal FireWall for iptables (see chapter above), we set the default policy of the FORWARD chain to DROP ("iptables -P FORWARD DROP"). This is actually unnecessary because you will never see a packet there unless you have multiple interfaces (and have also enabled routing via sysctl). We do the same for nftables, even though it is actually unnecessary (if no default policy is configured for a hook, everything is allowed by default).

To understand these rules, you need to know the following:

1. The kernel starts checking a packet using the rules of the chain in which a hook is defined; in the following example, these are the chains: INPUT, OUTPUT, and FORWARD. Deep link to a diagram of the netfilter hooks: [[9]] (Yes, it is the most important Wiki for nftables ;-) )

2. A "jump" is not a GOTO but a GOSUB, i.e., a jump to a subroutine that ends with an (invisible) "return". This means that at the end of the chains IN_IP4, IN_IP6, OUT_IP4 and OUT_IP6, the kernel jumps back to the original chain and continues there (just like with iptables).

If you don't want to filter outgoing traffic, simply delete everything after the line "### Firewall Outgoing traffic ###".

Warning
The following rules have not yet been reviewed by an expert on nftables. Send me a message if you find any errors.
flush ruleset

table inet FILTER {


### FORWARD - actually pointless; only to be on a safe side ###

    ## (Yes, it could really be deleted if you know that you will never install a second interface in your computer. ;-)

    chain FORWARD {
        type filter hook forward priority filter; policy drop;

        log prefix "!!! DROP FWD " counter
    }


### Firewall Incoming traffic ###

    chain IN_IP4 {
        # drop IGMP messages from your DSL-router that we do not want to log
        ip protocol 2 drop

        ## You can remove all these rules when you have only a single machine (and no network at all)

        # Allow SSH in only from local stations - HERE YOU MUST EDIT YOUR ADDRESS
        ip saddr 192.168.X.0/24 tcp dport 22 ct state new log prefix "!!! ACCEPT IN SSH " counter accept

        # Allow pings from local stations to this machine
        icmp type echo-request counter accept

        # If your DSL-router allows PINGs from the internet use this rule instead above
        # ip saddr 192.168.X.0/24 icmp type echo-request counter accept
    }

    chain IN_IP6 {
        # accept neighbour discovery otherwise connectivity breaks
        icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
    }

    chain INPUT {
        type filter hook input priority filter; policy drop;

        # Accept any localhost traffic
        iif lo accept

        # Accept traffic originated from us
        ct state { established, related } accept

        # Jump to chain according to layer 3 protocol using a verdict map
        meta protocol vmap { ip : jump IN_IP4, ip6 : jump IN_IP6 }

        ## Discard harmless packets that we do not want to log

        # stuff from WINDOWs machines (delete this rule if you don't have)
        udp dport { 67, 68, 137, 138 } drop
        
        # Drop more here to keep your system log clean; the following logging rule will help you with additions.

        # log all remaining packets
        log flags all prefix "!!! DROP IN " counter
    }


### Firewall Outgoing traffic ###

    chain OUT_IP4 {

        ## delete all what you don't have (and enable it in OUT_IPv6 if your services use IPv6)
        ## and enable what you need (more examples in chapter iptables -> link for 4b)

        # allow DNS (if you still use it; I recommend a local installed "unbound" for DNS over TLS; link is below)
        # HERE YOU MUST EDIT YOUR USED ADDRESS (look into your /etc/resolv.conf)
        ip daddr 192.168.1.1 udp dport 53 accept
        ip daddr 192.168.1.1 tcp dport 53 accept

        # allow DNS over TLS - HERE YOU MUST EDIT YOUR USED ADDRESS
        ip daddr { 185.95.218.42, 185.95.218.43 } tcp dport 853 accept

        # allow NTP - HERE YOU MUST EDIT YOUR USED ADDRESS
        ip daddr { 92.53.103.104, 92.53.103.108 } udp dport 123 accept

        # allow mail via (again: HERE YOU MUST EDIT YOUR USED ADDRESS)
        # pop3s
        # ip daddr { a.b.c.d, a.b.c.e } tcp dport 995 accept
        # smtp
        # ip daddr { a.b.c.d, a.b.c.e } tcp dport 587 accept

        # allow IPv4-pings to other machines (also into internet)
        icmp type echo-request log prefix "!!! ACCEPT OUT PING " counter accept

        # allow and log ssh only to stations in your local network
        ip daddr 192.168.1.0/24 tcp dport 22 log prefix "!!! ACCEPT OUT SSH " counter accept
    }

    chain OUT_IP6 {
        # accept neighbour discovery otherwise connectivity breaks
        icmpv6 type { nd-router-solicit, nd-neighbor-solicit, nd-neighbor-advert } accept

        # allow DNS over TLS - HERE YOU MUST EDIT YOUR USED ADDRESS
        # ip6 daddr { 2a05:fc84::42, 2a05:fc84::43 } tcp dport 853 accept

        # allow mail via
        # pop3s
        # ip6 daddr X:Y::Z tcp dport 995 accept
        # smtp
        # ip6 daddr X:Y::Z tcp dport 587 accept
    }

    chain OUTPUT {
        type filter hook output priority filter; policy drop;

        # Accept any localhost traffic
        oif lo accept

        # Accept all answers to us
        ct state { established, related } accept

        # Allow our webproxy (here: "privoxy") to do HTTPS and HTTP
        tcp dport { https, http } meta skuid privoxy accept

        # Jump to chain according to layer 3 protocol using a verdict map
        meta protocol vmap { ip : jump OUT_IP4, ip6 : jump OUT_IP6 }

        ## delete what you don't need
        ## and add what you need (more examples in chapter iptables -> link for 4b)

        ## The following rules apply equally to IPv4 and IPv6 (just like the previous rule for privoxy)
        
        # allow and log rsync
        tcp dport 873 log prefix "!!! ACCEPT OUT RSYNC " counter accept

        # allow and log hkp
        tcp dport 11371 log prefix "!!! ACCEPT OUT HKP " counter accept

        # reject and log all remaining
        log flags all prefix "!!! REJECT OUT " counter reject with icmpx type admin-prohibited
    }

## don't delete this bracket if you delete the complete outgoing traffic rules
}

A secure and working configuration for unbound (which I use myself) can be found in this (german) forum post: [[10]].

Why am I not working with defines in this example? Because it makes a difference whether I use one address or several (the same applies to all items in brackets). If I have two addresses for DNS over TLS, I can put them in brackets, but this is not necessary if only one is used. So if I would give you a

define dnssec_servers = { 185.95.218.42, 185.95.218.43 }

you must not change it to

define dnssec_servers = { 9.9.9.9 }

but should use this instead:

define dnssec_servers = 9.9.9.9

Just as with iptables, the same applies to nftables: if something no longer works after activating these rules, you will see a message about it in the system log.

Network-based FireWall

So you have a small network and want to secure it with a FireWall. As you have already learned in the chapters on Personal FireWalls, there are holes in a FireWall that are necessary but also dangerous. These holes are then secured by one or more proxy daemons. I'm not just thinking about a web proxy here, but also other services. For example, if you have a lot of Gentoo machines, it's always a good idea to use your own RSYNC proxy so that all Gentoo machines can run emerge --sync on it. This is even explained in this wiki article: Home_router#Rsync_server.

This means you need a machine on which these services run, because all other machines (your clients) should not be able to access the Internet directly (or only to specific destination servers, such as a mail provider). Now the question arises: Do you have an extra server for this? If not, you can run the Proxies on the FireWall machine. Of course, this is not as secure as having a separate server available for this purpose. The next question is: Do you want to build a FireWall with two interfaces, or one with three interfaces so that you also have a DMZ? These would be the possible topologies (Examples):

Network topology

Simple FireWall with Proxies included
+-------------------+
| DSL-Modem/-Router |
| 192.168.x.1/24    |
+-------------------+
    |
+-----------------------------------+
| eth0: 192.168.x.2/24              |
|-----------------------------------|      many Clients in your LAN
| Gentoo FireWall and Proxy         |      | | | | | | | |
|-----------------------------------|    +----------------------+
| eth1: 10.0.0.1/24                 |----|Switch for LAN network|
+-----------------------------------+    +----------------------+
                                           | | |
                                               |    +---------------------------+
                                               +----| Admin Station 10.0.0.2/24 |
                                                    +---------------------------+
Simple FireWall with a dedicated proxy server
+-------------------+
| DSL-Modem/-Router |
| 192.168.x.1/24    |
+-------------------+
    |
+-----------------------------------+
| eth0: 192.168.x.2/24              |
|-----------------------------------|      many Clients in your LAN
| Gentoo FireWall                   |      | | | | | | | |
|-----------------------------------|    +----------------------+
| eth1: 10.0.0.1/24                 |----|Switch for LAN network|
+-----------------------------------+    +----------------------+
                                           | | |
           +--------------------------+    |   |    +---------------------------+
           | Proxy Server 10.0.0.2/24 |----+   +----| Admin Station 10.0.0.3/24 |
           +--------------------------+             +---------------------------+
FireWall with a DMZ
+-------------------+
| DSL-Modem/-Router |
| 192.168.x.1/24    |
+-------------------+
    |
+-----------------------------------+
| eth0: 192.168.x.2/24              |
|-----------------------------------|      many Clients in your LAN
| Gentoo FireWall with 3 interfaces |      | | | | | | | |
|-----------------------------------|    +----------------------+
| eth1: 10.0.0.1/24                 |----|Switch for LAN network|
|-----------------------------------|    +----------------------+
| eth2: 172.16.0.1/24               |      | | |
+-----------------------------------+          |    +---------------------------+
    |                                          +----| Admin Station 10.0.0.2/24 |
+----------------------+                            +---------------------------+
|Switch for DMZ network|---- more server ?
+----------------------+
    |                |
+---------------+ +---------------+
| Proxy-Server  | | Webserver     |
| 172.16.0.2/24 | | 172.16.0.3/24 |
+---------------+ +---------------+

As you can probably imagine, this solution is the best of the three. However, you need NAT for all of them. Our article Home_router contains the succinct sentence:

NAT is the magic that makes this possible. For more information about NAT, please visit Wikipedia.

That's why we have the next chapter (which you can skip, of course, if you are already a network expert).

Understanding Source NAT and Destination NAT

Source NAT

If you are still using IPv4 (which is still active even with dual stack), then your DSL-router/modem must perform NAT. Why?

We have two layers with addresses: Layer-2 with its MAC addresses and layer-3 with IP addresses. Your ethernet adapter reads only incoming frames when the destination MAC address of a frame is the MAC address of your own ethernet adapter (or it is a broadcast address). All data behind the Layer-2 header: layer-3 and layer-4 and real data in higher layers are only data for your ethernet adapter. (See more in [[11]], [[12]], [[13]] and [[14]].)

But with a layer-2 address you cannot send packets in other networks - only to MAC adresses you are LOCALLY connected. This means you must have at least ONE station which is connected to your local network AND is connected to another network: This machine is called Gateway or Router (a router is a layer-3-gateway).

What happens when you want to browse forums.gentoo.org with a computer that is connected directly to your DSL-router?

1. Your computer doesnt know the IP address of "forums.gentoo.org" and have to ask a DNS Server. This IP address it gets from /etc/resolv.conf (I have omitted everything that came before, such as address resolution (ARP) or a DHCP request, as it is not necessary for understanding). Let it be 8.8.8.8 in this example.

2. The IP address of this DNS Server is NOT in your own local network, so a packet to 8.8.8.8 must be send to a gateway/router. Which one ?

3. If you have only one Router in your local network, it is the DEFAULT GATEWAY.

(If you have two routers - one to the internet and the other to a neighbor network - you have two routes: One specific route pointing to the neighbor network; the other route points to the internet-router as default gateway. If you have two router for two neighbor networks and no own direct connection to the internet, because you go into the internet over your neighbors connect, you have also one specific route pointing to the neighbor without the internet connect and a default route to the neighbor with the internet connect. Summary: First your Computer checks if there is a specific route to the destination network; if not it sends all other to the default router.)

4. After your Computer has decided which is the needed router, it sends a packet to this router; BUT NOT to the IP address of this router. It sends the packet to the MAC address of this router. Lets say your IP address is 192.168.1.7 and your router has 192.168.1.1, then a packet would look like (IF stands for interface) =>

Layer-2: TO MAC router-IF-1 FROM MAC mine  [...]  Layer-3: SRC IP 192.168.1.7 TARGET IP 8.8.8.8

So, you wont see the IP address of your router in this packet.

NOW - What does your DSL-router?

5. Usually your router has at least 2 interfaces; one into your local network 192.168.1.0; the other into the internet network from your provider. That also means your router has a default route to your provider and a specific route into your local network. Now it "throw away" the old MAC addresses of this packet (layer-2) and copy the rest of the data onto a new pair of MAC-adresses (exactly: a complete new layer-2-header) =>

Layer-2: TO MAC provider FROM MAC router-IF-2 [...]  Layer-3: SRC IP 192.168.1.7 TARGET IP 8.8.8.8

and sends this to the next hop in your providers network (ok, there a some more fields which must be changed also, like the checksum at the end of the packet; unimportant for now).

This would be the usually behavior IF ... if your IP address would be a global IP internet address. But you have a private IP address in your local network and as you already know a private IP address is not allowed to be routed into the internet and therefore your router must do something we call: NAT network address translation. Now your router does not only changes the layer-2; it changes the SOURCE IP address in the IP header (=layer-3) of this data packet also. Your router sets its own GLOBAL IP address as source address in layer-3 and sends this packet out to your provider - PLUS - your router keeps in mind that all answers from 8.8.8.8 should go to IP address 192.168.1.7. This packet now looks like (a.b.c.d is the global IP internet address of your router) =>

Layer-2: TO MAC provider FROM MAC router-IF-2  [...]  Layer-3: SRC IP a.b.c.d TARGET IP 8.8.8.8

This change is called SNAT source network address translation; the most routers do not only change this, they change also the port number in layer-4-protocols. This is called NAPT network address and port translation -OR- SNAPT -OR- SNAT/PAT -OR- ... IP masquerading.

Now - The way back:

6. Your router receives a packet from your provider with this data =>

Layer-2: TO MAC router-IF-2 FROM MAC provider  [...]  Layer 3: SRC IP 8.8.8.8 TARGET IP a.b.c.d

7. Your router knows: This is not for me, it is for host 192.168.1.7, and therefore changes the target IP address back to your host IP address (PLUS again the layer-2 addresses, because it is sent out on the inner interface) =>

Layer 2: TO MAC mine FROM MAC router-IF-1  [...]  Layer 3: SRC IP 8.8.8.8 TARGET IP 192.168.1.7

Now - we have an additional home router/Firewall.

What would happen if you install your FireWall between your private network and your DSL-router? What will be the problem? Your home router has two sides (or three): The side 192.168.1.0 (to your DSL) and the other side, e.g., 10.0.0.0 (and 172.16.0.0 if using a DMZ design as described above). What happens when a host with 10.0.0.7 sends a packet to 8.8.8.8?

Look above. These two adresses will be in the IP header (=layer-3) of the data packet. When your home router would do only routing, this packet gets unchanged on layer-3 to your DSL-router. But your DSL-router doesnt know anything about a network 10.0.0.0 or a host 10.0.0.7. It can do NATting only for hosts in network 192.168.1.0 So, the solution is: You must do NATting also in your home router: From 10.0.0.0 (and 172.16.0.0) to 192.168.1.0. The iptables rule for this is very simple:

# define interfaces
WAN="enps...."                  # Interface 192.168.1.2 to DSL-Router
LAN="enps...."                  # Interface 10.0.0.1 to LAN network
[...]
iptables -t nat -A POSTROUTING -o ${WAN} -j MASQUERADE

What happens when your web server in your DMZ (e.g. 172.16.0.3) wants to send a packet to 8.8.8.8? Your web server sends the following packet to your FireWall (FW) =>

Layer-2: TO MAC FW-IF-2 FROM MAC webserver(=mine)  [...]  Layer-3: SRC IP 172.16.0.3 TARGET IP 8.8.8.8

Your home router/firewall routes the packet to its other interface (because the default gateway of your FW points to the DSL-router) AND performs SNAT with the interface configured in the nat table = the interface connected to your DSL router; and this interface has the address 192.168.1.2, so it uses exactly this IP address =>

Layer-2: TO MAC router-IF-1 FROM MAC FW-IF-1  [...]  Layer-3: SRC IP 192.168.1.2 TARGET IP 8.8.8.8

Of course, the kernel must now remember that responses from 8.8.8.8 back to itself do not belong to it but must be forwarded to 172.16.0.3 ... And now the same process described above begins: Your DSL router now also performs SNAT and forwards the packet on its other interface.

Destination NAT

You could say that DNAT is the opposite of SNAT. Let's say you have a web server that needs to be accessible from the Internet. However, it has a private IPv4 address and cannot be accessed. At least not directly. The solution is to claim that it has the global IP address of your DSL-router - this means that the DNS servers on the Internet provide this global internet address of your DSL-router in response to a DNS query for your web server. Then at least your DSL-router will receive requests from browsers all over the world. But it can't and won't do anything with them. No problem, because you have specified in the DSL-router settings that it should forward these packets to your web server; in most SoHo routers, the setting for this is called "port forwarding". And yes, your DSL-router then performs DNAT with this packet. It exchanges the destination IP address (= its own global IP address) for the destination address of your web server.

And what happens if you have your FireWall between your DSL-router and your LAN (or DMZ)? Exactly the same problem as with SNAT. Your FireWall cannot handle HTTPS requests and must forward them to your web server. So your FireWall performs DNAT to your web server again. The iptables rule for this is also very simple:

# define interfaces
WAN="enps...."                  # Interface 192.168.1.2 to DSL-Router
[...]
# define addresses
ip_dmz_web="172.16.0.3"         # our Web-Server
[...]
iptables -t nat -A PREROUTING -i ${WAN} -j DNAT --to-destination ${ip_dmz_web}

With this rule, everything will be delivered to your web server. However, if you have different servers in your DMZ, you can of course determine what should be delivered where by selecting the ports:

iptables -t nat -A PREROUTING -p tcp --dport 443 -i ${WAN} -j DNAT --to-destination ${ip_dmz_web}
iptables -t nat -A PREROUTING -p tcp --dport 22 -i ${WAN} -j DNAT --to-destination ${ip_dmz_other_server}

Iptables Flowchart

Finally, I would like to present this diagram, which shows where the kernel performs which actions:

https://stuffphilwrites.com/fw-ids-iptables-flowchart-v2024-05-22/

Iptables for a network-based Firewall

I am "only" giving you links here, as I don't want to make this article any longer. Please consider everything as examples only and form your own rules with the knowledge you now have. I also recommend configuring and using a Personal FireWall first (you need this for your admin station anyway) before configuring a network-based FireWall. I also recommend using static IP addresses on your home router (DHCP should only be necessary for clients on the LAN, although you could also use static IP addresses there).

Nftables for a network-based Firewall

I'd like to reiterate something I've already said elsewhere: Before you try to set up a firewall for your home router, you should already have your network configured and up and running. In addition to the Home_router I mentioned earlier, you may also need IPv6_router_guide.

I'm afraid I have to disappoint you, I don't have any examples for nftables rules here because I don't use it myself (and therefore can't test it the way I tested everything from iptables). However, I believe that with your knowledge of iptables and the https://wiki.nftables.org/wiki-nftables/ page, you will be able to configure a network-based FireWall with nftables.

If you're now thinking, "Well, then I'll just use Shorewall," I want to warn you. You will then have to learn ShoreWall + nftables (and/or iptables). Yes, you can't avoid understanding nftables - You'll have to learn both. Shorewall is actually for complex systems that have multiple interfaces and are supposed to filter large networks. Even Shorewall itself says [[15]]:

Shorewall is not the easiest to use of the available iptables configuration tools but I believe that it is the most flexible and powerful. So if you are looking for a simple point-and-click set-and-forget Linux firewall solution that requires a minimum of networking knowledge, I would encourage you to check out the following alternatives:

UFW (Uncomplicated Firewall)

ipcop

If you are looking for a Linux firewall solution that can handle complex and fast changing network environments then Shorewall is a logical choice.

Yes, I also do not recommend ufw or other rule generators; if you understand iptables and/or nftables natively, you never run the risk of configuring something nonsensical.

I wish you every success and secure systems!

Confusion about the order of the first two rules

... or, let's talk about performance ...

These are 1. allowing all packets via loopback and then 2. allowing all packets from an existing session (in a stateful configuration). Why did I set the order this way, while other examples from the internet do it the other way around? To understand this, you need to know two things:

1. When the kernel compares whether a packet matches a rule, it requires a varying number of CPU cycles. Comparing whether a packet comes from loopback is done very quickly. Searching the list of all existing sessions requires a lot of CPU cycles. (You can view this list with iptstate from net-analyzer/iptstate )

2. Although it is logical, people often forget that in a stateful configuration there are TWO rules that allow packets: incoming and outgoing packets that belong to a connection. Try the following (thought) experiment:

Swap the order of the two rules where you allow outgoing SSH connections with the stateful rule (so that outgoing SSH is allowed first). Then do SSH to another machine. You will see that the packet counter for outgoing SSH packets becomes very high, while the packet counter for outgoing stateful remains at zero (the incoming packets are allowed by the stateful rule for incoming packets).

Change it back to our normal configuration where the rule allowing outgoing SSH packets is configured after the stateful rule (and delete the packet counters). Try SSH to another machine again. Now you can see that the packet counter for outgoing SSH packets is set to 1 and both stateful rules - incoming AND outgoing - are counting up. Yes, because we have an existing SSH connection - through the first outgoing SSH packet - all subsequent packets are captured (and allowed) by BOTH stateful rules.

The same principle also applies to our loopback packets (which we always want/need to allow).

What would happen if we had the stateful rule first and then the rule that allows loopback? Yes, ONLY the first loopback-packet would be allowed via the second rule, and all other loopback packets would be allowed via the first stateful rule.

Now remember how tedious it is for the kernel to check the list of existing sessions to see if a packet belongs to an existing connection... and how quickly the kernel can check whether a packet comes from (or goes to) loopback.

Yes, with the order we use, an unnecessary check is performed for each network connection: Is the packet a loopback packet? Before the stateful rules allow our network packet, if necessary. But in return, all internal packets are allowed immediately.

This means you should always check how many loopback packets you actually have on your machine first. If you have a lot (because you use a local proxy, for example), then leave the order as recommended. An example:

# iptables -L -vn
Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
 666M  823G ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
 405M  813G ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED

...      

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
 666M  823G ACCEPT     all  --  *      lo      0.0.0.0/0            0.0.0.0/0           
 271M   17G ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED

If we had reversed the order, the stateful rule would of course be searched immediately... but this would then also apply to all loopback packets... which takes a very long time. However, if you notice that almost no internal loopback packets occur with a network-based FireWall, THEN it may be useful to change the order.

The result is that for a Personal FireWall for a desktop, loopback should always be allowed first ... only for a Personal FireWall for Servers (which mainly serve the internet) could it be advantageous to reverse the order... BUT... THEN...

... you should consider whether it would be even better to use a stateless configuration for performance reasons.

For example, if you have a web server in a DMZ that mainly responds to HTTPS requests, it is better from a performance point of view to dispense with stateful rules altogether and allow all incoming HTTPS packets first (just as you would then allow HTTPS responses as the first rule for outgoing sessions). For a server, it can sometimes make more sense not to work with stateful rules (especially with high-load servers ... which you probably don't have).