Share your internet connections using iptables








From: http://www.debuntu.org/iptables-how-to-share-your-internet-connection

iptables is a command line tool which allow system administrators to configure Linux packet filtering ruleset.

Using iptables, you are able to tweak packet filtering, Network Address Translation (NAT) and packet mangling which in the end are going to allow you to secure your server, share your Internet connection and log unwanted traffic.

iptables is not really what we could call an easy to get with tool, but once you know the basis, it won't be that scary :).

This tutorial will provide a sample script you can use to share your Internet access and will give an overview on how to use iptables

1. Introduction

Most people will be freaked out when you pronounce the name iptables because it is not much of an easy to understand software, man page is huge as well as iptables capabilities.
To be able to set up a home router, you don't actually need to spend nights and nights going through iptables man page, a grasp of the basis is enough to get your firewall up and running.

This tutorial provides a sample script you should be able to use out of the box or at most, changing 2 parameters will be able to get you running.

2. Iptables

To be able to understand what the firewall do, there is some basis you need to know. Here I'm going to go over what make iptables handle network packets.

2.1. Chain Rules

Iptables use a set of chain rules to check weather or not a packet should be accepted. By default, there is 3 chains:

  • INPUT: packet is destinate to the machine running iptables
  • FORWARD: packet needs to be forwarded to another machine
  • OUTPUT: packet going out of the machine running iptables

So when a packet reaches the firewall, the first thing the kernel is going to do is to determine where the packet is going. According to the destination, the kernel will check the packet against the rules of the appropriate chain.

2.2. Actions (TARGET)

For each chain we define a list of rules and actions (called targets in iptables'jargon) to take when a packet match a rule. Main actions are:

  • ACCEPT: accept the packet :)
  • REJECT: discard the packet and inform the source
  • DROP: discard the packet but don't say anything to the source

As soon as a packet has matched a rule, the kernel will apply the action it is said to do and won't go further. If the packet did not match any rules, the kernel will use the default policy defined for that chain.

This beeing said, we can now get into the script.

3. Iptables Script

OK, now that we know the really basis, let see what the script is going to look like.

In this example, I assume that eth0 is the interface connected to the Internet, eth1 is the one connected to our local network.

#!/bin/sh
#
# this script requires iptables package to be
# installed on your machine


# Where to find iptables binary
IPT="/sbin/iptables"

# The network interface you will use
# WAN is the one connected to the internet
# LAN the one connected to your local network
WAN="eth0"
LAN="eth1"
# First we need to clear up any existing firewall rules
# and chain which might have been created
$IPT -F
$IPT -F INPUT
$IPT -F OUTPUT
$IPT -F FORWARD
$IPT -F -t mangle
$IPT -F -t nat
$IPT -X

# Default policies: Drop any incoming packets
# accept the rest.
$IPT -P INPUT DROP
$IPT -P OUTPUT ACCEPT
$IPT -P FORWARD ACCEPT

# To be able to forward traffic from your LAN
# to the Internet, we need to tell the kernel
# to allow ip forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

# Masquerading will make machines from the LAN
# look like if they were the router
$IPT -t nat -A POSTROUTING -o $WAN -j MASQUERADE

# If you want to allow traffic to specific port to be
# forwarded to a machine from your LAN
# here we forward traffic to an HTTP server to machine 192.168.0.2
#$IPT -t nat -A PREROUTING -i $WAN -p tcp --dport 80 -j DNAT --to 192.168.0.2:80
#$IPT -A FORWARD -i $WAN -p tcp --dport 80 -m state --state NEW -j ACCEPT
# For a whole range of port, use:
#$IPT -t nat -A PREROUTING -i $WAN -p tcp --dport 1200:1300 -j DNAT --to 192.168.0.2
#$IPT -A FORWARD -i $WAN -p tcp --dport 1200:1300 -m state --state NEW -j ACCEPT

# Do not allow new or invalid connections to reach your internal network
$IPT -A FORWARD -i $WAN -m state --state NEW,INVALID -j DROP

# Accept any connections from the local machine
$IPT -A INPUT -i lo -j ACCEPT
# plus from your local network
$IPT -A INPUT -i $LAN -j ACCEPT

# Here we define a new chain which is going to handle
# packets we don't want to respond to
# limit the amount of logs to 10/min
$IPT -N Firewall
$IPT -A Firewall -m limit --limit 10/minute -j LOG --log-prefix "Firewall: "
$IPT -A Firewall -j DROP

# log those packets and inform the sender that the packet was rejected
$IPT -N Rejectwall
$IPT -A Rejectwall -m limit --limit 10/minute -j LOG --log-prefix "Rejectwall: "
$IPT -A Rejectwall -j REJECT
# use the following instead if you want to simulate that the host is not reachable
# for fun though
#$IPT -A Rejectwall -j REJECT --reject-with icmp-host-unreachable

# here we create a chain to deal with unlegitimate packets
# and limit the number of alerts to 10/min
# packets will be drop without informing the sender
$IPT -N Badflags
$IPT -A Badflags -m limit --limit 10/minute -j LOG --log-prefix "Badflags: "
$IPT -A Badflags -j DROP

# A list of well known combination of Bad TCP flags
# we redirect those to the Badflags chain
# which is going to handle them (log and drop)
$IPT -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ACK,PSH PSH -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ACK,URG URG -j Badflags
$IPT -A INPUT -p tcp --tcp-flags FIN,RST FIN,RST -j Badflags
$IPT -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j Badflags
$IPT -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL ALL -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL NONE -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL FIN,PSH,URG -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j Badflags

# Accept certain icmp message, drop the others
# and log them through the Firewall chain
# 0 => echo reply
$IPT -A INPUT -p icmp --icmp-type 0 -j ACCEPT
# 3 => Destination Unreachable
$IPT -A INPUT -p icmp --icmp-type 3 -j ACCEPT
# 11 => Time Exceeded
$IPT -A INPUT -p icmp --icmp-type 11 -j ACCEPT
# 8 => Echo
# avoid ping flood
$IPT -A INPUT -p icmp --icmp-type 8 -m limit --limit 1/second -j ACCEPT
$IPT -A INPUT -p icmp -j Firewall

# Accept ssh connections from the Internet
$IPT -A INPUT -i $WAN -p tcp --dport 22 -j ACCEPT
# or only accept from a certain ip
#$IPT -A INPUT -i $WAN -s 125.124.123.122 -p tcp --dport 22 -j ACCEPT

# Accept related and established connections
$IPT -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

# Drop netbios from the outside, no log, just drop
$IPT -A INPUT -p udp --sport 137 --dport 137 -j DROP

# Finally, anything which was not allowed yet
# is going to go through our Rejectwall rule
$IPT -A INPUT -j Rejectwall


3.1. Iptables default settings


First of all, we define where iptables binary is located and to make the script easier to attapt to other situation, we define the interface as WAN and LAN.


So, if your machine uses eth1 as the interface connected to the Internet and eth0 connected to your local network, simply change:



WAN="eth0"

LAN="eth1"



to



WAN="eth1"

LAN="eth0"



Then we clean up iptables by flushing all the chain and tables:



$IPT -F xxx



and deleting all the optional user-defined chains:



$IPT -X



Then we define the default policies:



$IPT -P xxx



Which is to DROP any packet which is destinated to the local machine if they were not accepting, ACCEPT any packet which is going out of the local machine or going to/coming from our LAN if they were not discarded yet.



3.2. To and From Local Network


Because we want to be able to forward traffic, we need to say so to the kernel. This is what is done by setting /proc/sys/net/ipv4/ip_forward to 1.



Then, we need to tell the kernel to masquerade all outgoing traffic. This is what is achieved by triggering:



$IPT -t nat -A POSTROUTING -o $WAN -j MASQUERADE



Masquerading has the effect of allowing all computer from your internal network to access the internet. These machines will be seen as if there were the router itself.



Now, suppose you want your apache server on machine 192.168.0.2 to be visible from the outside. You need to tell the firewall to send those packets to machine 192.168.0.2 on port 80, this is what is achieved with:



$IPT -t nat -A PREROUTING -i $WAN -p tcp --dport 80 -j DNAT --to 192.168.0.2:80



But then, you also need to accept new connection on that specific port because, as you will see later on, we by default forbid NEW and INVALID connections coming from the outside to be forwarded.


So, for that specific service, we will allow NEW connections (RELATED and ESTABLISHED being allowed by default) to be forwarded:



$IPT -A FORWARD -i $WAN -p tcp --dport 80 -m state --state NEW -j ACCEPT



And now, let discard any NEW and INVALID connections:



$IPT -A FORWARD -i $WAN -m state --state NEW,INVALID -j DROP



To be able to have your local connection to work properly, you need to accept everything on localhost. This is done with:



# Accept any connections from the local machine

$IPT -A INPUT -i lo -j ACCEPT



We do not need to use the statement $IPT -A OUTPUT -i lo -j ACCEPT because the default OUTPUT policy is set to ACCEPT


If your default policy is different, you might have to add this statement



Then, because in our example we trust our local network (not a wise thing to do though), we need to allow any incoming connections from our LAN:



# plus from your local network

$IPT -A INPUT -i $LAN -j ACCEPT



3.3. Defining custom chains


In order to get a easier to maintain iptables script, it is handy to define some custom chains, also called user-defined chains. This way, you can gather common actions into 1 chain, then, using our target switch (-j) we will be able to send packets that match specific rules to that target.


In order to create a user-defined chain, we need to use:



iptables -N chain_name



and then simply add rules to that chain using the usual:



iptables -A chain_name [rules ...] -j target



Okie, now that this is explained, we are going to create 3 user-defined chains which are going to log packet matching rules to be sent to this specific chain:




  • Firewall: is going to log packets by prepending "Firewall: " and DROP them, as you will see, this will only deal with ICMP


  • Rejectwall: is going to log packets (prepending "Rejectwall: ") that were not accepted my any previous rules


  • Badflags: is going to log packets which TCP flags are not properly set. Some kind of packets are usually used during attack. (prepending "Badflags: ")



The bit of code that deals with the chain creation and which append rules to it is:



# Here we define a new chain which is going to handle

# packets we don't want to respond to


# limit the amount of logs to 10/min


$IPT -N Firewall


$IPT -A Firewall -m limit --limit 10/minute -j LOG --log-prefix "Firewall: "


$IPT -A Firewall -j DROP


# log those packets and inform the sender that the packet was rejected


$IPT -N Rejectwall


$IPT -A Rejectwall -m limit --limit 10/minute -j LOG --log-prefix "Rejectwall: "


$IPT -A Rejectwall -j REJECT


# use the following instead if you want to simulate that the host is not reachable


# for fun though


#$IPT -A Rejectwall -j REJECT --reject-with icmp-host-unreachable


# here we create a chain to deal with unlegitimate packets


# and limit the number of alerts to 10/min


# packets will be drop without informing the sender


$IPT -N Badflags


$IPT -A Badflags -m limit --limit 10/minute -j LOG --log-prefix "Badflags: "


$IPT -A Badflags -j DROP



As you can see, there is a new target (action), namely LOG. LOG is a specific target that logs the packet to /var/log/messages usually. LOG is a non-terminating target, this means that the packet is going to continue to the next rule after being logged.


by using the --log-prefix you can specify what is going to be prepended to your log.



So let's take the example of chain "Firewall".

First we create the chain: $IPT -N Firewall


Then, we ask the kernel to log the packet and to prepend "Firewall: " to the log string. But because we don't want our logs to be flooded by such logs, we cap the number of logs related to the Firewall chain to 10/minute: $IPT -A Firewall -m limit --limit 10/minute -j LOG --log-prefix "Firewall: "


Finally, after we logged the packet, we are simply going to DROP it: $IPT -A Firewall -j DROP



3.4. Using those rules


Creating user-defined chain will now make it easier and faster for us to operate specific actions on packets.


Let's go through the block of statements related to bad TCP flags:



# A list of well known combination of Bad TCP flags

# we redirect those to the Badflags chain


# which is going to handle them (log and drop)


$IPT -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j Badflags


$IPT -A INPUT -p tcp --tcp-flags ACK,PSH PSH -j Badflags


$IPT -A INPUT -p tcp --tcp-flags ACK,URG URG -j Badflags


$IPT -A INPUT -p tcp --tcp-flags FIN,RST FIN,RST -j Badflags


$IPT -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j Badflags


$IPT -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j Badflags


$IPT -A INPUT -p tcp --tcp-flags ALL ALL -j Badflags


$IPT -A INPUT -p tcp --tcp-flags ALL NONE -j Badflags


$IPT -A INPUT -p tcp --tcp-flags ALL FIN,PSH,URG -j Badflags


$IPT -A INPUT -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j Badflags


$IPT -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j Badflags



As you can see, for any of the packet matching a rule, we simply have to send the packet to the "Badflags" chain. If we were not using user-defined chains, the first statement would look like:



$IPT -A INPUT -p tcp --tcp-flags ACK,FIN FIN -m limit --limit 10/minute -j LOG --log-prefix "Badflags: "

$IPT -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j DROP



So this would be twice as much work :s. Now, imagine you want to change the target from DROP to REJECT, you simply have to edit 1 line instead of 11 :)



Those badflags rules are well known combinations of illegitimated TCP flags settings. Normal application should not use those, this is why we can DROP those packets safely.



Now, we are going to allow only a small set of ICMP packets. In our example, we want our firewall to be able to receive information such as Timeout (type 11), Host unreachable (type 3) and we want it to reply to pings (type 8 ) and get replies to ping initiated from our firewall (type 0).


In order to do this, we ACCEPT any ICMP packets which contains one of the following code type and then pass all other ICMP packets code to our Firewall chain.



# Accept certain icmp message, drop the others

# and log them through the Firewall chain


# 0 => echo reply


$IPT -A INPUT -p icmp --icmp-type 0 -j ACCEPT


# 3 => Destination Unreachable


$IPT -A INPUT -p icmp --icmp-type 3 -j ACCEPT


# 11 => Time Exceeded


$IPT -A INPUT -p icmp --icmp-type 11 -j ACCEPT


# 8 => Echo


# avoid ping flood


$IPT -A INPUT -p icmp --icmp-type 8 -m limit --limit 1/second -j ACCEPT


$IPT -A INPUT -p icmp -j Firewall



Note the -m limit --limit 1/second, by doing such, our firewall is going to reply to only 1 ping per second, any other ping will be logged (up to 10/min and then DROPped) through the Firewall chain



3.5. Traffic from the Internet


After we have dealt with not well formed packets and icmp packets, we should apply some global rules to streams coming from the outside (remember that our default policy for OUTPUT packets is ACCEPT, so we don't have to allow those).



The basic idea here is to only allow streams that are related to a previous connection (useful for FTP for instance) or already established.

But, we are going to make one exception for SSH because we want to be able to connect to our box from the outside.



We achieve this by accepting any ssh packets from the outside and then only connections in state RELATED or ESTABLISHED



# Accept ssh connections from the Internet

$IPT -A INPUT -i $WAN -p tcp --dport 22 -j ACCEPT


# or only accept from a certain ip


#$IPT -A INPUT -i $WAN -s 125.124.123.122 -p tcp --dport 22 -j ACCEPT


# Accept related and established connections


$IPT -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT



Then we are going to DROP silently netbios scan from the outside:



# Drop netbios from the outside, no log, just drop

$IPT -A INPUT -p udp --sport 137 --dport 137 -j DROP



And finally, REJECT any other packet through our user-defined chain Rejectwall:



$IPT -A INPUT -j Rejectwall



4. Using iptables'script



4.1. From the command line


One way to apply the rules we define, is simply to run the script from the command line like:



sudo sh /path/to/firewall-script.sh



but this has the bad effect of not being restore on reboot :s, but still, this will be of great help while tweaking up your firewall.



4.2. Using /etc/rc.local


/etc/rc.local is a custom file where you can add scripts to be executed at the end of each multiuser runlevel.



By default, this file only contain exit 0.


In order to have your iptables firewall script executed on reboot, simply add the path to your firewall script before exit 0.



Copy your firewall script file to /etc/firewall-script.sh for instance. Then make it executable:



sudo chmod 700 /etc/firewall-script.sh



Then edit /etc/rc.local and add /etc/firewall-script.sh before exit 0



Next time you are going to reboot, this script is going to be executed and therefore, your firewall set up restored.



4.3. Using /etc/network/if-up.d/ directory


This one is a bit more tricky.

Once you are done with setting up your firewall script, you will save it to the iptables format by trigerring:



$sudo sh /path/to/firewall/script.sh

$sudo iptables-save > /etc/firewall-iptables.conf



Now, open and edit /etc/network/if-up.d/iptables and make it look like:



#!/bin/sh
iptables-restore < /etc/firewall-iptables.conf


Then make it executable:



sudo chmod +x /etc/network/if-up.d/iptables



Finally, we need a way to set up /proc/sys/net/ipv4/ip_forward to 1. This can be achieved through /etc/sysctl.conf.


Simply add the following entry if not already there:



net.ipv4.ip_forward=1



which will set /proc/sys/net/ipv4/ip_forward to 1 next time you reboot.



We could have also used /etc/firewall-script.sh instead of the iptables-restore trick, but this way, you can see another way to do it



Reboot, your firewall should be up again :)



4.4. Once upon a time


Debian used to have this great /etc/init.d/iptables init script which allowed you to restore iptables settings on boot up, stop your firewall ...

This script is now gone... so we have got to do it by ourself now :(



4.5. Rescue script


A handy script to have around is a script that can erase all chains and rules in case you are getting lost with your firewall breakages. The following script will clear up all rules and reset all chain so your firewall will be inactive. I suggest you copy it and keep it somewhere close to you in case of emergency.



#!/bin/bash
IPT='/sbin/iptables'

for a in `cat /proc/net/ip_tables_names`; do
${IPT} -F -t $a
${IPT} -X -t $a

if [ $a = nat ]; then
${IPT} -t nat -P PREROUTING ACCEPT
${IPT} -t nat -P POSTROUTING ACCEPT
${IPT} -t nat -P OUTPUT ACCEPT
elif [ $a = mangle ]; then
${IPT} -t mangle -P PREROUTING ACCEPT
${IPT} -t mangle -P INPUT ACCEPT
${IPT} -t mangle -P FORWARD ACCEPT
${IPT} -t mangle -P OUTPUT ACCEPT
${IPT} -t mangle -P POSTROUTING ACCEPT
elif [ $a = filter ]; then
${IPT} -t filter -P INPUT ACCEPT
${IPT} -t filter -P FORWARD ACCEPT
${IPT} -t filter -P OUTPUT ACCEPT
fi
done


5. Conclusion



This tutorial covered iptables in order to set up a linux firewall which will share your internet connection amongst computer from your local network.


By explaining iptables basis, you should now be able to improve your script so you can allow or disallow specific types of traffic.



This is not the most secure set up though. Best practice would be to set up a policy which disallow all traffic by default and then only allow the traffic you believe that should be permitted.



Finally we went through different ways of recovering iptables setting on reboot.



Hope this helps and will give you enough basis to customize your firewall.




No comments:

Post a Comment