OpenVPN Server Tutorial

De Wiki LDN
Aller à : navigation, rechercher

We present the OpenVPN set up used by LDN. The goal of our VPN is to provide real IP connectivity: static IPv6, static public IPv4 (without NAT), no filtering.

Warning: In this document, the term "client" is a reference to the role of a given OpenVPN instance in a VPN connection. LDN does not have client but members.




  • public IP addresses (avoid NAT as much as possible);
  • good performance if possible;
  • good connectivity (should work on most paranoid/filtered networks)


  • covert operations (if your life is at risk you probably want to use a stealthier solution)


  • Addressing:
    • a IPv6 delegated prefix per user (because we should do it);
    • and one interconnection IPv6 address for per user;
    • one public IPv4 addresses per user ;
    • fixed IP addresses and ranges per user;
  • Transport:
    • accessible over UDP for optimal performance;
    • accessible over TCP as well for optimal connectivity over pranoid networks;
    • accessible over both IPv4 and IPv6;
    • accessible over any (TCP or UDP) port for optimal connectivity over pranoid networks;
  • L3 VPN (we provide IP connectivity, no Ethernet layer);
  • certificate-based authentication.


IPv6 is the protocol we should be using, we have to provide IPv6 connectivity to our users. Moreover, it is not necessary to provide a single IPv6 address, end users should have a delegated IPv6 they are allowed to manage for themselves.

Size of the delegated prefix:

  • RFC3177 recommends a /48 in general. A /64 could be used if it is known that only a single network will be used. A /128 when is is known that only one device will be used.
  • Nibbles should not be broken i.e. the prefix length should be a multiple of 4.

Benefits for the users:

  • Having at least one /64 means that SLAAC will work;
  • Having a larger block than a /64 means that the user will be able to create/manage many different /64 networks. With a /40, the user can create 2^(64-48) = 65536 networks (/64).

The VPN provider needs to allocate two IPv6 ranges for the VPN:

  • One /64 interconnection IP range. This range is assigned to the network interfaces of the VPN. The VPN server has one IP address in this range. Each VPN client has one IP address in this range.
  • Many /48 ranges (probably grouped into one range). Each range is allocated to a user and managed by this user. LDN uses a /40 range for this which is used to create 256 /48 ranges (for at most 256 users).


We do not have enough IPv4 address to allocate a delegated prefix for the user. Instead, we allocate a single IPv4 address for the user taken from an interconnection prefix. In order to be able to use more than one device, the user will have to use PAT.

The VPN provider needs to allocate one interconnection IPv4 range for the VPN. The VPN server will use one IP range in this range and each client will use an IPv4 address in this range. The first and last addresses cannot be used because they are reserved for the network address and broadcast address.

Underlying transport protocol

VPN should be used over unreliable transports protocols:

  • IP is supposed to work over unreliable channels: packets can de dropped, packets be come out-of-order. Using a reliable layer to transport IP ie unecessary and counter-protductive.
  • UDP is usually used for time-sensitive protocols and can contionue to work if packets are dropped. Adding a reliable protocol to transport UDP datagram will remove the benfits from using UDP: the latency will increase when packets are dropped because they will be retansmitted by the TCP layer.
  • TCP over TCP is a bad idea.

UDP should be prefered. However in many cases, on non-neutral the UDP protocol may be filtered. TCP is usually available (at least for ports such as 80 or 443). For this reason, the VPN is available over UDP and over TCP. However, a single OpenVPN instance can only use either UDP or TCP (not both): in order to support both we will be running two OpenVPN processes.

The standard OpenVPN port is 1194 but is is often filtered. For this reason, our VPN is available over (nearly) every port. By using TCP 80 or TCP 443, the user will probably be able to use the VPN even over crappy networks. In order to do this, we will use PAT.

If UDP is available it should be provide better results. However, the users' underlying network is using QoS to give priotity to TCP traffic (maybe on some ports), you might have better performance using TCP: if you find you have better performance with TCP, you might be right.

L3 vs. L2 VPN

OpenVPN can work either as a L2 or a L3 tunnel:

  • a layer 2 tunnel transport link-layer protocols (Ethernet frames in this case);
  • a layer 3 tunnel transport network-layer protocol (IPv4 and IPv6 in this case).

We can do IP over a L2 tunnel (IP over Ethernet) but as we want to provide IP connectivity, we don't need the Ethernet layer: we want a L3 tunnel. A L2 tunnel might be useful in order to bridge the VPN clients on an existing LAN Ethernet link. Moreover, using a L3 tunnel we can avoid L2 attacks over the VPN.

The type of VPN may as well be described by the type of virtual network interface:

  • a TAP interface is a virtual Ethernet/L2 interface (it transports Ethernet frames);
  • a TUN interface is a vritual IP/L3 interface (it transports IP packets).

In the OpenVPN, configuration we choose the type of VPN by choosing the type of interface: TAP for a L2/Ethernet VPN and TAP for a L3/IP VPN.

Layers \ VPN type L2/TAP L3/TUN
Overlay protocol Ethernet IPv4 + IPv6
VPN protocol OpenVPN
Underlying transport protocol (L4) TCP or UDP
Underlying network protocol (L3) IPv4 or IPv6


Here is our VPN protocol stack:

Overlay network protocol (L3) IPv4 + IPv6 (TUN)
VPN protocol OpenVPN
Underlying transport protocol (L4) Either TCP or UDP (any port)
Underlying network protocol (L3) Either IPv4 or IPv6


What you need to to:

  1. set up the routes to the VPN server for your VPN IP ranges;
  2. enable IP forwarding (IPv4 and IPv6) on the VPN host;
  3. setup NAPT for maximum accessibility;
  4. configure two OpenVPN server instances (one for UDP and one for TCP);
  5. setup a server CA and generate a keypair/certificate for the server;
  6. setup a client CA and generate a keypair/certificate for each client;
  7. add the handler script for dynamically routing the packets eo either the UDP instance or the TCP instance;
  8. add sudo configuration for the handler script (allow it to change the routing table);
  9. assign IP addresses and ranges to each client.

More details below.

OpenVPN Configuration

We create two OpenVPN instances with similar but slightly different configuration files (one for UDP, the other for TCP). In the following file, the differences are grouped into the "UDP" and "TCP" sections:

  • the underlying transport protocol (proto);
  • the interface name with explicit reference ot the instance (otherwise it is difficult to find the relation bewteen the interfaces and the instances);
  • the syslog name (not stricly necessary but thos names are enforced for OpenVPN instances launched by the init system on Debian and derivatives);
  • tls-auth is only relevant for UDP;
  • the socket for the management interface.
 # [Interface]
 ; listen a.b.c.d
 port 1194
 # [Enable UDP…]
 dev tunudp
 proto udp6
 tls-auth /etc/openvpn/keys/ta.key 0 
 syslog ovpn-udp
 management /var/run/openvpn.udp.socket unix
 # [… or TCP]
 dev tuntcp
 proto tcp6-server
 syslog ovpn-tcp
 management /var/run/openvpn.tcp.socket unix
 # [Mode]
 # See the 'dev' interface for TAP/TUN setting (L2/L3).
 mode server
 topology subnet
 push "topology subnet"
 # [User]
 user openvpn
 group openvpn
 # [TLS]
 ca /etc/openvpn/keys/ca.crt
 remote-cert-tls client
 cert /etc/openvpn/keys/server.crt
 key /etc/openvpn/keys/server.key
 dh /etc/openvpn/keys/dh2048.pem
 crl-verify /etc/openvpn/keys/crl.pem
 # [IPv4]
 ifconfig $IP4 $NETMAST4
 push "route-gateway $ROUTE4_1"
 push "route-gateway $ROUTE4_2"
 push "route-gateway $ROUTE4_3"
 push "route-gateway $ROUTE4_4"
 ; push "redirect-gateway def1"
 # [IPv6]
 ifconfig-ipv6 $IP6/$NETMASK6 $IP6
 push "route-ipv6 $ROUTE6_1"
 push "route-ipv6 $ROUTE6_2"
 push "route-ipv6 $ROUTE6_3"
 push "route-ipv6 $ROUTE6_4"
 # [DHCP emulation]
 push "dhcp-option DNS $DN1"
 push "dhcp-option DNS $DN2"
 push "dhcp-option DNS $DN3"
 push "dhcp-option DNS $DN4"
 # [Misc]
 keepalive 10 120
 comp-lzo adaptive
 push "comp-lzo adaptive"
 max-clients 64
 # [Scripts]
 script-security 2
 ; up /etc/openvpn/handler
 ; tls-verify /etc/openvpn/handler
 client-connect /etc/openvpn/handler
 ; route-up /etc/openvpn/handler
 client-disconnect /etc/openvpn/handler
 ; down /etc/openvpn/handler
 ; learn-address /etc/openvpn/handler
 ; auth-user-pass-verify /etc/openvpn/handler
 # [Logs]
 verb 4
 mute 10
 # See the 'syslog' configuration as well.
 # [Management]
 # Open console with: socat STDIO /var/run/
 management-client-user root
 # See the 'management' configuration as well.

The corresponding client configuration:

 # Choose one protocol:
 proto udp
 ; proto udp6
 ; proto tcp6-client
 ; proto tcp-client
 port 1194
 # For UDP:
 # Certs
 remote-cert-tls server
 ca /etc/openvpn/keys/ca_server.crt
 cert /etc/openvpn/keys/cmichu.crt
 key /etc/openvpn/keys/cmichu.key
 # Other
 dev tun
 keepalive 10 30
 comp-lzo adaptive
 verb 3
 mute 5
 status /var/log/openvpn-client.status
 log-append /var/log/openvpn-client.log
 route-ipv6 2000::/3
 redirect-gateway def1 bypass-dhcp

User configuration

The solution we are not using: client-config-dir

For assigning specific configuration to certain clients (such as IP addresses), OpenVPN ship with the client-config-dir. However, the client implementation has some limitations (related to IPv6 and IPv6 delegated prefix) so we used another solution. There is a RADIUS plugin but it seemed to be somewhat overkill so we did not try to use it.

The client-config-dir expect to find in a given directory one configuration file per client with the configurations specific to this user. This is the configuration specifc to the user:

# IPv4 interconnection address:

# IPv6 interconnection address:
ifconfig-ipv6-push 2001:913:0:2005::1/64 2001:913:0:2005::

# IPv6 delegated prefix:
iroute-ipv6 2001:DB8:1::/48 
# We pass the IPv6 delegated prefix to a (custom) environment variable,
# the client might use it is some handler script:
push "setenv-safe DELEGATED_IPV6_PREFIX 2001:DB8:1::/48"

Motivation for another solution

There are two issues with this:

  • the interconnection IPv6 address is not easily exported to the handler scripts but we need them to set up the routes correctly (because of we share the same IP addresses for the two server instances);
  • the IPv6 delegated prefix is not exported as well;
  • we need to repeat ourselves for the IPv6 delegates prefix.

Only the last issue is relevant if you do not need handler scripts (because you use a single instance).

Our solution

Instead of relying the client-config-dir, we define the client configuration as shell variables in a file /etc/openvpn/users/$common_name:

# IPv4 interonnection address:
# IPv6 interconnection address:
# IPv6 delegated prefix:

This configuration will be used:

  • to generate the client-specific configuration (in a DRY way);
  • will be used directly in handler scripts (to add/remove the routes).

Generated client configuration:

# IPv4 interonnection address:
# IPv6 interconnection address:
ifconfig-ipv6-push 2001:913:0:2005::1/64 2001:913:0:2005::
# IPv6 delegated prefix:
iroute-ipv6 2001:DB8:1::/48 
# IPv6 delegated prefix annoucement with a variable environment:
push "setenv-safe DELEGATED_IPV6_PREFIX 2001:DB8:1::/48"

Generated routes invocations:

# Add explicit routes for interconnection IPs in order to fix ambiguity:
ip -4 route add dev $dev protocol static
ip -6 route add 2001:DB8:2::1 128 dev $dev protocol static
# Add route for delegated prefix:
ip -6 route add 2001:DB8:1::/48 via 2001:DB8:2::1 dev $dev protocol static

The advantages of this solution over the client-config-dir:

  • we define all the variables that are relevant to us (if all IP ranges are connected we could only derive a client index and derive all the ranges from it) in a DRY way;
  • we could fetch those variables from another source of data;
  • the variables are direcly available in all handler scripts (it could juste as well come from a SQL database or whatever);
  • it is quite easy to add specific behaviour for a specific client (by adding a new variables and the corresponding behaviour in the handler scripts, we can add new behaviour).


All of this is handled by a single handler script which is called by the server for different events with intersting informations passed by the environment (the event, the identifier of the client…):

  • when a client connects (client-connect) we generate the client configuration in the file passed as "$1" and setup the routes;
  • when a client disconnects (client-disconnect), we tear down the routes;
  • script-security 2 is needed to be able to call external commands.

Handler script:

# File: openvpn/handler

if [ "$(id -ru)" = "0" ]; then

# ##### Utility

log() {
    local level
    logger -t"ovpn-script[$$]" -pdaemon."$level" -- "$@"

# ##### Functions

# Load user information from /etc/openvpn/users/$common_name
get_user_info() {
    if ! echo "$common_name" | grep ^[a-zA-Z][a-zA-Z0-9]*$; then
        log notice "Bad common name $common_name"
        return 1

    if ! . "/etc/openvpn/users/$common_name" ; then
        log notice "No configuration for user $common_name"
        return 1

# Write user specific OpenvPN configuration to stdout
create_conf() {
    if ! [ -z "$IP4" ]; then
        echo "ifconfig-push $IP4 $ifconfig_netmask"
    if ! [ -z "$IP6" ]; then
        echo "ifconfig-ipv6-push $IP6/64 $ifconfig_ipv6_local"
    if ! [ -z "$PREFIX" ]; then
        # Route the IPv6 delegated prefix:
        echo "iroute-ipv6 $PREFIX"
        # Set the OPENVPN_DELEGATED_IPV6_PREFIX in the client:
        echo "push \"setenv-safe DELEGATED_IPV6_PREFIX $PREFIX\""

add_route() {
    "$SUDO" ip route add "$@" || "$SUDO" ip route replace "$@"

# Add the routes for the user in the kernel
add_routes() {
    if ! [ -z "$IP4" ]; then
        log info "IPv4 $IP4 for $common_name"
        add_route $IP4/32 dev $dev protocol static
    if ! [ -z "$IP6" ]; then
        log info "IPv6 $IP6 for $common_name"
        add_route $IP6/128 dev $dev protocol static
    if ! [ -z "$PREFIX" ]; then
        log info "IPv6 delegated prefix $PREFIX for $common_name"
        add_route $PREFIX via $IP6 dev $dev protocol static

remove_routes() {
    if ! [ -z "$IP4" ]; then
        "$SUDO" ip route del $IP4/32 dev $dev protocol static
    if ! [ -z "$IP6" ]; then
        "$SUDO" ip route del $IP6/128 dev $dev protocol static
    if ! [ -z "$PREFIX" ]; then
        "$SUDO" ip route del $PREFIX via $IP6 dev $dev protocol static

set_routes() {
    if ! add_routes; then
        return 1

# ##### OpenVPN handlers

client_connect() {
    get_user_info || exit 1
    create_conf > "$conf"

client_disconnect() {
    get_user_info || exit 1

# ##### Dispatch

case "$script_type" in
    client-connect)    client_connect "$@" ;;
    client-disconnect) client_disconnect "$@" ;;

Multiple ports

Some users might be in network which filter some ports. In order to provide a better connectivity to the VPN endpoint, we use PAT to map (nearly) each port to the VPN one:

 modprobe ip_tables
 modprobe ip_conntrack
 iptables -t nat -F
 # Allow incoming SSH:
 iptables -t nat -A PREROUTING -d -p tcp -m tcp --dport 22 -j ACCEPT
 # NAT everyting else to OpenVPN:
 iptables -t nat -A PREROUTING -d -p tcp -m tcp --dport 1:65535 -j DNAT --to-destination
 iptables -t nat -A PREROUTING -d -p udp -m udp --dport 1:65535 -j DNAT --to-destination

We only need to appy connection tracking for packets involving the server IP addresses:

 iptables -t raw -A PREROUTING -s -j ACCEPT
 iptables -t raw -A PREROUTING -d -j ACCEPT
 iptables -t raw -P PREROUTING -j NOTRACK

The port 443 is especially usually not filtered. However, if DPI is used the connection won't work because OpenVPN does not use TLS/TCP (but TLS/OpenvPN/TCP).

The configuration is identical in IPv6 (by replacing ip6tables and the correct IP6v address).

Running as non root

It is better to avoid running the OpenVPN as root:

Let's create a user:

adduser --system --no-create-home --group openvpn

We can configure it in OpenVPN with:

user openvpn
group openvpn

The hanler script automatically dectects that it is launched as non root and uses sudo in order to change the routing table. We need to allow this user to user sudo for this purpose (in /etc/sudoers.d/openvpn):

openvpn	ALL=(root) NOPASSWD:/bin/ip route *, /bin/ip -6 route *

Another solution is to call the handler with sudo.

TLS configuration

Configuration details

This section contains some explanations about some configuration parameters.


We configured a layer 3 VPN, based on an TUN interface. This type of tunnel and interface does not handle layer 2 protocols (eg. Ethernet) but directly layer 3 protocols (namely IPv4 and IPv6):

  • the messages passing in and out of the TUN interface are IP packets;
  • the messages passing in and out of the tunnel are IP packets;
  • there is no notion of MAC address;
  • there is no notion of neighbors;
  • a TUN interface is expected to be a point-to-point interface (between two machines, denoted POINTOPOINT in `ip addr`).
 $ ip addr
 173: tunudp: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
     inet 8192.51.100.126/25 brd scope global tunudp
     inet6 2001:DB8:2::/64 scope global 
        valid_lft forever preferred_lft forever

In principlen the server should have one TUN interface per client. However this is not the case. Each instance of the OpenVPN server creates a single TUN interface for all the clients. AS this is a TUN interface, the OpenVPN instance only gets an IP packet from the kernel without any L2 information (MAC address) in order to device to which clients it should send the packet: the OpenVPN instance uses the destination IP address in order to device to which client it should send the packet. In a sence, the OpenVPN instance does some routing itself and has some form of internal (IP) routing table. The entries of this nternal routing tables are called `iroute` by OpenVPN: it associated IP addresses to a given client:

  • when the OpenVPN receives a packet from the kernel, it uses the iroute table in order to find to which client, it should forward the packet;
  • moreover, when OpenVPN receives a packet from a client, it checks that the source IP address mtaches one of the IP addresses associated with this client in the iroute table.

Those iroutes are added:

  • either implicitly by a `ifconfig-push` directive;
  • or explicitely by a `iroute` directive in user-specific file.

The latter is useful whe, IP addresses must be routed to the client but are not directly attached to the client such as for the IPv6 delegated prefixes.

Client-client communications

TLDR: client-to-client does not work with our configuration (two OpenVPN server instances).

When client-to-client is disabled (the default), the packets from a client to another client go through the host IP layer (iptables, routing table, etc.) of the machine hosting the VPN server: if IP forwarding is enabled, the host might forward the packet (using its routing table) again to the TUN interface and the VPN daemon will forward the packet to the correct client inside the tunnel.

            | IP Layer          |  (routing table)
               ^          |
           3   |          v  4   
            | TUN device (tun0) |
              ^           |
           2  |           v  5   
            | OpenVPN instance  | (iroute table)
              ^           |
           1  |           |  6   
              |           v
  .----------------.  .----------------.
  | Client a       |  | Client b       |
  '----------------'  '----------------'

When client-co-client is enabled, the VPN instance fowards client-to-client packets internally without involving the IP layer of the system. The system IP stack does not see the packets at all:

            | IP Layer          | (routing table)
            | TUN device (tun0) |
            | OpenVPN instance  | (iroute table)
              ^           |
           1  |           |  2   
              |           v
  .----------------.  .----------------.
  | Client a       |  | Client b       |
  '----------------'  '----------------'

This options is not suitable for our use case with two OpenVPN instances using the same IP ranges. If one client on the UDP instance try to communicate with one client of the TCP instance with client-to-client enabled, they won't be able to communicate:

  1. the UDP instance gets the packet from the tunnel;
  2. the destination address matches the range of the VPN instance;
  3. it uses its iroute table but does not find a match because the other client is connected on the TCP instance;
  4. as a result the packet is dropped.
            | IP Layer                                      |
            .-------------------.       .-------------------.
            | TUN device (tun0) |       | TUN device (tun1) |
            '-------------------'       '-------------------'
            .-------------------.       .-------------------.
            | OpenVPN instance  |       | OpenVPN instance  |
            '-------------------'       '-------------------'
           1  |                               
            .----------------.           .----------------.
            | Client a       |           | Client b       |
            '----------------'           '----------------'

Without client-to-client, the packet is propagated to the kernel IP stack. The routing table of the kernel will forward the packet to the correct TUN interface and, as a consequence, to the correct OpenVPN instance.

            | IP Layer                                      |
              ^                            |
           3  |                            |  4   
              |                            v
            .-------------------.       .-------------------.
            | TUN device (tun0) |       | TUN device (tun1) |
            '-------------------'       '-------------------'
              ^                            |
           2  |                            |  5
              |                            v
            .-------------------.       .-------------------.
            | OpenVPN instance  |       | OpenVPN instance  |
            '-------------------'       '-------------------'
              ^                            |
           1  |                            |  6  
              |                            v
            .----------------.           .----------------.
            | Client a       |           | Client b       |
            '----------------'           '----------------'

Management interface

We configured one UNIX socket managements interface per OpenVPN instance. We can connect to the management interfaces with:

 socat STDIO UNIX:openvpn.tcp.socket

Once connected we can issue some commands such as:

  • `help`
  • `status`, show the connected clients and the iroutes
  • `kill john`, kill the VPN connection for `john`

CA setup and management

Security considerations



  • using a well known CA (no!)
  • AC separation

OpenVPN and TLS

Even if OpenVPN is used with TLS, someone looking at the packets will be able to recognise that OpenVPN is used (and not HTTP/TLS for example) because TLS is used over VPN and not the other way around: if you do not want people to know that you are using OpenVPN, you have to encapsulate it over a secure protocol (such as TLS).

You can try it yourself: record the packets of a OpenVPN connection (over TCP, port 443).

# Record packets to the VPN server on port 443:
sudo tshark -i eth0 -w vpn.pcap "net & tcp port 443"

# In another shell, open the VPN connection:
openvpn openvpn.conf

# Kill both of them and loot at the file in wireshark:
wireshark vpn.pcap

Wireshark assumes that the traffic encapsulated in above TCP is TLS but cannot groke it (because it is not TCP). Now right click on any packet and choose to "Decode as OpenVPN": now you can see some of the content of the OpenVPN packets (not the encrypted payload):

Internet Protocol Version 4, Src: (, Dst: (
Transmission Control Protocol, Src Port: 49937 (49937), Dst Port: 443 (443), Seq: 1, Ack: 17, Len: 16
OpenVPN Protocol
 Packet Length: 14
 Type: 0x38 [opcode/key_id]
 Session ID: 11903765838711167671
 Message Packet-ID Array Length: 0
 Message Packet-ID: 0

On some packets, you can find TLS messages encapsulated over OpenVPN:

Internet Protocol Version 4, Src: (, Dst: (
Transmission Control Protocol, Src Port: 49937 (49937), Dst Port: 443 (443), Seq: 45, Ack: 69, Len: 281
OpenVPN Protocol
OpenVPN Protocol
 Packet Length: 23
 Type: 0x20 [opcode/key_id]
 Session ID: 11903765838711167671
 Message Packet-ID Array Length: 0
 Message Packet-ID: 3
 Message fragment (9 bytes)
 3 Message fragments (209 bytes): #12(100), #12(100), #12(9)
Secure Sockets Layer
 TLSv1 Record Layer: Handshake Protocol: Client Hello
  Content Type: Handshake (22)
  Version: TLS 1.0 (0x0301)
  Length: 204
  Handshake Protocol: Client Hello

Diffie Hellman parameters

Q: Should I generate my own Diffie-Hellman parameters?

Yes, unless you want to be hacked by the NSA.