The combination of an ACCEPT policy for the FORWARD chain and no blocking rules in that chain, means you're letting
everything through (in both directions, I might add).
The
nat table is not meant for filtering, but that's how you're using it: all traffic goes through the firewall, but some go out with a local source address. Since you're using RFC 1918 addresses, the remote server can't reply and the traffic appears to be blocked, but in reality you're informing the world about which local IP addresses you're using.
The above would be of little consequence, except some of the rules have the wrong interface match criteria:
Code:
# Accept NAT for...
iptables -A FORWARD -s 192.168.20.2 -i $IF_INET -j ACCEPT
iptables -A FORWARD -s 192.168.20.3 -i $IF_INET -j ACCEPT
iptables -A FORWARD -s 192.168.21.2 -i $IF_INET -j ACCEPT
iptables -A FORWARD -s 192.168.21.3 -i $IF_INET -j ACCEPT
Here, you're explicitly allowing spoofed traffic from the Internet to be forwarded to your internal network. Hopefully, the reverse path verification setting in the kernel will have blocked any such packets.
As for why things aren't working, my guess would be DNS. DNS is UDP-based, but you're only allowing TCP. True, DNS falls back to TCP if the reply message is too big to fit in a single packet, but the fallback mechanism only kicks in when the client receives a "reply too big" message in response to a UDP query.
Here's my (very much untested) version:
Code:
# Internet I/F
IF_INET="eth1"
# LAN I/F
IF_LAN="eth0"
## Flush all tables
iptables -F
iptables -X
iptables -Z
iptables -t nat -F
## Set policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
iptables -t nat -P PREROUTING ACCEPT
iptables -t nat -P POSTROUTING ACCEPT
## Activate forwarding
# (a separate forwarding setting is likely to exist in your
# distribution, making this redundant)
echo 1 > /proc/sys/net/ipv4/ip_forward
## Access rules: INPUT
# localhost must be allowed
iptables -A INPUT -i lo -j ACCEPT
# established sessions
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Squid proxy; both ports 80 and 3128 must be allowed
# (transparent proxying)
iptables -A INPUT -i $IF_LAN -s 192.168.20.2 -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -i $IF_LAN -s 192.168.20.3 -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -i $IF_LAN -s 192.168.21.2 -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -i $IF_LAN -s 192.168.21.3 -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -i $IF_LAN -s 192.168.20.2 -p tcp --dport 3128 -j ACCEPT
iptables -A INPUT -i $IF_LAN -s 192.168.20.3 -p tcp --dport 3128 -j ACCEPT
iptables -A INPUT -i $IF_LAN -s 192.168.21.2 -p tcp --dport 3128 -j ACCEPT
iptables -A INPUT -i $IF_LAN -s 192.168.21.3 -p tcp --dport 3128 -j ACCEPT
## Access rules: FORWARD
# established sessions
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
# DNS
iptables -A FORWARD -i $IF_LAN -s 192.168.20.2 -p udp --dport 53 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.20.3 -p udp --dport 53 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.21.2 -p udp --dport 53 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.21.3 -p udp --dport 53 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.20.2 -p tcp --dport 53 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.20.3 -p tcp --dport 53 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.21.2 -p tcp --dport 53 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.21.3 -p tcp --dport 53 -j ACCEPT
# no HTTP rules are required, as transparent proxying is handled by
# the relevant NAT/INPUT rules
# HTTPS
iptables -A FORWARD -i $IF_LAN -s 192.168.20.2 -p tcp --dport 443 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.20.3 -p tcp --dport 443 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.21.2 -p tcp --dport 443 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.21.3 -p tcp --dport 443 -j ACCEPT
# IMAP/SSL
iptables -A FORWARD -i $IF_LAN -s 192.168.20.2 -p tcp --dport 993 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.20.3 -p tcp --dport 993 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.21.2 -p tcp --dport 993 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.21.3 -p tcp --dport 993 -j ACCEPT
# SMTP/TLS Message Submission
iptables -A FORWARD -i $IF_LAN -s 192.168.20.2 -p tcp --dport 587 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.20.3 -p tcp --dport 587 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.21.2 -p tcp --dport 587 -j ACCEPT
iptables -A FORWARD -i $IF_LAN -s 192.168.21.3 -p tcp --dport 587 -j ACCEPT
## NAT rules
# transparent proxying
iptables -t nat -A PREROUTING -i $IF_LAN -p tcp --dport 80 -j REDIRECT --to-port 3128
# NAT outbound traffic
iptables -t nat -A POSTROUTING -o $IF_INET -j MASQUERADE
I changed the FORWARD policy to DROP, and hence had to add a state matching rule for established sessions in the FORWARD chain. Since the policy for both the INPUT and the FORWARD chain is now DROP, no rules were needed for explicitly dropping non-allowed packets.
I chose to invert the NAT interface match logic from "-i
<internal_IF>" to "-o
<internet_IF>", as the former will also NAT internal intra-subnet traffic.
Technically, a "NEW" state match criteria could be added to all the FORWARD rules dealing with specific TCP protocols. That would only serve to prevent internal clients from sending invalid TCP packets, so I left it out. It would probably not improve security in any meaningful way.
I kept the host-specific matches, but if what you really mean is to allow all hosts on the 192.168.20.0/24 and 192.168.21.0/24 networks, using network matches would eliminate no less than 14 rules.
Finally, you may want to add a rule or two to the INPUT chain for remote administration (SSH?).