Opening a shell connected to wireguard, but not the rest of your system (Linux)
Problem: you’re trying to access a blocked website and want to curl
something
so the request goes over a VPN. Sometimes you can solve this with --proxy
variants but let’s say that’s not an option. Maybe you’re using software other
than curl. What you’re looking for is a shell that any command you run, even
software not aware of proxies or VPNs, gets routed over the VPN.
This is surprisingly easy to do, but requires some prior know-how.
Let’s assume you have a wg-quick
-compatible wireguard config file like this,
[Interface]
PrivateKey =
Address = 1.2.3.4/32,aaaa:bbbb:cccc:dddd:eeee:ffff:0000/128
[Peer]
PublicKey =
AllowedIPs = 0.0.0.0/0,::0/0
Endpoint = 5.6.7.8:51820
Simply run,
modprobe wireguard
ip netns add vpn
ip link add dev wg0 type wireguard
ip link set wg0 netns vpn
ip netns exec vpn bash
# Inside shell launched by last command
wg setconf wg0 <(wg-quick strip wireguard.conf)
ip address add dev wg0 '1.2.3.4/32'
ip address add dev wg0 'aaaa:bbbb:cccc:dddd:eeee:ffff:0000/128'
ip link set up dev wg0
ip route add default dev wg0
ip link set up dev lo
curl https://api.ipify.org/ # Test internet, returns external IP address
These commands will likely need to be run as root, but then you can downgrade
with sudo
.
The Explanation
We start with modprobe wireguard
to ensure the wireguard kernel module is
loaded. If something isn’t working you may also want to add
echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control
which will
enable more debugging info to dmesg.
Next we create a named network namespace1 (abbreviation netns) with ip netns add vpn
. Every process on Linux (since 2.6.24) is attached to a netns that
determines how traffic is routed to and from processes. There is always a
default netns which often is the only one.
From the documentation,
A network namespace is logically another copy of the network stack, with its own routes, firewall rules, and network devices.
You may notice the default netns doesn’t show up when running ip netns list
.
This is because it isn’t a named network namespace, which is a higher level
concept managed by the ip
command. The ip
command manages a list of named
namespaces in /var/run/netns
.
Now we create the wireguard device with ip link add dev wg0 type wireguard
.
The device name is wg0
but we could’ve chosen other names as well. Then we
move the device to the netns we just created with ip link set wg0 netns vpn
.
The wireguard device has the nice feature that it remembers what netns it got created in, not just the one it’s currently in. When the wireguard device connects to peers it does so via the netns it was created in. This is important because our “vpn” netns doesn’t have internet – the default namespace does – so we need some sort of connection to the default netns. This also means you don’t have to mess with configuring the default netns and possibly messing up other automated configurations.
Next we set the peers and private keys. First wg-quick strip wireguard.conf
removes the Address
field from the config file above, since this field is not
supported by wg setconf
. Then the result gets passed as wg setconf wg0 <output of previous wg-quick strip>
.
Since the Address
field isn’t supported by wg setconf
this needs to be setup
manually. We do this with ip address add dev wg0 <addr>
for each address. If
you have other “Interface” fields you want to set that aren’t supported, such as
DNS, you can see the wg-quick
source
and copy how that’s setup.
Then we need to “turn on” the wireguard device with ip link set up dev wg0
.
Sometimes this will fail if there’s a problem with your config. We then turn on
the local device, which is created automatically with ip link set up dev lo
.
You can use wg show
at this point for debugging, to inspect the peers and
detect if traffic is flowing through the peers. Note even if everything is setup
correctly. If not traffic is passing through (as there won’t be any at this
point) the connection to the peers will appear silent, but once they connect it
should show inbound and outbound traffic statistics.
Last piece is to setup the netns’s routes. The routes define how outgoing
traffic should be routed, which in this case will be to the wireguard
device. We can set this up with ip route add default dev wg0
. We don’t have to
add a route for localhost since this will automatically work.
The last command curl https://api.ipify.org/
is there to test everything.
Opening a Browser
You could do something like,
sudo ip netns exec vpn sudo -u eric google-chrome-stable
To open a browser running in the netns. This should work but could be a bit dangerous if leaks are a potential problem. For one if the browser could decide to use an existing process that is in the correct netns. You also have to make sure DNS is setup correctly, or use DNS over HTTPS.
Isolating only a single set of processes in a VPN will be more dangerous, because you can’t make an effective “kill switch”.
Sharing services
If you listen on a port in the default netns you will not be able to access from
the vpn
netns or vice versa. We can solve this by setting up a veth.
Let’s say on the default netns I start a server,
python -m http.server 8000 # Start web sever on port 8000
We can’t connect to this as localhost in the default netns. Instead, on the default netns create a veth pair,
ip link add veth0 type veth peer name veth0_peer
Then add the peer to the netns,
ip link set veth0_peer netns vpn
On each side the IP address needs to be setup. We’ll give the default namespace
IP 10.255.255.0
and the vpn netns IP 10.255.255.1
.
ip address add dev veth0 '10.255.255.0/31'
ip netns exec vpn ip address add dev veth0_peer '10.255.255.1/31'
ip link set veth0 up
ip netns exec vpn ip link set veth0_peer up
Now this should work,
ip netns exec vpn curl http://10.255.255.0:8000/
The veth0
and veth0_peer
devices can be thought of as either ends of an
ethernet cable, capable of passing traffic back and forth between them. If we
want more flexibility we hook one end up to a bridge which allows, for example,
many different network namespaces to be connected together using a single
subnet. I found this
article
helpful in understanging how that’s usually set up.
Cleanup
All the resources used by the our vpn
will get cleaned up when the namespace
is deleted. To do this,
- Delete all processes using the netns, for instance
kill $(ip netns pids vpn)
- Remove the netns with
ip netns del vpn
The netns gets cleaned up by the kernell when there’s no more references to
it, which could be either the file ip
maintains in /var/run/netns
or a
process currently using the namespace. When the netns is removed you should
notice veth0
no longer shows up in ip address
.
-
Linux namespaces are a broad topic and there’s more types than just network namespaces; for this reason I’m being careful not to refer to a network namespace as simply a namespace. ↩︎