Let's build a few filter rules. As background, we'll use the following IP addresses:
The rules we want to build must accomplish the following:
When we load ip_tables and all the other modules, our default rules look like this:
Chain INPUT (policy ACCEPT) target prot opt source destination Chain FORWARD (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination
Let's take the rules in order:
The first rule is to permit all internal connections to go out. Basically, this has been accomplished if forwarding is turned on in the kernel (echo 1 > /proc/sys/net/ipv4/ip_forward). Our FORWARD chain policy is ACCEPT, so all connections will pass. Do not just drop all incoming packets on the external interface, or systems on the inside won't be able to surf the Web because TCP connections will not be established.
If you want to really stop all except external access from internal systems by changing the FORWARD chain policy to drop, then you'll need to use rules such as these:
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A FORWARD -m state --state NEW -i ! eth1 -j ACCEPT
These two rules can also be applied to the INPUT chain and require the ip_conntrack and ip_conntrack_ftp modules (unless you want to prohibit all but passive FTPthen you can drop the second module).
The difference between the two previous approaches is that with the second, only internally originating traffic can pass, while with the first, if it's not later prohibited, all traffic, originating either internally or externally, can pass.
How you approach these rules depends on your security policy. A security policy of PERMIT all that is not specifically PROHIBITED is reflected in the default chain policy ACCEPT. A security policy of PROHIBIT all that is not specifically PERMITTED is reflected in the default chain policy DROP. But then for anything you want to enter that originates from the outside, you must have a rule to ACCEPT.
To drop pings originating on the Internet, the easiest way is just to drop them at the incoming interface. Because the internal network is using public IPs, we'll need to block both the INPUT and the FORWARD chains:
iptables -t filter -A INPUT -i eth1 --protocol icmp --icmp-type echo-request -j DROP iptables -t filter -A FORWARD -i eth1 --protocol icmp --icmp-type echo-request -j DROP
If you have a lot of rules that will double up on both the INPUT and the FORWARD chains, it might be easier to create a user-defined chain, put all the common rules in it, and then have it called from each of the INPUT and FORWARD chains. You can create a user-defined chain like this:
iptables -t filter -N droprules
Now just use -A droprules in place of -A INPUT in the previous rules, and then put a line in each of FORWARD and INPUT chains like this:
iptables -t filter -A INPUT -j droprules
You can create a user-defined chain for logging and other purposes.
Important point: Once a match is had for a chain, that terminates the chain. That prevents a more generic rule from firing when a more specific rule has matched. That also brings up the point of ordering your rules. Order is important. Put your more specific rules first.
I'd also like to point out that dropping pings to hide your systems is like trying to hide an elephant behind a post card. Okay, so you can't see a 5-inch-by-7-inch area of the elephant, but there's no missing that it's still there. Some scanning software sends TCP pings, which are ACK packets. Use the ipt_unclean module to filter unrelated and unrequested response packets.
Now we want to drop all new connections coming in from the outside. New connections (here we're obviously talking TCP connections) are initiated by a packet with the SYN bit set and the ACK and RST bits unset. So, let's look at two different but equivalent ways to do this:
iptables -t filter -A FORWARD -i eth1 --protocol tcp --tcp-flags SYN,ACK,RST SYN -j DROP iptables -t filter -A FORWARD -i eth1 --protocol tcp --syn -j DROP
Looking at the first line, you should recognize everything up to the --tcp-flags. The --tcp-flags takes two arguments, the first a comma-separated list of the bits to check, followed by a comma-separated list of the only ones permitted to be set for a match (the others must be unset). So, this line says, look at the SYN, the ACK, and the RST bits, and of these three, only the SYN bit can be set (all other tcp-flags are ignored). Any match is dropped. The tcp-flag SYN bit set and ACK and RST unset is the definition of a SYN packet, a packet used to initiate a TCP connection.
The second line is identical. But here, you have a shorthand for --tcp-flags SYN,ACK,RST SYN with the --syn extended match option. This is easier to remember (and write without error).
The final rule we want to implement is a little trickier. This one we need to come at from behind. We have three interfaces: eth0, eth1, and lo. We don't want anyone outside (coming in from any external interface, those being eth0 and eth1) talking to this host, but the host almost always requires the ability to talk to itself. So, we'll block all but the interface lo:
iptables -t filter -A INPUT -i ! lo -j DROP
Realistically, you probably won't ever do this. Or, you'll want a rule or two to permit connections such as SSH (port 22) before this one so that you can remotely administer the server. So, you may need this rule:
iptables -t filter -I INPUT 1 --protocol tcp --dport 22 -j ACCEPT
This rule will be inserted before rule 1. If you don't specify the rule number, iptables assumes 1.
That's enough for now. Next, we'll look at the nat table.