Sunday, January 10, 2021

Forwarding Ports over OpenVPN

How to configure OpenVPN server to forward ports through to VPN clients.

I recently tested this setup with my Oracle Cloud free tier VM running Ubuntu 20.04 and OpenVPN server (setup using this script https://github.com/angristan/openvpn-install) to forward the ports used by my IRLP node (running on Debian 8) connected to the VPN. This would work with any type of service, it's just a matter forwarding the right tcp/udp ports, and configuring the VPN client to auto connect on startup.

ISPs are increasingly using CGNAT to deal with IPv4 address exhaustion, this is a problem for those of us that like to run systems or services that need to have ports forwarded into them from a publicly routable IP address to work. An example in ham radio circles are IRLP (Internet Radio Linking Project), EchoLink and AllStarLink which need ports forwarded in order to work. A number of the ham radio VoIP systems offer varying levels of support for VPNs from full service (IRLP - they send you a config and away you go with a publicly routed 44 address I believe) to supporting different providers to make the setup easy.

If you want to do it all your self using a free tier (no cost) Oracle cloud VM for the fun/experience/control (mwahaha) then this is how I did it. To setup an ad blocking VPN check out Setup free Cloud VPN with Ad Blocking (this is the primary purpose of my VPN setup).

There are four pieces to getting this working:

  1. OpenVPN client auto connect on startup.
  2. OpenVPN server assigns the same static VPN IP address to the client.
  3. Cloud networking config to allow the ports in to OpenVPN server host.
  4. Update iptables rules on the OpenVPN server host to forward the ports to the VPN client.

In this example I created a VPN client config (using the install script) with the CommonName IRLP1, and configured OpenVPN server to always assign it 10.8.0.10.

1. Configuring the client to auto connect VPN on startup


Created a passwordless client config and copied into /etc/openvpn on the client (not sure if this varies between Linux distros), the file needs to be renamed from .ovpn extension to .conf extension, or in my case I made a symlink: ln -s IRLP1.ovpn client.conf

Test that it connects from command line:
openvpn --client --config /etc/openvpn/client.conf
Ctrl C to disconnect.

To get OpenVPN client to connect on boot, I had to use information from several sources to get it working on Debian 8.

Edit /etc/default/openvpn uncomment AUTOSTART="all" 

Ran these commands:
systemctl daemon-reload
systemctl enable openvpn

Connect OpenVPN client/check status/disconnect:
systemctl start openvpn
systemctl status openvpn
systemctl stop openvpn

All things being equal, reboot the system and the OpenVPN client auto connects on system boot up.

Debian 8 had OpenVPN 2.3 installed, had to upgrade it to 2.4 to get it to work, this guide covers the steps.

2. Configuring OpenVPN server to assign a unique 10.8.0.x address to each unique client


Because we're going to create iptables rules to forward ports to specific IP addresses, the VPN client needs to receive the same VPN IP address every time, otherwise the port forwarding wont work if it gets a different address.

The OpenVPN server script I used includes the ifconfig-pool-persist ipp.txt option in /etc/openvpn/server.confOpenVPN server adds an entry to /etc/openvpn/ipp.txt the first time a new client connects in the format CommonName,10.8.0.x, this is used to maintain long term association between clients and IP addresses.

According to the OpenVPN reference manual, maintaining a long-term association is good for clients because it allows them to effectively use the persist-tun option. Note that the entries in this file are treated by OpenVPN as suggestions only based on past associations between a common name and IP address. They do not guarantee that the given common name will always receive the given IP address. If you want guaranteed assignment, use ifconfig-push.

For the clients we always want to have the same address assigned, added this line to /etc/openvpn/server.conf:

client-config-dir /etc/openvpn/ccd

Create a file in /etc/openvpn/ccd with common name of the client - in my case that's /etc/openvpn/ccd/IRLP1, and add this one line (the VPN IP address to assign and the netmask):

ifconfig-push 10.8.0.10 255.255.255.0

Edit ipp.txt to also match, e.g. IRLP1,10.8.0.10

What's not clear is if we need to keep ifconfig-pool-persist?


3. Updating the cloud networking config


Each cloud provider will be different. This is how to do it in Oracle Cloud.

Oracle Cloud Console > Networking > Virtual Cloud Networks > [network name you created] > Public Subnet > Default Security List:

Stateless

Source Type

Source CIDR

IP Protocol

Src Port Range

Dest Port Range

Type and Code

Description

No

CIDR

0.0.0.0/0

ICMP



Type 8

Allow PING

No

CIDR

0.0.0.0/0

TCP


15425


IRLP Control

No

CIDR

0.0.0.0/0

UDP


2074-2093


IRLP Audio



This opens the ports from the public internet to the cloud VM which is the first half. Allow ping isn't needed, but sometimes it's nice to be-able to ping the cloud VM.


4. Update iptables on OpenVPN server to forward the ports through to the VPN client


Different OpenVPN setup scripts can setup the iptables rules in different files/places. You'll need to read the script to see how it's being setup. In the case of the one I use linked above, it sets up a systemd script to call these two scripts:

/etc/iptables/add-openvpn-rules.sh
/etc/iptables/rm-openvpn-rules.sh

First one adds the OpenVPN related iptables rules when the OpenVPN server starts, the second removes them when OpenVPN server stops.

Examples that forward external port 22000 through to port 22 for SSH (not recommend to open SSH to the world any more than needed, but maybe useful to have in some cases), and the IRLP TCP control port, and UDP audio port ranges. ens3 is the VM's network interface name (change to suit where needed).

These commands add rules into iptables, these go in the add-openvpn-rules.sh script:

# SSH
iptables -t nat -A PREROUTING -p tcp -i ens3 --dport 22000 -j DNAT --to-destination 10.8.0.10:22
iptables -A FORWARD -p tcp -d 10.8.0.10 --dport 22 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# IRLP TCP Control Port
iptables -t nat -A PREROUTING -p tcp -i ens3 --dport 15425 -j DNAT --to-destination 10.8.0.10:15425
iptables -A FORWARD -p tcp -d 10.8.0.10 --dport 15425 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# IRLP UDP Audio Port Range
iptables -t nat -A PREROUTING -p udp -i ens3 --dport 2074:2093 -j DNAT --to-destination 10.8.0.10:2074-2093
iptables -A FORWARD -p udp -d 10.8.0.10 --dport 2074:2093 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

To put the added rules into effect, run the script.


These commands remove rules from iptables, these go in the rm-openvpn-rules.sh script:

# SSH
iptables -t nat -D PREROUTING -p tcp -i ens3 --dport 22000 -j DNAT --to-destination 10.8.0.10:22
iptables -D FORWARD -p tcp -d 10.8.0.10 --dport 22 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# IRLP TCP Control Port
iptables -t nat -D PREROUTING -p tcp -i ens3 --dport 15425 -j DNAT --to-destination 10.8.0.10:15425
iptables -D FORWARD -p tcp -d 10.8.0.10 --dport 15425 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# IRLP UDP Audio Port Range
iptables -t nat -D PREROUTING -p udp -i ens3 --dport 2074:2093 -j DNAT --to-destination 10.8.0.10:2074-2093
iptables -D FORWARD -p udp -d 10.8.0.10 --dport 2074:2093 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

Reboot the system to make sure the rules are applied on startup.

The above 3 sets of rules would cover most examples, such as mapping ports through on different port numbers, sometimes this is useful (SSH), a TCP rule for a single port (IRLP Control), and a UDP rule for a range of ports (IRLP Audio). Creating rules for the other amateur radio VoIP should be easy with those as examples.

iptables -L -v or iptables -S will show the rules.

Netcat is good for testing ports from Linux/macOS systems (-u is for UDP):
nc -z hostname_or_ip port_number
nc -zu hostname_or_ip port_number

Connecting to reflectors 9050, 9100, and 9109 which are quite busy for hours at times, and node to node connections have worked with out issue.

Thats it!


Bonus item, a script that'll reconnect the OpenVPN client if the tunnel is no longer up and the client for whatever reason failed to restart the tunnel.

Script looks at the exit code of the ping command, if it's anything other than 0 then the ping failed, and a reconnect is attempted.
Outputs one line to the IRLP log/messages file if it reconnects.

#!/bin/bash
# Test VPN tunnel by pinging remote endpoint, if down restart OpenVPN client.

IP='10.8.0.1'
LOGFILE='/home/irlp/log/messages'
LOGDATETIME=`date +"%b %d %Y %T %z"`

ping -W 1 -c 3 $IP 2>/dev/null 1>/dev/null
if [ ! "$?" = 0 ]; then
 echo "$LOGDATETIME VPN tunnel down reconnecting.." >> $LOGFILE
 systemctl restart openvpn
fi

Put that into a file say called /home/irlp/custom/vpn-check.sh

Make it executable: chmod +x vpn-check.sh

As root edit the crontab: crontab -e

Add this line:

*/5 * * * * /home/irlp/custom/vpn-check.sh >/dev/null 2>&1

That'll run the script every 5 minutes.