====== Iptables: How-to Share your internet connection ====== http://www.debuntu.org/iptables-how-to-share-your-internet-connection ---- **[[http://netfilter.org/|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, 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. ----