OpenVPN Linux Client does not bring up tap0 interface

14,635

Getting TAP devices to work well on Ubuntu 17.10 and upwards (and other OS's that use systemd-resolved and/or network-manager) is no picknick in my experience, but after a lot of experimenting I've arrived at a setup which works well.

Before I will describe my solution, here's my situation and requirements. I run OpenVPN server on a router (Asus RT-AC87U with Asus Merlin firmware) in a home network, which also runs a DHCP server. The DHCP server is configured to hand out IP's for the TAP interface, and also pushes a DNS search domain. This allows discovery of connected systems by hostname (so for example a system with hostname "desktop" is discoverable as "desktop" which expands to "desktop.mydomain.com" because of the search domain "mydomain.com"). I use a TAP-network so that I can use wake-on-lan straight over the tunnel (wake-on-lan magic packets must be broadcasted on the x.x.x.255 address to the MAC-address of the network adapter that should wake the system, something a TUN-device cannot do because it operates at the wrong networking layer to allow broadcasting level 2 packages). The server has to be able to push DNS options to the client. I do NOT want all internet traffic to be routed through the tunnel - this is not that kind of VPN (I run a separate TUN-server on another port for that use case, but that's out-of-scope for this answer). Finally, when I close the tunnel, I need everything to return to the original state (which does not happen automatically).

It turns out that it was all of the experimentation that was difficult. The solution is not very complicated to configure at all, in the end. I installed OpenVPN from Ubuntu repositories, the version is 2.4.4 at the time of writing.

The OpenVPN server uses the following configuration (the server runs DNSMasq at 10.75.233.1 (the gateway IP as well) which functions as the DHCP server):

# Automatically generated configuration
daemon ovpn-server1
server-bridge   # proxy the DHCP server
push "route 0.0.0.0 255.255.255.255 net_gateway"
proto udp
port 1194
dev tap21
ncp-ciphers AES-256-GCM
auth SHA384
comp-lzo adaptive
keepalive 15 60
verb 2
push "dhcp-option DNS 10.75.233.1"   # Pushes the DNS server
tls-crypt static.key
ca ca.crt
dh dh.pem
cert server.crt
key server.key
crl-verify crl.pem
status-version 2
status status 5

# Custom Configuration
mssfix 1420  # You might not need this, it depends on your local network conditions
tun-mtu 1500  # You might not need this, it depends on your local network conditions
tls-version-min 1.2
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384

All of which is not that interesting. This is the client config:

client
dev tap0
persist-tun
persist-key
proto udp
remote mydomain.com 1194
float
ncp-ciphers AES-256-GCM
auth sha384
comp-lzo adaptive
remote-cert-tls server
auth-nocache
tls-version-min 1.2
verb 2

ca /etc/openvpn/secrets/mydomain/ca.crt
cert /etc/openvpn/secrets/mydomain/client.crt
key /etc/openvpn/secrets/mydomain/client.key
tls-crypt /etc/openvpn/secrets/mydomain/ta.key

resolv-retry infinite
nobind

Which is not that interesting either, apart from one thing - there are no up/down scripts [blow mind gif here].

The reason for this is that I think it is most elegant to let existing operating system services handle as much as possible - to move with the grain of the OS and not against it, in other words. The default up/down scripts that are installed are for manipulating /etc/resolv.conf, which Ubuntu 18.04 does not use (directly) anymore. It uses systemd-resolved. Directly manipulating resolv.conf shall lead to many tears, young jedi. This has not, however, lead to the Ubuntu devs installing up/down scripts for systemd-resolved by default either. This is left up to you (sudo apt install openvpn-systemd-resolved - the scripts will be located in /etc/openvpn). They work fine for TUN-devices, but don't get the job done properly for TAP-devices either, in my experience.

What does work very well, is explicitly adding the tap0 device to your network interfaces (/etc/network/interfaces):

# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback

allow-hotplug tap0
iface tap0 inet dhcp

Restart your networking stack using:

sudo systemctl daemon-reload
sudo systemctl restart networking

BOOM! The OS will now query the DHCP server all by itself and bring up the adapter, just like a normal interface. When you connect to your server, ifconfig tap0 should show you an adapter with an assigned IP-address. Also, systemd-resolve --status should show you, on the very first lines of the output, that the DNS search domain and DNS server pushed by the OpenVPN server are set as global config, and a quick nslookup desktop should now work (provided that host exists somewhere on the other side of the tunnel).

This however, turns out to cause some issues when you bring the tunnel down. It goes down fine, and the tap0 device is no longer visible in ifconfig output. However, systemd-resolve --status will show you that your search domain and DNS server very much continue to be part of your existence. In my case, because the domain on which I run my VPN server is "mydomain.com" and the search domain is also "mydomain.com", this caused me to be unable to connect to the VPN server again once the tunnel is down, because its actual IP can no longer be resolved as long as systemd-resolved remains befuddled with the pesky search domain. Restarting the systemd-resolved service does not solve the problem, but a reboot does. Oh mortal agony!

Thankfully, there's a solution for this as well. It turns out a sort-of-temporary configuration file is made at /run/systemd/resolved.conf.d/isc-dhcp-v4-tap0.conf which causes systemd-resolved to remain stubbornly attached to my VPN-server's DNS server. Removing the file and then restarting the service fixes the problem (sudo rm /run/systemd/resolved.conf.d/isc-dhcp-v4-tap0.conf && sudo systemctl restart systemd-resolved.service).

So, for a bare-bones setup, this will do. However, I like my luxury, and we can use a nice systemd service to provide it for us! Note that with Ubuntu's OpenVPN package, a "wildcard" systemd service is available for all OpenVPN-configs in /etc/openvpn/client. You can take a look at the unit file which should be located at /lib/systemd/system/[email protected]. It can be controlled with sudo systemctl start [email protected]. It works totally fine for TUN-style tunnels, so leave it alone. We will duplicate it to work for TAP-devices.

Create the file /lib/systemd/system/openvpn-my-tap-service-name.service and add:

[Unit]
Description=OpenVPN tunnel for mydomain.com
After=syslog.target network-online.target
Wants=network-online.target
Documentation=man:openvpn(8)
Documentation=https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
Documentation=https://community.openvpn.net/openvpn/wiki/HOWTO

[Service]
Type=notify
PrivateTmp=true
WorkingDirectory=/etc/openvpn/client
ExecStart=/usr/sbin/openvpn --suppress-timestamps --nobind --config mydomain.com.conf
ExecStopPost=/etc/openvpn/post-tap0-service-stop.sh  # Removes search domain and DNS server from systemd-resolved config
CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE
LimitNPROC=10
DeviceAllow=/dev/null rw
DeviceAllow=/dev/net/tun rw
ProtectSystem=true
ProtectHome=true
KillMode=process

[Install]
WantedBy=multi-user.target

It is a simple adaption of the default service. For it to work, your client config must be located at /etc/openvpn/client/mydomain.com.conf. Only one thing is added - a little script to remove the "temporary" systemd-resolved config file.

Create the file /etc/openvpn/post-tap0-service-stop.sh and set it executable (using chmod +x /etc/openvpn/post-tap0-service-stop.sh):

#!/bin/bash

FILE_MESSING_WITH_SYSTEMD_RESOLVED=/run/systemd/resolved.conf.d/isc-dhcp-v4-tap0.conf

echo "Removing $FILE_MESSING_WITH_SYSTEMD_RESOLVED..."
rm -f $FILE_MESSING_WITH_SYSTEMD_RESOLVED 

echo "Restarting systemd-resolved service..."
systemctl restart systemd-resolved.service

Tadaa! You can now control OpenVPN using sudo systemctl start/stop/restart/status openvpn-my-tap-service-name (after a quick sudo systemctl daemon-reload after you created the service file, of course).

There's only one finishing touch left. It's a pain to control the service with a password all the time. We can fix this by adding the commands required to control the service to a sudoers-file.

Execute the following command pkexec visudo -f /etc/sudoers.d/openvpn and enter the following:

Cmnd_Alias OPENVPN = /bin/systemctl start openvpn-my-tap-service-name, \
                     /bin/systemctl stop openvpn-my-tap-service-name, \
                     /bin/systemctl restart openvpn-my-tap-service-name

%my-username ALL= NOPASSWD: OPENVPN

Now you can execute the sudo systemctl commands for your service without a password.

Sure hope this helps!

Share:
14,635

Related videos on Youtube

Chris
Author by

Chris

Computer Engineer currently working on SCADA systems. Masters in Computer Engineering, focusing on embedded systems and FPGA system design. Experience with Linux/Windows systems administration, C/C++ development, LaTeX, Verilog/VHDL design. Former Computer Engineering instructor.

Updated on September 18, 2022

Comments

  • Chris
    Chris over 1 year

    I have an OpenVPN client on Linux connecting to an OpenVPN server. The server assigns IPs via DHCP, thus I connect using the tap interface rather than the tun interface.

    OpenVPN connects, authenticates, chats with the server, and grabs a cup of coffee, but neglects to bring up the tap0 interface. After it connects, I have to manually run ifup tap0 to bring up the interface and get an IP.

    I tried adding an up script in the config file that ran

    ip link set tap0 up
    dhclient tap0
    

    but it only brought up the device, it didn't get the IP.

    sanitized client.conf:

    # Openvpn config to connect to <DOMAIN>
    tls-client
    dev tap0
    ; dev tap ; this didn't work either
    
    ; run script after init (supposedly)
    ; script-security 2 ; to run up script
    ; up /etc/openvpn/tap0up.sh ; bring up tap0
    ; up-delay ; Didn't work with or without this; 
    
    proto udp
    remote <DOMAIN> 1194
    resolv-retry infinite
    nobind
    persist-key
    persist-tun
    
    ca ca.crt
    cert monkey.crt
    key monkey.key
    
    ns-cert-type server
    comp-lzo
    verb 3
    

    And ifcfg-tap0, because I refuse to believe in NetworkManager

    DEVICE=tap0
    BOOTPROTO=dhcp
    ONBOOT=yes
    PEERDNS=no
    ZONE=trusted
    

    Thanks in advance!

    Edit: Fun fact: it adds the correct static routes to my routing table for the network it forgot to bring up.

    Edit2: OpenVPN Server config, by request:

    local <my.ext.ip>
    port 1194
    mode server
    tls-server
    proto udp
    
    dev tap0
    ; dev tap
    
    ca keys/ca.crt
    cert keys/zombie.crt
    key keys/zombie.key
    dh keys/dh2048.pem
    
    keepalive 10 120
    comp-lzo
    
    persist-key
    persist-tun
    
    status zombievpn-status.log
    verb 3
    
    • Thomas
      Thomas almost 10 years
      could you also post your openvpn config from the server?
    • Chris
      Chris almost 10 years
      @Lawrence Close; Fedora 19.
    • Chris
      Chris almost 10 years
      @Thomas I posted the server config above
    • Cray
      Cray over 9 years
      Isn't the problem in "tls-client" setting. It probably should just be "client". According to forums.openvpn.net/topic8924.html, that setting makes the client not load all the settings it can from the server.
    • Chris
      Chris over 9 years
      @Cray If I use "client" instead of "tls-client" then the client would be unable to be allocated an IP via DHCP; the "client" param sets a static IP. Once the interface is up, clients can connect fine - without "client". The problem is that it just doesn't bring up the interface.