My *nix world

How iptables works - netfilter

If you are a keen Linux user then you know the importance of iptables in your life, right? Sometimes I feel that iptables is for Linux what is Assembly language for a computer: if it does not exists then it should be invented, otherwise computing could be boring.

So, what's iptables anyway? iptables is just a bunch of chains that are a bunch of rules that are a bunch of "target-criteria-verdict thing" that we define for the packets that are coming and that are going via our NIC interfaces. Too many that, isn't it? At this point the picture bellow says more than one thousands words:

how iptables work

source: http://linux-ip.net/nf/nfk-traversal.png

How iptables works

So basically a packet inbounds from the network towards your NIC. But at the NIC gate (at the entrance and also at the exit) there is a guardian (which stays like a firewall against the enemies) that will ask everyone that what to pass through for the permission to pass. The "guardian" is called Netfilter, "everyone" are the network packets that inbound or outbound and the "permission" are the rules that we are establishing like the parliament enact a law.

In fact this guardian is divided in more specialized departments, because we deal with thousands or hundred of thousands of "enemies" and we want to dispatch everyone with the speed of the light.

We have one "department" called filter, another one called nat, mangle or raw/conntrack.

The "filter department" is specialized in filtering the "enemies at the gate", to see if they meet those laws that we established, and if they don't then they will not be accepted into our "citadel" (I mean PC). In fact they could be DROP-ped or REJECT-ed or even sent elsewhere, (e.g.) into the wilderness.

The "nat department" is specialized in meeting those people that they don't intended to visit/stay in your citadel, they intended in fact to visit other but your citadel is just a transit point, like Frankfurt is for flights in Europe. So what the "nat department" is doing is to route those people on their way out to the desired destination. That's all.

The "mangle department" is specialized with something like "counterfeiting". It could make the people that came from your citadel to look (to the other citadels) like they came from Paris or from Baghdad or from whatever else. And more things like that. No more words about it!

The "raw department" is a very special one: it deals only with the VIP situations, so it is used for establishing exemptions from the rules. Those VIP people are not forced to be checked like the others, the poor people, they are "special".

So in fact any packet that has to pass through the "Netfilter defense system" is checked by the iptables guardians. They do not behave chaotic, they are programmed like robots and they always follow what they were taught:

how iptables work

source: http://www.faqs.org/docs/iptables/traversingoftables.html

Because I don't work "every Thursday" with iptables, or just because I'm getting old, sometimes I forget essential details about this defense system. In fact, on my local PC, I seldom have to make some adjustments so it is normal to forget those tiny little details about the "inner machine" but I found a effective way to combat this: I have created a preface that I keep it just before the rules are written.

So I have created a bash script file called iptables.rules that stores all my iptables rules and which could be loaded at system boot (just create a daemon or something that run this script once the system has booted). This file is prefaced with comments that contains hints /references about those tiny little details that one can forget.

The following is a example of how you could keep your rules on your system together with some sort of a short guide (like a preface) that is going to help you every time you have to open this book (iptables.rules) and to lecture again:

#!/bin/bash

##########################################################################################################
# IMPORTANT: check out http://linux-ip.net/nf/nfk-traversal.png to understand Netfilter packet traversal #
# Check out the http://en.wikipedia.org/wiki/IPv4_header#Header to understand ipv4 header format #
##########################################################################################################
# Netfilter has 4 standard tables, each of them with two or more policy chains:
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#| Table name | PREROUTING FORWARD INPUT OUTPUT POSTROUTING | Module name | Supported targets |
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#| conntrack | 1 6 | iptable_raw | |
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#| mangle | 2 4 4 7 10 | iptable_mangle | MARK, TOS, TTL |
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#| nat | 3 8 11 | iptable_nat | PREROUTING supports DNAT, POSTROUTING supports SNAT, MASQUERADE |
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#| filter | 5->10 5 9 | iptable_filter | |
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

# In the table above the traversal is from 1->11. Where is a jump it is marked with -> i.e 5->10 means
# that the forward chain within filter table jump directly to POSTROUTING chain policy of mangle table.
#####################################################################################################################
# NOTE: if you want to debug the Netfilter packets then use the LOG target instead ACCEPT/DENY/REJECT
# Ex: iptables -A INPUT -p tcp -j LOG # JECT) so the whole info about the packet will appears in /var/log/messages (or tty12) but with that jump (-j) the
# Netfilter flow will end up to the LOG, so no other tables/chains will be traversed anymore. Anyway, we can LOG and
# ACCEPT/DROP in the same time:
# - we create a new chain named LOGDROP/ACCEPT (or whatever): iptables -N LOG; target=ACCEPT/DROP
# - instead sending a packet to DROP we send it to the chain: iptables -A LOG -j LOG
# - after logging the packet we can just ACCEPT/DROP it: iptables -A LOG -j
######################################################################################################################
# What should be known about ipv4 packet (advanced info, you may skip the lecture):
# - the packet min size is 20 bytes; the packet max size is 60 bytes
# - the 1st byte contains the ip version (first 4 MSB) and the internet header length(IHL, in the last 4 bits)
# - the 2nd byte contains the Differentiated Services Code Point (DSCP) and Explicit Congestion Notification (ECN)
# - the 3rd+4th contain the total length of the packet (its total size: header + data):
# - the min packet length is 20 bytes and the max packet length is 64Kb
# - the 5th+6th contains the identification code that help us to uniquely identify fragments of an original ip datagram
# - the 7th, the first 3 MSB contain a flag (0=reserver;1=Don't Fragment;2=More Fragment)
# - If the DF flag is set, and fragmentation is required to route the packet, then the packet is dropped
# - the next 5 from the 7th byte and the 8th byte contain the fragment offset of a particular fragment relative to the
# beginning of the original unfragmented ip datagram
# - the 8th byte contains the TTL (in seconds). Each router that handle the packet will substract 1 second from this field
# - the 9th byte contains the protocol: ICMP=1, TCP=6, UDP=17 (see http://en.wikipedia.org/wiki/List_of_IP_protocol_numbers)
# - 10th+11th byte contain the header checksum (because the router decrease TTL the router should recalculate it)
# - the 12th to 15th byte contain the source ipv4 address of the packet
# - the 16th to 19th byte contain the destination ipv4 address of the packet
# - the 20th to 23th byte contain optional info (when IDL>5), not very often used.
######################################################################################################################
# What should be known about iptables rules (fundamental info, read it!):
# - Rules contain a criteria and a target (ACCEPT, DROP, QUEUE, RETURN)
# - If the criteria is matched, it goes to the rules specified in the target (or) executes the special values mentioned
# in the target.
# - If the criteria is not matached, it moves on to the next rule
# - DNAT is also known as port-forwarding
######################################################################################################################
# What we should know about iptables syntax (fundamental info, read it! I mean it!):
# -A chain Append to chain
# -D chain Delete matching rule from chain
# -D chain rulenum Delete rule rulenum (1 = first) from chain
# -I chain [rulenum] Insert in chain as rulenum (default 1=first)
# -F [chain] Delete all rules in chain or all chains
# -Z [chain [rulenum]] Zero counters in chain or all chains
# -N chain Create a new user-defined chain
# -X [chain] Delete a user-defined chain
# -P chain target Change policy on chain to target
# Options:
# -4 Nothing (line is ignored by ip6tables-restore)
# -6 Error (line is ignored by iptables-restore)
# -p proto protocol: by number or name, eg. `tcp'
# -s address[/mask][...] source specification
# -d address[/mask][...] destination specification
# -i input name[+] network interface name ([+] for wildcard)
# -j target target for rule (may load target extension)
# -g chain jump to chain with no return
# -m match extended match (may load extension)
######################################################################################################################

if_name="quot;wlan0"quot;
br_name="quot;virbr0"quot;
br_net="quot;192.168.100.0/24"quot;
iptables_bin=/sbin/iptables

echo "quot;Insert EUGEN's rules..."quot;

########################################################################################################
# Set the default Policy for INPUT chain of 'filter' table
########################################################################################################
${iptables_bin} -t filter -P INPUT ACCEPT # valid options are: ACCEPT, DROP, QUEUE, RETURN

########################################################################################################
# MUST_TO_HAVE : allow localhost to talk with self otherwise X will not be able to start
# (excepting when you flush the filter's rules so X will not encounter any localhost filter restriction)
########################################################################################################
${iptables_bin} -t filter -A INPUT --protocol all --in-interface lo --source 127.0.0.1 --destination 127.0.0.1 -j ACCEPT
${iptables_bin} -t filter -A OUTPUT --protocol all --out-interface lo --source 127.0.0.1 --destination 127.0.0.1 -j ACCEPT

########################################################################################################
# DNAT/forward all traffic that comes to host interface port HOST_PORT to kvm guest network interface
########################################################################################################
HOST_IP="quot;1.2.3.4"quot;
KVM_IP="quot;192.168.100.10"quot;
HOST_SQL_PORT=1433
KVM_SQL_PORT=1433
HOST_WEB_PORT=80
KVM_WEB_PORT=80
${iptables_bin} -t nat -A PREROUTING -p tcp -d ${HOST_IP} --dport ${HOST_SQL_PORT} -i ${if_name} -j DNAT --to-destination ${KVM_IP}:${KVM_SQL_PORT}
${iptables_bin} -t nat -A PREROUTING -p tcp -d ${HOST_IP} --dport ${HOST_WEB_PORT} -i ${if_name} -j DNAT --to-destination ${KVM_IP}:${KVM_WEB_PORT}
${iptables_bin} -t nat -A PREROUTING -p tcp --dport ${HOST_WEB_PORT} -j DNAT --to ${KVM_IP}:${KVM_WEB_PORT}

########################################################################################################
# change ip source address of all packets from ${br_net} to any other network (both tcp/udp or
# others) to the ip address of default interface (eg wlan0) and change also the port the packets are
# comming from to a random port between 1024-65535 (only when related to tcp/udp protocols)
########################################################################################################
${iptables_bin} -t nat -A POSTROUTING -s ${br_net} ! -d ${br_net} -p tcp -j MASQUERADE --random
${iptables_bin} -t nat -A POSTROUTING -s ${br_net} ! -d ${br_net} -p udp -j MASQUERADE --random
${iptables_bin} -t nat -A POSTROUTING -s ${br_net} ! -d ${br_net} -j MASQUERADE

########################################################################################################
# accept DNS(53) request that comes to virbr0
########################################################################################################
${iptables_bin} -t filter -A INPUT -i ${br_name} -p udp -m udp --dport 53 -j ACCEPT
${iptables_bin} -t filter -A INPUT -i ${br_name} -p tcp -m tcp --dport 53 -j ACCEPT

########################################################################################################
# accept SSH(22) request that comes to "quot;if_name"quot;
########################################################################################################
# ${iptables_bin} -t filter -A INPUT -i ${if_name} -p tcp -m tcp --dport 22 -j ACCEPT
# ${iptables_bin} -t filter -A INPUT -i ${if_name} -p udp -m udp --dport 22 -j ACCEPT

########################################################################################################
# accept DHCP(67) request that comes to virbr0
########################################################################################################
${iptables_bin} -t filter -A INPUT -i ${br_name} -p tcp -m tcp --dport 67 -j ACCEPT
${iptables_bin} -t filter -A INPUT -i ${br_name} -p udp -m udp --dport 67 -j ACCEPT

########################################################################################################
# accept connection to port 9751 (Lexmark printer)
########################################################################################################
${iptables_bin} -t filter -A INPUT -i ${if_name} -m state --state NEW -m tcp -p tcp --dport 9751 -j ACCEPT

########################################################################################################
# accept connection to port 631 (CUPS)
########################################################################################################
${iptables_bin} -t filter -A INPUT -i ${if_name} -m state --state NEW -m tcp -p tcp --dport 631 -j ACCEPT

########################################################################################################
# by default accept those packets for established/related connection
########################################################################################################
${iptables_bin} -t filter -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

########################################################################################################
# LAST FILTER : reject any others package and send a generic 'service not found' message and drive
# to a false piste the potential atacker
########################################################################################################
${iptables_bin} -t filter -A INPUT -p tcp -i ${if_name} -j REJECT --reject-with tcp-reset
${iptables_bin} -t filter -A INPUT -p udp -i ${if_name} -j REJECT --reject-with icmp-port-unreachable

########################################################################################################
# prior to masquerading, the packets are routed via the filter table's FORWARD chain
# allow outbound : related, established connections to virbr0 interface
# allow inbound : any connection to virbr0 interface
########################################################################################################
${iptables_bin} -t filter -A FORWARD -d ${br_net} -o ${br_name} -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT # accept outgoing packets from virbr0 that go towards virbr0 (see br_net), which were already seen traveled in both directions (i.e ESTABLISHED)
${iptables_bin} -t filter -A FORWARD -s ${br_net} -i ${br_name} -j ACCEPT # accept packets that comes to virbr0 from another ip that is served by the same interface (see its source: ${br_net})
${iptables_bin} -t filter -A FORWARD -i ${br_name} -o ${br_name} -j ACCEPT # accepts packets that travel between 2 IPs within the same virbr0 network
${iptables_bin} -t filter -A FORWARD -o ${br_name} -j REJECT --reject-with icmp-port-unreachable
${iptables_bin} -t filter -A FORWARD -i ${br_name} -j REJECT --reject-with icmp-port-unreachable

Now, if you think that this article was interesting don't forget to rate it. It shows me that you care and thus I will continue write about these things.

The following two tabs change content below.
How iptables works - netfilter

Eugen Mihailescu

Founder/programmer/one-man-show at Cubique Software
Always looking to learn more about *nix world, about the fundamental concepts of math, physics, electronics. I am also passionate about programming, database and systems administration. 16+ yrs experience in software development, designing enterprise systems, IT support and troubleshooting.
How iptables works - netfilter

Latest posts by Eugen Mihailescu (see all)

Leave a Reply

Your email address will not be published. Required fields are marked *