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://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