While TCP wrappers can be used to restrict the set of hosts that can establish connections to certain services on a machine, in many cases it is desirable to exert finer-grained control over the packets that can enter (or leave!) a given system. It’s also the case that TCP wrappers only work with services configured using inetd or xinetd; some services (such as sshd on some systems) are “standalone” and provide their own access control features. Still other services don’t implement any access control themselves, so it’s necessary to provide another level of protection if we wish to control the connections made to these services.
Today it is commonplace for Internet users to protect themselves against the threat of network-based attacks using a technique called IP filtering. IP filtering involves having the kernel inspect each network packet that is transmitted or received and deciding whether to allow it to pass, to throw it away, or to modify it in some way before allowing it through. IP filtering is often called “firewalling,” because by carefully filtering packets entering or leaving a machine you are building a “firewall” between the system and the rest of the Internet. IP filtering won’t protect you against virus and Trojan Horse attacks or application defects, but it can protect you against many forms of network-based attacks, such as certain types of DoS attacks and IP spoofing (packets that are marked as coming from a system they don’t really come from). IP filtering also provides an additional layer of access control that prevents unwanted users from trying to gain access to your system.
To make IP filtering work, we need to know which packets to allow and which to deny. Usually, the decision to filter a packet is based on the packet headers, which contain information such as the source and destination IP addresses, the protocol type (TCP, UDP, and so on), and the source and destination port numbers (which identify the particular service for which the packet is destined). Different network services use different protocols and port numbers; for example, most web servers receive requests on TCP port 80. If we wanted to filter out all incoming HTTP traffic from our system, we’d set up an IP filter that rejects all TCP packets destined for port 80.
Sometimes inspecting just the header of a packet is not sufficient to accomplish a particular filtering task, so we need to inspect and interpret the actual data carried within the packet. This technique is sometimes called “stateful inspection” because a packet is considered in the context of an ongoing network connection rather than in isolation. For example, we might want to allow users inside our network to use FTP servers outside our network. FTP is a complex protocol that uses one TCP connection to send commands to the server, but another to transfer the actual data. Unfortunately the FTP specification does not mandate a particular port number for data transfers, so the client and server must negotiate port numbers using the command session. Without stateful packet inspection, allowing FTP transfers would require allowing TCP connections to arbitrary ports. Stateful inspection solves this problem by interpreting the port number negotiation between the client and server, and automatically allowing TCP packets on the negotiated port to pass through.
IP filtering is implemented by the Linux kernel, which contains code to inspect each packet that is received and transmitted, applying filtering rules that determine the fate of the packet. The rules are configured using a user-space configuration tool that accepts arguments from the command line and translates them into filter specifications that are stored and used as rules by the kernel.
There are three generations of kernel-based IP filtering in Linux, and each has had its own configuration mechanism. The first generation was called ipfw (for “IP firewall”), and provided basic filtering capability but was somewhat inflexible and inefficient for complex configurations. ipfw is rarely used now. The second generation of IP filtering, called IP chains, improved greatly on ipfw, and is still in common use. The latest generation of filtering is called netfilter/iptables. netfilter is the kernel component and iptables is the user-space configuration tool; these terms are often used interchangeably. netfilter is not only much more flexible to configure, but is extensible as well. In the following sections we’ll describe netfilter and some simple configurations as examples.
netfilter is implemented in Linux kernels 2.4.0 and newer. The primary tool for manipulating and displaying the filtering tables is called iptables and is included in all current Linux distributions. The iptables command allows configuration of a rich and complex set of firewall rules and hence has a large number of command-line options. We’ll address the most common of these here. The iptables manpage offers a complete explanation.
Just to whet your appetite, take a look at a sneak preview of where we’re heading:
iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
This command installs an IP filtering rule that accepts new incoming connections to TCP port 22 (the ssh service) on our local system. It also uses an extension module called state to perform connection tracking. On the following pages we’ll explain how all this works.
An important concept in netfilter is the notion of a chain, which consists of a list of rules that are applied to packets as they enter, leave, or traverse through the system. The kernel defines three chains by default, but the administrator can specify new chains of rules and link them to the predefined chains. The three predefined chains are:
INPUT
This chain applies to packets that are received and are destined for the local system.
OUTPUT
This chain applies to packets that are transmitted by the local system.
FORWARD
This chain applies whenever a packet will be routed from one network interface to another through this system. It is used whenever the system is acting as a packet router or gateway, and applies to packets that are neither originating from nor destined to this system.
Each rule in a chain provides a set of criteria that specify which packets match the rule, and an action that should be taken on packets that match. Actions that can be taken on a packet include accepting the packet (allowing it to be either received or transmitted), dropping the packet (simply refusing to receive or transmit it), or passing the packet onto another chain. (The latter is useful when building user-defined chains, which allow complex packet-filtering rules to be built up hierarchically.) A packet traverses each rule in the chain until it is accepted, dropped, or reaches the end of the chain; if it reaches the end, the default action of the chain determines the fate of the packet. The default action of a chain can be configured to either accept or drop all packets.
The Linux netfilter supports a number of other interesting things you can do in filtering rules. One of the key advantages of netfilter is that it is extensible. It is possible to develop extensions that enhance the way netfilter operates. Some examples of more sophisticated packet handling actions are:
You can create rules that do nothing more than log a description of the matching packet so that it can be captured for analysis later. This is very useful for detecting attacks and for testing a filtering configuration.
netfilter includes a set of helper modules that can perform stateful connection inspection, such as management of FTP connections, as described earlier.
Network Address Translation (NAT), also called IP masquerading, provides a means of rewriting the IP addresses and port numbers of packets as they pass through a chain. NAT is most commonly used to allow systems on a private network to use a connection to the Internet with a single IP address. NAT is a complex subject that we don’t discuss at length, but a simple example is provided later in this chapter. You can learn more about NAT in the NAT HOWTO or in TCP/IP Network Administration, 3rd Edition (O’Reilly).
netfilter provides counters that allow you to measure how the network traffic handled each rule, and several IP accounting systems are based on these statistics. These counters are visible when you use iptables to list rulesets in verbose mode; we’ll demonstrate this in Example 17-3.
The iptables command is used to make changes to the netfilter chains and rulesets. You can create new chains, delete chains, list the rules in a chain, flush chains (that is, remove all rules from a chain), and set the default action for a chain. iptables also allows you to insert, append, delete, and replace rules in a chain.
The iptables command has a large number of command-line arguments and options, but once you’ve used it a few times, the syntax becomes fairly obvious. In this section we are only going to cover the most common uses of iptables, so some arguments and options are left out of the following discussion. Specifically, we don’t discuss user-defined chains here. Table 17-1 lists a summary of the iptables arguments that operate on chains, and Table 17-2 summarizes the iptables arguments that operate on individual rules.
Table 17-1. iptables operations on chains
Argument |
Description |
---|---|
-L |
List the rules in the specified chain or all chains. |
-F |
Flush (delete) the rules from the specified chain or all chains. |
-Z |
Zero the byte counters in the specified chain or all chains. |
-P |
Set the default action on the specified chain to
|
Table 17-2. iptables operations on rules
Argument |
Description |
---|---|
-A |
Append a rule to |
-D |
Delete the rule with rule number |
-R |
Replace rule number |
-I |
Insert a rule into |
Each filtering rule includes parameters that describe which packets
match the rule. The most common rule parameters are summarized in
Table 17-3. Using an exclamation point
(!
) before a parameter inverts it. For example,
the parameter -dport 80
means
“match destination port 80,” while
the parameter -dport ! 80
means
“match any destination port except
80.”
Table 17-3. iptables rule parameters
Parameter |
Matches |
---|---|
-p ! |
The packet protocol. Valid settings are |
-s ! |
Source address of the packet, specified as a hostname or
IP address. |
-d ! |
Destination address of the packet. Uses the same syntax as the source address. |
-- sport ! |
The source port of the packet. Specifies as a literal port number or
as a service name from |
-- dport ! |
The destination port of the packet. Uses the same syntax as the source address. |
-i ! |
The network interface on which the packet was received. |
-o ! |
The network address on which the packet will be sent. |
A number of important options are used when building rulesets, summarized in Table 17-4.
Table 17-4. Other important iptables options
Option |
Description |
---|---|
-v |
Enable verbose output. Most useful when listing rules with
|
-n |
Display IP addresses in numeric form (i.e., avoid DNS lookup). |
-m |
Load the iptables extension named
|
In addition to specifying matching parameters, each netfilter rule must specify some action to take for each packet matching the rule. Generally a rule specifies that a packet should be accepted or dropped, as described next. If no action is specified for a rule, the packet and byte counters for that rule will be incremented and the packet passed on to the next rule in the chain. This allows a rule to be used for accounting purposes only. To specify an action for a rule, use the syntax:
-j target
Here, -j
stands for
“jump,” meaning that if a packet
matches this rule, processing will jump to the action named by
target
. target
can be one of:
ACCEPT
Allow this packet to be transmitted or received.
DROP
Drop the packet.
QUEUE
Pass the packet to a userspace program for processing.
RETURN
If used with a user-defined chain, causes the packet to be returned to the “calling” chain. If used with a built-in chain, causes the packet to jump to the end of the chain (where it is subject to the default action for that chain).
When using the -j
option,
target
can also be the name of a
user-specified chain, which allows the user to define a
“subchain” of rules that will
process this packet. As described earlier, the target
RETURN
is used to cause a packet to return from a
user-defined chain back to the
“calling” chain.
Often the most difficult part of IP firewall implementation is deciding what you actually want it to do. Do you want to allow outgoing connections freely? Should you allow ICMP packets? What UDP services do you want? What kind of logging do you want to do?
One of the great challenges with building filtering rulesets is that most people aren’t accustomed to thinking in terms of addresses, protocols, and port numbers. Instead, we more often think in terms of applications and end users. To build filtering rulesets, we must be able to translate our higher-level requirements into the low-level detail with which the filtering operates.
You can’t get around the need to understand a bit of
how the services that you are managing with IP filtering actually
work. First and foremost, it is important to know whether a service
uses TCP or UDP, and which port
numbers it uses. The /etc/services
file can
often provide a good deal of what you need to know. For example,
searching for smtp
in this file yields
tcp/25
, which indicates that the
SMTP protocol uses TCP port 25.
Likewise, searching for the DNS returns two
entries, one for udp/53
and another for
tcp/53
; this means that the service uses port 53,
but uses either the TCP or UDP
protocols.
Some protocols, such as FTP, have two related but
different entries in /etc/services
. As described
earlier, FTP uses one port for the command session
(tcp/21
) and another for the data transfer
sessions (tcp/20
). Unfortunately, FTP clients and
servers are free to use different ports for the data transfer
session. Therefore, FTP has been somewhat of a
nuisance for filtering rules. Fortunately,
netfilter provides some assistance with a
feature called connection tracking, along with a
helper module that specifically understands the
FTP service. Because of this it is necessary only
to create a rule for the FTP command session, and
netfilter will automatically track and allow the
data transfer sessions for you. We demonstrate this later in Example 17-2.
If /etc/services
doesn’t
provide enough information, you may need to read the relevant
RFC document that specifies the protocol used by
the service. Usually you don’t need to know much
more about a service other than what protocols and ports it uses,
which is generally easy to find in the RFC.
Filtering rules are stored and used by the kernel in much the same way as routing entries: when the system reboots, IP filtering rules must be reconfigured. To ensure that a firewall configuration is reinstated when a reboot occurs, you should place the appropriate iptables commands in a script file that is automatically executed at system boot time. Bundled with the iptables software package come two programs called iptables-save and iptables-restore that respectively save the current netfilter configuration to a file and restore it from that file. These tools greatly simplify the task of managing firewall configuration.
Each Linux distribution takes a slightly different approach to managing firewall configuration:
First configure your IP filtering rules using the appropriate iptables commands. Then, execute the following command:
/sbin/service iptables save
This causes the filtering rules to be saved to
/etc/sysconfig/iptables
, which is automatically
read at boot time.
Set up your iptables rules as follows:
Edit /etc/default/iptables
and set
enable_iptables_initd=true
.
Manually configure your iptables using iptables commands.
Invoke /etc/init.d/iptables save_active to save the configuration.
At system boot time the saved configuration will be restored automatically.
For a simple, albeit not as flexible, configuration, run yast2 and select the firewall configuration module Security&Users → Firewall. Otherwise:
Edit /etc/sysconfig/SuSEfirewall2
. This file is
thoroughly documented.
If necessary, define custom filter rules in
/etc/sysconfig/scripts/SuSEfirewall2-custom
.
This requires deeper knowledge about how firewalls work on Linux.
Start the firewall by invoking /sbin/SuSEfirewall2
start
.
In this section we’ll provide some simple but useful IP filtering configurations. The aim here is not to provide you with a set of solutions that you accept uncritically. Instead, we’ll introduce you to what a useful set of IP filtering rules looks like and provide you with a skeleton on which you could base your own configurations.
Here we’ll demonstrate the basic use of IP filtering, which is similar to our use of TCP wrappers described earlier in the chapter. Here we want to screen out packets from all hosts on the Internet, except for packets destined for the finger daemon from a small set of hosts. While TCP wrappers can be used to perform the same function, IP filtering can be used to screen many different types of packets (for example, ICMP “ping” packets), and is often necessary to protect services that aren’t managed by TCP wrappers.
Unlike TCP wrappers, iptables rules cannot use hostnames to identify the origin or destination of a packet; you must use IP addresses when specifying rules. This is a good idea, anyway, since reverse hostname lookup is not a completely secure way to identify a packet (it is possible to spoof DNS, making it appear as though some IP address has a different hostname). In Example 17-1 and Example 17-2, we use IP addresses instead of hostnames, which can be obtained using a tool such as nslookup.
Example 17-1. Simple ipchains example
# Load the connection tracking modules if they're not compiled into the # kernel.modprobe ip_conntrack
modprobe ip_conntrack_ftp
# Set default policy on the INPUT chain to DROP.iptables -P INPUT DROP
# ACCEPT packets belonging to an existing connection. # '-A INPUT' is used to append to the INPUT chain. # '-m state' uses the stateful inspection module.iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# ACCEPT all packets that have come from the loopback interface, that # is, from the local host. '-i lo' identifies the loopback interface.iptables -A INPUT -i lo -j ACCEPT
# ACCEPT new incoming connections, and packets belonging to existing # connections, to port 22 (ssh).iptables -A INPUT -m state --state NEW -m tcp -p tcp
--dport 22 -j ACCEPT
# ACCEPT new incoming FTP connections from 192.168.1/24.iptables -A INPUT -m state --state NEW -m tcp -p tcp -s 192.168.1/24
--dport 21 -j ACCEPT
# ACCEPT new incoming FTP connections from spaghetti.vpizza.com, # which has IP address 10.21.2.4.iptables -A INPUT -m state --state NEW -m tcp -p tcp -s 10.21.2.4
--dport 21 -j ACCEPT
# ACCEPT new incoming FTP connections from *.vpizza.com. # They have two networks: 172.18.1.0 and 172.25.3.0.iptables -A INPUT -m state --state NEW -m tcp -p tcp -s 172.18.1/24
--dport 21 -j ACCEPT
iptables -A INPUT -m state --state NEW -m tcp -p tcp -s 172.25.3/24
--dport 21 -j ACCEPT
The ruleset specifically accepts all packets that belong to an
existing connection. This is needed in the case of FTP, in which the
client and server may negotiate an alternate port for the data
transfer connection. The connection tracking module (specified with
-m state
in the rules) ensures that the data
transfer connection can be accepted.
The previous example demonstrated IP filtering on a single host. In
this section, we deal with the case where a network of machines (such
as all the machines in a home or small office) are connected to the
Internet through a gateway machine. We can write
netfilter rules to filter the traffic between
the Internet and the internal network. In this case, we place rules
on both the INPUT
and FORWARD
chains. Recall that INPUT
is used to filter
incoming packets destined for this host, while
FORWARD
is used for packets being forwarded by the
gateway (i.e., packets destined for the internal network or the
Internet). Here, we assume that the gateway machine uses the
ppp0
interface to communicate with the Internet.
Example 17-2. Using netfilter to protect an IP network
# Load the connection tracking modules if they're not compiled into the # kernel.modprobe ip_conntrack
modprobe ip_conntrack_ftp
# Set default policy on INPUT and FORWARD chains to DROP.iptables -P INPUT DROP
iptables -P FORWARD DROP
# ACCEPT all packets from the loopback interface.iptables -A INPUT -i lo -j ACCEPT
# Create a new user-defined chain. This chain will contain rules # relevant to both INPUT and FORWARD, so by grouping them together on # a single chain we avoid stating the rules twice.iptables -N allowfwdin
# ACCEPT packets belonging to an existing connection. # Note that this rule (and subsequent rules) are placed # on the user-defined chain.iptables -A allowfwdin -m state --state ESTABLISHED,RELATED -j ACCEPT
# ACCEPT new connection requests from machines on the internal network. # This allows machines on the internal network to establish connections # to the Internet, but not the other way around. Note the use of # '-i ! ppp0' to specify packets coming from interfaces other than ppp0.iptables -A allowfwdin -m state --state NEW -i ! ppp0 -j ACCEPT
# ACCEPT new incoming connections to port 22 (ssh).iptables -A allowfwdin -m state --state NEW -m tcp -p tcp
--dport 22 -j ACCEPT
# ACCEPT new incoming FTP connections from 192.168.1/24.iptables -A allowfwdin -m state --state NEW -m tcp -p tcp -s 192.168.1/24
--dport 21 -j ACCEPT
# ACCEPT new incoming FTP connections from spaghetti.vpizza.com.iptables -A allowfwdin -m state --state NEW -m tcp -p tcp -s 10.21.2.4
--dport 21 -j ACCEPT
# ACCEPT new incoming FTP connections from *.vpizza.com.iptables -A allowfwdin -m state --state NEW -m tcp -p tcp -s 172.18.1/24
--dport 21 -j ACCEPT
iptables -A allowfwdin -m state --state NEW -m tcp -p tcp -s 172.25.3/24
fs
# Any packets that have passed through the user-defined chain are now # subject to the action LOG, which causes them to be logged. # Use the 'limit' module to prevent logging blocked packets too # rapidly.iptables -A allowfwdin -m limit --limit 2/sec -j LOG
# Set default action on the user-defined chain to DROP.iptables -A allowfwdin -j DROP
# Direct all packets received for INPUT or FORWARD to our user-defined chain.iptables -A INPUT -j allowfwdin
iptables -A FORWARD -j allowfwdin
# Enable IP routing (required by all IP routers, regardless of the use # of IP filtering).echo 1 >/proc/sys/net/ipv4/ip_forward
To keep track of any attempts to breach security,
we’ve added a rule that will log any packets that
would be dropped. However, if a large number of bad packets were to
arrive, this rule might fill up the disk with log entries, or slow
down the gateway to a crawl (as it takes much longer to log packets
than it does to forward or filter them). So, we use the
limit
module which controls the rate at which a
rule action is taken. In the preceding example, we allowed an average
rate of two bad packets per second to be logged. All other packets
will pass through the rule and simply be dropped.
To view the rules that have been configured (see Example 17-3), use the iptables list
option -L
. Using the verbose mode
(-v
) displays more information than the basic output
of the command.
Example 17-3. Listing iptables rulesets for Example 17-2
# iptables -L -v
Chain INPUT (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
16 1328 ACCEPT all -- lo any anywhere anywhere
0 0 allowfwdin all -- any any anywhere anywhere
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 allowfwdin all -- any any anywhere anywhere
Chain OUTPUT (policy ACCEPT 9756 packets, 819K bytes)
pkts bytes target prot opt in out source destination
Chain allowfwdin (2 references)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- any any anywhere anywhere
state RELATED,ESTABLISHED
0 0 ACCEPT all -- !ppp0 any anywhere anywhere
state NEW
0 0 ACCEPT tcp -- any any anywhere anywhere
state NEW tcp dpt:ssh
0 0 ACCEPT tcp -- any any 192.168.0.0/24 anywhere
state NEW tcp dpt:ftp
0 0 ACCEPT tcp -- any any 10.21.2.4 anywhere
state NEW tcp dpt:ftp
0 0 ACCEPT tcp -- any any 172.18.0.0/24 anywhere
state NEW tcp dpt:ftp
0 0 ACCEPT tcp -- any any 172.25.0.0/24 anywhere
state NEW tcp dpt:ftp
0 0 LOG all -- any any anywhere anywhere
limit: avg 2/sec burst 5 LOG level warning
0 0 DROP all -- any any anywhere anywhere
netfilter rules can also be used to implement IP masquerading, a specific type of NAT that rewrites packets from an internal network to make them appear as though they are originating from a single IP address. This is often used in cases where one has a number of machines connected to a LAN, with a single Internet-connected machine with one IP address. This is a common situation in home networks where the ISP has allocated a single IP address; using IP masquerading, however, an entire network of machines can share the address. By having the gateway perform IP masquerading, packets from the internal LAN will appear as though they are originating from the gateway machine, and packets from the Internet will be forwarded back to the appropriate host on the internal LAN. You can accomplish all of this with a bit of clever packet rewriting using netfilter.
Configuring netfilter to support IP masquerading is much simpler than explaining how it works! More complete information about how IP masquerading and NAT are accomplished is provided in the NAT HOWTO. We’ll show the most basic configuration in Example 17-4.
In this configuration we’ve assumed that we have a
Linux system that will act as a gateway for an internal network. The
gateway has a PPP connection to the Internet on interface
ppp0
, and a LAN connection to the internal network
on interface eth0
. This configuration allows
outgoing connections from the internal network to the Internet, but
will block incoming connections from the Internet to machines on the
internal network except for the gateway. As it turns out, we
don’t need to provide explicit commands to achieve
this, as it is the default behavior when using NAT
in this fashion.
Example 17-4. Basic IP masquerade configuration
# Load the module supporting NAT, if not compiled into the kernel.modprobe iptables_nat
# Masquerade any routed connections supported by the ppp0 device.iptables -t nat -A POSTROUTING -p ppp0 -j MASQUERADE
# Enable IP routing.echo 1 >/proc/sys/net/ipv4/ip_forward
There are some important details to note in this configuration. The
NAT functionality is provided in a module of its
own, which must be loaded unless it is built into your kernel. The
NAT module uses a new chain called
POSTROUTING
that processes packets after the
kernel performs routing operations on them (that is, decides whether
the packets are destined for the Internet or for internal LAN
machines). The MASQUERADE
target does the hard
work of the address translation and tracking.
Note that this configuration provides no filtering of outgoing connections. All hosts on the private network will be able to establish outgoing connections to any host and any port. The packet filtering HOWTO provides useful information about how to combine IP filtering with address translation.