Taming the Wild Netfilter

Before we look at examples, let's set some things up. Scripts
are nice, and one that runs from rc.local (wherever that is on your
system) is always good. So let's write an rc.iptables script as
part of our example to implement Netfilter during bootup (see
Listing 1). (Note: all iptables rules will start $IPT and should
continue unbroken to the end of the line, i.e., the next command.
Rules should not be broken on a line.)

We've set up some variables to start, stopped forwarding
traffic through the system, then inserted some modules. The
ip_tables module allows us to start writing rules. The ip_nat_ftp
module is necessary if we are using the NAT table to do SNAT or
MASQUERADE (covered below) to allow active FTP. The
ip_conntrack_ftp allows connection tracking of FTP. This module
automatically loads the ip_conntrack module because it is dependent
on ip_conntrack. If you don't need or want the ip_conntrack_ftp
module, but want to make sure your firewall does IP defragmentation
(a good idea), you can substitute ip_conntrack for
ip_conntrack_ftp. Let's continue with our script:

for i in filter nat mangle
do
$IPT -t $i -F
$IPT -t $i -X
done

The above lines will flush (-F) all rules in the chain shown
as the argument to -F. Since no chain name was given as an
argument, -F will flush all chains. Note that this has to be done
for each table, hence the loop. If you aren't using a particular
table, you can delete it from the for statement. As each loop
starts, you will note that a new module has been loaded: first the
iptable_filter module, then the iptable_nat module and finally the
iptables_mangle module. If you remove mangle from the loop, the
iptables_mangle module will not be loaded. Unused modules can be
removed at your leisure.

Under ipchains, you would have used something like

ipchains -F -X

to accomplish the same thing.

Before we continue, let's assume the following setup: a home
user and three systems that access the Internet through our
firewall/workstation PC. Access is via dial-up (ppp0). If your
setup is different, substitute your external interface for ppp0.
The systems are not offering any services outside their own
network, which is on eth0. We do, however, want all systems inside
to be able to surf the Web. For this first example, we'll assume a
dynamic IP from an outside ISP [see Listing 2, rc.iptables.dynamic,
at
ftp.linuxjournal.com/pub/lj/listings/issue89/4815.tgz].

Since our firewall PC is also a workstation, it will be
originating and terminating its own traffic. While not a good idea
for a firewall (these should be dedicated systems) on a home
network, we really don't need a dedicated firewall. With this in
mind, remember that one difference between iptables and ipchains is
in the INPUT, FORWARD and OUTPUT chains. In ipchains, packets
traversing the FORWARD chain came from INPUT and went through
FORWARD to OUTPUT, so we could locate our rules in the INPUT chain
and be safe for packets going to the FORWARD chain. The iptables
implementation only uses INPUT for the local system and FORWARD for
the other systems. In our case we need identical rules in each of
the FORWARD and INPUT chains. To prevent duplicating a lot of
rules, let's create a user chain called tcprules and call it from
both INPUT and FORWARD. Continuing our script then:

$IPT -t filter -N tcprules

The ipchains equivalent of this rule would be the same,
excluding the -t filter option of the incantation:

ipchains -N tcprules

Now a little Netfilter magic. We want to prevent someone from
connecting to our systems from the outside but permit connections
to the outside to be established by our users. The following rules
make use of Netfilter's stateful capability:

If you want to do something similar in ipchains, the closest you
could come is to deny syn packets to the ppp+ interface:

ipchains -A input -i ppp+ ! -y -j DENY

A couple of quick notes at this point. First, the “!” negates
whatever follows it. So ! ppp+ is the same as
specifying all other interfaces (in the case of our home user, lo
and eth0). The “+” on the end of the ppp tells Netfilter this
rule applies to all ppp interfaces.

As for the ESTABLISHED, RELATED, NEW and INVALID arguments,
they are more than they appear to be. ESTABLISHED permits traffic
to continue where it has seen traffic before in both directions.
ESTABLISHED obviously applies to TCP connections but also to UDP
traffic, such as DNS queries and traceroutes as well as ICMP pings.
In fact, packets are first checked to see if the connection exists
in the connection tracking table (/proc/net/ip_conntrack). If so,
the chains aren't run, the original rule is applied and the packets
pass. In some cases, Netfilter is faster than its predecessor
because of this check. The RELATED argument covers a multitude of
sins. This argument is applied to active FTP, which opens a related
connection on port 20, but also applies to ICMP traffic related to
the TCP connection. The NEW argument applies to packets with only
the SYN bit set (and the ACK bit unset). The INVALID applies to
packets that have invalid sets of options, as in an XMAS tree
scan.

The above rules permit the internal systems to pass new
connections internally and externally, but don't permit incoming
new or invalid packets.

Since we're going to be masquerading our network behind our
firewall, we'll need to set up a masquerading rule. This process is
very similar to the one used in ipchains. Assuming our internal
network is 192.168.0.0/24, we'll use the following rule:

One difference ipchains users should note is the use of -o
ppp+ instead of -i ppp+. This is because with ipchains, we dealt
with interfaces as -i. With iptables, -i stands for input
interface, and -o stands for output interface. If you mistakenly
use -i ppp+ in the line above, no masquerading will take place. In
fact, the rule should never match at all.

Let's finish our script by implementing the tcprules above
where they are needed, implementing our filter-table policies and
turning ip_forwarding back on:

By default, the filter-table policies are all ACCEPT. Given
the stateful nature of Netfilter, this affords sufficient
protection, unlike ipchains. If you think about the stateful rules
we've implemented, you'll see that no harm should come with the
default policy; in fact, nothing should ever arrive at the default
policy. But some believe that it's always better to play it safe,
so we can drop anything not addressed already. Note that the policy
doesn't have a target per se, so we don't use -j. This is also why
only built-in targets can be policies—policies aren't really
targets. If you really want REJECT as your policy, you'll need to
add something like the following:

the next rule (which may or may not also match this packet)
will be processed. If a rule match does not drop, reject, accept or
queue the packet and is not a return, the chain will continue.

The ipchains crowd will note that under iptables, LOG is a
target by itself rather than a simple -l option following the
target, as with ipchains. This permits some flexibility, as can be
seen from the above rule. The limit match prevents someone
malicious from flooding your logs. The LOG target can send a prefix
with its message so that it's easy to grep from the logs.

Before we move on, all ipchains users know you can view the
chains by using something like

ipchains -L -n

This will show you the chains. With Netfilter, we need to
look at the chains, table by table:

Next is an example to demonstrate the new SNAT and DNAT. If you
understand the mangle table and its use, you probably don't need
this article and will be e-mailing me about any errors you've
noticed.

In this example, we'll assume our home user has a broadband
connection and is using eth0 for the internal network and eth1 for
the external network. The script begins the same as before, but
when we get to the tcprules, we're going to do something a little
different. Here, we're going to assume the user has a static IP of
209.127.112.17, which gives us more leeway [see Listing 3,
rc.iptables.static, at
ftp.linuxjournal.com/pub/lj/listings/issue89/4815.tgz].
In fact, we can also assume the user has his own domain name and is
running his own e-mail server on port 25, which is located on an
internal system with IP 192.168.0.2 (the firewall is 192.168.0.1).
His DNS entry points to 209.127.112.17 as his mail server, as shown
in Listing 4.

The first two tcprules are the same as in the first example.
But before we drop all other connections, we accept connections on
port 25. Then, in the nat table, we take the port 25 connection and
forward it to another internal host on the same port. You can do
this for all your connections. Remember that if you do this forward
for DNS, you need to forward UDP as well as TCP. In fact, unless
someone outside is going to do zone transfers, you can drop the TCP
part and only pass UDP.

Notice that now, instead of using MASQUERADE as a target for
outgoing connections, we're using SNAT. In case you wondered, the S
stands for source, which is what is being changed. In the case of
DNAT, we changed the destination of the packet. The argument
--to-source can take a range of IPs, so your firewall can look like
several hosts. If you have five usable IPs from your ISP, you can
use all five as outgoing. Then you can point different services to
different IPs and have up to five systems behind your firewall
answering DNS queries, hosting web sites, accepting mail, etc.
Netfilter will also allow you to do rudimentary load balancing.
When a range of destinations is used with DNAT, the system with the
least number of connections (not necessarily the system with the
lightest load) gets the connection.

About the only other thing you may want to know to get
started is how to increase the maximum number of connections
tracked. This number is arrived at by default depending on the
amount of RAM your system has. However, the number is conservative
and can be increased. You can find this number this way:

Comment viewing options

Hi, Iam new to netfilter. I want to know whether i can use netfilter API to send/receive (and possibly replace) ethernet packets irrespective of the protocol used in the ethernet payload(without writing my own device driver) or netfilter just provides api to work only at IP level.

It seems dificult to find this information.
How does one limit the amount of connections by IP?
For example, one may want to limit the amount of connections from an IP to 10, so from a single IP you only allow up to 10 connections.
It is useful to avoid a single IP eating all the connections to an email server or webserver or whatever service, to avoid simple to do DoS.
Using the programs for this isn't that nice, since one would like to limit by DROP and not REJECT in some cases, and also some programs don't even have the option to limit the connections allowed by each IP.
Thanks for your article!

i am using netfilter hook to capture a simple packet which contains an http request then it is been sent to a particular node by dev_queue_xmit function by changing its source ip and hardware address but am unable to transmit i have used nf_stolen option please help

Hi,
I have implemented, IP tunnelling in kernel by hooking to Netfilters. The implementation goes as a module. I am sending the packet out by calling dev_queue_xmit by filling the required fields in the skb and returning NF_STOLEN. I just want to know, if I capture a packet at pre/post routing hook and mangle the IP addresses(which could result in local/forward delivery) and return NF_ACCEPT to corresponding NF hook, would the packet be routed. This is required so that, if any module registered to NF after this tunneling module, can also get the packet and also I don't need to fill in the fields in the skb and call dev_queue_xmit.
BTW, I am Suresh.
thanks
regards
Suresh

Snort can be run on Netfilter. Look into snort_inline, It takes packets using ip_queue.o module to user space where snort is run.
I suggest you rewrite snort engine in kernel space and reduce the packet moving time from kernel space to user space and thus increase the performance.
Raviravivsn@rocsys.com
ROCSYS technologies ltd
Hyderabad

THANK YOU DAVID!!!!!! For a VERY long time now I have been trying to figure out why, when I made my INPUT chain's policy DROP/REJECT, my connection would fall off too. After months of searching, including a few wild goose chases, the answer was your discussion about allowing ESTABLISHED,RELATED connections on the INPUT chain. I now have a default policy of deny, whereas before I had to keep track of what was being broadcast and lock it down. THANKS AGAIN!!