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
server-bridge # proxy the DHCP server
push "route 0.0.0.0 255.255.255.255 net_gateway"
keepalive 15 60
push "dhcp-option DNS 10.75.233.1" # Pushes the DNS server
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
All of which is not that interesting. This is the client config:
remote mydomain.com 1194
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 (
# interfaces(5) file used by ifup(8) and ifdown(8)
iface lo inet loopback
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/openvpn-client@.service. It can be controlled with
sudo systemctl start openvpn-client@my-config-name-without-.conf. 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:
Description=OpenVPN tunnel for mydomain.com
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
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):
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!