Netfilter 2: in the POM of Your Hands
Following the publication of “Taming the Wild Netfilter” in the September 2001 issue of LJ [/article/4815], I received a number of e-mails, most asking for more detailed information on working with Netfilter. To satisfy those requests, this time I will delve a little deeper. For those of you who haven't read and tried out a basic setup, I suggest you do so. This article is slightly more advanced and intended for those who have a grasp, tenuous as it may be, on the basics as described in the aforementioned article.
In order to get the most out of Netfilter and the user-land component iptables, you'll need to upgrade both your kernel and iptables. While there's nothing wrong with the kernel and iptables that came with your distribution, the Netfilter code is under constant development. You also certainly might have no idea what patches your distribution saw fit to include in the iptables area (probably none). And, not all patches show up as Netfilter modules or iptables match extensions. I do, however, recommend you don't try what's in this article for the first time on your currently running firewall. Make sure you know what to expect by experimenting on a test system.
The final recommendation in the paragraph above brings up a very important point. This article is based on iptables-1.2.4 and the Linux kernel 2.4.17. Your results will almost certainly vary if you use different versions. The principles will be the same. Don't panic; just try to make some intelligent decisions about what you want. Also understand that just as oil and water don't normally mix, some of the choices you make regarding the modules you want will affect other modules in the same way—that is, some modules don't mix well with others. Looking at the 2.4.18-prepatches, some of the iptables patches applied for this article will be incorporated in 2.4.18. I suggest a look at the 2.4.18 Changelog when this kernel version is finally released to see what you now won't need to try to patch (the patch would fail anyway, detected or not—see below).
In this article, we'll be using Rusty Russell's patch-o-matic that will patch both the iptables and kernel sources. This patch-o-matic (POM) isn't completely automatic and will not attempt to patch anything without your approval. It also will test the patch to be applied first to see if it applies correctly. If it doesn't, you will be told and given several options. If a patch doesn't succeed, your best and safest bet is to continue without applying it. But we'll see that as we go along.
First, download the latest kernel version you want to use (available from www.kernel.org). It can be 2.4.16 or higher. I always recommend waiting at least a week after the latest stable version is out before trying it. That way, if some small bug has made its way into the latest kernel (2.4.15's shutdown filesystem corruption bug comes to mind), you'll probably know about it and avoid a potentially nasty situation.
Using whatever method appeals to you, open and configure your new kernel. This article won't cover kernel building, but a number of articles and sites can bring you up to speed on this (the definitive guide is found in your kernel source tree under Documentation/Changes). I suggest you configure as modules all the Netfilter code. For now, you'll need to choose at least:
1. Code maturity level options-->Prompt for development and/or incomplete code/drivers
and
2. Networking options-->Network packet filtering (replaces ipchains)
and from here also go ahead and enter
-->TCP/IP Networking-->IP: Netfilter Configuration (click to go to subpage)
3. On the IP: Netfilter Configuration subpage configure all modules.
If you want, select the IPv6 protocol, and you can then also configure the IPv6 Netfilter modules. You'll need to proceed at least as far as the “make dep” step with this kernel to get everything prepared.
By the way, if you read near the bottom of the Help that comes with the Network packet-filtering choice, you'll find you should choose Y if your system will act as a router; otherwise select N, and if unsure, select no. I don't know how sage this advice is. Even simple hosts often need the extra protection that can be afforded by Netfilter. You'll have to decide that question for yourself based on your best risk assessment for your network and how the host will be used. We'll see how Netfilter is, in fact, used on machines other than routers, below.
Once you have your kernel ready, download and open the latest iptables (available from netfilter.samba.org). Change into the iptables directory, and you're almost ready to start. If your kernel is not located in /usr/src/linux, then you'll need to tell iptables where to find it. Additionally, if you don't want to install iptables in /usr/local/, you'll need to specify where you want to install it. There's a reason for each of these parameters (listed below) to be included. Since iptables will be patching the kernel, it must know where to find the kernel, and the iptables binary must know where to find the extensions. The location of the extensions is hard-coded into the binary, so you can't arbitrarily move things around later—you'll have to rebuild and re-install.
The following arguments are available for iptables builds:
KERNEL_DIR=/path/to/kernel/source (default: /usr/src/linux)
BINDIR=/path/to/install/binaries (default: /usr/local/bin)
LIBDIR=/path/to/install/lib-extensions (default: /usr/local/lib)
MANDIR=/path/to/install/manpages (default: /usr/local/man)
At this point, I must note that I often work in a chroot environment, particularly when beating on the kernel sources, etc., so I don't inadvertently damage a working system. However, I've found that the patch-o-matic doesn't work properly chroot-ed. Normally, patch-o-matic will create a temporary directory just above the kernel source tree where it patches and tests, then replaces the kernel sources from there. In a chroot environment (at least on my systems), this directory is never created, and the directory above the kernel source tree becomes a mess as it fills with the kernel source. I've been remiss and not taken the time to look at the problem sufficiently to identify the root cause. But it's not important if you just back up your kernel source before continuing.
The first command you'll want to use (assuming the kernel source directory is located in your $HOME directory) is:
make pending-patches KERNEL_DIR=$HOME/linux
You should have no problems with this target. It will tell you what patches it wants to install. You should say “yes” to all these. If, for some reason, any patch doesn't apply (the program may tell you the patch failed to apply), don't worry. The patch already may be incorporated in the kernel source, but the patch logic was unable to detect it. Just tell the script “no” the second time around. Do not force the patch on. Although this is an option, it normally will result in the script aborting. Once pending-patches completes, it will tell you the kernel is ready for compilation. But we're not quite ready yet.
Patches applied or attempted on my system were ipt_LOG.patch (successful) and tos-fix.patch (failed). The tos-fix.patch failed because a fix was applied, but it did not correspond exactly to the patch in the patch-o-matic.
Once you have applied all pending patches to the kernel source, you're ready to take a look at new patches that have not been incorporated into the kernel.
Some time back, Rusty Russell, lead Netfilter developer, introduced the “make patch-o-matic target” to help folks incorporate new things in the kernel without having to know how to use patch. This target works fairly well, but don't expect it to work perfectly. Sometimes the patch logic is sufficiently old, and the kernel source sufficiently changed, that a particular patch won't work. In recent months, the patch-o-matic has grown quite a bit and some patches break others. So Rusty incorporated yet another target, “most-of-pom”, to allow new iptables builders to get access to as many of the patches as possible but reduce the possibility of failure.
My recommendation to you is to run make most-of-pom, first saying “no” to everything but noting those patches you're interested in. Then run make patch-o-matic, noting any new patches not in most-of-pom that you might be interested in. If no new patches interest you in patch-o-matic, stick to most-of-pom. If any new patches do interest you that are only available in patch-o-matic, take careful note of any other patches those new, interesting patches might break. The worst offender as of this writing seems to be the drop-table patch. We'll not look at that patch for this very reason. But if you need it, just read and heed the warnings with that patch and others that tell you they will be broken by it.
In some cases, such as with the H323-conntrack-nat patch, you will not be able to apply a patch to the kernels we use in this article. If you can't do without this particular patch, you probably won't be able to use the experimental make targets for patching (patch-o-matic or most-of-pom). If this is you (I had this need for one customer's system), you need to go to roeder.goe.net/~koepi. The patch there includes newnat5, h323, talk, ftp and irc nat helpers. This is a standard patch applied using the usual patch utility.
While running make KERNEL_DIR=$HOME/linux patch-o-matic, I selected the following patches:
NETLINK.patch (successful) NETMAP.patch (successful) iplimit.patch (successful) mport.patch (successful) pkttype.patch (successful) psd.patch (successful) realm.patch (successful) snmp-nat.patch (failed: already in kernel) string.patch (successful) tos-fix.patch (failed: already in kernel) ulog.patch (failed: incompatible with kernel or previously applied patch) LOG.patch.ipv6 (failed: already in kernel) REJECT.patch.ipv6 (successful)
While going through pending-patches and the patch-o-matic, you'll want to note a few things. The screen is divided by a line. Above the line is a welcome note and a warning. Below the line is the information you should look at.
First, you will have one or more lines that will tell you which patches already have been applied. You'll note as you go through patch-o-matic that it lists the patches applied in pending-patches. In fact, because we're running patch-o-matic, we don't need to run pending-patches; those patches also would have been applied here. You only need to run pending-patches if you don't also run most-of-pom or patch-o-matic, such as if you decided to use the newnat5/h323 patch mentioned above.
Below the already-applied lines comes a line:
Testing... Patchname.patch STATUS (comment)
The patchname.patch is the patch being tested. The STATUS will normally be NOT APPLIED. The comment will be one of (x missing files) or (x reject of y hunks). Missing files means the patch hasn't been applied (or the particular corresponding files wouldn't be missing), or the patch doesn't match what's in the kernel sources. In general, the reject means a patch has been applied, it just doesn't match the patch in the patch-o-matic for whatever reason. The most common reason is a small fix was made between the patch in patch-o-matic and the patch in the kernel. When you see reject, it is a foregone conclusion that patch won't apply. Not all patches will work. Note that the ulog.patch failed. This failed either because it was incompatible with a previous patch or with the (changed) kernel sources since the patch was originally created.
Third comes information about the patch, author, status of patch, what the patch is, what it does, often an example to clarify how to use it and perhaps a comment.
Finally, the question, do you want to apply this patch? The choices are No (default), yes, test, force, quit and help as indicated by [N/y/t/f/q/?].
Once we've added the patches we want, we're done. Now the kernel is ready for compilation. Or is it? Well, yes. However, we've added targets to the kernel. I suggest you return to the kernel tree, run make oldconfig and select the new Netfilter matches and targets we've incorporated (or what was the sense?). Now you can continue to compile the kernel. After you install the kernel and reboot into it, you're ready to put your new matches and targets to work.
While the kernel source is compiling, there's plenty of time to compile and install iptables. Remember to supply the KERNEL_DIR= (if it's not in /usr/src/linux) and the BINDIR=, LIBDIR= and MANDIR= arguments if you don't want your new binaries and extensions installed in /usr/local/.
One small fix before we start compiling. For whatever reason, the NETLINK extension does not compile. So if you chose the NETLINK.patch (as I did) you need to make a minor adjustment. Just cd into the extensions directory and open the Makefile using your favorite text editor. The first line is our shebang line. The second line is blank. The third line starts off PF_EXT_SLIB: and contains various extensions to be made and installed. Add NETLINK to the end of the line and save the file back.
Now cd back up to the root of the iptables source tree and run your make and make install, adding the arguments noted above if required.
Above, we used a modified patch process to patch the kernel. If you, like me, grab kernel patches whenever they come out, you'll find that some will no longer apply cleanly because the kernel sources have been modified. So when I do want to try out a new kernel, I save the old .config file, wipe out the old kernel sources and start fresh. You can do that or remember to save a tarball of your kernel source tree before modifying it.
If you built a modular kernel previously using patch-o-matic (or pending-patches or most-of-pom) and are only adding a few more modules, after you use make oldconfig to add the new modules, you can do a make modules; make modules_install and start using those modules.
If you want to see the information again that you saw while adding the patch-o-matic patches, it's available in the iptables-x.x.x/patch-o-matic/ directory. The files *.patch.help contain the information. In most cases, the examples in these files are duplicated in the kernel configuration help.
Now that we have the modules we want compiled and installed, we're ready to put them to work. But before we start, we need to decide exactly what we're going to do. In order to do that we need to lay some groundwork. This groundwork isn't so important when all we have is our home system and we want to let everyone inside out but keep everyone outside out as well. Our state table alone practically assures us that's what we'll have; add masquerading or SNAT and we're done. This is what we had using the basic scripts from the “Taming the Wild Netfilter” article (September 2001 LJ).
But firewalls in use at companies are rarely so simple. They demand that we first understand (and maybe even restructure) our network topology. We also need to understand exactly what it is we want our firewall to accomplish. Sometimes, this is not much more than for our home system, but often it is radically different. We can use the company's network security policy to assist us (we do have an NSP, don't we?), plus some knowledge of what we want from our network access. We won't discuss risk assessments here [see Mick Bauer's “Practical Threat Analysis and Risk Management” in the January 2002 issue of LJ], but their findings should be kept in mind to help guide us in the overall scheme.
Many years ago we talked about our internet-connected hosts. They were all directly connected to the Internet. No big deal, as all the system administrators knew each other and things were friendly. Then everyone else discovered the Internet, and we had to make some changes. As things mushroomed out of control, we forgot or never knew who our neighbor system administrators were. We found our systems under attack. So we left our public systems directly connected but started hiding our users' hosts behind packet filters to help protect them. The systems between our router and packet filter were said to be on our DMZ, or demilitarized zone. The rest were on our trusted network behind the packet filter.
Today, few companies would configure their systems this way. In our current situation, usually only honeypots are deliberately left defenseless. Today, the two most common configurations either have a firewall with two internal NICs (one for the trusted network and one for the internal, public access network) or two separate firewalls (the first allowing public traffic into a controlled, but not trusted network and a second permitting entry into our inner sanctum or trusted network).
While small companies may mix the trusted and controlled networks on one private internal network, it is best to keep these separated whenever possible. You also should control who has access to which area. Firewalls do a lot to keep bad guys out, but do little to protect against bad guys already inside. In fact, you may find it's prudent to put a firewall up between accounting and marketing and engineering and production. None has much business in any of the other's files.
Because this article is principally about iptables, I'll not cover more on network topology. But we needed to understand the above to see how the configurations below work. They really aren't too much different from the point of view of the external firewall, only the internal one(s), if needed, will look a bit more like the basic firewall I presented in the first article. That is, the internal firewalls won't accept new traffic except from the trusted side. What goes out also can be moderated to an extent, and we'll look at that a little bit also.
There are times we might want to run iptables on a nonfirewall system. Despite the advice you may have read (as noted in the last paragraph of the “Preparing Your System for an iptables Upgrade” section above), there are times you'll want to run iptables on simple hosts. The simplest, but most common example would be a student system on a university network. In this case, you really should trust no other system. So you'll probably want to accept only related, established traffic.
Another example might be if you have decided to use an XDM server where most users work, but your internet policy only permits certain employees rights to surf the Web. How to deal with this? Well, fortunately, we can deal with this fairly simply with rules like the following:
iptables -t filter -I OUTPUT -p tcp --dport 80 -m owner --uid-owner 500 -j REJECT iptables -t filter -I OUTPUT -p tcp --dport 80 -j ACCEPT // only required if OUTPUT // policy is DROP/REJECT
or:
iptables -t filter -I OUTPUT -p tcp --dport 80 -m owner --uid-owner 500 -j ACCEPT iptables -t filter -I OUTPUT -p tcp --dport 80 -j REJECT // only required if OUTPUT // policy is ACCEPTNaturally, you'd need a list of either those permitted access or those denied. Also, you wouldn't want to write individual rules. I suggest handling the rules like this: for i in cat surfweb.txt, do
iptables -t filter -I OUTPUT -p tcp --dport 80 -m owner --uid-owner $i -j REJECT doneJust create a list of users to REJECT (or to ACCEPT and change the rule to match) as the file surfweb.txt. Add user IDs to this list as needed. You might find the above construct valuable for other repetitive rules as well. Note, however, this only prevents them from surfing from the XDM server, not from their local system.
So how might they be stopped from surfing from their local system? Well, the firewall simply could drop or reject packets coming from the disallowed IP. Easy, right? I mean, this is what packet filters are all about. But wait, we're using DHCP and don't necessarily know in advance what the IP will be. Looks like we've outsmarted ourselves—or have we? While we may not know the IP address, one thing we can know is the MAC address. So we get a list of MAC addresses from the systems (or via arp, or from the dhcpd.leases file). Then we use a rule like the following:
iptables -t filter -I FORWARD -i eth0 -m mac --mac-source <MAC> -j ACCEPT iptables -t filter -A FORWARD -i eth0 -p tcp --dport 80 -m state --state NEW -j DROP
This is best done in a loop like we did earlier, with the MAC addresses in one file and then looping through them.
Note: to use the MAC address to permit or deny systems, remember that they must be on your local network—that is, directly connected, via a hub, to the firewall. If the systems in question are behind an internal firewall, and not connected on the same LAN segment as the external firewall, you must put this rule on the inner firewall.
My point here is crucial: you must know what the system with the rule on it can know about packets it is to control. Only a system where packets originate can know which user ID belongs to the process originating the packets. Only a system on the local LAN segment can know the MAC address of an originating system. After that, we have only the information available in the IP header.
This month we looked at Rusty's patch-o-matic, installing an updated kernel and the user-land iptables utility. Probably the most important part is making sure that if things go wrong you can recover. Meanwhile, Rusty has worked very hard on ensuring you don't need to recover. After that we looked at a couple of common network configurations. You'll need to remember these when you dive into next month's Kernel Korner, which will be a part two of this article. Finally, we took a quick look at how and where iptables might be used in nonfirewall situations to control network resources.
Next month we'll look at managing services behind our NAT-ed firewall, specifically how to make the most use of the IPs your ISP has assigned, and how, with this configuration, to handle services like e-mail properly. We'll also look at more matches, targets, tables and some common errors when building rules.
David A. Bandel (david@pananix.com) is a Linux/UNIX consultant currently living in the Republic of Panama. He is coauthor of Que Special Edition: Using Caldera OpenLinux.