Cookie consent

This website uses cookies to personalise content and to analyse traffic.

Basic (mandatory) - Cookies needed to store user preferences.
Marketing (optional) - Marketing cookies are used to track users activity and to provide them with personalized advertisements.

You can learn about cookies on our Privacy Policy page.
Cookie settings can be changed at any time by clicking the icon located at the beginning of the navigation bar at the top of the page.

LINUX
  ADMIN
    SMART
      GUIDE

Linux firewall basics

Outline
  1. Introduction
  2. Making firewall rules persistent
  3. Filter configuration
  4. NAT configuration
  5. Packet mangling
  6. Iptables commands summary

Introduction

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

Making firewall rules persistent

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

Filter configuration

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.

Incoming packets filtering

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

Outgoing packets filtering

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.

NAT configuration

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 (SNAT) and Masquerading

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 (DNAT)

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

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

...

Iptables commands summary

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.

Commands

-t, --table table-name
Specify the table to operate on. Available tables are filter (the default one), nat, mangle, raw, and security.
-A, --append chain-name rule-specification
Append a rule to the end of the selected chain.
-I, --insert chain-name [rule-num] rule-specification
Insert the rule in the position specified by rule-num. If no position is given, the rule is added in position 1.
-R, --replace chain-name rule-num rule-specification
Replace the rule in the position specified by rule-num.
-D, --delete chain-name rule-num
Delete the rule number rule-num from the selected chain (rule numbering starts from 1).
-F, --flush [chain-name]
Delete all the rules in the chain (in all the chains in the table if none is given).
-Z, --zero [chain-name]
Zero the packet and byte counters in the selected chain (all chains fi none is given).
-L, --list [chain-name]
List all rules in the selected chain. If no chain is selected, all chains are listed. This command is typically used together with the options -v, -n and -t to obtain a detailed list of the rules in a table.
-N, --new-chain chain-name
Create a new user-defined chain by the given name.
-X, --delete-chain [chain-name]
Delete a user-defined chain by the given name, or all the user defined chains if the chain name is not specificed. The target must be empty, i.e., it must not contain any rule.
-E, --rename-chain old-chain-name new-chain-name
Rename a user-defined chain.
-P, --policy chain-name TARGET
Set the policy target for a given built-in chain. The section Targets explains the built-in targets.
-v, --verbose
Verbose output.
-n, --numeric
Numeric output. IP addresses and port numbers will be printed in numeric format instead of DNS names and service names.
--line-numbers
Add rule numbers when listing rules.

Parameters

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.

-p, --protocol [!] protocol
Protocol specification. Typical values are tcp, udp, and icmp, but every protocol listed in the file /etc/protocols can be used. If no protocol is specified, the rule applies to all protocols.
-i, --in-interface [!] name
The name of the interface via which the packet was entered. It applies to all the chains except the OUTPUT one. Appending the character "+" at the end of the interface name creates a rule that matches with all the interfaces beginning with this name. If this parameter is omitted, any interface name will match.
-o, --out-interface [!] name
The name of the interface via which the packet is going to be sent. It applies to all the chains except the INPUT one. Appending the character "+" at the end of the interface name creates a rule that matches with all the interfaces beginning with this name. If this parameter is omitted, any interface name will match.
-s, --source [!] address[/mask]
The source address. The address can be numerical or a DNS name to be resolved (this latter form is not recommended for safety reasons). The mask can be either a network mask or a plain number, so a mask of 24 is equivalent to 255.255.255.0.
-d, --destination [!] address[/mask]
The destination address. The address can be numerical or a DNS name to be resolved (this latter form is not recommended for safety reasons). The mask can be either a network mask or a plain number, so a mask of 24 is equivalent to 255.255.255.0.
-m, --match match
Specifies a match to use, that is, an extension module that tests for a specific property (see Match Extensions). Multiple matches can be used in a single rule and are evaluated first to last working in short-circuit fashion, i.e., if one extension yields false, evaluation will stop.
-j, --jump target
The target of the rule, i.e., what to do if the packet matches the rule. The target can be one of the built-in targets (see Targets), a target extension (see Target Extensions), or a user-defined chain. If the target is a user-defined chain and no rule is matched on that chain (or a rule with the RETURN target is matched on that chain), rule processing continues on the next rule in the previous chain.
-g, --goto chain-name
This specifies that the processing should continue in a user-defined chain, and is alternative to the -j option. If no rule is matched on the target chain (or a rule with the RETURN target is matched), rule processing continues in the most recent chain that actually specified the next chain with -j, thus skipping all chains that were entered in between with the -g option.

Match Extensions

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.

icmp
This extension is loaded if -p icmp is specified. It provides the following option:
[!] --icmp-type typename
This allows specification of the ICMP type, which can be one of the ICMP type names shown by the command iptables -p icmp -h
udp
This extension is loaded if -p udp is specified. It provides the following options:
[!] --source-port port[:port]
Source port or port range specification. This can either be a service name or a port number. An inclusive range can also be specified, using the format port:port. The flag --sport is a convenient alias for this option.
[!] --destination-port port[:port]
Destination port or port range specification. The flag --dport is a convenient alias for this option.
tcp
This extension is loaded if -p tcp is specified. It provides the following options:
[!] --source-port port[:port]
Source port or port range specification. This can either be a service name or a port number. An inclusive range can also be specified, using the format port:port. The flag --sport is a convenient alias for this option.
[!] --destination-port port[:port]
Destination port or port range specification. The flag --dport is a convenient alias for this option.
[!] --tcp-flags mask comp
Match when the specified TCP flags are set. The first argument is the flags the must be tested, written as a comma-separated list, and the second argument is a comma-separated list of flags that must be found set. Flags are: SYN ACK FIN RST URG PSH ALL NONE.
[!] --syn
Match packets that are related to TCP connection initiation, that is, the ones with the SYN flag set and all of RST, ACK, and FIN unset. It is equivalent to --tcp-flags SYN,RST,ACK,FIN SYN. If the "!" flag precedes the --syn, the sense of the option is inverted.
[!] --tcp-option number
Match if TCP option set.
tcpmss
Match the TCP MSS (maximum segment size) field of the TCP header. You can only use this on TCP SYN or SYN/ACK packets, since the MSS is only negotiated during the TCP handshake at connection startup time.
[!] --mss value[:value]
Match a given TCP MSS value or range. If a range is given, the second value must be greater than or equal to the first value.
iprange
Match packets on a given IP range specified by:
[!] --src-range from[-to]
Match source IP in the specified range.
[!] --dst-range from[-to]
Match destination IP in the specified range.
multiport
Match a set of source or destination ports. Up to 15 ports can be specified. A port range (port:port) counts as two ports. It can only be used in conjunction with -p tcp or -p udp. Ports are specified by means of the following options:
[!] --source-ports port[,port[,port:port...]]
Match if the source port is one of the given ports. The flag --sports is a convenient alias for this option.
[!] --destination-ports port[,port[,port:port...]]
Match if the destination port is one of the given ports. The flag --dports is a convenient alias for this option.
[!] --ports port[,port[,port:port...]]
Match if either the source or destination ports are equal to one of the given ports.
mac
Match packets with specific MAC address. It can be used only for packets coming from an Ethernet device and entering the PREROUTING, FORWARD or INPUT chains.
[!] --mac-source mac-address
Match source MAC address. It must be of the form XX:XX:XX:XX:XX:XX.
mark
Match the netfilter mark field associated with a packet, which can be set using the MARK target (see Target Extensions below).
[!] --mark value[/mask]
Match packets with the given unsigned mark value (if a mask is specified, this is logically ANDed with the mask before the comparison).
connmark
Match the netfilter mark field associated with a connection, which can be set using the CONNMARK target (see Target Extensions below).
[!] --mark value[/mask]
Match packets in connections with the given mark value (if a mask is specified, this is logically ANDed with the mark before the comparison).
state
Match the state of the connection to which the packet belongs.
[!] --state state
Specify the comma separated list of the connection states to match. Possible states are INVALID meaning that the packet could not be identified for some reason, ESTABLISHED meaning that the packet is associated with a connection which has seen packets in both directions, NEW meaning that the packet has started a new connection, or otherwise associated with a connection which has not seen packets in both directions, RELATED meaning that the packet is related to a connection that is starting, and UNTRACKED if the tracking for that packed is disabled.
limit
Match on a rate based limit. A rule using this extension will match until this limit is reached.
--limit rate
Maximum average matching rate specified as a number, with an optional "/second", "/minute", '/hour', or "/day" suffix; the default is 3/hour.
--limit-burst number
Maximum initial number of packets to match: this number gets recharged by one every time the limit specified above is not reached, up to this number; the default is 5.
comment
Add a comment to the rule.
--comment comment
The comment is a text string enclosed in double quotes of a maximum length of 256 characters.

Targets

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.

ACCEPT
Let the packet go through.
DROP
Drop the packet without giving any response.
RETURN
Stop traversing this chain and resume at the next rule in the previous (calling) chain.

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.

Target Extensions

In the following, the most used extended target modules are explained.

REJECT
Send back an error packet in response to the matched packet: otherwise it is equivalent to DROP so it is a terminating target, ending rule traversal. This target is only valid in the INPUT, FORWARD and OUTPUT chains, and user-defined chains which are only called from those chains. The following option controls the nature of the error packet returned:
--reject-with type
For IPv4, the type given can be icmp-net-unreachable, icmp-host-unreachable, icmp-port-unreachable, icmp-proto-unreachable, icmp-net-prohibited, icmp-host-prohibited, or icmp-admin-prohibited, which return the appropriate ICMP error message (icmp-port-unreachable is the default). The option tcp-reset can be used on rules which only match the TCP protocol: this causes a TCP RST packet to be sent back.
SNAT
Modify the source address of the packet. This target is only valid in the nat table, in the POSTROUTING and INPUT chains, and user-defined chains which are only called from those chains. It takes the following options:
--to-source [ipaddr[-ipaddr]][:port[-port]]
Specify a single new source IP address, or an inclusive range of IP addresses; optionally a port or port range for protocols supporting ports, such as tcp and udp.
--random, --random-fully
Randomize port mapping.
--persistent
Give a client the same source-/destination-address for each connection.
MASQUERADE
This target is only valid in the nat table, in the POSTROUTING chain. It should only be used with dynamically assigned IP (dialup) connections: if you have a static IP address, the SNAT target should be used instead. Masquerading is equivalent to specifying a mapping to the IP address of the interface the packet is going out, but also has the effect that connections are forgotten when the interface goes down. This is the correct behavior when the next dialup is unlikely to have the same interface address (and hence any established connections are lost anyway). Additional options can be used to alter the port:
--to-ports port[-port]
Specifies a range of source ports to use for protocols supporting ports, such as tcp and udp.
--random, --random-fully
Randomize port mapping.
DNAT
Modify the destination address of the packet. This target is only valid in the nat table, in the PREROUTING and OUTPUT chains, and user-defined chains which are only called from those chains. It takes the following options:
--to-destination [ipaddr[-ipaddr]][:port[-port]]
Specify a single new destination IP address, or an inclusive range of IP addresses; optionally a port or port range for protocols supporting ports, such as tcp and udp.
--random
Randomize port mapping.
--persistent
Give a client the same source-/destination-address for each connection.
REDIRECT
Redirect the packet to the machine itself by changing the destination IP to the primary address of the incoming interface (locally-generated packets are mapped to the localhost address). This target is only valid in the nat table, in the PREROUTING and OUTPUT chains, and user-defined chains which are only called from those chains. Additional options can be used to alter the port:
--to-ports port[-port]
Specifies a range of source ports to use for protocols supporting ports, such as tcp and udp.
--random
Randomize port mapping.
MARK
Set the netfilter mark value associated with the packet. It can, for example, be used in conjunction with routing based on fwmark (needs iproute2). If you plan on doing so, note that the mark needs to be set in the PREROUTING chain of the mangle table to affect routing. See, as an example, the use case described here. The mark field is 32 bits wide.
--set-xmark value[/mask]
Zero out the bits given by mask and XORs value into the packet mark (nfmark). If mask is omitted, 0xFFFFFFFF is assumed.
--set-mark value[/mask]
Zero out the bits given by mask and ORs value into the packet mark (nfmark). If mask is omitted, 0xFFFFFFFF is assumed.
The following mnemonics are available:
--and-mark bits
Binary AND the nfmark with bits (mnemonic for --set-xmark 0/invbits, where invbits is the binary negation of bits).
--or-mark bits
Binary OR the nfmark with bits (mnemonic for --set-xmark bits/bits).
--xor-mark bits
Binary XOR the nfmark with bits (mnemonic for --set-xmark bits/0).
CONNMARK
Set the netfilter mark value associated with a connection. The mark is 32 bits wide.
--set-xmark value[/mask]
Zero out the bits given by mask and XOR value into the connection mark (ctmark).
--save-mark [--nfmask nfmask] [--ctmask ctmask]
Copy the packet mark (nfmark) to the connection mark (ctmark) using the given masks. The new ctmark value is determined as follows:
ctmark = (ctmark & ~ctmask) ^ (nfmark & nfmask)
i.e. nfmask defines what bits to clear and ctmask what bits of the ctmark to XOR into the nfmark. ctmask and nfmask default to 0xFFFFFFFF.
--restore-mark [--nfmask nfmask] [--ctmask ctmask]
Copy the connection mark (ctmark) to the packet mark (nfmark) using the given masks. The new nfmark value is determined as follows:
nfmark = (nfmark & ~nfmask) ^ (ctmark & ctmask)
i.e. ctmask defines what bits to clear and nfmask what bits of the nfmark to XOR into the ctmark. ctmask and nfmask default to 0xFFFFFFFF.
--restore-mark is only valid in the mangle table.
The following mnemonics are available:
--and-mark bits
Binary AND the nfmark with bits (mnemonic for --set-xmark 0/invbits, where invbits is the binary negation of bits).
--or-mark bits
Binary OR the nfmark with bits (mnemonic for --set-xmark bits/bits).
--xor-mark bits
Binary XOR the nfmark with bits (mnemonic for --set-xmark bits/0).
--set-mark value[/mask]
Set the ctmark. If a mask is specified then only those bits set in the mask are modified.
--save-mark [--mask mask]
Copy the nfmark to the ctmark. If a mask is specified, only those bits are copied.
--restore-mark [--mask mask]
Copy the ctmark to the nfmark. If a mask is specified, only those bits are copied. This is only valid in the mangle table.
TCPMSS
This target allows to alter the MSS value of TCP SYN packets, to control the maximum size for that connection (usually limiting it to the outgoing interface's MTU minus 40 for IPv4 or 60 for IPv6, respectively). Of course, it can only be used in conjunction with -p tcp. It takes two mutually exclusive options:
--set-mss value
Explicitly set MSS option to value. If the MSS of the packet is already lower than value, it will not be increased.
--clamp-mss-to-pmtu
Automatically clamp MSS value to (path_MTU - 40) for IPv4 and (path_MTU - 60) for IPv6.
LOG
Turn on kernel logging of matching packets. When this option is set for a rule, the Linux kernel will print some information on all matching packets (like most IP/IPv6 header fields) via the kernel log (where it can be read with dmesg(1) or read in the syslog). This is a "non-terminating target", i.e. rule traversal continues at the next rule. So if you want to LOG the packets you refuse, use two separate rules with the same matching criteria, first using target LOG then DROP (or REJECT).
--log-level level
Level of logging, which can be (system-specific) numeric or a mnemonic. Possible values are (in decreasing order of priority): emerg, alert, crit, error, warning, notice, info or debug.
--log-prefix prefix
Prefix log messages with the specified prefix; up to 29 letters long, and useful for distinguishing messages in the logs.
--log-tcp-sequence
Log TCP sequence numbers. This is a security risk if the log is readable by users.
--log-tcp-options
Log options from the TCP packet header.
--log-ip-options
Log options from the IPv4/IPv6 packet header.
--log-uid
Log the userid of the process which generated the packet.

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.


< Prev Go to Home ↑