A Quick Guide to Firewalls with ipchains and iptables

Warning: The firewall made here is for learning purposes. Do NOT use it as your firewall.

The warning above is a bit melodramatic--the firewall is probably as good as the RedHat 7.1's default firewall. Still, I don't know if I would trust it. It is really intended to break down how ipchains can be constructed.

This is a work in progress--lately, I've gotten sidetracked and haven't been able to do that much work with it. It started with simply duplicating RH's default firewall, but was supposed to progress with changing default policies to deny (or DROP in iptables.) Well, one of these days.

The key word here is quick. This tutorial is intended to give you the basics of using ipchains and iptables. I saw a couple of other newbie guides on the web, but they all, in my mind, left out a couple of important gotchas.

Before I begin, however, let me say that I am not an expert on this. Ok, if I'm not an expert, then why am I even writing it? Well, I'm gradually learning how to make it work, and I'm writing the sort of thing that I wish I'd had. I do think that my article is a decent introduction, and will help prepare you to go on with the ipchains and iptables howtos. There are no doubt, better ways to do many of these things however, these are things that have worked, more or less, for me.

ipchains also takes care of masking and forwarding, but this article is solely about using them for firewalling. For a simplified, albeit insecure treatment of using them for masquerading, I have an article here. It's dated, however, and when I tried parts of it recently, I found that various changes either in distro or kernel kept it from working properly.

This article deals more with ipchains than iptables. Although, aside from those using 2.2.x kernels, tables are gradually becoming the standard, the format is similar enough so that the transition is relatively easy.

I'm assuming you know the basics of IP addressing, including the loopback address of 127.0.0.1, a little bit about DNS and the difference between TCP and UDP. If you don't, then even this simplified article might be hard going. I'll explain them briefly, however, as we get to them.

I'm also putting in a link to Skylinux's page which has some firewall scripts. They're heavily commented, and are far more sophisticated than what we're doing here. I'd say start by trying to understand my article, then go to his scripts.

For those who have come back to this page after seeing it before, he has fixed the problem with one of his scripts so the warning that used to be here is no longer applicable.

All of this was done using RedHat 7.1, but should be applicable to all distros using ipchains.

As mentioned in my ssh article, RedHat includes their own firewall that can be either chosen during installation and/or customized afterwards by typing

setup

at a command prompt.

Most distros have ipchains installed, but to check you can always type at a command prompt

which ipchains

This should give you an answer like /sbin/ipchains

If you're running RH 7.1 then they've already installed a default firewall. You can check this by typing

ipchains -L (don't worry about not understanding the output yet, we'll get to that shortly.)

You'll probably see something like


Chain input (policy ACCEPT):
target     prot opt     source                destination           ports
ACCEPT     udp  ------  your ISP's DNS Server anywhere              any ->   any
ACCEPT     udp  ------  your ISP's second DNS Server anywhere              any ->   any
ACCEPT     all  ------  anywhere             anywhere              n/a
DENY     tcp  -y----  anywhere             anywhere              any ->   0:1023
DENY     tcp  -y----  anywhere             anywhere              any ->   nfs
DENY     udp  ------  anywhere             anywhere              any ->   0:1023
DENY     udp  ------  anywhere             anywhere              any ->   nfs
DENY     tcp  -y----  anywhere             anywhere              any ->   x11:6009
DENY     tcp  -y----  anywhere             anywhere              any ->   xfs
Chain forward (policy ACCEPT):
Chain output (policy ACCEPT):
Note the list of chain names. There is input, forward and output. These are the three default chains included with ipchains. You can't delete them.

The listing above doesn't show which interfaces are affected by these chain rules. Doing ipchains -L -v (for verbose) will give additional information, including packets accepted, interfaces, etc. (This will become important in a few mintutes)

For example, note the line

 ACCEPT   all ---anywhere       anywhere      n/a

It looks as if it's saying to accept all protocols from anywhere--however, doing an ipchains -L -v shows

 pkts bytes target     prot opt in     out     source               destination 
 0     0         ACCEPT     all  --  lo     any   --           anywhere  

(Depending upon your browser, that might not have lined up properly--at any rate, lo would appear under the heading of in for interface.

So, we can see it's only accepting everything on lo, which stands for loopback, the local interface. (This is one of those things that hopefully you already know. If not, a quick explanation. lo, the loopback address is a way to test TCP/IP on your own machine, even if you're not hooked up to a network. You can for example, type

ping localhost

If you get a response then it indicates that TCP/IP is properly installed. You can also ping 127.0.0.1 which is its default address. This works not only on Linux, but also on Windows.)

This will all be simpler to understand as we build our own firewall, so let's begin. Two things --as mentioned at the top of this article, this is not intended to be your firewall--it's for learning purposes, and should be done on a machine that is either not connected to the internet or that is behind some other sort of firewall. Secondly, in the course of doing this, at times your machine will be totally open to the world. Therefore, rather than typing at a command prompt, save the commands in a text editor and we'll run them when we're done. That isn't because I expect you to use this firewall, but simply to develop good habits.

Basically, like a Cisco IOS access list, ipchains examines a packet until it matches a condition. The packet is then either acccepted or dropped. We start with our three default chains and decide what we'll do if the packet doesn't match any of the rules we're about to make. We're going to make a firewall based on RH's default one.

(A quick note on this--some of the below did not work in Slackware 7.1. I don't know if this is do to an older kernel, or some other reason--but, for example, using xfs or nfs as a destination port did not work)

ipchains -F

ipchains -P input ACCEPT
ipchains -P forward ACCEPT
ipchains -P output ACCEPT

Ok, here's what we've done. In the first command, ipchains -F, the -F flag means flush. We've gotten rid of whatever ipchains commands are in effect right now. The next three commands set the default policy.

As mentioned, there are three default ipchains that can't be deleted--these are input, forward and output. The -P flag indicates the policy that we're setting. Our default policy is going to be to accept everything. So, right now, if you typed this at a command line, your machine is wide open to everything. As you get better at this, you'll probably start by setting input to REJECT or DENY, but at present, we're keeping it simple. Keep in mind, the firewall we are building is not all that great, and certainly not be used in a situation where there is serious danger of attack. This firewall is just for practice. Of course, you took my advice about typing it in a text editor, so that's not a problem, right?

Next, RH's default firewall gives udp access to my ISP's DNS Servers. Hopefully you know the numbers of your DNS server. If not then, at a command prompt, type

netcfg

This should have a list of what they call nameservers. Those are your ISP's DNS Server addresses. If you're using RH 7.1 and have their default firewall in place, you can also find it with that ipchains -L command mentioned earlier. In Windows, you can type, at a command prompt, ipconfig /all | more and you will get your DNS Servers' addresses. (You can also call your ISP, but who wants to be on hold for an hour?)

At any rate, we are now going to give our DNS Servers udp access--otherwise, web browsing and the like will be pretty difficult.

(Another quick explanation--DNS, Domain Name Service. This is what translates IP addresses to names. For instance, if you type www.yahoo.com the request goes to a DNS Server at your ISP. This Server matches the name to yahoo's address and directs you there.)

So,you've got your ISP's DNS Servers' addresses. If you have the addresses as IP addresses then type in as below, substituting the IP address for the X.X.X.X, or, if you have the FQDN (Fully Qualified Domain Name, such as DNSServer1.ISP.COM) then type that instead. Either will work.

ipchains -A input -p udp -s X.X.X.X -d 0/0 -j ACCEPT

Here's what we've done. ipchains -A appends a rule to the chain input. -p specifies the protocol. (If you leave it out, the rule assumes it applies to any protocol.) DNS usually uses udp rather than tcp, so we're allowing udp. (Sometimes, if a connection is bad, a DNS server will use tcp, however, most ISP's have a few DNS servers, so, hopefully, this won't be a problem. Just in case you don't know, TCP, transmission control protocol, is connection oriented--it sets up a connection between the two computers and checks that it's working. UDP, user datagram protocol is connectionless--it doesn't check the connection. Therefore, though less reliable than TCP, it's far faster because there's no overhead involved in checking the connection.)

-s refers to the source--here, we can either use the IP address of your ISP's DNS servers or their FQDN. Next, we specify what destination will accept it. 0/0 is a wild card meaning any destination is acceptable.

Next the -j flag. This stands for jump and shows us what to do with the packet. It jumps to ACCEPT. We're allowing this packet in.

As we go through this chain, you will see that at times we specify an interface, protocol or port and sometimes we don't. If one is not specified, then the default is any. In this chain, we're only going to specify an interface in one rule--in the other rules, the default will be any interface.

By the same token, on most of these, we specify a protocol with the -p flag. When we don't, the rule will apply to any protocol. So, if you see a -p flag on one rule, then on the next wonder, "What happened to the -p flag?" the answer is that for that particular rule, we're applying it to all protocols. The same goes for destination ports--if we specify one, that is the port affected in the rule, if we don't, then it applies to all ports.

So, if your ISP's DNS Server was at 10.0.0.1 you would type in

ipchains -A input -p udp -s 10.0.0.1 -d 0/0 -j ACCEPT

The next line would be for your ISP's 2nd DNS server and would follow the same pattern. Another thing to keep in mind--if we had set the default input policy to DENY then we MUST use the IP address, rather than the FQDN of our DNS Servers.

Next, in RH's default ipchains is a line that accepts everything on the loopback interface. When I tried leaving it out, various things didn't work on my box, so, let's add it in. (A quick reminder--to see what interface is being affected,when listing your chains with the ipchains -L command, add the -v flag.)

ipchains -A input -i lo -s 0/0 -d 0/0 -j ACCEPT

So, with the -A flag, we're adding another rule. -i is for the interface, lo (that's a small letter 0) is the loopback interface, or local machine. -s 0/0 means any source, -d 0/0 means any destination. -j ACCEPT again means jump it to ACCEPT.

A quick note here. The 0/0 (those are zeroes, not the letter "O") is actually redundant. If a particular option isn't specified, the default is any. I have them in here for practice. If we had typed the above command as

ipchains -A input -i lo -j ACCEPT it would have had the same effect. Both source and destination would be listed as any.

Next, RH's default firewall doesn't allow telnet, ftp, etc. They do it this way

ipchains -A input -p tcp -y -s 0/0 -d 0/0 0:1023 -j DENY

So, we have the -p being tcp. The -y flag refers to connection using syn. A syn packet is used with telnet, ftp and various other protocols that want to initiate a connection. Basically, it tells your computer that the other computer wants to talk to it. So a tcp protocol that wants to initiate a connection will be sending a syn packet. The -s flag is redundant; however the -d flag, may be necessary. (See below) It means we're refusing connections using TCP with syn set from anywhere to anywhere. We haven't put in an -i flag for interface, so this applies to all interfaces. The 0:1023 are the port numbers. We're refusing all of this sort of connection on well-known ports, 0-1023.

I'm not sure if it's supposed to work this way or not, but I've found that with ipchains (as opposed to iptables) that if I'm giving a numerical destination port such as 0:1023 or 22 rather than a name such as ssh or if I'm giving a name separated by a colon such as bootps:bootpc I've neededthe -d 0/0. This varies from distro to distro, but you're probably safest putting in the -d 0/0 to avoid having the aggravation of an error message. As mentioned, Slackware won't accept some things that RH will.

Next, we use the -j flag to jump to DENY. We could also use REJECT, however, DENY makes it seems as if no connection exists, whereas REJECT will simply refuse the connection.

Next we're going to block all udp connections on well-known ports, ie 0-1023. Why don't you look at what we've done so far and see if you can do it yourself?

ipchains -A input -p udp -s 0/0 -d 0/0 0:1023 -j DENY

Next, they block udp for nfs

ipchains -A input -p udp -s 0/0 -d 0/0 nfs -j DENY

The new thing in this line is the listing by name of what they block. After the destination, they put the name of the protocol. Hopefully, by now you're beginning to get the syntax. Next, they block x11 (port 6000) to port 6009

ipchains -A input -p tcp -y -s 0/0 -d 0/0 x11:6009 -j DENY

Nothing new in that one, save for the combination of protocol and port.

Lastly, they block tcp using syn to xfs with the -y option. Why don't you try it?

ipchains -A input -p tcp -y -s 0/0 -d 0/0 xfs -j DENY

The above is RH's default medium security ipchains setup. However, suppose you want to allow ssh from a machine on your local network. Let's assume that your local network is using the 192.168.0.1-255 addressing scheme. There's a couple of ways to do this. If you just want to allow only one machine to access it, then you could put that machine's address as the source. However, the chances are that you want to allow a group of machines to access it. This is done by adding a subnet mask to the -s flag, eg

ipchains -A input -p tcp -y -s 192.168.1.0/24 -d 0/0 :22 -j ACCEPT

If you're familiar with subnetting, the above should be pretty clear. /24 equals a netmask of 255.255.255.0. For a better explanation of subnetting see my own subnetting page. For the moment, just accept that /24 means that any machine with an IP address beginning with 192.168.1 will be able to ssh to our machine. (If I wanted to be less restrictive, and for example, allow anything beginning with 192.168 to ssh in, I would have written 192.168.0.0/16.) I could have added, before the :22, ssh, but chose not to. SSH usually uses port 22, so it should be sufficient. Had I just written ssh, it would accept ssh connections on any port.Note that I also specified protocol and the -y flag.

(A quick note. These days, most people prefer SSH to telnet, as it's more secure, encrypting passwords and such. As security becomes more and more important, it comes down to---DON'T USE TELNET.)

However, there's one problem with this. Putting it where I have, at the end, it won't work. The ssh packet has already been rejected by the earlier line rejecting tcp connections using syn on any port between 0 and 1023. So, we're going to get around this with the -I flag. I stands for insert.

If we use the -I flag with no added numbers, it goes in as the very first rule. We might or might not want that. Let's say, going back to our ssh example, that we simply want it to go before the first rule that rejects tcp connections using syn on all well-known ports.

Remember what happens when ipchains are in use. A packet comes in. It goes into the input chain. It's then matched against the rules until one matches it. In this case, an attempt to ssh (hrmm, is that a verb?) would go down the list until it got to the one rejecting tcp connections on well-known ports using syn. It would then jump to being rejected, and that would be it. It won't go further down the list of rules. So, we have to accept this ssh connection BEFORE it hits that DENY rule.

So, let's assume that we have set up our ipchains as above. Or perhaps we did a default RH install and now decided that we want to allow ssh connections. While we could do this with the setup command (see the ssh article mentioned above) we decide to simply add a rule to our ipchains. So, first we see what we have with

ipchains -L --line-numbers

This will list our ipchains rules and number them as well. So, we see that the rule rejecting tcp packets using syn is, for example, rule number 4. (Its actual number would depend upon how many DNS servers were listed in the earlier rules.) Therefore, we want to insert our ssh rule as rule number 4. The syntax is

ipchains -I input 4 -p tcp -y -s 192.168.1.0/24 -d 0/0 :22 -j ACCEPT

If you again list your ipchains you'll see that that rule has been inserted before the one rejecting tcp from well known ports.

If you later decide you want to change that rule, it's quite simple

ipchains -D input 4

The rule is then deleted.

If you're still with me, then you probably have your ipchains file all set in your text editor and want to try it out. Before doing that you can check how close you are to RH's default ipchains. At a command prompt type cat /etc/sysconfig/ipchains | less

This will show you the ipchains that they have given you by default. (However, at least on my box, it doesn't show the DNS server entries--there are remarks in the file about this.) It will also give you actual port numbers, rather than names such as xfs, etc. The reason we began with ipchains -L -v is that now you have an idea which ports refer to what.

So, go back in and change the names such as xfs, nfs etc to their proper port numbers. You can also check these by adding an -n flag (for numeric listing) to the iptables -L command. Then, if you're feeling brave, back up that file

mv /etc/sysconfig/ipchains /etc/sysconfig/ipchains.bak and put in your ipchains file. Type

ipchains-save > /etc/sysconfig/ipchains

(The format of the sysconfig/ipchains file is slightly different than what you typed, as you probably noticed when you did the cat /etc/sysconfig/ipchains)

You can then restart ipchains with

/etc/rc.d/init.d/ipchains

Before doing that or saving it as a script, (which is probably an easier way to fix things if they go wrong) I would first test it by typing each command one at a time. Few things are as aggravating as running a script and getting an error and not knowing where the error is. It's even worse if you simply used it to replace your current ipchains file.

If you test your ipchains script by typing each command, you can see if you have any errors in syntax. If so, go back to this article and check your typing. The chances are that you did something like -d 0/0/ by mistake, or something minor like that.

This will, by the way, have to be done as root. So, do an su to root before testing the file.

If you aren't quite brave enough to replace your current /etc/sysconfig/ipchains, you could test it in this way:

Save the text file somewhere as whatever you want to call it, for example, firewall. I save all my scripts in a folder called /usr/bin/scripts and I call this one chainx. Do a chmod on it--I did chmod 755 so that I could test it as any user. I then added /usr/bin/scripts/chainx to my /etc/rc.d/rc.local file. Therefore, even though RH runs its own ipchains at startup, after that, my ipchains script goes into action. You can test that it works after a restart by doing

ipchains -L and seeing that your rules are all in effect. (It's very important, if you do it this way, that you have that first line

iptables -F

to remove the current ipchains.)

Slackware does things a bit differently. With Slack, you make your ipchains script, then copy it to /etc/rc.d as rc.ipchains. Do a chmod on it
chmod 755 /etc/rc.d/rc.ipchains

Now edit /etc/rc.d/rc.M so that it can be executed. Although explaining Slack's rc.M file is beyond the scope of this page, in this case you would add

if [ -x /etc/rc.d/rc.ipchains ]; then
. /etc/rc.d/rc.ipchains
fi

If it was done correctly, it is as secure as RH's default medium security firewall, with the exception of having added ssh from the LAN, but still, we would rather err on the side of caution. So, please reread the warning at the top of the page.

iptables

The iptables program, written by the author of ipchains, is an improved version of ipchains. RH suggests that you start using it in preparation, as it is intended to replace ipchains. Ipchains covers firewalling, masquerading and NAT whereas iptables only covers firewalls.

Now, this one to me, is almost funny. Makes me think that RH is getting more like MS every day. Despite their saying you should use iptables, what happens? If, in a default RH 7.x install, you type iptables, you'll get an error message, giving various possible reasons. What it comes down to is this: If you have ipchains, RH's default, running, you can't run iptables. So, if you're going to make an iptables script, you'd have to put the following line at the head of the script

rmmod ipchains

(Once you're sure your iptables is set up the way you want it, you can, in RH, by typing "ntsysv" without quotes at a command prompt, go to the services menu and remove ipchains from being run at startup. If you do this, then, of course, you can take out the rmmod ipchains line in your script)

This removes the ipchains module, allowing you to use iptables.

There are a couple of differences. The default chains, input, forward and output are now named in upper case. The option DENY is replaced by DROP. The -y flag has been replaced by --syn and the port after a colon has been replaced by --dport

So, going back to our examples, I'll take a few lines and show how they would be done in iptables.

Take the line where we denied various connections with the syn packet. We wrote

ipchains -A input -p tcp -y -s0/0 -d 0/0 0:1023 -j DENY

With iptables we would write it this way

iptables -A INPUT -p tcp --syn --dport 0:1023 -j DROP

Our line allowing ssh from our LAN was written

ipchains -A input -p tcp -y -s 192.168.1.0/24 -d 0/0 :22 -j ACCEPT

With iptables it would be

iptables -A INPUT -p tcp --syn --dport 23 -j ACCEPT

Something like the x11:6009 port would simply be

iptables -A INPUT -p tcp --syn -s 0/0 -d 0/0 --destination-port x11:6009 (That's simply for example--in reality we'd use --dport 6000:6009)

(--dport is actually an alias for --destination-port, which is included here for example)

Hopefully, the other substitutions are pretty straightforward by now. If we used a protocol's name, such as nfs, or a port number such as 0:1023 we now preface it with --destination-port and if we had the -y option we now use --syn.

It could be tested in the same way--run it as a script at startup, then, if it's working properly

iptables-save > /etc/sysconfig/iptables

(One thing--there is a 4th default table nat--I haven't played with it at all and the only difficulty it's caused is a comment upon shutdown--see the iptables howto if it bothers you. It can be fixed by adding support for NAT when you compile your kernel )

On occasion, you run into problems with iptables--I'm not sure which versions. At any rate, what might happen if you type, for example

iptables -A INPUT -s 0/0 -p tcp --syn -d 0/0 --dport 22 -j ACCEPT

you may get an error message that --dport is an unknown option.

I believe that this is considered a bug that they're working on (though I haven't kept up with it enough to be sure.) However, the way around it is to add an -m for match, to match the protocol. Therefore you can type

iptables -A INPUT -s 0/0 -p tcp -m tcp --syn -d 0/0 --dport 22 -j ACCEPT

and it should fix the problem. The other possible problem--I confess this once happened to me-is that you might have typed --deport rather than --dport.

At any rate, if you're still with me, then you probably understand ipchains well enough to go onto the ipchains howto, as well as the Network Admins guide mentioned above. The iptables howto can also be found there. Hopefully, this article has gotten you started well enough so that they won't seem overwhelming. Comments and criticisms are welcome. You can drop me a line.

More Advanced References

If you've gotten through this article and understand it fairly well, you can then check out the ipchains howto

The iptables how to is available here. Additionally, the 2nd Edition of the Linux Network Administrators Guide has a good section on using ipchains for firewalling here.

Newbie ones:
The one that I liked on ipchains is here and the one on iptables is here.

A lot of people helped me figure this out--I wish to thank them, even though most of them don't like to be named.