Iptables

Uit De Vliegende Brigade
Naar navigatie springen Naar zoeken springen

A firewall is a piece of hard- or software, that regulates traffic across the interface between networks with different levels of trust.

  • iptables is more-or-less the default (software) firewall on Linux. It's the successor to ipchains and in some distributions, it seems to be succeeded by nftables
  • iptables can filter (block) traffic, as well as throttle.

Filtering & OSI model

Firewalls, including iptables, typically operate on layer 3 and layer 4 of the OSI network model, although they often can extend a bit beyond that. The reason why this isn't black-and-white, is because the OSI model is a theoretical framework. It's never implemented with such clear boundaries between its layers.

Let's have a look at the OSI model, how iptables can interact with the different layers, and where its limitations are - To make sure that iptables is the right tool for the job at hand.

OSI model

OSI model layers
Layer Remarks
1. Physical layer
  • Transmits raw bit stream over the physical medium
  • Examples: Cables (Ethernet, fiber optic), hubs, repeaters, electrical signals, and radio waves.
2. Data link layer
  • Provides node-to-node data transfer—a link between two directly connected nodes, and handles error correction from the physical layer
  • Examples: Switches, bridges.

Sublayers:

  • MAC (Media Access Control): Controls how devices in a network gain access to a medium and permission to transmit data
  • LLC (Logical Link Control): Provides flow control and error management.
3. Network layer

Determines the best physical path for data transmission and handles packet forwarding including routing through intermediate routers:

  • Examples: Routers
  • Protocols and Standards: IP (Internet Protocol), ICMP, IPsec, RIP, OSPF, BGP.
4. Transport layer

End-to-end data transfer including error detection and correction:

  • Examples: Gateways
  • Protocols and Standards: TCP (Transmission Control Protocol), UDP (User Datagram Protocol), SCTP.
5. Session Layer

Manages sessions between applications. It establishes, manages, and terminates connections between local and remote applications:

  • Examples: APIs, sockets
  • Protocols and Standards: NetBIOS, PPTP, SAP.
6. Presentation layer Translates data between the application layer and the network. It translates data from a format used by the application layer into a common format and vice versa. This layer also handles encryption and decryption
  • Examples: Data encryption, data compression
  • Protocols and Standards: SSL/TLS, JPEG, GIF, MPEG.

Maybe using protocols on top of other protocols, is an example of this layer. E.g.:

  • SSL + HTTP
  • MySQL client/server protocol over SSH
  • FTP over SSH
  • XML data by default being compressed when transmitted or stored.
7. Application layer Provides network services directly to applications. It interacts with software applications that implement a communicating component.

Examples of applications:

  • Browsers
  • Email clients
  • FTP clients
  • SSH-clients
  • MySQL Workbench, connecting to MySQL databases using various protocols
  • Nemo (Linux file browser) accessing files on a remote server using SFTP.

Examples of protocols:

  • HTTP
  • FTP
  • SMTP
  • DNS
  • Telnet
  • SNMP
  • SSH
  • MySQL client/server protocol
  • MySQL client/server protocol using standard TCP/IP
  • MySQL client/server protocol using standard TCP/IP over SSH
  • SSH File Transfer Protocol (SFTP).

Filtering at Layer 3: Network layer

The Network layer is responsible for packet forwarding including routing through intermediate routers. iptables can filter packets based on various Layer 3 attributes, such as:

iptables filtering on various Layer 3 attributes
Attribute Example
Source IP address sudo iptables -A INPUT -s 198.168.1.1 -j DROP
Destination IP address sudo iptables -A OUTPUT -d 8.8.8.8 -j ACCEPT
Protocol (e.g., TCP, UDP, ICMP) sudo iptables -A INPUT -p icmp -j ACCEPT

Filtering at Layer 4: Transport layer

The Transport layer is responsible for end-to-end communication and error handling. iptables can filter packets based on various Layer 4 attributes. E.g.:

iptables filtering on various Layer 4 attributes
Attribute Example
Source port sudo iptables -A INPUT -p tcp --sport 22 -j ACCEPT
Destination port sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
TCP flags sudo iptables -A INPUT -p tcp --tcp-flags SYN,ACK SYN -j DROP

Combined filtering at Layer 3 & 4

You can combine attributes from Layer 3 & 4. E.g.:

sudo iptables -A INPUT -p tcp -s 192.168.1.1 --dport 22 -j ACCEPT

Filtering at Layer 2: Data link layer?

iptables doesn't really do any filtering using Layer 2 attributes, but related tools like ebtables can filter ethernet frames at this layer.

Filtering at Layer 5: Session layer

Extending iptables' functionality 'down' to layer 2, wasn't very hopeful. Extending this in the other direction, goes much better: iptables can track the state of connections, allowing for stateful packet inspection, using keywords NEW, ESTABLISHED and RELATED.

I guess this is actually critical, for example, to filter FTP traffic: The handshake is done over port 22, but the actual communication subsequently happens over an arbitrary port. Without some awareness of state, this would be very difficult for a firewall to manage.

Example:

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

BTW: Arguably, this is layer 3 and/or layer 4 stuff. I assumed it was layer 5 - Whatever: The OSI model is terribly theoretical anyway, so who cares?

Filtering at Layer 6: Presentation layer?

iptables can do some stuff at Layer 6, but like most stuff outside layer 3 & 4, these are rather extra-curricular activites, and require additional add-ons.

Examples:

Tracking FTP connections

Maybe a good example: The nf_conntrack_ftp module that helps iptables to track FTP connections, which involves parsing commands and data trandfers, embedded in the payload, to track the control channels and data channels. E.g.:

sudo modprobe nf_conntrack_ftp
sudo iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

SSL/TLS interception

Use iptables to redirect HTTPS traffic to a local SSL interception proxy:

sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443

Filtering at Layer 7: Application layer

Finally, we made it to what is probably "the big disappointment" for many a new firewall user, when they discover that what they actually want, is not what a firewall is designed for. And again, with a bit of force, quite nice results can be obtained. But iptables isn't the right tool for it.

User-agent filtering

For me, the "big disappointment" was, when I discovered that a firewall isn't the appropriate tool for filtering incoming traffic on a webserver, based on user-agent strings

  • iptables can be more-or-less forced to do this, e.g.: sudo iptables -A INPUT -p tcp --dport 80 -m string --string "User-Agent: MyCustomAgent" --algo bm -j DROP, but it isn't unreliable at it, and not efficient either
  • nftables, usually seen as the successor to iptables, is a bit better at this, but it's still using the wrong tool.

More appropriate tools for application level filtering are e.g.:

  • Reverse proxy servers or application firewalls: Squid, HAProxy, Nginx
  • Web application firewalls (WAFs): ModSecurity, which can be integrated with Apache and Nginx.

Chains, rules & order

iptables applies rules in a specific order. It is important to thoroughly understand how that works

Chains

Rules in iptables are organized into chains, which are lists of rules that packets traverse as they pass through the firewall. The main chains are:

  • INPUT: Handles incoming packets destined for the local machine.
  • FORWARD: Handles packets being routed through the machine (i.e., packets not destined for the local machine but for another machine).
  • OUTPUT: Handles packets originating from the local machine.

For packets that do not match any rule in a specific chain, they may be subject to additional chains or rules depending on the configuration:

  • PREROUTING: Chain used for packets before routing decisions are made.
  • POSTROUTING: Chain used for packets after routing decisions are made.

The order of these chains and their interactions can affect packet processing and should be considered when configuring complex firewall rules. Additional chains can be defined. E.g., fail2ban and ufw define additional chains including their place amonst the other chains

Incoming Packet Handling

  • When a packet arrives, it is first processed by the INPUT chain if it is destined for the local machine
  • It is processed by the FORWARD chain if it is being routed through the machine
  • It is processed by the OUTPUT chain if it is originating from the local machine.

The remainder of this article is limited to the INPUT chain, unless stated otherwise.

Rule Matching

When a packet arrives:

  1. The packet is compared against each rule in the chain, starting from the top
  2. A rule is found that matches the packet
  3. The rule is executed with regards to this packet
  4. No further matching happens for this packet.

With other words: The first rule (starting from the top) that matches the packet, gets executed.

Rule matching: Rules & subchains

When a chain (INPUT chain in this case) contains a mix of rules and subchains, the same principle applies:

  1. Start from the top
  2. first rule that matches, gets executed, regardless whether this rule is included in a subchain or in the INPUT chain.

Example:

$ sudo iptables -L INPUT

Chain INPUT (policy ACCEPT)

target          prot opt source    dest      comments
--------------- ---- --- ------    --------  ---------------------------
ACCEPT          all  --  1.2.3.4   anywhere  /* Office NL */
f2b-a2-get-dos  tcp  --  anywhere  anywhere  multiport dports http,https
f2b-a2-noscript tcp  --  anywhere  anywhere  multiport dports http,https
f2b-a2-auth     tcp  --  anywhere  anywhere  multiport dports http,https
ACCEPT          tcp  --  anywhere  anywhere  tcp dpt:ssh
ACCEPT          tcp  --  anywhere  anywhere  tcp dpt:12345
ACCEPT          all  --  srv17     anywhere  /* srv17 */
ACCEPT          all  --  2.3.4.5   anywhere  /* Office PL */

Someone at Office NL visits a site that is hosted on this server:

  • The very first rule matches → It gets executed → Done

Someone at Office PL visits a site that is hosted on this server:

  • When there is no matching rules in any of the f2b subchains, the last rule matches and will get executed
  • When there is a matching rule in any of the f2b subchains, it will get executed, becauce it comes earlier.

Actions

The action specified by the matching rule is applied to the packet. Actions can include ACCEPT, DROP, REJECT, LOG, or other targets. If a rule specifies an action (e.g., DROP), no further rules in the chain are processed for that packet. The packet is handled according to the action specified. Default Policy:

If no rules in the chain match the packet, the default policy for the chain is applied. The default policy is specified using iptables -P <CHAIN> <TARGET>. For example, if the default policy for the INPUT chain is DROP, packets that do not match any rules in the INPUT chain will be dropped.

Example

Consider the following iptables rules in the INPUT chain:

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j DROP
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
  • Rule 1: -p tcp --dport 22 -j ACCEPT – Allows incoming TCP traffic on port 22 (SSH).
  • Rule 2: -p tcp --dport 80 -j DROP – Blocks incoming TCP traffic on port 80 (HTTP).
  • Rule 3: -p tcp --dport 443 -j ACCEPT – Allows incoming TCP traffic on port 443 (HTTPS).

If a packet destined for port 80 arrives, it will be processed as follows:

  • The packet is compared against Rule 1. It does not match (port 22), so the rule is not applied
  • The packet is compared against Rule 2. It matches (port 80), so the packet is dropped, and no further rules are checked
  • Rule 3 is not evaluated because Rule 2 has already handled the packet.

Frontends

  • ufw and firewalld are command line front-ends to iptables (and in the case of firewalld, also for nftables), to make it a bit easier to interact with iptables
  • gufw is a graphical frontend for ufw
  • firewall-config is a graphical front-end for firewalld.

If you are comfortable with directly interacting with iptables and have a good understanding of its syntax and functionality, you can certainly manage your firewall rules directly using iptables commands without needing any of the frontends like ufw, firewalld, gufw, or firewall-config. Each approach has its pros and cons.

I do prefer to directly use iptables, without any of these frontends.

Directly using iptables

Pros

  • Fine-Grained Control: Directly using iptables allows you to configure detailed and specific rules, as you have full control over all the options and parameters available
  • Flexibility: You can create complex rules and chains tailored to specific needs that might be difficult to achieve with higher-level tools.

Cons

  • Complexity: iptables can be complex and unintuitive, especially for advanced configurations. Errors in rules or syntax can potentially disrupt network traffic
  • Manual Management: You need to manually save and restore rules, manage rule ordering, and ensure consistency across reboots.

Using Frontends

Pros

  • Ease of Use: Frontends like ufw, firewalld, gufw, and firewall-config provide a more user-friendly interface for managing firewall rules. They often come with simplified commands or graphical interfaces
  • Configuration Management: These tools often handle saving and restoring rules automatically, and some provide better integration with system services and configuration management.

Cons

  • Limited Flexibility: While these tools cover most common use cases, they might not offer the same level of granularity and flexibility as direct iptables commands
  • Abstraction Layer: They abstract away iptables complexity, which can be a disadvantage if you need to perform advanced configurations or troubleshoot detailed issues
  • Added dependency: I don't like the idea of introducing new layers of tools, that can break down.

ufw (Uncomplicated Firewall)

  • Aimed at simplifying iptables management for Ubuntu and Debian-based systems
  • Provides a straightforward command-line interface and basic functionality for most users.

In case ufw is installed on a server, while also directly using iptables, is probably not a problem. Otherwise:

  • sudo ufw disable
  • su apt remove --purge ufw

firewalld

  • A dynamic firewall management tool that provides zones and services to manage rules
  • Supports nftables as well as iptables and is commonly used on Red Hat-based systems.

gufw

  • A graphical frontend for ufw on Ubuntu and other Linux distributions
  • Provides a user-friendly graphical interface to manage firewall rules

firewall-config

  • A graphical interface for managing firewalld, available on Red Hat-based distributions
  • Allows for detailed configuration through a GUI.

List all rules

Various ways to list current list:

  • sudo iptables -L
  • sudo iptables -L -v -n - Show contents of all chains
  • sudo iptables -L --line-numbers
  • sudo iptables -L INPUT
  • sudo iptables -L INPUT
  • sudo iptables -L INPUT --line-numbers
  • sudo iptables -L INPUT --line-numbers -v

Switches:

  • -L - list. By default, it shows the rules for chains INPUT, FORWARD and OUTPUT
  • -v - verbose: Include additional information, including the number of packets and bytes matched by each rule as well as the target and options associated with the rule
  • -n - numeric: Display IP addresses and port numbers in numeric form, rather than trying to resolve these to hostnames or service names. This makes the output faster as it avoids DNS lookups
  • --line-numbers: Include a line number for each rule - Handy for e.g., deleting rules.

Lastly, INPUT is not a switch, but a chain.

Examples - without rules

Kinda boring, but an easy place to get familiar with this stuff:

Just list all rules in all chains:

$ sudo iptables -L

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination  

Only list rules in the INPUT list:

$ sudo iptables -L INPUT

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
strompf:src$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Add some verbose info:

$ sudo iptables -L -v

Chain INPUT (policy ACCEPT 1928 packets, 534K 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 1787 packets, 2525K bytes)
 pkts bytes target     prot opt in     out     source   destination

Default policies

Example of an INPUT chain with default policy DROP:

sudo iptables -L INPUT -n --line-numbers

Chain INPUT (policy DROP)
num  target                  prot  opt  source    destination         
1    ufw-before-logging-input all  --  0.0.0.0/0   0.0.0.0/0           
2    ufw-before-input         all  --  0.0.0.0/0   0.0.0.0/0           
3    ufw-after-input          all  --  0.0.0.0/0   0.0.0.0/0           
4    ufw-after-logging-input  all  --  0.0.0.0/0   0.0.0.0/0           
5    ufw-reject-input         all  --  0.0.0.0/0   0.0.0.0/0           
6    ufw-track-input          all  --  0.0.0.0/0   0.0.0.0/0

Let's change that to ACCEPT:

sudo iptables -P INPUT ACCEPT

First line of the output above, now reads

Chain INPUT (policy ACCEPT)

Drop rules

E.g.:

  • Display rules including line numbers: sudo iptables -L --line-numbers
  • Delete rules, referring to chain and line number: sudo iptables -D INPUT 12

Flush (delete) all rules

Use -F or --flush to flush or delete rules - Not to flush chains.

To get rid of all rules in the INPUT chain:

sudo iptables -F INPUT

To get rid of all rules:

sudo iptables -F

Flush (delete) chains

Use -X or --delete-chain to remove a chain. It is only possible to flush empty chains.

Reorder rules

It's critical to have the rules in the correct order, as they are executed in order. However, there is no direct way for doing so. To reorder rules in iptables, you typically need to delete and re-add the rules in the desired order:

View current rules

First, list the current rules to see their order:

sudo iptables -L --line-numbers

Delete the rules

Note the line numbers of the rules you want to reorder. You’ll need to delete them using their line numbers. For example, to delete the first rule in the INPUT chain:

sudo iptables -D INPUT 1

Repeat this for all the rules you want to reorder.

Add the rules in the desired order

After deleting the rules, re-add them in the order you desire. For example, to add a rule allowing SSH traffic:

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

Save the new configuration

Finally, save the iptables configuration to ensure it persists across reboots.

On systems using iptables-persistent:

sudo netfilter-persistent save

On systems using systemd to manage iptables:

sudo service iptables save

Reorder chains

It's quite easy to reorder chains.

Rules are not automatically saved

It seems that by default, newly added rules are just kept in memory. When the server reboots or otherwise is on for a long time, these rules seem to disappear.

Location & files

The usual location for storing these rules would be:

  • /etc/iptables/rules.v4
  • /etc/iptables/rules.v6

There seem to be several ways to make the rules persistent:

iptables-save & iptables-restore

One way to make these rules permanent, is through iptables-save and iptables-restore.

Export the rules from memory to storage:

  • sudo iptables-save > /etc/iptables/rules.v4
  • sudo ip6tables-save > /etc/iptables/rules.v6

Subsequently, restore these rules at startup:

  • sudo iptables-restore < /etc/iptables/rules.v4
  • sudo ip6tables-restore < /etc/iptables/rules.v6

iptables-persistent

On systems using iptables-persistent:

sudo netfilter-persistent save

systemd

On some systems using systemd:

sudo service iptables save

Interference with ufw?

Still not ready to accept that these rules are not stored by themselves? Than maybe check that ufw doesn't hijack these rules. Its rules are typically saved at /etc/ufw/.

Sources

CIDR

In specifying IP addresses, CIDR (Classless Inter-Domain Routing) notation can be used to specify ranges of IP addresses. This is the common formulation using

<IP address>/<number of fixed bits>   <=>
<Subnet base address>/<prefix length>

E.g.: 192.168.100.0/24

Subnets

To have an impression of the significance of the prefix length:

Class B subnets
Prefix Network mask Available hosts
/16 255.255.0.0 65,536 - 2 = 65,534
/17 255.255.128.0 32,768 - 2 = 32,766
/18 255.255.192.0 16,384 - 2 = 16,382
/19 255.255.224.0 8,192 - 2 = 8,190
/20 255.255.240.0 4,096 - 2 = 4,094
/21 255.255.248.0 2,048 - 2 = 2,046
/22 255.255.252.0 1,024 -2 = 1,022
/23 255.255.254.0 512 - 2 = 510
Class C subnets
Prefix Network mask Available hosts
/24 255.255.255.0 256 - 2 = 254
/25 255.255.255.128 128 - 2 = 126
/26 255.255.255.192 64 - 2 = 62
/27 255.255.255.224 32 - 2 = 30
/28 255.255.255.240 16 - 2 = 14
/29 255.255.255.248 8 - 2 = 6
/30 255.255.255.252 4 - 2 = 2

Bits, numbers & prefixes

Bits, numbers & prefixes
Bits Numbers
1 {0, 1) ⇒ 2 = 2^1
2 {00, 01, 10, 11} ⇒ 4 = 2^2
3 {000, 001, 010, 011, 100, 101, 110, 111} ⇒ 8 = 2^3
4 2^4 = 16
5 2^5 = 32
6 2^6 = 64
7 2^7 = 128
8 2^8 = 256

Example: 192.168.1.0/24

  • Base address: 192.168.1.0
  • Prefix: /24
  • The prefix indicates that the first 24 bits of the IP address are the network part, and the remaining bits are for hosts within that network
  • Subnet mask: 11111111.11111111.11111111.00000000 - 255.255.255.0 - ff.ff.ff.0
  • The subnet mask is straightforwardly derived from the prefix
  • A /24 prefix, leaves 32-24 = 8 bits = 2^8 = 256 addresses for the subnet
  • Substract 2 addresses from these (1 for the network address and 1 for broadcast), and there are '254 usuable addresses in this subnet.
  • Network address: 192.168.1.0
  • First usable address: 192.168.1.1
  • Last usable address: 192.168.1.254
  • Broadcast address: 192.168.1.255.

Example: 192.168.1.15/24

  • Let's now change the base address to make it look a bit less binary: 192.168.1.15
  • The rest stays she same
  • /24 indicates that the first 24 bits are fixed ⇒ The base address is still 192.168.1.0

So, it's less confusing to notate such a network as 192.168.1.0/24, rather than 192.168.1.15/24, because the 15 part will be stripped anyway.

Example: [168.119.64.245 - 168.119.64.254]

How to define in CIDR notation, the range of IP addresses from [168.119.64.245 - 168.119.64.254]?

Prefix

  • 254-245 = 9 addresses ⇒ add network address + broadcast address ⇒ 11 addresses ⇒ round up to closest power of two: 16 = 4 bits ⇒ /28
  • /28 <=> 11111111.11111111.11111111.11110000.

Network address

  • The first three bytes are easy: 168.119.64
  • Concerning the last byte: It's like an AND operation: (245 AND 0b11110000)
  • 245 = 0b11110101
  • 0b11110101 AND 0b11110000 = 0b11110000 = 240

Putting it together

  • 168.119.64.240/28
  • Network address: 168.119.64.240
  • First available address: 168.119.64.241
  • Last available address: 168.119.64.254
  • Broadcast address: 168.119.64.255.

Example: [168.119.68.117 - 168.119.68.254]

  • Total range: 137 hosts ⇒ 139 addresses ⇒ rounded up to closest power of 2: 256 ⇒ 8 bits ⇒ /24 - 8 bits left for submask
  • Network address: 168.119.68.64 - 168.119.68.0b01000000 ⇒ That's not possible: It has to be full 1 from the left + it doesn't fit with the prefix
  • Network address: 168.119.68.0.

Together: 168.119.68.0/24.

Example: [168.119.65.44 - 168.119.65.123]

  • Hosts: 123-44=79 hosts ⇒ Add 2 ⇒ 81 ⇒ round up to closest power of 2: 128 = 7 bits ⇒ /25
  • Subnet mask: 11111111.11111111.11111111.10000000
  • Network address: (168.119.65.44 AND 0xff.ff.ff.f0) <=> (168.119.65.0b00101100) = 168.119.65.0

Together: 168.119.65.0/25:

  • Subnet: [168.119.65.0 - 168.119.65.127]
  • First host: 168.119.65.1
  • Last host: 168.119.65.126

What may be confusing about this example, is that the network address doesn't start with an empty last byte.

Network address: Full bits on the left

One of the strange thangs about CIDR is, that the network address has to have full 1s at the left, because of the logical AND it performs with the subnet mask:

Possible values of the last byte of the network address, before the submask
Binary Decimal
00000000 0
10000000 128
11000000 192
11100000 224
11110000 240
11111000 248
11111100 252
11111110 254

Example: [168.119.65.254 - 168.119.65.255]

  • Only 2 IP addresses ⇒ Add 2 ⇒ 4. This requires 2 bits ⇒ /30
  • Network address: 168.119.65.254
  • Together: 168.119.65.254/30 ⇒ Not correct, as the network address itself, cannot be used
  • To expand, the network address becomes 168.119.0.0 with 12 bits for the mask
  • It's not practical to insist on having .255 as a usefull address, as it is broadcast address of a /26 block.

Get the names of all chains

To get the names of only the chains:

sudo iptables -L -n | grep '^Chain' | awk '{print $2}'

Example of a server that appearantly, also uses ufw:

sudo iptables -L -n | grep '^Chain' | awk '{print $2}'
INPUT
FORWARD
OUTPUT
ufw-after-forward
ufw-after-input
ufw-after-logging-forward
ufw-after-logging-input
ufw-after-logging-output
ufw-after-output
ufw-before-forward
ufw-before-input
ufw-before-logging-forward
ufw-before-logging-input
ufw-before-logging-output
ufw-before-output
ufw-logging-allow
ufw-logging-deny
ufw-not-local
ufw-reject-forward
ufw-reject-input
ufw-reject-output
ufw-skip-to-policy-forward
ufw-skip-to-policy-input
ufw-skip-to-policy-output
ufw-track-forward
ufw-track-input
ufw-track-output
ufw-user-forward
ufw-user-input
ufw-user-limit
ufw-user-limit-accept
ufw-user-logging-forward
ufw-user-logging-input
ufw-user-logging-output
ufw-user-output

Throttling using 'limit'

Some examples:

Filter by frequency

Filtering by frequency - number of requests per time:

sudo iptables -A INPUT -p tcp --dport 80 -m limit --limit 10/s -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j DROP

Filter by bandwidth

To throttle traffic based on bandwidth rather than connection rate, you need to use the iptables hashlimit module or a combination of iptables and tc (Traffic Control). For simple rate limiting with hashlimit, you can use:

sudo iptables -A INPUT -p tcp --dport 80 -m hashlimit --hashlimit 1/sec --hashlimit-burst 5 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j DROP

Connections per IP

To limit the number of simultaneous connections per IP address, you can use the connlimit module:

sudo iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 10 -j REJECT

Redundant rules

What happens if you twice introduce the same rule, e.g. sudo iptables -A INPUT -s 101.32.115.96 -j DROP

Well, it just being added twice:

  • iptables doesn't have some tool to remove duplicate rules
  • Maybe export rules and use e.g., Calc, for identifying redundancies.

Include a comment

Create a new rule including a comment:

sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT -m comment --comment "Allow HTTP traffic"
  • -m comment: match (or load) the comment module
  • --comment "...": Option for the comment module to add the supplied string as a comment

To add a comment to an existing rule: Remove the line and add it again.

Conditionally adding a rule

By default, you can add the same rule as often as you can. Quite inconvenient, as it makes script not being repeat-safe. An example of fixing this (assuming the INPUT chain):

rule="-p tcp --dport 22 -j ACCEPT"
#
sudo iptables -C INPUT $rule >/dev/null 2>&1 && sudo echo "Rule exists already"

sudo iptables -C INPUT $rule >/dev/null 2>&1 || sudo echo "Rule doesn't exists already"

or in action:

rule="-p tcp --dport 22 -j ACCEPT"
sudo iptables -C INPUT $rule >/dev/null 2>&1 || sudo iptables -A INPUT $rule

$rule in the second line, doesn't have to be quoted: It's aready formatted as expected by iptables.

Including comments

rule="-s 12.34.56.78 -j ACCEPT -m comment --comment 'server12'"
sudo sh -c "iptables -C INPUT $rule >/dev/null 2>&1 || sudo iptables -A INPUT $rule"

Create a new chain

  • Use -N or --new-chain to create a new chain
  • It probably only makes sense to create subchains within an existing chain: INPUT, FORWARD or OUTPUT
  • However, AFAIK, there is no command to create a subchain within a specific chain. You have to do this in two steps: (1) Create the chain; (2) Move the chain to somewhere
  • Use -I or --insert to insert this chain at a specific place, rather than using -A or --append to add it at the end of the INPUT chain
  • Use -j or --jump to 'jump' or add this subchain to the existing INPUT chain. Like the execution or cursor 'jumps' to to the subchain and continues execution there. This flag is required.

Example:

1. Create chain whitelist and move it to the first line of the INPUT chain:

sudo iptables -N whitelist
sudo iptables -I INPUT 1 -j whilelist

sudo iptables -L

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
whitelist  all  --  anywhere             anywhere            

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain whitelist (1 references)
target     prot opt source               destination  

2. Add a chain whatever and append it to INPUT:

sudo iptables -N whatever
sudo iptables -A INPUT -j whatever

sudo iptables -L

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
whitelist  all  --  anywhere             anywhere            
whatever   all  --  anywhere             anywhere            

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain whatever (1 references)
target     prot opt source               destination         

Chain whitelist (1 references)
target     prot opt source               destination  

3. Copy whatever to the first line in INPUT

  • AFAIK, you can't move a chain, only copy it
  • After copying it, you delete the original chain. But be careful: There will now be two chains with the same name.
sudo iptables -I INPUT 1 -j whatever

sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
whatever   all  --  anywhere             anywhere            
whitelist  all  --  anywhere             anywhere            
whatever   all  --  anywhere             anywhere            

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain whatever (2 references)
target     prot opt source               destination         

Chain whitelist (1 references)
target     prot opt source               destination

Example: Block incoming traffic from a subnet

Example:

sudo iptables -A INPUT -p tcp -s 52.230.152.0/24 --dport 80 -j DROP
sudo iptables -A INPUT -p tcp -s 52.230.152.0/24 --dport 443 -j DROP
  • -A INPUT: Appends this rule to the INPUT chain
  • -p tcp: tcp protocol
  • -s 52.230.252.0/24: Source subnet
  • --dport 80: Destination port 80
  • --dport 443: Destination port 443
  • -j DROP: Matching packages should be dropped (blocked)

However, maybe more practical:

sudo iptables -A INPUT -p tcp -s 52.230.152.0/24 -j DROP

In this case, everything is dropped from this source, regardless of protocol. That seems just fine, since I'm filtering spam accounts.

Example: filter for your own IP address?

One day, I came across a strange rule where 123.45.67.89 is actually the IP address of the server on which iptables is running:

sudo iptables -A INPUT -s 123.45.67.89 -j DROP
  • This was most likely a mistake: There is probably no reason to filter on your own IP address
  • In theory, this wouldn't do much, as internal traffic doesn't go through the INPUT chain
  • However, it caused at least one problem: curl couldn't check sites on the same server anymore.

See also

Sources