Port Forwarding using OpenVPN
Rationale
I frequently find myself needing to be able to host a temporary server accessible from outside of my LAN. There are many reasons I find myself in this situation, including temporarily hosting game servers for playing with my friends, hosting a webserver to easily share a large file over the internet, or developing a web application that needs to be able to respond to webhooks and other 3rd-party requests.
However, with the ubiquitousness of NAT in modern ipv4 networks, port forwarding is required to host any sort of public server from inside a LAN. This is infeasible on large public or corporate networks, where you have no control over the router and UPnP is almost certainly disabled. While there are commercial solutions like ngrok, these are often expensive or bandwidth-limited.
Personally, I developed this solution when I was living in university housing and I wanted to host servers for games like Minecraft, Factorio, or Terraria to play with my friends. The network on-campus was heavily locked down, and while I could access the internet, I was unable to enact any form of port forwarding. While I could have rented a virtual server to run these games, I preferred to forward the traffic to my local machine since it allowed me to rent the cheapest EC2 tier instead of having to pay for the resources required to run more complicated game servers.
Solution
My solution is to use OpenVPN to create a tunnel from my computer to a remote server, so that the VPN host becomes essentially my “router.” Then, since I have complete control over the remote server, I can just use port forwarding to send the traffic from the remote server to my local computer through the OpenVPN tunnel.
Step 1: Setup a remote host
The remote host should have a static ip or a dyndns setup.
I used an AWS EC2 t2.nano instance, with AWS Route53 as a dns provider.
First, I installed the AWS CLI and other dependencies.
sudo apt install unzip jq
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
rm -r aws awscliv2.zip
Then I created an AWS IAM user with the following policy and saved it using aws configure
.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets"
],
"Resource": "arn:aws:route53:::hostedzone/<YOUR HOSTED ZONE ID HERE>"
}
]
}
After that I created a folder for the dyndns script, downloaded it, and edited the configuration variables at the top.
mkdir -p ~/dyndns
cd dyndns
curl -O http://jordanpowers.link/assets/ovpn-portforward/dyndns.sh
vim dyndns.sh
Finally, I created and enabled a systemd script to run the dyndns on boot.
curl -O http://jordanpowers.link/assets/ovpn-portforward/dyndns.service
sudo cp dyndns.service /usr/lib/systemd/system/dyndns.service
sudo systemctl daemon-reload
sudo systemctl start dyndns
sudo systemctl enable dyndns
Step 2: Install OpenVPN and EasyRSA
Install OpenVPN.
sudo apt update
sudo apt install openvpn
Download and extract EasyRSA.
cd ~
wget https://github.com/OpenVPN/easy-rsa/releases/download/v3.1.0/EasyRSA-3.1.0.tgz
tar xvf EasyRSA-3.1.0.tgz
rm EasyRSA-3.1.0.tgz
mv EasyRSA-3.1.0 openvpn-ca
Step 3: Generate the CA
Have easyrsa generate the pki folder.
./easyrsa init-pki
Then, edit the pki/vars
file to uncomment the set_var instructions and fill in the requisite
values.
set_var EASYRSA_REQ_COUNTRY "US"
set_var EASYRSA_REQ_PROVINCE "California"
set_var EASYRSA_REQ_CITY "Los Angeles"
set_var EASYRSA_REQ_ORG "Powers Co"
set_var EASYRSA_REQ_EMAIL "admin@example.com"
set_var EASYRSA_REQ_OU "Community"
Finally, generate the CA. When prompted for a ‘Common Name’, accept the default value.
./easyrsa build-ca nopass
Step 4: Generate the Server Certificate, Key, and DH Files
First, generate and sign the server certificate. When prompted for a ‘Common Name,’ just hit enter
to accept the default values. When prompted to ‘Confirm request details’, type yes
and hit enter.
./easyrsa gen-req server nopass
./easyrsa sign-req server server
Next, generate a strong Diffie-Hellman key and a HMAC signature. This will take a few minutes to complete.
./easyrsa gen-dh
openvpn --genkey secret ta.key
Finally, copy all the newly generated files to /etc/openvpn
.
sudo cp pki/private/server.key /etc/openvpn
sudo cp pki/issued/server.crt /etc/openvpn
sudo cp pki/ca.crt /etc/openvpn
sudo cp pki/dh.pem /etc/openvpn
sudo cp ta.key /etc/openvpn
Step 5: Configure the OpenVPN Service
First, download the server configuration provided here and install it.
cd ~
curl -O http://jordanpowers.link/assets/ovpn-portforward/server.conf
sudo chown root:root server.conf
sudo chmod 644 server.conf
sudo mv server.conf /etc/openvpn
Next, make the client configuration directory.
sudo mkdir /etc/openvpn/ccd
Decide on a name for your client and remember it. Create a file with that same name as described
below. Be sure to replace <CLIENT NAME>
with the actual name you decided.
sudo tee /etc/openvpn/ccd/<CLIENT NAME> << 'EOF'
ifconfig-push 10.8.0.201 255.255.255.0
EOF
Step 6: Enable network forwarding
First, enable ufw.
sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status
Next, enable ip forwarding. Edit /etc/sysctl.conf
:
sudo vim /etc/sysctl.conf
Set the following value:
net.ipv4.ip_forward=1
And reload the parameters from disk
sudo sysctl -p
Next, edit iptables to enable forwarding traffic from OpenVPN clients. First, get the network interface of the server.
ip route | grep default
You’re looking for the value that follows the word dev
. It’ll usually be something like eth0
or enp2s0f0
.
Next, edit /etc/ufw/before.rules
:
sudo vim /etc/ufw/before.rules
Near the top, add the following lines. Make sure to change eth0
with the interface you just
found.
#
#
# rules.before
#
# Rules that should be run before the ufw command line added rules. Custom
# rules should be added to one of these chains:
# ufw-before-input
# ufw-before-output
# ufw-before-forward
#
# START OPENVPN RULES
# NAT table rules
*nat
:POSTROUTING ACCEPT [0:0]
# Allow traffic from OpenVPN client to eth0
-A POSTROUTING -s 10.8.0.0/8 -o eth0 -j MASQUERADE
COMMIT
# END OPENVPN RULES
# Don't delete these required lines, otherwise there will be errors
...
Next, edit /etc/default/ufw
:
sudo vim /etc/default/ufw
And set the default forward policy:
DEFAULT_FORWARD_POLICY="ACCEPT"
Finally, allow OpenVPN traffic on port 1194 and restart the firewall.
sudo ufw allow 1194/udp
sudo ufw disable
sudo ufw enable
Step 7: Enable the OpenVPN Service
First, start the service and make sure there are no errors.
sudo systemctl start openvpn@server
sudo systemctl status openvpn@server
If all is well, enable the service so the server will start on boot.
sudo systemctl enable openvpn@server
Step 8: Generate the Client Configuration
First, make a directory to hold the client configurations.
mkdir -p ~/client-configs/files
Then, download the client configuration base and the generation script.
cd ~/client-configs
curl -O http://jordanpowers.link/assets/ovpn-portforward/base.conf
curl -O http://jordanpowers.link/assets/ovpn-portforward/make_config.sh
chmod +x make_config.sh
Next, open base.conf and replace <SERVER_IP>
with the server’s static ip or dyndns address.
remote <SERVER_IP> 1194
Run the generation script with the name of your client. Be sure to use the same name as configured
in the ccd
directory from step 5.
./make_config.sh <CLIENT_NAME>
Finally, copy the file at ~/client-configs/files/
Step 9: Forward Ports
First, download the forwarding script.
cd ~
curl -O http://jordanpowers.link/assets/ovpn-portforward/ports.py
chmod +x ports.py
Then run the script to forward the desired ports.
./ports.py forward 8000 tcp
Note that forwarded ports will be reset when the server is rebooted. The forwarding script will have to be re-run every time the server is started.