Ubuntu: Using iptables to forward tcp and udp requests

In previous articles I have described how to use a Squid proxy as a bridge to outside services such as http and ftp. Having hosts in your private networks use a single access point to services in the outside world institutes the type of control often required in production data centers.

Having a single host act as the access point to an outside service allows you to keep your internal hosts isolated in their own private network, using NAT to transparently forward the request and route back the responses.

In this article, I will show you how to use iptables to forward tcp and udp port requests from one private network to another using a single bridging host (port f0rwarding).

Overview

For this exercise, we will use the network shown below.   “privateVM1” (as well as all the other VMs in that network) are segmented and isolated in the 172.28.128.0/24 network.

+----------------------+      +--------------------------+                           
|                      |      |                          |                           
| host: privateVM1     |      | host: proxyVM            |                           
|                      |      |                          |                           
| eth0: 172.28.128.8 ----------> enpos8: 172.28.128.9    |                           
|                    <-----------                        |                           
|                      |      |  enp0s9:192.168.2.125 -------> ext svc          
|                      |      |                          |                           
+----------------------+      +--------------------------+

To reach critical external services, they rely upon a host named “proxyVM” that has an interface on the 172.28.128.0 network, but has an additional interface in the 192.168.2.0/24 network that allows it to reach external networks.

privateVM1 will only make calls to proxyVM (and will have no knowledge of the outside world).  The external services will have no idea they are actually serving a request originating from privateVM1 (and will believe they are simply talking to proxyVM).  This is the essence of NAT.

The reason why proxyVM has ethernet interfaces named “enpos3” and “enp0s9” is because it is an Ubuntu 16.04 xenial host that incorporates systemd’s predictable network interface name scheme.  On an older version of Ubuntu, they would have been named “eth0” and “eth1”.  But other than the naming convention, this iptables forwarding solution would work the same on either version.

Prerequisites

Before we can start modifying iptables, IP forwarding must be enabled on “proxyVM”.  Check if IP forwarding is enabled.

$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0

If it is not, then set the value immediately and persistently in the sysctl.conf file.

$ sudo sysctl -w net.ipv4.ip_forward=1
$ sudo sed -i -e "s/^$net.ipv4.ip_forward.*/net.ipv4.ip_forward=1/" /etc/sysctl.conf
$ cat /etc/sysctl.conf | grep ip_forward
$ sudo sysctl -p /etc/sysctl.conf

Then validate the changes are in effect:

$ sysctl net.ipv4.ip_forward
1

iptables for external udp service

For the UDP example, we will use a public network time protocol (NTP) server that is known to listen on port 123/udp, “0.ubuntu.pool.ntp.org”.

You can verify the basic functionality by pulling a single IP out of this load balanced name, and then checking the network time using the ntp client.

$ myntpserver=$(nslookup 0.ubuntu.pool.ntp.org | grep -i Address | tail -n 1 | awk {'print $2'})
$ echo going to use $myntpserver as my ntp server
$ sudo apt-get install ntpdate -y
$ ntpdate -q $myntpserver

RULE #1 – accept packet and send to ext service

Now let’s create the iptables rules on proxyVM.  We have to enable iptables to accept packets coming from the private interface, and send it to the external udp service [man].   This command comes in the form:

sudo iptables -t nat -A PREROUTING -i <sourceIF> -p <protocol> --dport <targetPort> -j DNAT --to <targetHost>

And looks like this for our described environment:

sudo iptables -t nat -A PREROUTING -i enp0s8 -p udp --dport 123 -j DNAT --to $myntpserver

RULE #2 – (for ext svc) make it look like proxyVM is making the request

In order for the request to be sent back properly from the external service, we have to pretend that proxyVM is the one making the request from its outward facing IP address (192.168.2.125).  You can either use MASQUERADE if your proxyVM interface addresses are dynamic, or SNAT to set an explicit IP address (which is more efficient).

sudo iptables -t nat -A POSTROUTING -o <targetIF> -p <protocol> --dport <targetPort> -j MASQUERADE 
OR 
sudo iptables -t nat -A POSTROUTING -o <targetIF> -p <protocol> --dport <targetPort> -j SNAT --to-source <targetIFAddress>

This looks like the following in our described network:

sudo iptables -t nat -A POSTROUTING -o enp0s9 -p udp --dport 123 -j MASQUERADE
OR
sudo iptables -t nat -A POSTROUTING -o enp0s9 -p udp --dport 123 -j SNAT --to-source 192.168.2.125

NOTE: One mistake that is easy to make in this step is assuming the <targetIF> you specified is the one actually used for the outbound communication.  Use “route -n” to make sure you understand what the default gateway is and how routing might be affected.  One trick you can use is running “sudo tcpdump -i <interface> -n port 123” from proxyVM in one console, while running “ntpdate -q <externalNTP>” from another console of proxyVM and if you see the traffic in tcpdump, then it is the correct one.

RULE #3 – (for privateVM1) make it look like response is coming from proxyVM

In order for the client to receive the request properly, it has to believe the response is coming from proxyVM’s internal IP address (172.28.128.9).  This last rule modifies the source IP to the private IP address of proxyVM.  Here is the form of the command:

sudo iptables -t nat -A POSTROUTING -p <protocol> --sport <targetPort> -j SNAT --to-source <sourceIFAddress>

Which looks like this in our described environment:

sudo iptables -t nat -A POSTROUTING -p udp --sport 123 -j SNAT --to-source 172.28.128.9

You can now list the iptables rules using:

$ sudo iptables -L -t nat -v

Enable tcpdump for port 123 of the proxyVM so you can see the traffic flowing through from the client.

$ sudo tcpdump -n port 123

Now switch over to your client, privateVM1, and issue an ntp client time query against proxyVM1’s internal IP address, and you should receive a valid time response.

privateVM1$ ntpdate -q 172.28.128.9

If you get “no server suitable for synchronization found” from this client command, then there is a problem with the communication with proxyVM.  Try this same command directly from proxyVM, disable firewalls as a test, and check if the outbound

The tcpdump on the proxyVM should also show the results of ntp udp packets flowing out and then back.

iptables for external tcp service

For the TCP example, we will use a public HTTP server that is known to listen on port 443/tcp, www.ubuntu.com.

You can verify the basic functionality by pulling a single IP out of this load balanced name, and then pulling the main page using curl.

$ myhttpserver=$(nslookup www.ubuntu.com | grep -i Address | tail -n 1 | awk {'print $2'})
$ echo going to use $myhttpserver as my http server
$ sudo apt-get install curl -y
$ curl https://$myhttpserver:443 --insecure --header 'Host: www.ubuntu.com'

Notice that we manually sent the ‘Host’ header to make up for the fact that we are sending the $myhttpserver

Just like the UDP rules above, we will add 3 rules to the proxyVM host using iptables.  I won’t bother to explain them in detail this time.  If you need more detail, see the UDP section above.

RULE #1 – accept packet and send to ext service

sudo iptables -t nat -A PREROUTING -i enp0s8 -p tcp --dport 443 -j DNAT --to $myhttpserver

RULE #2 – (for ext svc) make it look like proxyVM is making the request

sudo iptables -t nat -A POSTROUTING -o enp0s9 -p tcp --dport 443 -j SNAT --to-source 192.168.2.125

RULE #3 – (for privateVM1) make it look like response is coming from proxyVM

sudo iptables -t nat -A POSTROUTING -p tcp --sport 443 -j SNAT --to-source 172.28.128.9

You can now list the iptables rules, and should see your new PREROUTING and POSTROUTING tcp rules:

$ sudo iptables -L -t nat -v

Enable tcpdump for port 443 of the proxyVM so you can see the traffic flowing through from the client.

$ sudo tcpdump -n tcp and port 443

Now switch over to your client, privateVM1, and issue an HTTP GET against port 443 of proxyVM1’s internal IP address, and you should receive a web page.

privateVM1$ curl https://172.28.128.9:443 --insecure --verbose --header 'Host: www.ubuntu.com'

The tcpdump on the proxyVM should also show 15-20 packet communications.

Persisting the rules

By default, the rules you just added will not be persisted through a reboot.  In order to save them:

# Ubuntu trusty 14.04
$ sudo apt-get install iptables-persistent
$ sudo iptables-save > /etc/iptables/rules.v4

# Ubuntu xenial 16.04
$ sudo apt-get install iptables-persistent
$ sudo netfilter-persistent save

 

 

REFERENCES

https://serverfault.com/questions/208428/getting-iptables-to-properly-forward-ntp-traffic (starting point with iptables rule)

https://serverfault.com/questions/366736/how-to-create-udp-proxy-using-iptables

https://unix.stackexchange.com/questions/243451/iptables-change-local-source-address-if-destination-address-matches (change source address of packets destined for)

https://www.digitalocean.com/community/tutorials/how-to-forward-ports-through-a-linux-gateway-with-iptables

https://linuxconfig.org/how-to-turn-on-off-ip-forwarding-in-linux (enable IP forwarding)

https://www.tcpdump.org/manpages/tcpdump.1.html (manual)

https://staff.washington.edu/dittrich/talks/core02/tools/tcpdump-filters.txt (tcpdump cheatsheet, packet bits)

https://www.linode.com/docs/security/firewalls/control-network-traffic-with-iptables/ (great explanations and examples)

https://www.booleanworld.com/depth-guide-iptables-linux-firewall/ (iptables walkthrough)

https://geek-university.com/linux/configure-ntp-client/ (ntp client)

https://www.thegeekstuff.com/2014/06/linux-ntp-server-client (install NTP server)

https://www.commandlinefu.com/commands/using/tcpdump (tcpdump example commands)

https://www.digitalocean.com/community/tutorials/how-to-list-and-delete-iptables-firewall-rules

https://www.cyberciti.biz/faq/linux-port-redirection-with-iptables/ (single iptables rule for REDIRECT locally)

https://terrywang.net/2016/02/02/new-iptables-gotchas.html (MASQUERADE has more overhead than explicit SNAT)

https://unix.stackexchange.com/questions/21967/difference-between-snat-and-masquerade (diff between MASQUERADE and SNAT)

https://drewish.com/2010/03/29/using-curl-and-the-host-header-to-bypass-a-load-balancer/ (curl using IP, but still send host header)

https://www.thomas-krenn.com/en/wiki/Saving_Iptables_Firewall_Rules_Permanently (persisting iptables in different flavors OS)

NOTES

show rules by chain and line number for deletion

sudo iptables -L -t nat --line-numbers (list all in chains/rules in nat)
sudo iptables -t nat -D PREROUTING 5 (delete 5th rule in PREROUTING)
sudo iptables -t nat -F PREROUTING (flush all rules in PREROUTING)

all udp, and then initiating tcp (SYN)

$ sudo tcpdump -n -v "tcp[13]&2!=0 or udp"

iptables can’t be seen as process, check kernel

$ lsmod| grep iptable