If your ISP gives you a block of public IPs (even just a /29), you can use 1:1 NAT to map each public address directly to an internal host. This is cleaner than port forwarding when you have multiple servers that all need to listen on port 80, 443, or 25. Here is how to set it up on a Linksys WRT54G running DD-WRT.
Why 1:1 NAT
Standard NAPT (what most people call "NAT") translates all internal addresses to a single public IP, using port numbers to track connections. This works fine for outbound traffic and simple port forwards, but it breaks down when you need multiple internal hosts to receive traffic on the same port from the outside.
With 1:1 NAT, you assign a dedicated public IP to each internal host. All traffic to 82.x.x.2 goes to 192.168.1.10, all traffic to 82.x.x.3 goes to 192.168.1.20, and so on. The internal host behaves as though it has the public IP directly, but it is still behind your firewall.
Prerequisites
You need a WRT54G (v1 through v4, or a WRT54GL) running DD-WRT v23 SP2 or later. The micro build will not work - you need at least the standard build for the iptables modules required. You also need a block of public IPs from your ISP, routed to your WAN interface.
My setup: ISP provides 82.x.x.0/29, giving me 82.x.x.1 through 82.x.x.6 as usable addresses. The WAN interface has 82.x.x.1. I want to map 82.x.x.2 to my web server at 192.168.1.10, and 82.x.x.3 to my mail server at 192.168.1.20.
Adding the public IPs
First, add the extra public IPs as aliases on the WAN interface. In DD-WRT, go to Administration - Commands and paste:
ifconfig vlan1:1 82.x.x.2 netmask 255.255.255.248
ifconfig vlan1:2 82.x.x.3 netmask 255.255.255.248
Save this as a Startup script. Note: on some DD-WRT versions the WAN interface is vlan1, on others it is eth1. Check with ifconfig on the command line first.
iptables rules for 1:1 NAT
Now the actual NAT rules. Add these to the Firewall script in Administration - Commands:
#!/bin/sh
WAN="vlan1"
# 1:1 NAT for web server
# Inbound: rewrite destination from public to private
iptables -t nat -A PREROUTING -i $WAN -d 82.x.x.2 \
-j DNAT --to-destination 192.168.1.10
# Outbound: rewrite source from private to public
iptables -t nat -A POSTROUTING -o $WAN -s 192.168.1.10 \
-j SNAT --to-source 82.x.x.2
# Allow forwarding
iptables -A FORWARD -d 192.168.1.10 -j ACCEPT
# 1:1 NAT for mail server
iptables -t nat -A PREROUTING -i $WAN -d 82.x.x.3 \
-j DNAT --to-destination 192.168.1.20
iptables -t nat -A POSTROUTING -o $WAN -s 192.168.1.20 \
-j SNAT --to-source 82.x.x.3
iptables -A FORWARD -d 192.168.1.20 -j ACCEPT
# Everything else: standard NAPT (masquerade)
# This catches all LAN traffic that is not from a 1:1 host
iptables -t nat -A POSTROUTING -o $WAN -s 192.168.1.0/24 \
-j MASQUERADE
Save this as the Firewall script. The key thing here is the order: the SNAT rules for 1:1 hosts must come before the MASQUERADE rule, otherwise all outbound traffic will be masqueraded to the primary WAN IP regardless.
How it works
When a packet arrives on the WAN destined for 82.x.x.2, the PREROUTING DNAT rule rewrites the destination to 192.168.1.10 and forwards it to the LAN. When 192.168.1.10 sends a reply, the POSTROUTING SNAT rule rewrites the source address back to 82.x.x.2. The remote end sees the public IP throughout.
For LAN hosts that are not mapped (say, 192.168.1.50), the MASQUERADE rule at the end handles them as normal - they all share the primary WAN IP.
Testing
From the web server (192.168.1.10), check your outbound IP:
$ curl -s http://checkip.dyndns.org
Current IP Address: 82.x.x.2
From any other LAN host:
$ curl -s http://checkip.dyndns.org
Current IP Address: 82.x.x.1
If the web server shows 82.x.x.1 instead of 82.x.x.2, your SNAT rule is not being hit before the MASQUERADE rule. Check the rule order with iptables -t nat -L -n --line-numbers.
Caveats
The FORWARD rule I used above (-j ACCEPT) allows all traffic to reach the internal host. In production you would want to restrict this to specific ports. For the web server:
iptables -A FORWARD -d 192.168.1.10 -p tcp -m multiport \
--dports 80,443 -j ACCEPT
Also note that DD-WRT's web GUI does not know about these manual rules. If you use the GUI to change firewall settings, it may flush your custom rules. Always keep your rules in the Firewall script so they get reapplied on boot.
This approach works well on the WRT54G. The little Broadcom processor handles NAT translation without breaking a sweat at home broadband speeds. I have been running this setup for a couple of months now with no issues.