In Linux, firewall capabilities are provided by the netfilter kernel module. The configuration of that module can be currently carried out by two commands: iptables, available since kernel version 2.4.x, and the more recent nftables, available since kernel version 3.13.x. This guide will focus on the iptables command. While a summary of the most used commands and options for iptables can be found in the section Iptables commands summary at the end of this chapter, the next sections show configuration examples for filtering, Network Address Translation (NAT), and packet mangling.
To check the installed version of iptables, run the following command:
root@gw:~# apt list iptables
Listing... Done
iptables/jammy-updates,now 1.8.7-1ubuntu5.1 amd64 [installed,automatic]
Basically, the package provides two main commands:
As the name suggests, the Linux firewall is structured as a set of tables relating to the different states of the packet routing process. Each table can be filled with rules that determine what action to take on packets.
The complete guide can be obtained from a shell by running the command man 8 iptables and man 8 iptables-extensions; here we will focus on the most used options.
Before starting our tour on iptables, it is important to remember that if we want to configure a Linux machine to act as a router/gateway, i.e. we need packets to be routed through network interfaces, we must set IP forwarding before. To do this for IPv4, uncomment the following line in /etc/sysctl.conf and restart the machine.
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
# Uncomment the next line to enable packet forwarding for IPv6
# Enabling this option disables Stateless Address Autoconfiguration
# based on Router Advertisements for this host
#net.ipv6.conf.all.forwarding=1
An important thing to do, before starting to write iptables rules, is to assure that the work done will not be swept away at the next system reboot.
In Ubuntu Linux, there are two ways to do so. The fisrt one is to rely on the Uncomplicated FireWall (UFW), that is a package designed for managing the firewall in an user friendly way. The second one is to install the iptables-persistent package, that is only aimed at saving and restoring firewall rules at each system reboot. Since the UFW is a little bit rigid in its current version and we want to have full flexibility in rules definition, we will proceed with the second way.
Hence, the following commands will set up the required environment.
root@gw:~# apt list ufw
Listing... Done
ufw/jammy-updates,now 0.36.1-4ubuntu0.1 all [installed]
root@gw:~# apt purge ufw
root@gw:~# apt install iptables-persistent
The package iptables-persistent creates the directory /etc/iptables/ containing the files rules.v4 and rules.v6 in which the firewall rules for IPv4 and IPv6, respectively, have to be saved.
root@gw:~# cd /etc/iptables/
root@gw:/etc/iptables# ls -l
total 2
-rw-r--r-- 1 root root 0 Nov 8 16:36 rules.v4
-rw-r--r-- 1 root root 0 Nov 8 16:36 rules.v6
To populate them, the following commands must be run every time firewall rules are added or modified.
root@gw:~# iptables-save > /etc/iptables/rules.v4
root@gw:~# ip6tables-save > /etc/iptables/rules.v6
Hence, at every system reboot, the service iptables.service will reload the firewall rules, before the network interfaces will go up, according to what saved in the files rules.v4 and rules.v6.
Iptables rules can also be manually restored by the following commands:
root@gw:~# iptables-restore < /etc/iptables/rules.v4
root@gw:~# ip6tables-restore < /etc/iptables/rules.v6
Let us suppose to have a Linux machine acting as a gateway/router with a WAN interface named wan0 and having public IP address 100.100.100.100, and a LAN interface named lan1 having IP address 192.168.10.254/24.
We want now to define a set of rules to protect our machine and internal network from external unwanted connection attempts, hence we have to operate on the INPUT chain of the filter table, that is the default one.
The first set of rules to deploy is the following:
root@gw:~# iptables -A INPUT -i lo -j ACCEPT
root@gw:~# iptables -A INPUT -i lan1 -j ACCEPT
root@gw:~# iptables -A INPUT -i wan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
As can be seen, the first two commands allow any packet coming from the localhost and the LAN to enter the system, while the third command permits only to packets belonging to established connections, or to connections in the initial handshaking phase, to enter the WAN interface.
The next step is to define what to do with the icmp packets, that are fundamental for connection control and performance optimization. While accepting every icmp type can rise safety issues, there is a minimum set of types that should be allowed to enter the system from the WAN interface to make the connection working better. Those are type 3 (destination unreachable) for permitting MTU path discovery (i.e, packet size auto-tuning) and type 8 (echo request) to allow pings and traceroute.
So, a good setting can be the following, where some limit is set to the icmp type 8 to mitigate potential ping attacks.
root@gw:~# iptables -A INPUT -i wan0 -p icmp -m icmp --icmp-type 3 -j ACCEPT
root@gw:~# iptables -A INPUT -i wan0 -p icmp -m icmp --icmp-type 8 -m limit --limit 5/s --limit-burst 10 -j ACCEPT
In case the whole icmp set is required to be enabled, the command iptables -A INPUT -i wan0 -p icmp -j ACCEPT can be used in place of the above reported two.
The filter table should now look like this.
root@gw:~# iptables -L -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- lo any anywhere anywhere
0 0 ACCEPT all -- lan1 any anywhere anywhere
0 0 ACCEPT all -- wan0 any anywhere anywhere state RELATED,ESTABLISHED
0 0 ACCEPT icmp -- wan0 any anywhere anywhere icmp destination-unreachable
0 0 ACCEPT icmp -- wan0 any anywhere anywhere icmp echo-request limit: avg 5/sec burst 10
Chain FORWARD (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
The next step is to define rules for remote administration. To better organize the filter, two custom chains will be created. The first one, named n_admin, will contain the sources for which remote administration is allowed, while the second one, named srvc_admin, will open the target ports of the administration services.
root@gw:~# iptables -N n_admin
root@gw:~# iptables -N srvc_admin
Now, we will link the chain srvc_admin from the chain INPUT, targeting to the interface wan0 and the only protocols tcp and udp. Moreover, only packets corresponding to a connection initiation are passed to the srvc_admin chain.
root@gw:~# iptables -A INPUT -i wan0 -p tcp -m tcp --syn -j srvc_admin
root@gw:~# iptables -A INPUT -i wan0 -p udp -m state --state NEW -j srvc_admin
At this point, we have to populate the chain srvc_admin to open the ports we use for remote administration, targeting to the chain n_admin for checking whether or not the remote machine is allowed to connect. In this example, we suppose to open TCP port 22 (for the SSH), while other ports can be added as needed by repeating the same command.
root@gw:~# iptables -A srvc_admin -p tcp -m tcp --dport 22 -j n_admin
Then, what remains to do is populating the chain n_admin with the list of remote machines, identified by their IP addresses, allowed to access the remote administration ports. In this example, we suppose to allow remote administration from the single IP address 200.200.200.200 and from the network 190.190.190.0/24. A rule for logging connection attempts on administration ports will also be added on top of the chain.
root@gw:~# iptables -A n_admin -m limit --limit 1/s -j LOG --log-prefix "NETFILTER Admin: "
root@gw:~# iptables -A n_admin -s 200.200.200.200/32 -j ACCEPT
root@gw:~# iptables -A n_admin -s 190.190.190.0/24 -j ACCEPT
Finally, since the rules for remote administration are correctly defined, we can set the policy for the INPUT chain to DROP, effectively making the firewall operational.
root@gw:~# iptables -P INPUT DROP
The final result is as follows:
root@gw:~# iptables -L -v
Chain INPUT (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- lo any anywhere anywhere
0 0 ACCEPT all -- lan1 any anywhere anywhere
0 0 ACCEPT all -- wan0 any anywhere anywhere state RELATED,ESTABLISHED
0 0 ACCEPT icmp -- wan0 any anywhere anywhere icmp destination-unreachable
0 0 ACCEPT icmp -- wan0 any anywhere anywhere icmp echo-request limit: avg 5/sec burst 10
0 0 srvc_admin tcp -- wan0 any anywhere anywhere tcp flags:FIN,SYN,RST,ACK/SYN
0 0 srvc_admin udp -- wan0 any anywhere anywhere state NEW
Chain FORWARD (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 n_admin (1 references)
pkts bytes target prot opt in out source destination
0 0 LOG all -- any any anywhere anywhere limit: avg 1/sec burst 5 LOG level warning prefix "NETFILTER Admin: "
0 0 ACCEPT all -- any any 200.200.200.200 anywhere
0 0 ACCEPT all -- any any 190.190.190.0/24 anywhere
Chain srvc_admin (2 references)
pkts bytes target prot opt in out source destination
0 0 n_admin tcp -- any any anywhere anywhere tcp dpt:ssh
The approach seen so far for controlling the remote administration access allows for great flexibility and ease of maintainement. The same approach can be used to add access for public services that we want to provide to the wide internet, such as HTTP, HTTPS, DNS, NTP, SMTP, SMTPS, SUBMISSION, and IMAPS.
To do this, it is sufficient to create a chain named srvc_internet, linking it from the INPUT chain, and populating it as reported in the following.
root@gw:~# iptables -N srvc_internet
root@gw:~# iptables -A INPUT -i wan0 -p tcp -m tcp --syn -j srvc_internet
root@gw:~# iptables -A INPUT -i wan0 -p udp -m state --state NEW -j srvc_internet
root@gw:~# iptables -A srvc_internet -p udp -m udp --dport 53 -m comment --comment "Domain Name System" -j ACCEPT
root@gw:~# iptables -A srvc_internet -p udp -m udp --dport 123 -m comment --comment "Network Time Protocol" -j ACCEPT
root@gw:~# iptables -A srvc_internet -p tcp -m multiport --dports 80,443 -m comment --comment "Hypertext Transfer Protocol" -j ACCEPT
root@gw:~# iptables -A srvc_internet -p tcp -m multiport --dports 25,465,587,993 -m comment --comment "Mail system" -j ACCEPT
Here is the result.
root@gw:~# iptables -L -v
Chain INPUT (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- lo any anywhere anywhere
0 0 ACCEPT all -- lan1 any anywhere anywhere
0 0 ACCEPT all -- wan0 any anywhere anywhere state RELATED,ESTABLISHED
0 0 ACCEPT icmp -- wan0 any anywhere anywhere icmp destination-unreachable
0 0 ACCEPT icmp -- wan0 any anywhere anywhere icmp echo-request limit: avg 5/sec burst 10
0 0 srvc_admin tcp -- wan0 any anywhere anywhere tcp flags:FIN,SYN,RST,ACK/SYN
0 0 srvc_admin udp -- wan0 any anywhere anywhere state NEW
0 0 srvc_internet tcp -- wan0 any anywhere anywhere tcp flags:FIN,SYN,RST,ACK/SYN
0 0 srvc_internet udp -- wan0 any anywhere anywhere state NEW
Chain FORWARD (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 n_admin (1 references)
pkts bytes target prot opt in out source destination
0 0 LOG all -- any any anywhere anywhere limit: avg 1/sec burst 5 LOG level warning prefix "NETFILTER Admin: "
0 0 ACCEPT all -- any any 200.200.200.200 anywhere
0 0 ACCEPT all -- any any 190.190.190.0/24 anywhere
Chain srvc_admin (2 references)
pkts bytes target prot opt in out source destination
0 0 n_admin tcp -- any any anywhere anywhere tcp dpt:ssh
Chain srvc_internet (2 references)
pkts bytes target prot opt in out source destination
0 0 ACCEPT udp -- any any anywhere anywhere udp dpt:domain /* Domain Name System */
0 0 ACCEPT udp -- any any anywhere anywhere udp dpt:ntp /* Network Time Protocol */
0 0 ACCEPT tcp -- any any anywhere anywhere multiport dports http,https /* Hypertext Transfer Protocol */
0 0 ACCEPT tcp -- any any anywhere anywhere multiport dports smtp,submissions,submission,imaps /* Mail system */
What seen so far refers to the configuration of the firewall filter for IPv4. IPv6 filter configuration can be done in the same manner, using the command ip6tables.
In the case IPv6 is not used, it is a good idea to block all the incoming traffic. To do so, the following commands can be used.
root@gw:~# ip6tables -A INPUT -i lo -j ACCEPT
root@gw:~# ip6tables -P INPUT DROP
The result is as follows:
root@gw:~# ip6tables -L -v
Chain INPUT (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all lo any anywhere anywhere
Chain FORWARD (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
When a machine acting as a gateway is going to be deployed, we want to prevent that some kind of outgoing connection attempts (i.e., sourcing from our local network) happen. Typical cases are prohibiting connections towards external email servers on port 25, so as to prevent that potential infected computers of our local network send spam emails, or forcing our local network to use internal servers for some basic services, such as DNS.
Let us now suppose that in our local network 192.168.10.0/24 there are an email server, having ip address 192.168.10.5 and MAC address aa:01:23:45:67:89 and a DNS server, having ip address 192.168.10.7 and MAC address bb:01:23:45:67:89.
We will then configure the outgoing filter to limit the possibility of the internal computers to reach the wide internet, specifically in the reserved port range 1:1023, while allowing only our servers to operate on sensitive ports.
Since we are regulating the traffic coming from our internal LAN directed to the internet and vice versa, we will have to operate on the FORWARD chain. The same, or similar, can be done on the OUTPUT chain if the traffic sourced from our gateway had to be regulated.
First, we need to set two rules to allow packets related to established (and in handshaking) connections, as well as ICMP packets, to traverse the firewall.
root@gw:~# iptables -A FORWARD -o wan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
root@gw:~# iptables -A FORWARD -o wan0 -p icmp -j ACCEPT
Then, some custom chains can be created to better organize our rules. First, a custom chain called out_free is created with the aim to authorize packets related to services that are always allowed. The chain out_free will be linked from the chain FORWARD by a couple of rules catching the valid initial packets of TCP and UDP connections. This is done with the following commands:
root@gw:~# iptables -N out_free
root@gw:~# iptables -A FORWARD -o wan0 -p tcp -m tcp --syn -j out_free
root@gw:~# iptables -A FORWARD -o wan0 -p udp -m state --state NEW -j out_free
The out_free chain can then be filled up with rules that accept packets directed to destination ports of basic services that are freely permitted. A minimum set of those is reported in the following; notice that TCP connections on destination port 25, representing plain text SMTP connections, are not included to avoid that potentially infected conputers in our local network can send spam emails all over the internet.
root@gw:~# iptables -A out_free -p tcp -m tcp --dport 22 -m comment --comment "Secure Shell" -j ACCEPT
root@gw:~# iptables -A out_free -p udp -m udp --dport 123 -m comment --comment "Network Time Protocol" -j ACCEPT
root@gw:~# iptables -A out_free -p tcp -m multiport --dports 80,443 -m comment --comment "Hypertext Transfer Protocol" -j ACCEPT
root@gw:~# iptables -A out_free -p tcp -m multiport --dports 465,587,993 -m comment --comment "Secured mail system" -j ACCEPT
At this point, what remains to do is to restrict to our internal servers the possibility to reach the wide internet towards sensitive ports, that basically are TCP port 25 for sending emails and UDP port 53 for DNS queries (other can be added based on specific installation needs). Hence, the computers in our local network will be able to access those services only by means of our servers.
To achieve this result, we need to create two custom chains, the one for identifying our servers, while the other for catching the packets related to the sensitive services.
In the following, a new custom chain named internal_srvs is created and populated with rules to identify our email and DNS servers. As can be seen, the MAC address based matching has been used to increase security, instead of matching the simple IP addresses. Those rules end with the ACCEPT target meaning that they will be the last verification step.
root@gw:~# iptables -N internal_srvs
root@gw:~# iptables -A internal_srvs -m mac --mac-source aa:01:23:45:67:89 -j ACCEPT
root@gw:~# iptables -A internal_srvs -m mac --mac-source bb:01:23:45:67:89 -j ACCEPT
What we need now is another custom chain to match packets related to the connections that we want to restrict to only our servers. We will name this chain out_restricted. The following commands create the custom chain ad populate it with rules to match SMTP and DNS service ports; the target is the chain internal_srvs.
root@gw:~# iptables -N out_restricted
root@gw:~# iptables -A out_restricted -p udp -m udp --dport 53 -m comment --comment "Domain Name System" -j internal_srvs
root@gw:~# iptables -A out_restricted -p tcp -m tcp --dport 25 -m comment --comment "Simple Mail Transfer Protocol" -j internal_srvs
Finally, two more rules must be added to the FORWARD filter chain to send TCP and UDP connection initiation packets routed out of the interface wan0 to the out_restricted chain.
root@gw:~# iptables -A FORWARD -o wan0 -p tcp -m tcp --syn -j out_restricted
root@gw:~# iptables -A FORWARD -o wan0 -p udp -m state --state NEW -j out_restricted
To summarize the filtering mechanism, TCP and UDP connection initiation packets routed out of the interface wan0 will be caught in the FORWARD chain and sent to the out_restricted chain to check the matching with the list of services that we want to restrict. In case of matching, the packets will be sent to the chain internal_srvs for the sender verification and, in case of success, they will be accepted. This scheme of chains allows for easy adminsitration of the list of authorized servers and restricted services.
The last remaining step is to set the filter to block every packet that does not verifiy the implemented rules. In this case, we will not set the policy of the FORWARD chain to DROP because (i) we need to promptly inform the sender that the connection attempt it made is not allowed, task that can be done only by using the REJECT target (see Target Extensions), and (ii) we have to only limit this behavior to the destination ports in the range 1:1023. Hence, we will add two rules at the very end of the FORWARD chain to send back an icmp-port-unreachable message to every packet targeting destination ports 1:1023.
root@gw:~# iptables -A FORWARD -o wan0 -p tcp -m multiport --dports 1:1023 -j REJECT --reject-with icmp-port-unreachable
root@gw:~# iptables -A FORWARD -o wan0 -p udp -m multiport --dports 1:1023 -j REJECT --reject-with icmp-port-unreachable
We can see now the final result (limited to the outgoing filtering part):
root@gw:~# iptables -L -v
...
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- any wan0 anywhere anywhere state RELATED,ESTABLISHED
0 0 ACCEPT icmp -- any wan0 anywhere anywhere
0 0 out_free tcp -- any wan0 anywhere anywhere tcp flags:FIN,SYN,RST,ACK/SYN
0 0 out_free udp -- any wan0 anywhere anywhere state NEW
0 0 out_restricted tcp -- any wan0 anywhere anywhere tcp flags:FIN,SYN,RST,ACK/SYN
0 0 out_restricted udp -- any wan0 anywhere anywhere state NEW
0 0 REJECT tcp -- any wan0 anywhere anywhere multiport dports tcpmux:1023 reject-with icmp-port-unreachable
0 0 REJECT udp -- any wan0 anywhere anywhere multiport dports 1:1023 reject-with icmp-port-unreachable
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain internal_srvs (2 references)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- any any anywhere anywhere MACaa:01:23:45:67:89
0 0 ACCEPT all -- any any anywhere anywhere MACbb:01:23:45:67:89
...
Chain out_free (2 references)
pkts bytes target prot opt in out source destination
0 0 ACCEPT tcp -- any any anywhere anywhere tcp dpt:ssh /* Secure Shell */
0 0 ACCEPT udp -- any any anywhere anywhere udp dpt:ntp /* Network Time Protocol */
0 0 ACCEPT tcp -- any any anywhere anywhere multiport dports http,https /* Hypertext Transfer Protocol */
0 0 ACCEPT tcp -- any any anywhere anywhere multiport dports submissions,submission,imaps /* Secured mail system */
Chain out_restricted (2 references)
pkts bytes target prot opt in out source destination
0 0 internal_srvs udp -- any any anywhere anywhere udp dpt:domain /* Domain Name System */
0 0 internal_srvs tcp -- any any anywhere anywhere tcp dpt:smtp /* Simple Mail Transfer Protocol */
...
Again, the reported configuration refers to the IPv4 protocol, but it can be done in a similar way for the IPv6 protocol. In the case the IPv6 protocol is not used, there is no need to add specific rules for the outgoing filter provided that IPv6 packets are already blocked in the INPUT chain as explained at the end of the Incoming packets filtering section.
In this section, the use of the Linux firewall to alter outgoing and incominc traffic is dealth with. We will refer to the same configuration as the one considered in the Filter configuration section, that is, a Linux machine acting as a gateway/router with a WAN interface named wan0 and having public IP address 100.100.100.100, and a LAN interface named lan1 having IP address 192.168.10.254/24.
Please note that NAT operations also require IP forwarding to be enabled in the kernel, as explained in the Introduction section.
Source NAT and masquerading are techniques that enable machines in private networks to access remote machines on the internet by appearing to be the gateway, using its public IP address. The gateway achieves this by replacing the local machine's IP address with its own public IP address, and then restoring the original IP address when response packets arrive from the remote machine. This process is completely transparent to both the local and remote machines, with the gateway maintaining connection tracking throughout.
It is then clear that SNAT and Masquerading apply specifically to outgoing packets from the gateway.
SNAT is used when the WAN interface has a fixed public IP address, which is uniquely identifiable and permanent. Conversely, masquerading is employed when the WAN interface's IP address is dynamically assigned by an Internet Service Provider. In such cases, the kernel has to update the interface's IP address each time the dial-up connection is re-established.
The appropriate rules for SNAT and Masquerading must be added to the POSTROUTING chain within the netfilter nat table. This is because only packets that leave the gateway need to be NATted, and this determination can only be made after the routing policies have been applied.
Below is the code for adding a rule to enable SNAT for packets exiting the wan0 interface.
root@gw:~# iptables -t nat -A POSTROUTING -o wan0 -j SNAT --to-source 100.100.100.100
root@gw:~# iptables -t nat -L -v
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 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
0 0 SNAT all -- any wan0 anywhere anywhere to:100.100.100.100
As shown in the output, the rule instructs the kernel to change the source IP address of every packet exiting the wan0 interface, regardless of its destination, to the gateway's WAN IP address 100.100.100.100. Now, any machine within the LAN network 192.168.10.0/24 can access any machine on the internet.
To enable Masquerading, the following rule has to be used instead of the SNAT rule.
root@gw:~# iptables -t nat -A POSTROUTING -o wan0 -j MASQUERADE
root@gw:~# iptables -t nat -L -v
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 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
0 0 MASQUERADE all -- any wan0 anywhere anywhere
The kernel is now instructed to replace the IP address of each packet exiting the wan0 interface with the interface's current IP address. As mentioned earlier, the MASQUERADING target must be used if the WAN link is established via a dial-up connection, where the assigned IP address may change every time the connection is interrupted and re-established. If the WAN connection has a known and static IP address, using the SNAT target is preferable.
Destination NAT is used to modify the destination IP address and/or port of incoming packets to redirect them to a different destination. A common use case is to redirect packets targeting specific service ports on the gateway to machines within the LAN that are configured to provide those services.
For example, suppose our gateway provides an HTTPS service on port 443 via its public IP address 100.100.100.100, but the web server is actually running on a machine within the LAN with the IP address 192.168.10.10. We want to instruct the kernel on our gateway to redirect any packet arriving on the wan0 interface with destination IP address of 100.100.100.100 and destination port 443 to the machine at 192.168.10.10.
To achieve this, we need to add a rule in the netfilter nat table before packets are routed to their destination, specifically in the PREROUTING chain.
The following command sets the appropriate rule in the gateway.
root@gw:~# iptables -t nat -A PREROUTING -i wan0 -d 100.100.100.100 -p tcp -m tcp --dport 443 -j DNAT --to-destination 192.168.10.10:443
root@gw:~# iptables -t nat -L -v
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DNAT tcp -- wan0 any anywhere 100.100.100.100 tcp dpt:https to:192.168.10.10:443
...
Packet mangling involves performing specific manipulations on packets, typically aimed at executing advanced packet management tasks or configuring low-level connection attributes and parameters.
This is achieved using the netfilter mangle table, which allows operations in all built-in chains: PREROUTING, INPUT, FORWARD, OUTPUT, and POSTROUTING.
The use cases for packet mangling are diverse, as it allows for a wide range of packet manipulations. Below are some examples of particularly significant applications.
A particularly important use case involves optimizing packet payloads in tunneled connections, such as IPSec connections. Since packets are transmitted over the network with a standardized Maximum Transmission Unit (MTU) of 1500 bytes, which determines a Maximum Segment Size (MSS), i.e. the actual payload, of 1460 bytes for a TCP connection, any tunnel encapsulated within such a connection requires an adjustment to the MSS of the packets transmitted within the tunneled connection.
The reason lies in the fact that the tunneled connection also includes its own headers, which consume additional bytes; for instance, IPSec headers can occupy up to 73 bytes when modern encryption algorithms are used. Therefore, to avoid excessive packet fragmentation and the resulting loss of connection speed, the MSS of the tunneled connection should be reduced to 1387 bytes (this practice is also called MSS clamping). It should be noted that modern routers implement MTU discovery techniques, enabling the automatic determination of MTU and MSS for the connection. In cases where MTU discovery is unavailable or not feasible, the following rule can be used to enforce the desired MSS for the tunneled connection.
root@gw:~# iptables -t mangle -A POSTROUTING -o ipsec0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1387
root@gw:~# iptables -t mangle -L -v
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 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 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 TCPMSS tcp -- any ipsec0 anywhere anywhere tcp flags:SYN,RST/SYN TCPMSS set 1387
As can be seen, the rule applies to packets routed through the IPSec interface, named ipsec0 in this example, and associated with the initial TCP handshake (where the SYN flag is set). This is because the MSS is determined during this phase and remains unchanged afterward.
Another relevant use case for packet mangling is the implementation of advanced routing policies. In this context, packets with specific characteristics can be marked to make them identifiable during subsequent routing steps.
In the following example, packets originating from the host on port 22 (the SSH server port) are marked with the hexadecimal number 41 (decimal 65) to ensure they are assigned to a specific routing table during subsequent routing steps. The complete example and its related use case, focusing on VPN bypass, are described here.
root@gw:~# iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 22 -j MARK --set-xmark 0x41/0xffffffff
root@myhost:~# iptables -t mangle -L -v
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
...
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 MARK tcp -- any any anywhere anywhere tcp spt:ssh MARK set 0x41
...
The Linux firewall is structured in "tables" containing the rules to be applied to packets. The tables of interest for the regular user are filter and nat. While the first is devoted to ruling access, the second one allows for some manipulation of packets to obtain the so-called Network Address Translation (i.e., NAT).
The following commands show the contents of the filter and nat tables for a system with no rules configured (notice that there is no need to specify the filter table name since it is the default one).
root@gw:~# iptables -L -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (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
root@gw:~# iptables -t nat -L -v
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 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
As can be seen, each table contains a set of system built-in chains. A chain is basically a container of firewall rules that will be examined in strict sequence; as soon as a rule applies, the rule is executed and the chain will be exited, thus skipping the remaining rules.
The built-in chains catch packets traversing the system in specific direction and at specific routing moments, as here described.
Custom user-defined chains can also be added.
In the following, the most relevant commands and options are explained.
The parameters are used to make up rule specification. The character "!" can be used in some cases to invert the sense of a parameter, as shown in the sequel.
Extended packet matching modules are availabe in iptables and are automatically loaded in function of the parameters -p, --protocol and -m, --match invoked by the rule.
In the following, the most used matching modules are explained. The most of these can be preceded by a "!" to invert the sense of the match.
Firewall rules specify criteria for packet match testing and a target. If the packet does not match, the next rule in the chain is examined; if it does match, then the next rule is specified by the value of the target, which can be the name of a user-defined chain, one of the extended targets (see Target Extensions below), or one of the special values described in the following.
If the end of a built-in chain is reached or a rule in a built-in chain with target RETURN is matched, the target specified by the chain policy determines the fate of the packet.
In the following, the most used extended target modules are explained.
DISCLAIMER
The material and methods reported in Linux Admin Smart Guide, even if tested, are provided without any guarantee. All the commands are run as privileged (root) user, so it is highly recommended to try them first on non-production machines and, in any case, to always do backups first. Linux Admin Smart Guide is not responsible for any damage or data loss caused by misformulated commands or inadvertently launched commands.
To gain a root shell, run the command sudo su -l from the shell of a regular user who is included in the sudoers list, or simply the command su -l and then providing the root password.