in networking, Tools

Build a OpenVPN server on Ubuntu to provide a IPv6 tunnel over IPv4

Like a lot of computer engineers, I use to work from various places such as my office, home, at a customer’s, in the train… It’s important to always have same LAN/Internet configuration wherever I am and the best solution to achieve this is of course using a VPN.

If you have a dedicated server connected to the Internet running Ubuntu (or any other Linux OS), this can be achieved quite easily and for totally free with OpenVPN. OpenVPN clients are available on almost all platforms, Linux, MacOS, Windows, Android and iOS. If your server has IPv6 connectivity, you can also take advantage of it, even if all you have is a terrible IPv4 connection with a lot of restrictions.

Here is how I did it, on a fresh Ubuntu 14.04 LTS installation.

All commands in this tutorial must be run as root.

We consider the server has external IPv4 1.2.3.4 and IPv6 aaaa:bbbb:cccc:dddd::/64 on interface eth0. The subnet aaaa:bbbb:cccc:dddd:80::/112 is allocated to the VPN clients on interface tun0.

Enable NAT and NDP proxy

The first step is to enable NAT for IPv4 and NDP proxy for IPv6. We could do NAT with IPv6 but it’s worthless since only the IP address of the VPN server would be reachable externally. NDP proxy is the only way to make the clients directly reachable from the Internet using their IPv6 address.

First, allow the server’s kernel to forward traffic from client devices out to the Internet.
vim /etc/sysctl.conf

Uncomment/add the following lines:

	...
net.ipv4.ip_forward=1
	...
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.all.proxy_ndp = 1
	...
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
	...
net.ipv4.conf.all.send_redirects = 0
	...

Now we can enable NAT for IPv4 (considering eth0 is the interface that is connected to the Internet):
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables-save > /etc/iptables.rules

To enable NAT again after reboot, create the following up script:
vim /etc/network/if-up.d/iptables

#!/bin/sh
iptables-restore < /etc/iptables.rules

Make the script executable.
chmod +x /etc/network/if-up.d/iptables

You can now restart the networking services
sysctl -p
/etc/init.d/networking restart

Install OpenVPN on the server

First of all, update all your packages.
apt-get update

Then install the OpenVPN packages.
apt-get install openvpn easy-rsa

Generate server certificate and key

Here comes the boring part: generating the certificate files and other security-related stuff that is necessary to make the connection possible.

First copy over the Easy-RSA generation scripts.
cp -r /usr/share/easy-rsa/ /etc/openvpn

Then create the storage directory for the generated keys.
mkdir /etc/openvpn/easy-rsa/keys

Now, modify the default values that will be used to build the certificates. This will avoid you to type them every time you generate a new certificate. Edit the file /etc/openvpn/easy-rsa/vars to modify the following keys:
vim /etc/openvpn/easy-rsa/vars

	...
export KEY_COUNTRY="France"
export KEY_PROVINCE="BZH"
export KEY_CITY="Nantes"
export KEY_ORG="My Company Name"
export KEY_EMAIL="contact@mycompany.com"
export KEY_OU="MYOrganizationalUnit"
	...

The server keys will be named server so in the same file, also modify the KEY_NAME value as follows:

	...
export KEY_NAME="server"
	...

Save.

Now generate the Diffie-Hellman parameters. This takes a few minutes to process so feel free to take a break and a cup of coffee.
openssl dhparam -out /etc/openvpn/dh2048.pem 2048

Then, build the certificate authority (CA). You will be invited to modify the values, just press [ENTER] to keep the information you previously entered in the vars file.
cd /etc/openvpn/easy-rsa
. ./vars
./clean-all
./build-ca

It's now time to generate the certificate and the key for the server. As before, you will be invited to modify the values. Leave the 2 last fields blank (challenge password and optional company name) then press Y.
cd /etc/openvpn/easy-rsa
./build-key-server server

When you're done, copy the files to the OpenVPN directory.
cp /etc/openvpn/easy-rsa/keys/{server.crt,server.key,ca.crt} /etc/openvpn

Server configuration

Here is an example of configuration for the following settings:

  • Full IPv6 connectivity over IPv4.
  • IPv4 TCP connection on port 443. The default setting (UDP 1194) may offer better performance but may be blocked on some connections such as mobile networks.
  • Clients can communicate with each other which can be useful if you are developing a mobile website and you want to test it directly on the device.
  • Fixed IP addresses for some specific clients, for example for the laptop on which the web server of the development website is running.
  • NDP proxying is enabled on demand using client-connect scripts and provides two-way Internet access to the clients.

First of all, create a vpn user for the daemon:
sudo useradd -r -s /bin/false vpn

Create the client config directory:
mkdir /etc/openvpn/ccd

Create a OpenVPN variables file that will be used by the client connection scripts. Modify the tunnel subnet prefix by yours:
sudo vim /etc/openvpn/variables

# Tunnel subnet prefix
prefix=aaaa:bbbb:cccc:dddd:80:
# netmask
prefixlen=112

Create the server-clientconnect.sh and server-clientdisconnect.sh scripts. These scripts will create and remove the NDP proxy rules for each connecting client.
sudo vim /etc/openvpn/server-clientconnect.sh

#!/bin/sh

# Check client variables
if [ -z "$ifconfig_pool_remote_ip" ] || [ -z "$common_name" ]; then
        echo "Missing environment variable."
        exit 1
fi

# Load server variables
. /etc/openvpn/variables

ipv6=""

# Find out if there is a specific config with fixed IPv6 for this client
if [ -f "/etc/openvpn/ccd/$common_name" ]; then
        # Get fixed IPv6 from client config file  
        ipv6=$(sed -nr 's/^.*ifconfig-ipv6-push[ \t]+([0-9a-fA-F\\:]+).*$/\1/p' "/etc/openvpn/ccd/$common_name")
fi

# Get IPv6 from IPv4
if [ -z "$ipv6" ]; then
        ipp=$(echo "$ifconfig_pool_remote_ip" | cut -d. -f4)
        if ! [ "$ipp" -ge 2 -a "$ipp" -le 254 ] 2>/dev/null; then
                echo "Invalid IPv4 part."
                exit 1
        fi
        hexipp=$(printf '%x' $ipp)
        ipv6="$prefix$hexipp"
fi

# Create proxy rule
/sbin/ip -6 neigh add proxy $ipv6 dev eth0

sudo vim /etc/openvpn/server-clientdisconnect.sh

#!/bin/sh

# Check client variables
if [ -z "$ifconfig_pool_remote_ip" ] || [ -z "$common_name" ]; then
        echo "Missing environment variable."
        exit 1
fi

# Load server variables
. /etc/openvpn/variables

ipv6=""

# Find out if there is a specific config with fixed IPv6 for this client
if [ -f "/etc/openvpn/ccd/$common_name" ]; then
        # Get fixed IPv6 from client config file  
        ipv6=$(sed -nr 's/^.*ifconfig-ipv6-push[ \t]+([0-9a-fA-F\\:]+).*$/\1/p' "/etc/openvpn/ccd/$common_name")
fi

# Get IPv6 from IPv4
if [ -z "$ipv6" ]; then
        ipp=$(echo "$ifconfig_pool_remote_ip" | cut -d. -f4)
        if ! [ "$ipp" -ge 2 -a "$ipp" -le 254 ] 2>/dev/null; then
                echo "Invalid IPv4 part."
                exit 1
        fi
        hexipp=$(printf '%x' $ipp)
        ipv6="$prefix$hexipp"
fi

# Delete proxy rule
/sbin/ip -6 neigh del proxy $ipv6 dev eth0

Make the scripts executable.
chmod +x /etc/openvpn/server-clientconnect.sh
chmod +x /etc/openvpn/server-clientdisconnect.sh

OpenVPN needs root privileges to run the scripts so add the following in the sudoers file:
visudo

# OpenVPN
Defaults:vpn env_keep += "ifconfig_pool_remote_ip common_name"
vpn ALL=NOPASSWD: /etc/openvpn/server-clientconnect.sh
vpn ALL=NOPASSWD: /etc/openvpn/server-clientdisconnect.sh

Now, let's create the main config file:
vim /etc/openvpn/server.conf

# Listen port
port 443

# Protocol
proto tcp-server

# IP tunnel
dev tun0
tun-ipv6
push tun-ipv6

# Master certificate
ca ca.crt

# Server certificate
cert server.crt

# Server private key
key server.key

# Diffie-Hellman parameters
dh dh2048.pem

# Allow clients to communicate with each other
client-to-client

# Client config dir
client-config-dir /etc/openvpn/ccd

# Run client-specific script on connection and disconnection
script-security 2
client-connect "/usr/bin/sudo -u root /etc/openvpn/server-clientconnect.sh"
client-disconnect "/usr/bin/sudo -u root /etc/openvpn/server-clientdisconnect.sh"

# Server mode and client subnets
server 10.8.0.0 255.255.255.0
server-ipv6 aaaa:bbbb:cccc:dddd:80::/112
topology subnet

# IPv6 routes
push "route-ipv6 aaaa:bbbb:cccc:dddd::/64"
push "route-ipv6 2000::/3"

# DNS (for Windows)
# These are OpenDNS
push "dhcp-option DNS 208.67.222.222"
push "dhcp-option DNS 208.67.220.220"

# Configure all clients to redirect their default network gateway through the VPN
push "redirect-gateway def1 bypass-dhcp"
push "redirect-gateway ipv6" #For iOS

# Don't need to re-read keys and re-create tun at restart
persist-key
persist-tun

# Ping every 10s. Timeout of 120s.
keepalive 10 120

# Enable compression
comp-lzo

# User and group
user vpn
group vpn

# Log a short status
status openvpn-status.log

# Logging verbosity
verb 4

Start the server

The server should now be fully operational by starting its service.
service openvpn restart

You can check if it's correctly running by typing:
service openvpn status

Client configuration

The last step consists in creating the configuration files for each client. The same client can't open simultaneous sessions so it's recommended to create a client configuration per user and per device "Peter PC, Paul iPhone, Mary laptop...".

Let's create a configuration for Nicolas's iPhone, that we'll name iphone-nicolas.

We first need to create the key and certificate, just the same way we did for the server. Leave the 2 last fields blank (challenge password and optional company name) then press Y.
cd /etc/openvpn/easy-rsa
source ./vars
./build-key iphone-nicolas

Now download these 3 files on your workstation:

  • /etc/openvpn/easy-rsa/keys/ca.crt
  • /etc/openvpn/easy-rsa/keys/iphone-nicolas.crt
  • /etc/openvpn/easy-rsa/keys/iphone-nicolas.key

Then, create the .ovpn client config file that you will then provide to the end user. Replace 1.2.3.4 By your VPN server IP address or domain name. You may have to comment the user nobody and group nobody statements on some Unix OS (for example when using Tunnelblick on MacOS X).

# Client mode
client

# IPv6 tunnel
dev tun

# TCP protocol
proto tcp-client

# Address/Port of VPN server
remote 1.2.3.4 443

# Don't bind to local port/address
nobind

# Don't need to re-read keys and re-create tun at restart
persist-key
persist-tun

# User/Group
;user nobody
;group nobody

# Remote peer must have a signed certificate
remote-cert-tls server
ns-cert-type server

# Enable compression
comp-lzo


-----BEGIN CERTIFICATE-----
(paste certificate contents from file ca.crt here)
-----END CERTIFICATE-----


-----BEGIN CERTIFICATE-----
(paste certificate contents from file iphone-nicolas.crt here)
-----END CERTIFICATE-----


-----BEGIN PRIVATE KEY-----
(paste key contents from file iphone-nicolas.key here)
-----END PRIVATE KEY-----

If you want to set a static IP for some clients, you have to create a configuration file named after the client name on the server. For example, to give IPv4 10.8.0.101 and IPv6 aaaa:bbbb:cccc:dddd:80::1001/112 to the client iphone-nicolas:

vim /etc/openvpn/ccd/iphone-nicolas

ifconfig-push 10.8.0.101 255.255.255.0
ifconfig-ipv6-push aaaa:bbbb:cccc:dddd:80::1001/112 aaaa:bbbb:cccc:dddd:80::1

Testing

If everything went fine, you should now be able to use IPv6 services using your OpenVPN server. A good way to find out if it's working is to do a IPv6 traceroute on the client:
traceroute6 ipv6.google.com

You can also test your setup compatibility by heading to ipv6-test.com with a IPv6-capable browser such as Google Chrome.