Wednesday, March 16, 2016

Port Forwarding with iptables (NAT)

I am running a server at home, and it does all kinds of things. It is placed between the modem and a switch where workstations and wireless access points plug into. This is because the server also acts as a firewall.

If you also want to do this, you probably won't have a bare modem, but a one-in-all box acting as a modem, router, DHCP server, DNS proxy, WiFi access point, simple firewall, etc.
As your internal network won't be accessible from the modem, you will need to run a DHCP server yourself (or delegate it to your access point if you only have wireless clients). Also you can't use it as a WiFi AP, so you'll need a dedicated AP for that, which is better anyway. As for the DNS proxy, you don't need it but you can use Bind to do that, and even cache entries for you and, best thing in the world, act as an internal DNS server so you can refer to your machines with meaningful names (such as printer.myhome.lan).
You will need to figure out how to transmit all traffic to your firewall. It is okay to leave security features on (malformed IP packets, etc.)

By the way, I said "DNS proxy" for you to understand it, but what usually happens is known as DNS faking. The firewall / server is going to transform packets destined to itself on the DNS port (UDP 53) to the IP address of the actual DNS server. At the end of this article, you should be able to figure out how to do that. The principle is exactly the same as NAT, except that it is used in the other direction: we are letting the LAN clients think that the service they are looking at is on their network, except that we actually forward the packets further to another host. The only difference being that in this case, they could actually be routed to the DNS server, but that doesn't matter.

There are pros and cons of putting a home server acting as a firewall between the modem and the rest of the network, but in any case, if you are operating a network seriously, there is no way computers on the network can use uPnP to open ports so that Internet traffic comes right to them on certain ports. That would be a security breach.
However, there are times where you actually want to do that. For instance, you might want to access a service on a Raspberry Pi or speed up Bittorrent downloads, which involves reaching that machine from the Internet on a predefined port. You could actually have port 1000 from the Internet mapped to port 2345 on the actual machine. I have done that, but I would recommend against it if you can, simply because it is only going to confuse you. But if you have several services running on HTTP port and you want to access them from the Internet you don't have any other solution than this or the VPN (but in that case you aren't really accessing "from the Internet" per se...)

Let's assume a workstation with the address 192.168.1.100 wants to be reachable from the Internet on TCP port 6881.

It is also safe to assume that you are currently forwarding traffic (see net.ipv4.ip_forward in sysctl, and the approriate rule in the FORWARD chain) from the LAN-facing interface (here eth1) to the Internet-facing interface (here eth0), otherwise your LAN could not access the Internet.
And if you are doing this, you should also be masquerading already (see below).
Here's the way to do it:

# iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

What you shouldn't be doing is allowing all traffic to cross your firewall from the outside to the inside. Even if you did, if the outside network is the Internet, that would not work, since Internet routers don't route LAN addresses, technically called "private" addresses (10.0.0.0/8, 172.16.0.0/16, 192.168.0.0/24). However we are going to route some well-chosen traffic in that direction.

Note that all commands should be run with sudo or as root. If you run the commands as a regular UNIX user, you are going to encounter something such as

-bash: iptables: command not found                                  

Just prepend sudo to the command, and you will be fine.

First off, let's check what is currently in the filter table (-t filter is actually the default, you can omit it):

# iptables -vnL -t filter

Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
  85M   44G ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
[ other rules ]

Chain FORWARD (policy DROP 324 packets, 17404 bytes)
 pkts bytes target     prot opt in     out     source               destination         
 165M  132G ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
1076K  106M ACCEPT     all  --  eth1   eth0    192.168.1.0/24      0.0.0.0/0            ctstate NEW
[ other rules ]

Chain OUTPUT (policy DROP 527K packets, 279M bytes)
 pkts bytes target     prot opt in     out     source               destination         
[ other rules ]

Let's look at the sections in bold. You firewall is useless if the default policy is to accept everything. Instead of blacklisting (which is what you will do if the chain is set to ACCEPT by default), we are going to whitelist some traffic flows (the chain is set to DROP).

The line that is highlighted shows that machines in the LAN can access the Internet. The source parameter is only required if you somehow happen to have several networks on the LAN side and you want to have allow only one of them.

Now we will be allowing traffic in the other direction, on TCP port 6881, which will eventually reach 192.168.1.100:

# iptables -A FORWARD -d 192.168.1.100 -i eth0 -o eth1 -p tcp --dport 6881 -m conntrack --ctstate NEW -j ACCEPT

and this is how the filter table is going to look after that:

# iptables -vnL

Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
  85M   44G ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
[ other rules ]

Chain FORWARD (policy DROP 324 packets, 17404 bytes)
 pkts bytes target     prot opt in     out     source               destination         
 165M  132G ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
  633 37579 ACCEPT     tcp  --  eth0   eth1    0.0.0.0/0            192.168.1.100       ctstate NEW tcp dpt:6881
[ other rules ]

Chain OUTPUT (policy DROP 527K packets, 279M bytes)
 pkts bytes target     prot opt in     out     source               destination         
[ other rules ]


You can leave out the "-m conntrack --ctstate NEW" part. If you look at my INPUT and FORWARD chain, you will see that the first rule allows any packet whatever they are, if they are part of or relate to a connection that was permitted. But to enforce that, I need to verify that the rule for port 6881 checks for new connections. What this achieves is blocking TCP packets coming out of nowhere. If someone wants to transmit packets over a TCP connection, they have to establish that TCP connection first.

You might be curious and ask "Aren't the packets coming from the Internet destined to the public IP address? They will never match the rule!"
I understand your concern. The reason behind this is that by the time the FORWARD chain is evaluated, the PREROUTING will have already taken place, so the IP packets will have been modified to point to their final destination (or the next gateway that might be doing NAT).

In the previous paragraph, I used the term "modified IP packets". In the context of Network Address Translation (NAT), the process is called "masquerading". The firewall is going to manipulate IP packets.

Now let's enable that PREROUTING rule:

# iptables -t nat -A PREROUTING -p tcp --dport 6881 -j DNAT --to 192.168.1.100:6881

Let's see what the NAT table looks like:

# iptables -vnL -t nat

Chain PREROUTING (policy ACCEPT 45817 packets, 6266K bytes)
 pkts bytes target     prot opt in     out     source               destination     
 1845  109K DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:6881 to:192.168.1.100:6881

Chain INPUT (policy ACCEPT 23486 packets, 2920K bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 25162 packets, 4378K bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 3217 packets, 208K bytes)
 pkts bytes target     prot opt in     out     source               destination         
2189K  274M MASQUERADE  all  --  *      eth0    0.0.0.0/0            0.0.0.0/0 

VoilĂ ! You should be up and running now!
You can test it with CanYouSeeMe.org

If it doesn't work, first make sure you can access the server from your LAN, and then check your tables look like in my examples, that you didn't mix interfaces up, that your modem lets everything through, and so on.

No comments:

Post a Comment