The information that a computer exchanges with other computers on the internet travels in units called packets. The packets contain the data intended to be communicated between two computers, as well as a header. The header contains information about the packet (e.g. the size of the packet, various flags, etc.), and routing information (such as the source and the destination). The usual analogy is with a postal letter: if the letter itself is the data, the envelope (containing the "TO:" and the "FROM:" fields) is the header. Again, the packet header contains other information as well, and the exact fields in the header depend on the protocol (such as tcp, udp or icmp). We will be interested mainly in the source address/port and the destination address/port fields, the protocol of the packet, as well as some packet flags.
A firewall is a list of rules used to decide the fate of the packets that come into or leave a computer, according to certain criteria, or parameters. For example, we may want to allow all outgoing traffic, but we may want to drop all incoming packets, except perhaps the secure shell packets, email packets and http packets. At this point in Linux history, there are two mechanisms to implement a firewall: ipchains and iptables. Ipchains was the sole method until relatively recently, and for the average needs of the average home user, it may do just fine. It is aging slowly, passing on the responsibility to the newer, preferred method - iptables. Iptables is not just an improvement over ipchains. It was re-designed from scratch, to allow for far more flexible firewalls, while trying to preserve the ipchains syntax for user level command tools. So for the user familiar with ipchains, the transition to iptables should be fairly smooth. Familiarity with ipchains will not be required in this tutorial though.
Ipchains has three chains (INPUT, OUTPUT and FORWARD), each with its own set of rules. A packet travels along one of these chains and it is being matched against each of the rules, from top to bottom. If the packet matches one of the rules, then the action specified by that rule is applied (e.g. in ipchains, ACCEPT the packet, or DENY it, etc.). Certainly, there must be a default rule (the policy) in each chain, in case the packet does not match any of the explicit rules. In ipchains, there are 6 built-in actions (targets): ACCEPT, DENY, REJECT, MASQ, REDIRECT, RETURN).
By contrast, iptables has 3 tables now, each with its own chains. Specifically, in iptables the tables are
Note 1: This tutorial only covers how to write FILTER rules for the INPUT chain. Writing rules for the OUTPUT chain is similar, and unless you want to restrict your own users access to certain sites, for starters you can just set the OUTPUT policy to ACCEPT. The intention is only to provide a starting point in creating a firewall. Subsequently, you can make your firewall arbitrarily tight.
Note 2: This tutorial now covers very briefly the essentials of MASQUERADING, useful in the simplest of cases, i.e. when a home user has a single IP address from the provider, and wants his home lan computers to be able to connect transparently to the internet. We do not explain other NAT uses, such as destination nat, or transparent proxies. We do provide pointers to references to these topics though.
On to examples. You probably do not have an iptables firewall running. You will have to build the rules first. So you will probably not be able to run the first examples right now. Nevertheless, I do have a firewall up and running, so we shall take a look at some rules that I have in it. In what follows,
1) root:~>
is my prompt, configured to my liking. It is not part of the command. It shows the number of the command, who I am (root) and the directory I'm in.Root creates and manages the rules. The command for this is /sbin/iptables. Various flags will do various things. To see (list) the rules we already have in the firewall, run /sbin/iptables -L:
4) root:~> /sbin/iptables -L # list rules
5) root:~> /sbin/iptables -L -n # list in numeric format
6) root:~> /sbin/iptables -L --line-numbers # show also the rule number
Here's what an empty firewall looks like:
7) root:~> /sbin/iptables -L 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
All 3 chains in the default table (FILTER) are listed, but contain no rules. For each channel, the default rule (policy) is set to ACCEPT. Let
us now see a firewall with some contents.
10) root:~> /sbin/iptables -L -n --line-numbers Chain INPUT (policy DROP) num target prot opt source destination 1 LOG tcp -- 0.0.0.0/0 0.0.0.0/0 tcp flags:!0x16/0x02 state NEW LOG flags 0 level 4 prefix `NEW NOT SYN: ' 2 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp flags:!0x16/0x02 state NEW 3 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:1214 4 DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:1214 5 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:67 dpt:68 6 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHEDThese are only the very first few rules listed in numeric format, in the INPUT chain (of the default table - FILTER). There are more rules for the INPUT chain, as well as rules for the FORWARD and OUTPUT chains, and possibly rules for the other tables NAT and MANGLE, but they are not listed, for brevity.
We can now explain the "anatomy" of a rule. The first column is obviously the rule number. Upon arrival of a packet, the rules will be examined in the order in the "num" column. The "target" column represents the action that will be taken against a packet that matches a rule. We see here the DROP and ACCEPT targets (these are built-in) but for the first rule, we have a LOG target, which logs the packets to a system log file (/var/log/messages).
The remaining columns show the parameters used by each rule to match a packet. These criteria may include the protocol, various options, the source and the target address, the destination port (e.g. dpt:1214, dpt:68), the source port (e.g. spt:67), as well as the STATE of the packet. Yes, iptables is statefull, i.e. it can keep track of packets according to their state. For instance, if a packet is part of an already established connection (e.g. an ftp transfer), then it is in state ESTABLISHED.
Let us explain the first rule.
1 LOG tcp -- 0.0.0.0/0 0.0.0.0/0 tcp flags:!0x16/0x02 state NEW LOG flags 0 level 4 prefix `NEW NOT SYN: 'All tcp packets, coming from anywhere (source=0.0.0.0/0) addressed anywhere (destination=0.0.0.0/0) which are in state NEW, but have/do not have certain flags set (SYN, ACK/SYN, RST) will be LOGged. In fact, we would like to drop these packets, and we will shortly (rule number 2), but before we drop them, we want to log. The packets matching rule (1) will be logged using a string ("NEW NOT SYN"). This way we can grep after this string in the log file. The second rule simply drops the packets logged by the first rule.
3 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:1214DROPs all tcp packets, from anywhere to anywhere, addressed to port 1214 (on the destination machine, obviously). Rule 4 is similar to 3, except that we drop the udp packets. Why this rule? To get rid of the Kazaa people looking for music around the globe. Notice that the default policy in the INPUT chain is set to DROP, so why do we need an explicit rule to drop the port 1214 packets? It's all part of a "grand scheme" which will be explained after the examples. For now just try to understand the structure of a rule.
5 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:67 dpt:68It ACCEPTs all udp packets from (source) port 67 on any machine to port 68 (on any machine). These are the dhcp packets. At boot, when the connection is negociated, we do not know the IP address of the dhcp server, nor do we know our own IP address. That's why we must set both the source and the destination address to 0.0.0.0/0 (any). If we knew the IP address of the dhcp server and we were certain it never changes, then we could be specific and use that address as the source address (instead of "any").
6 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHEDAll packets (in any protocol, from anywhere to anywhere) which are in state RELATED or ESTABLISHED will be allowed to come through. Why? Without this rule, the firewall will drop anything in sight, heading towards our computer. When we initiate a connection (e.g. telnet work.com, or ftp some.ftp.site.org or open a URL) we usually expect a reply to our request. The remote site will send us replies (e.g. a login prompt, or a web page), but the firewall will drop them, if it were not for this rule.
Do not worry too much why we need these particular rules. Each computer will require a different security policy, so the administrator will end up writing up his own set of rules. For now the goal is to understand what a firewall is, and what rules look like.
This section explains how we create the rules and insert them in the firewall. It is the core of building a firewall. The command for this is still /sbin/iptables, with the flags -A or -I, depending if we want to append a rule to whatever we already have, or if we want to insert a rule at a specific point in the firewall.
Here is the precise command used to put the first rule in the firewall:
4) root:~> /sbin/iptables -A INPUT -p tcp -m tcp ! --tcp-flags SYN,RST,ACK SYN -m state --state NEW -j LOG --log-prefix "NEW NOT SYN: "Here -A INPUT means the obvious: we want to append a rule to the INPUT chain (in the default table FILTER). The part
-p tcp... --state NEWis the parameters part: the rule will affect the (a) tcp packets that (b) do not have the SYN,RST,ACK, SYN flags set (! --tcp-flags) and (c) are in state NEW. The action taken on these packets is -j LOG (jump to target LOG), whose effect is to log the packet in /var/log/messages. Finally, in order to be able to grep after these packets, we use a log preffix - the string "NEW NOT SYN". We are not specifying any particular source address/port, nor a destination/port, so these will be set to "any". If this computer firewalls all traffic to an entire LAN, then this rule will be applied to packets intended for any of the local machines.
5) root:~> /sbin/iptables -A INPUT -p tcp -m tcp --dport 1214 -j DROPHere we append to the INPUT chain the rule that simply DROPs the tcp packets whose destination port is 1214 (regardless of the source address/port, destination address, flags, state, etc.). Again, no source/destination addresses, so no machine on the local network (if any) will receive these packets.
Suppose now that we already have a list of rules in the firewall. Suppose also, that for some reason, we want to place one extra rule, somewhere in the middle of the list, just before rule X. The append command will only add rules to the end of the list. We already mentioned that to insert, we use -I rather than -A. For the sake of example, let us assume again we have the first 6 rules listed above with -L --line-numbers. Suppose also that we want to insert just before rule (3) a new rule. To keep things simple, assume we want the new rule to drop all tcp packets coming from the following address: 207.46.249.190 (nothing personal, just an example). Before insertion:
17) root:~> /sbin/iptables -L -n --line-numbers Chain INPUT (policy DROP) num target prot opt source destination 1 LOG tcp -- 0.0.0.0/0 0.0.0.0/0 tcp flags:!0x16/0x02 state NEW LOG flags 0 level 4 prefix `NEW NOT SYN: ' 2 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp flags:!0x16/0x02 state NEW 3 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:1214 4 DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:1214 5 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:67 dpt:68 6 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHEDInsertion proper:
18) root:~> /sbin/iptables -I INPUT 3 -p tcp -m tcp -s 207.46.249.190 -j DROPNow, if we list again:
19) root:~> /sbin/iptables -L -n --line-numbers Chain INPUT (policy DROP) num target prot opt source destination 1 LOG tcp -- 0.0.0.0/0 0.0.0.0/0 tcp flags:!0x16/0x02 state NEW LOG flags 0 level 4 prefix `NEW NOT SYN: ' 2 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp flags:!0x16/0x02 state NEW 3 DROP tcp -- 207.46.249.190 0.0.0.0/0 tcp 4 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:1214 5 DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:1214 6 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 7 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp spt:67 dpt:68Observe how the new rule has been inserted in the 3rd place, and the remaining rules have been pushed down one place. If we change our mind and we decide that we actually don't need this rule (or any rule, for that matter), we can remove it with the -R flag:
20) root:~> /sbin/iptables -R INPUT 3It's as simple as that. Just specify that we want to remove (-R) rule number (3) from the INPUT chain (in the default table FILTER).
Now that we have a few examples of how to append, insert and remove rules dynamically into the firewall, we can move on to actually building a viable firewall, to satisfy the security policy appropriate for our network.
The ability to insert rules dynamically into iptables comes in handy in case of an emergency, and in building the firewall, but it won't take us too far without a thorough consideration of the design of the firewall and without a sound security policy. Ad-hoc insertion/removal or rules, often leads to the inability to connect to the internet, or to excessive logging, or, worst of all, an insecure firewall.
We must have the correct mindset to build a firewall. If you were to build yourself a house and then wanted to protect it with fence, you wouldn't build the fence just in front of the front door, would you? You would want to surround the entire house with fence, and only allow your guests to come in one by one through a designated place that you control. Plain common sense. The most common mistake in designing the firewall is to set the policy to ACCEPT then try to drop the unwanted packets one by one. All firewalls that do that are plain stupid and unsafe. It's just too many packets to drop individually, and, more importantly, we cannot possibly anticipate all possible malitious packets that will hit us.
The correct logic is this: drop everyting by default, then punch holes into the firewall as needed, to let the "good" packets come through. Assume that all packets are bad, and single out the good ones, not viceversa. I cannot empahsize this enough. The DROP policy in the INPUT chain acts as a safety net.
Here is the logical structure of our INPUT chain.| ACCEPT/DROP packets that we do not want to log. | ACCEPT packets in state ESTABLISHED, RELATED | /sbin/iptables -A INPUT -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT |
| ACCEPT udp packets on port 68 (dhcp) | /sbin/iptables -A INPUT -p udp -m udp --sport 67 --dport 68 -j ACCEPT | |
| ACCEPT all traffic on the lo interface | /sbin/iptables -A INPUT -i lo -j ACCEPT | |
| DROP harmless but frequent and annoying probes on various ports (e.g. 1214 - kazaa) |
/sbin/iptables -A INPUT -p tcp -m tcp --dport 1214 -j DROP /sbin/iptables -A INPUT -p udp -m udp --dport 1214 -j DROP |
|
| Coarse logging: everything at and below this point will be logged. | LOW TCP CONNECTION | /sbin/iptables -A INPUT -p tcp -m tcp --dport 0:1023 -m state --state NEW -j LOG --log-prefix "LOW PORT TCP CONNECTION: " |
| LOW UDP CONNECTION | /sbin/iptables -A INPUT -p udp -m state --state NEW -m udp --dport 0:1023 -j LOG --log-prefix "LOW PORT UDP CONNECTION: " | |
| HIGH TCP CONNECTION | /sbin/iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 1024:65535 -j LOG --log-prefix "HIGH PORT UDP CONNECTION: " | |
| HIGH UDP CONNECTION | /sbin/iptables -A INPUT -p udp -m state --state NEW -m udp --dport 1024:65535 -j LOG --log-prefix "HIGH PORT UDP CONNECTION:" | |
| NEW NOT SYN | /sbin/iptables -A INPUT -p tcp -m tcp ! --tcp-flags SYN,RST,ACK SYN -m state --state NEW -j LOG --log-prefix "NEW NOT SYN: " | |
| ECHO | /sbin/iptables -A INPUT -p icmp -j LOG --log-prefix "ECHO: " | |
| Punch holes. These are below the LOG section, so they will be logged. | ACCEPT ssh | /sbin/iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT |
| ACCEPT ntp | /sbin/iptables -A INPUT -p udp -m udp --sport 123 -j ACCEPT | |
| ACCEPT ping from a designated address only | /sbin/iptables -A INPUT -s 35.9.20.20 -p icmp -j ACCEPT | |
| Policy | DROP | /sbin/iptables -P INPUT DROP |
The foundation of our firewall (again, only the INPUT chain in the FILTER table) is the red block: policy is set to DROP. We build on top of that (although the actual insertion of the rules can take place in a top-down manner, as it is indicated by the -A INPUT in the rightmost column). After we've set the policy to DROP, we punch holes to allow useful packets, such as ssh, ntp, http, smtp, ftp, and whatever the needs of your particular site require. Anything above the yellow block (logging) will not be logged. Anything below, will. All this is straightforward. We detail logging in a separate section.
Logging is not mandatory for the proper operation of the firewall, but it is useful. There are obviously two extremes: log nothing, or log everything. If we log everything (or nearly everything), we must decide on the granularity of the logging. Specifically, the logs can be coarse, i.e. we can log irrespective of the nature of the packet (e.g. the same log prefix, or no log prefix at all for all packets), or fine: a special log prefix for each type of packet. In the latter case, we would have to accompany each "real" rule in the firewall, with a log rule, with a special log prefix. This way, we would be able to figure out in the logs, which rule was responsible for dropping/accepting a packet. As an aside, ipchains had builtin the capability to report in the logs, the rule number of the particular rule that accepted/dropped a packet. That was a cheap and effective way to see what each rule did. We can still achieve this with iptables, but it requires a little more care.
Our approach is to try to find a compromise between these extremes. We will log nearly everything. Exceptions are packets that occur with high frequency. Candidates are in the first place, packets in state ESTABLISHED and RELATED. This is a must. These must be ACCEPTed without logging. If we log these, we will flood the log files each time we open a web page, transfer a file, etc. Additionally, we can DROP without logging known, harmless probes (e.g. on port 1214 - kazaa). We can also accept without logging the dhcp packets, or the ntp packets. These are numerous enough to clutter the logs. To make the long story short, there are a number of packets that should not be logged due to their large numbers, whether or not we ACCEPT or DROP them.
Once we've decided what packets to exclude from logging, we must decide how fine the logging should be. Should we write a log rule for each real rule? Or perhaps a more coarse categorization would be more appropriate? Our approach is shown in the yellow block in the above table. We divide the ports into low ports (1-1023) and high ports (1024-65335), with appropriate prefixes. With a single grep then we can see, for instance, all tcp attempts on the sensitive, privileged ports ( under 1023). A little script (iptlog) provided below using gawk can then provide a very nice summary of what's going on. We also have special log rules for icmp packets and for the new not syn packets. Chances are these packets are not exactly innocent, so we want to be able to see them quickly.
Referring again to the above table, what happens above and below the logging section (the yellow block)? A packet that we do not want to log, must be either ACCEPTed or DROPped BEFORE it reaches the LOG block. Hence, the place for the rules for packets not to be logged is above the LOG block (the purple block). On the other hand, anything that makes it to the LOG section, will make it below it as well (the green block). This is because LOG on its own, does not accept or drop packets. If a packet "hits" a log rule, it will continue to move down the rules list. Thus, if we allow, for instance, ssh connections (port 22), if we put the corresponding ACCEPT rule below the yellow block, the ssh packets will be allowed to come in, but they've just been logged. If we didn't want to log ssh connections, we would put the ACCEPT rule above the yellow block.
Here is a sample output of the iptlog) script, which greps in /var/log/messages after the log prefixes and summarizes the results:
1) root:~> iptlog LOW PORT TCP: Dec 1 09:46:32 211.110.39.96 3171 my.att.add.ress 445 Dec 1 09:46:35 211.110.39.96 3171 my.att.add.ress 445 Dec 1 09:46:41 211.110.39.96 3171 my.att.add.ress 445 Dec 1 18:28:43 12.245.219.154 4580 my.att.add.ress 80 Dec 1 18:28:46 12.245.219.154 4580 my.att.add.ress 80 Dec 1 19:21:13 12.84.7.138 1996 my.att.add.ress 80 Dec 1 19:21:16 12.84.7.138 1996 my.att.add.ress 80 Dec 1 20:36:18 12.245.219.154 3009 my.att.add.ress 80 Dec 1 20:36:21 12.245.219.154 3009 my.att.add.ress 80 Dec 1 21:28:22 12.245.219.154 4169 my.att.add.ress 80 Dec 2 19:33:59 12.238.121.87 4023 my.att.add.ress 80 Dec 2 19:34:03 12.238.121.87 4023 my.att.add.ress 80 LOW PORT UDP: HIGH PORT TCP: Dec 1 07:16:15 80.178.106.143 1751 my.att.add.ress 1433 Dec 1 07:16:18 80.178.106.143 1751 my.att.add.ress 1433 HIGH PORT UDP: Dec 2 16:38:17 204.127.198.4 53 my.att.add.ress 32845 Dec 2 16:38:34 63.240.76.4 53 my.att.add.ress 32845 Dec 2 16:39:08 204.127.198.4 53 my.att.add.ress 32846 ECHO: Dec 2 07:52:05 195.101.216.197 my.att.add.ress Dec 2 07:52:07 195.101.216.197 my.att.add.ress Dec 2 07:52:08 195.101.216.197 my.att.add.ress NEW NOT SYN: Dec 1 10:16:21 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:16:22 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:16:24 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:16:28 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:16:36 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:16:52 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:21:49 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:22:53 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:23:57 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:25:01 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:26:05 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:27:09 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:28:13 137.208.16.37 80 my.att.add.ress 40129 Dec 1 10:29:17 137.208.16.37 80 my.att.add.ress 40129There are a number of probes on my http port (port 80) - nothing new. The NEW NOT SYN probes originating from port 80 addressed to some high port (40129) on my machine I find worrisome (only in principle worrisome, they've been dropped by the firewall).
2) root:~> /etc/rc.d/init.d/ipchains stop
3) root:~> /etc/rc.d/init.d/iptables stop (now we have no firewall at all)
4) root:~> cp /etc/sysconfig/iptables /etc/sysconfig/iptables.old (backup any previous iptables rules table, if any).
Insert the rules into the firewall dynamically, as discussed above, using /sbin/iptables -A INPUT. Then save the rules:
20) root:~> /sbin/iptables-save > rules_file
Let us now see how to load the rules. After we've saved the rules in the "rules_file", the rules are still loaded (check with /sbin/iptables -L). Unload them first, then load them with iptables-restore
21) root:~> /etc/rc.d/init.d/iptables stop (turn off iptables) 22) root:~> /sbin/iptables-restore < rules_file (the rules file we saved above)
When we're satisfied with the rules we have inserted/saved, we can store them in a more permanent file: /etc/sysconfig/iptables (back up any old version of this file first). The boot script that starts iptables is /etc/rc.d/init.d/iptables. It checks if the rules file /etc/sysconfig/iptables exists, and if it does, it loads some kernel iptables modules, then loads the rules file. Fairly straightforward.
So, if we have stored our rules as /etc/sysconfig/iptables, we can
30) root:~> /etc/rc.d/init.d/iptables start 30) root:~> /etc/rc.d/init.d/iptables stopat will. These commands will of course be run at boot/shutdown.
Masquerading the source address (SNAT) is part of what the nat table can do. The destination address can also be masqueraded (DNAT). This is in general, not necessary for the simplest needs of a home user.
Assuming that the external interface is eth0 and the internal interface is eth1, the commands used to turn on masquerading are as follows:21) root:~> /sbin/iptables -P FORWARD ACCEPT
22) root:~>/sbin/iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADECommand 21 sets the policy in the FORWARD chain (in the FILTER table) to ACCEPT. Command 22) appends (-A) to the POSTROUTING chain in the nat table (-t nat) the rule that ALL outbound packets that are about to be put on eth0, must be first masqueraded. This means in particular that if there are more than one LAN (e.g. 192.168.1.0/24 and 192.168.2.0/24), all LANs will be masqueraded: the only criterion is -o eth0. If only, say, 192.168.1.0/24 must be masqueraded, then specify the source, i.e. replace "-o eth0" with "-s 192.168.1.0/24" in command (22). Various other combinations are possible, including specifying source/destination port, state, protocol, etc. MASQUERADE is used when the IP of the external interface eth0 is dynamic (if ppp, the external interface is ppp0). If the external interface has a static IP address (more uncommon for an average home user), then instead of -j MASQUERADE use -j SNAT (i.e. source nat). MASQUERADE itself is a special type of SNAT. For instance,
22) root:~>/sbin/iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -j SNAT --to your.static.ip
Needless to say, one can masquerade an individual machine, not necessarily an entire range. Just specify the address of that particular machine.
Last, for all this to work, the ipt_MASQUERADE and iptable_nat kernel modules must be loaded into the kernel (modprobe iptable_nat). Also, forwarding must be turned on in the kernel (echo 1 > /proc/sys/net/ipv4/ip_forward). Modern distributions already run these commands from startup scripts, but if they don't, put them e.g. in /etc/rc.d/init.d/iptables. If this is the case, there are probably other iptables initializations necessary, so you may want to make a little iptables.init script (e.g. in /usr/sbin) and call this entire script from /etc/rc.d/init.d/iptables).