26.4. Firewalls: Filtering IP Packets
Although 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 whether 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 have been 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 than earlier filters, but is extensible as well. In the following sections we describe netfilter and some simple configurations as examples.
26.4.1. netfilter Basics
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 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 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 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 as follows:
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 on to 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 the following:
18.104.22.168. Using the iptables command
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 only 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 26-1 summarizes the iptables arguments that operate on chains, and Table 26-2 summarizes the iptables arguments that operate on individual rules.
Each filtering rule includes parameters that describe which packets match the rule. The most common rule parameters are summarized in Table 26-3. Using an exclamation point (!) before a parameter inverts it. For example, the parameter -dport 80 means "match destination port 80," whereas the parameter -dport ! 80 means "match any destination port except 80."
A number of important options are used when building rulesets , summarized in Table 26-4.
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 will be 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 following syntax:
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 the following:
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.
26.4.2. Developing IP Filtering Rulesets
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 of 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 about 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 both the TCP and 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 the chapter in Example 26-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.
26.4.3. IP Filter Management and Script Files
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 are 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:
26.4.4. Sample netfilter Configurations
In this section we 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 introduce you to what a useful set of IP filtering rules looks like and provide you with a skeleton on which you can base your own configurations.
22.214.171.124. Simple IP filtering example
Here we 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. Although 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 an IP address has a different hostname). In Examples 26-1 and 26-2, we use IP addresses instead of hostnames, which can be obtained using a tool such as host.
Example 26-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.
126.96.36.199. IP filtering to protect an entire network
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 Example 26-2, we place rules on both the INPUT and FORWARD chains. Recall that INPUT is used to filter incoming packets destined for this host, whereas 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 26-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 26-3), use the iptables list option -L. Using the verbose mode (-v) displays more information than the basic output of the command.
Example 26-3. Listing iptables rulesets for Example 26-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
188.8.131.52. IP masquerading example
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 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 26-4.
Example 26-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
In this configuration we 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.
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 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.