namespaced-openvpn
is a wrapper script for OpenVPN on Linux that uses network namespaces to solve a variety of deanonymization, information disclosure, and usability issues. Relative to OpenVPN's default behavior, it can be used to provide additional hardening or additional isolation (e.g., running some processes inside a VPN and some outside it, or running multiple VPN sessions concurrently and assigning different processes to different VPNs).
# create an openvpn tunnel in a new namespace, named `protected` by default:
sudo /path/to/namespaced-openvpn --config ./my_openvpn_config_file
# start an unprivileged shell in the new namespace
# anything started from this shell will use the openvpn tunnel exclusively for connectivity:
sudo ip netns exec protected sudo -u $USER -i
The main implementation idea of namespaced-openvpn
is this: instead of connecting a network namespace to the physical network using virtual Ethernet adapters and bridging, it suffices to transfer a tunnel interface into the namespace, while the process managing the tunnel (in this case openvpn
) remains in the root namespace.
OpenVPN is widely used as a privacy technology: both for concealing the user's IP address from remote services, and for protecting otherwise unencrypted IP traffic from interception or modification by a malicious local Internet provider. However, default configurations of OpenVPN are susceptible to various "leaks" that can violate these guarantees. Notable examples include route injection attacks and the so-called "Port Fail" vulnerability.
My view is that these issues have two root causes. One is that VPNs are not inherently a privacy technology --- their core function is to bridge two trusted networks using an untrusted network (generally the public Internet) as a link. On this reading, since the fundamental role of VPNs is to connect networks, rather than to isolate them (as required in the privacy use case), it is unsurprising that in the absence of additional precautions, they can allow information to escape. The second cause is that the openvpn
process itself, the system-level network configuration software (e.g., NetworkManager
), and the user's applications are all sharing a single routing table --- and when their use cases for this shared resource come into conflict, it can lead to expectations being violated.
This repository contains two approaches to these problems:
namespaced-openvpn
, a wrapper script which systematically solves all of the enumerated security issues by moving the tunnel interface into an isolated network namespaceseal-unseal-gateway
, a helper script which solves "Port Fail" only, but which otherwise preserves the default OpenVPN routing behavior
This code is released under the MIT (Expat) license.
OpenVPN's --redirect-gateway
option (described in detail in its manual page) is the basic mechanism by which it attempts to provide network-layer privacy. In the following examples, a physical interface eth0
is connected to a LAN on 192.168.1.0/24
, with 192.168.1.1
as its default gateway; 1.2.3.4
is the publicly routable address of the remote VPN server, and 10.10.10.10
is the default gateway of the tunnel interface tun0
. If the --redirect-gateway
option is set by the client or pushed from the server side, the openvpn
process performs these three steps after establishing the tunnel:
- First, create a static route for
1.2.3.4
going overeth0
via192.168.1.1
- Delete the default route over
eth0
via192.168.1.1
- Add a new default route over
tun0
via10.10.10.10
(Under a near variant of this option, --redirect-gateway def1
, steps 2 and 3 are combined by adding routes to 0.0.0.0/1
and 128.0.0.0/1
via tun0
. These routes override the default route, while being in turn overridden by the static route defined in step 1, without requiring the deletion of the original default route. The distinction between these two configurations will not be important.)
The result is a routing table that routes all public IPs over tun0
, with the exception of 1.2.3.4
, which is still routed over eth0
: without this exception, the openvpn
process itself would be stuck in a routing loop, unable to send its encrypted output packets to the Internet. The LAN subnet 192.168.1.0/24
will still be routed over eth0
as well: otherwise the system would lose access to LAN services, such as printers. We can now describe the aforementioned problems in detail:
Route injection attacks are described by Perta et al., 2015. A brief example: suppose that you're connected to a VPN, but your physical interface is connected to a malicious local gateway (e.g., a rogue wireless access point). Your DNS server is set to 8.8.8.8
, which is correctly being routed over the VPN. If the local gateway guesses the address of your DNS server, it can force a DHCP renew on your physical interface and then claim that the gateway's IP is 8.8.8.8
. Your DHCP client will then add a route for 8.8.8.8
over the physical interface, allowing interception and modification of your DNS requests.
"Port Fail" is an issue first documented by the commercial VPN provider Perfect Privacy. Some VPN providers offer to forward a port from the publicly routable VPN gateway back to the client, over the tunnel interface. Suppose a malicious adversary is a client of the same gateway 1.2.3.4
as you, and that 1.2.3.4
is also the egress for the VPN traffic. If the adversary gets a forward of port 56789 on the gateway, then tricks you into accessing a network service (e.g., by including a tracking pixel in a webpage they control) hosted at 1.2.3.4:56789
, your request will be routed over eth0
instead of tun0
and the adversary will see your real IP address.
A more salient concern than such a deanonymization attack may be the possibility of "Port Fail" occurring by accident. For example, a BitTorrent client running over the VPN with port 56789 forwarded will see trackers reporting 1.2.3.4:56789
as a leecher in its swarms --- it will then attempt to request chunks from itself over eth0
. Depending on the client's support for BitTorrent protocol encryption, this traffic may be identifiable to ISP deep packet inspection as BitTorrent traffic, or result in the disclosure of BitTorrent infohashes.
In a Medium article, ValdikSS describes a different deanonymization vulnerability related to port forwarding. Suppose that you first enable your VPN, which forwards port 56789 to you from the egress 1.2.3.4
, and then also forward port 56789 from the egress IP of your physical interface eth0
(call it 2.4.6.8
) via a NAT traversal mechanism like UPnP. An adversary who knows that you have a network service accessible at 1.2.3.4:56789
, and who can guess a set of candidate IPs including 2.4.6.8
, can attempt to access port 56789 on each of the candidate IPs. When the adversary sends a packet to 2.4.6.8
and gets a reply from 1.2.3.4
, your real IP will be revealed. (In particular, for UDP services, the IPv4 space is small enough that it's feasible to send a UDP packet to every publicly routable address.)
On Linux, this attack already has an effective mitigation: the sysctl options net.ipv4.conf.*.rp_filter
, when set to 1
(as is the default in many distributions), will drop incoming packets with "asymmetric routes" (such as the attacker's probes, which come in on eth0
but whose replies would go out on tun0
).
CVE-2019-14899 is a similar attack based on asymmetric routing (and hence is similarly mitigated by rp_filter
). The attacker sends packets to the physical interface with source IPs in the private VPN space, then monitors the replies from the VPN interface, which disclose information about the state of its TCP/IP stack (including the IP address and TCP sequence numbers, which can then be used for data injection).
"IPv6 leaks" are a fairly trivial problem: on a dual-stack system, changing the default route for IPv4 has no effect on the IPv6 stack, so applications will continue to route their IPv6 traffic over the physical interface. Despite the straightforward nature of the issue, its incidence in the wild is apparently high: Perta et al., 2015 discuss the scope of the problem.
Many residential gateways include a caching DNS resolver (such as dnsmasq
) and then push their own IP to their DHCP clients as the nameserver. Since by default, OpenVPN does not remove LAN routes and does not modify /etc/resolv.conf
, it will not prevent clients in this situation from having their DNS requests routed over the physical interface to the gateway, which will then forward them in cleartext over the public Internet.
This is a usability issue, rather than a privacy issue, but it stems from the same root cause (the shared routing table) as many of the privacy issues. Commercial VPN providers commonly balance traffic to their endpoints via round-robin DNS: the remote endpoint (e.g., vpn.example.com
) will have A records for multiple IPs. Periodically, a server may be shut down and replaced with another. By default, OpenVPN's --ping-restart
option will respond to this by gracefully restarting the session after 120 seconds of inactivity: without bringing down the routes, the client will attempt to re-resolve the server's address, then reconnect to the remote. But if name resolution returns a new IP (say 1.2.3.5
) for the remote, the client will be unable to connect to it, because only 1.2.3.4
was whitelisted to use eth0
--- 1.2.3.5
is still being routed over the defunct tun0
interface. (In fact, if DNS is being routed over tun0
, we won't even get this far, because we'll be unable to resolve the name vpn.example.com
a second time.)
The network namespace functionality of Linux provides, in effect, additional isolated copies of the entire kernel networking stack. The idea behind namespaced-openvpn
is this: the openvpn
process itself can run in the root namespace, but its tunnel interface tun0
can be transferred into a new, protected network namespace. This new namespace will have the loopback adapter lo
and tun0
as its only interfaces, and all non-localhost traffic will be routed over tun0
. The openvpn
process is not disrupted by this because it communicates with tun0
via the file descriptor it opened to /dev/net/tun
, which is unaffected by the change of namespace.
As long as sensitive applications are correctly launched within the new, isolated namespace, most of the enumerated issues are systematically resolved:
- Route injection is impossible because
NetworkManager
,dhclient
, etc. are running in the root namespace, so they can respond to routing changes in the external network environment without affecting the protected namespace. - "Port Fail" is blocked because the protected namespace has no routing exception for the remote gateway: every packet must go to
tun0
. - Asymmetric routing attacks (including CVE-2019-14899) and IPv6 leaks are blocked because the protected namespace has no access to any physical interface.
- The
openvpn
process can freely restart because it runs in the root namespace, which has unmodified routes --- so its DNS request for the remote, and then its handshake with the resulting remote IP, all useeth0
. - Accidental DNS leaks via LAN resolvers are typically blocked because the protected namespace has no direct layer-3 access to the LAN. However, additional hardening is needed to protect against some attacks and leaks --- see the "DNS hardening" section below.
This approach has some further strengths:
- It does not require any configuration changes to the root namespace, e.g., recreating
eth0
as a virtual bridge. - Non-sensitive applications are free to use the physical interfaces, which may have better bandwidth or latency characteristics.
- A
namespaced-openvpn
instance can peacefully coexist with another OpenVPN connection in the root namespace, without any concerns about conflicting private IPv4 addresses and routes. (Use the--nobind
option to prevent the secondopenvpn
process from trying and failing to reuse port 1194 in the root namespace.) openvpn
can be stopped and started without exposing processes in the protected namespace. Iftun0
goes away, those processes don't revert to using a physical interface; instead, they have no connectivity at all.
Use it like this:
sudo /path/to/namespaced-openvpn --config ./my_openvpn_config_file
The new, isolated namespace will be named protected
by default. Start an unprivileged shell in it like this:
sudo ip netns exec protected sudo -u $USER -i
Any applications started from this shell will inherit the namespace.
The Wireguard documentation describes a technique where the physical interface is moved into an isolated network namespace (named, e.g., physical
), then a tunnel is used as the sole source of connectivity for the root namespace. namespaced-openvpn
supports this configuration as well: pass the empty string as the namespace (e.g., --namespace ''
).
namespaced-openvpn
can also be used to "stack" VPN tunnels, e.g.,
sudo namespaced-openvpn --namespace levelone --config ./config_one
sudo ip netns exec levelone namespaced-openvpn --namespace leveltwo --config ./config_two
The new namespace will come up with an empty set of iptables/nftables rules. If additional firewalling is desired inside the protected namespace (although I think this is likely unnecessary), it can be added in an openvpn --up
script, which will run before the tunnel interface is transferred and given its routes. (openvpn will execute all scripts in the root namespace, so the script should first enter the namespace and then apply, or remove and reapply, the desired rules.)
Name resolution is unfortunately a complex issue, presenting several hardening challenges. Here are two known issues:
- Due to limitations in the implementation of
ip-netns(8)
and the semantics of bind mounts on Linux,namespaced-openvpn
running on a typical Linux system is still vulnerable to certain active attacks against DNS, affecting both confidentiality and integrity of DNS queries. - Certain local DNS daemons can allow DNS queries to escape the namespace.
These problems can be fully mitigated in the following way:
- Disable
systemd-resolved
,resolvconf
, andnscd
. - Instead, create
/etc/resolv.conf
as a static file. (A non-mobile system can use a LAN resolver; a mobile system can use one of the standard public resolvers, such as OpenDNS or 1.1.1.1.)
The first problem is that ip netns exec
masks /etc/resolv.conf
inside the namespace by bind-mounting /etc/netns/${namespace}/resolv.conf
on top of it. Due to an implementation detail of the bind mount system, if the inode of the mountpoint /etc/resolv.conf
changes in the root mount namespace, the bind mount will silently disappear in the protected namespace, uncovering the external /etc/resolv.conf
. Consequently, on a system configured to use resolvconf(8)
or a similar mechanism for automatically rewriting /etc/resolv.conf
in response to DHCP changes, an active attacker can inject a malicious publicly-routable DNS server into /etc/resolv.conf
inside the namespace. DNS queries to this server will be routed correctly over the VPN, but since the attacker controls the server, confidentiality and integrity are still violated. This attack is fully mitigated if /etc/resolv.conf
cannot be rewritten, i.e., if it is maintained as a static file.
systemd-resolved presents a different problem; like the earlier nscd, it provides name resolution over UNIX domain sockets (via D-Bus), which can cross network namespace boundaries. That is to say, a name resolution inside the protected namespace may be delegated to a systemd-resolved
instance running outside it, which will then issue a DNS request in cleartext. This is similar to a conventional DNS leak. Although most systems and applications are not yet using this functionality, we recommend against using systemd-resolved
for this reason.
Unfortunately, namespaced-openvpn
sacrifices one of the traditional strengths of VPNs as privacy tools: it is relatively prone to user error, because the user must be careful to start any sensitive applications in the protected namespace. Processes running in the root namespace receive no protection. Therefore, it's worth presenting an alternative approach, one applicable to a traditional configuration that alters routes in the root namespace.
seal-unseal-gateway
is a helper script that addresses the "Port Fail" vulnerability. It attempts to stop processes other than openvpn
itself from using the whitelisted route. Specifically, it uses the owner matching functionality of iptables
to drop outgoing packets to the remote gateway, unless they originate from a process with the same EUID as openvpn
. Since openvpn
will typically run as root or as a dedicated user, ordinary applications will be unable to use the route. (Some sources on the Internet claim that iptables
can do owner matching by PID, which would be more precise. However, this functionality was removed from the kernel in 2005.)
Use it by adding these lines to your OpenVPN config file (or adding the equivalent command-line options):
script-security 2
up /path/to/seal-unseal-gateway
down /path/to/seal-unseal-gateway
The other privacy issues have relatively standard mitigations. To wit, route injection can be mitigated by using only trusted DHCP servers (e.g., trusted residential gateways), IPv6 leaks can be mitigated by disabling IPv6 (sysctl -w net.ipv6.conf.all.disable_ipv6=1
), DNS leaks can be mitigated by ensuring that no LAN nameservers appear in /etc/resolv.conf
, and asymmetric routing attacks can be mitigated with the rp_filter
sysctl.
This is relatively new software. It has only been tested with a few VPN configurations, and with modern versions of OpenVPN (>=2.3.11) and the Linux kernel (>=4.4). If privacy is critical for your use case and you're not comfortable with monitoring that namespaced-openvpn
is working as expected, I can't recommend it yet. (You can use tools like iftop
and ss
, in the root namespace and the protected namespace, to verify that your traffic is being routed correctly over the VPN.)
To borrow a phrase from Stroustrup, namespaced-openvpn
"protects against accident, not against fraud." It should be impossible for any normal application to have its traffic escape from the protected namespace back to the physical interface. However, without additional hardening, there is no guarantee that a malicious application can't force such an escape --- therefore, namespaced-openvpn
should not be used by itself to "jail" an untrusted application.
There are a few different ways to launch applications in the new namespace. Unfortunately, none of them is perfect:
- Commands can be run in the
protected
namespace by prepending them withsudo ip netns exec protected sudo -u $USER
. The firstsudo
invocation gets root privileges in order to change the namespace, then the second invocation runs the command as the original user. By default,sudo
has the effect of unsetting most environment variables, which can break some application functionality (e.g., on my system,XDG_RUNTIME_DIR
must be set correctly for Pulseaudio to work). This can be fixed by adding the-E
option to bothsudo
invocations:sudo -E ip netns exec protected sudo -E -u $USER
- In addition to its jail functionality, firejail can easily launch applications in named network namespaces using its
--netns=
option, e.g.,firejail --netns=protected firefox
. I usefirejail
personally, but I'm hesitant about recommending it to others due to concerns that have been raised about its architecture. - bubblewrap is a more conservative approach to the same problem as
firejail
. It works with the double-sudo technique; see this recipe as an example for jailing Firefox.
It might be helpful to set up a trusted script or binary, with either a sudoers(5)
entry or CAP_SYS_ADMIN
, that can be used to enter the protected namespace but not to re-enter the root namespace, but I haven't fully investigated this.
Bugs:
namespaced-openvpn
tries to be a drop-in replacement foropenvpn
, but due to implementation details,route-up
directives that use multiple levels of quotes or escaping may not be handled correctly. It is recommended that anyroute-up
directive be syntactically valid both as an OpenVPN script directive and as Bourne shell.
Wishlist:
- Ideally, there would be an option to offer both limited protection to the root namespace (e.g., without protecting against route injection) and full protection to an isolated namespace. This seems difficult to achieve in a nondisruptive way.