Skip to content

Instantly share code, notes, and snippets.

@talisein
Last active November 30, 2023 02:40
Show Gist options
  • Save talisein/f6637e0ae04b592c293922566eb1d45a to your computer and use it in GitHub Desktop.
Save talisein/f6637e0ae04b592c293922566eb1d45a to your computer and use it in GitHub Desktop.
Torrenting in a network namespaced wireguard vpn

Network Namespaced NordVPN & rtorrent

This is my approach for running NordVPN in a network namespace, and then running rtorrent in that netns.

I did this on Fedora 39 in November 2023.

NordVPN Details

You need to get the wireguard details that the NordVPN client uses to get the private key, local ip, and local port number. This post is helpful.

Basically, run the official nordvpn client in Nordlynx mode and connect. Run wg to collect the local port number in use -- it may be different from the standard, and its helpful to know which one is definitely working. You also need to sudo wg show nordlynx private-key to get the private key. It should go without saying that if anyone in the world gets this key they can use your NordVPN account, so keep it secret & safe. NordVPN doesn't have an official way to issue these private keys, so I don't know how you might get a new one if you screw up...

NOTE: If you change your NordVPN password, your private key might get changed too.

You can also collect the public key & endpoint:port of the server you're connecting too. If you want an API to get public keys from NordVPN, this gist has a method.

After you collet all that data, you'll want to disconnect from the vpn and systemctl stop nordvpnd.service and disable it as well after you get this setup working.

Network Namespace

I was originally working from this blog and this repo of systemd services. However, these examples are from 2019 and systemd changed a little, so the services in this gist are what I am using. In particular, PrivateNetwork=yes seems to be out of vogue.

/etc/netns/nordvpn/resolv.conf sets up DNS resolution for stuff in the nordvpn namespace. netns@.service creates a new network namespace and nothing more. wireguard-nordvpn.service gets the vpn running in the namespace. Wireguard is the only iterface in the namespace, so anything that goes into the nordvpn namespace can only talk through the VPN.

One thing you need to realize is that NetworkManager isn't going to be able to touch this thing, so its also not managing routing or dns resolution or telling anything over D-Bus about it.

Testing

First you can check your 'normal' environment to obvserve it isn't VPN'd:

$ curl -s 'https://api.nordvpn.com/vpn/check/full' | jq
{
  "ip": "1.2.3.4",
  "isp": "ISP",
  "status": "Unprotected",
  "country": "United States",
  "code": "US"
}

Then you can check from within the namespace:

$ sudo ip netns exec nordvpn curl -s 'https://api.nordvpn.com/vpn/check/full' | jq
{
  "ip": "4.5.6.7",
  "isp": "WhateverNordvpnCallsItself",
  "status": "Protected",
  "country": "United States",
  "code": "US"
}

rtorrent

Personally I run rtorrent in a shell, so here's how I run it: sudo ip netns exec nordvpn sudo -u MYUSERNAME rtorrent

Just to explain what that command does if you have copy and pasted everything until now without understanding... ip netns exec nordvpn CMD will run CMD in an environment where it is in the nordvpn network namespace. One of the features of this command is that /etc/netns/nordvpn/resolv.conf will be bind mounted to /etc/resolv.conf, so things should magically work even if they don't know about network namespaces.

ip netns exec has to be run as root, thus we used sudo. But that means CMD will be run as root. That's why CMD is sudo -u MYUSERNAME rtorrent there's no reason to run rtorrent as root after all!

If you are starting rtorrent as a systemd service, just add NetworkNamespacePath to the unit file, e.g. NetworkNamespacePath=/run/netns/nordvpn

That will set the namespace. The rest of your unit file should already take care of setting the proper user or whatever. Keep in mind I'm not using this method myself, so maybe its missing something; you might need to bind mount resolv.conf yourself.

About DNS resolution

Normally, Fedora is running systemd-resolved at something like 127.0.0.53. This address is unreachable from inside the network namespace! That's why you need to set a DNS server for inside the namespace. Its reasonable to use NordVPN's dns servers for your NordVPN connection, but you could set it to anything else if you really desire 'DNS leaks'

About IPv6

I'd like to improve this setup to route IPv6 too, but I don't think NordVPN supports it yet. Here's the minimum of what has to change:

  • Add a local ipv6 address -- ip -n nordvpn addr add SOME_KIND_OF_LOCAL_IPV6_ADDRESS/128 dev wg-nordvpn0
  • Changed AllowsIPs in wg-nordvpn.conf -- AllowedIPs = 0.0.0.0/0,::0/0

About other approaches

My first attempt was to simply ip netns exec nordvpn nordvpn connect, which is to say to run the official client in a network namespace. What this meant was actually getting nordvpnd.service into the network namespace. But ultimately it didn't work because nordvpnd tries to talk to NetworkManager to set the DNS, and NetworkManager doesn't know about managing network namespaces. For other vpn clients this might be an easier option than snooping through all the wireguard secrets yourself.

Actually it might be possible to spin up a new NetworkManager instance in that namespace, but I was afraid that'd be too deep of a rabbit hole to run down.

# /usr/lib/systemd/system/netns@.service
[Unit]
Description=Named network namespace %I
Documentation=https://github.com/systemd/systemd/issues/28694
After=network-pre.target
Before=network.target network-online.target
[Install]
WantedBy=network-online.target
WantedBy=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=yes
# precaution
ExecStartPre=-/usr/bin/env ip netns delete %I
# Add vpn dedicated namespace
ExecStart=/usr/bin/env ip netns add %i
# remove the netns
ExecStop=/usr/bin/env ip netns delete %I
# /etc/netns/nordvpn/resolv.conf
nameserver 103.86.96.100
nameserver 103.86.99.100
search .
# obviously you'll have your own rtorrent.conf, but this addition is helpful to set the local ip address
# Get public IP address without the need of having dynamic DNS service, also works from behind NAT, through tunnel
method.insert = get_public_ip_address, simple|private, "execute.capture=bash,-c,\"eval echo -n \$(dig TXT +short o-o.myaddr.l.google.com @ns1.google.com)\""
# The IP address reported to the tracker. (ip) This handles dynamic IP's as well.
schedule2 = ip_tick, 0, 1800, "network.local_address.set=(get_public_ip_address)"
# /etc/wireguard/wg-nordvpn.conf
[Interface]
PrivateKey = NORDVPN_SOURCED_PRIVATE_KEY_HERE
ListenPort = NORDVPN_SOURCED_PORT_NUMBER_HERE
#Address = local address here # This line is commented out because we will use wg setconf instead of wg-quick
#DNS = 103.86.96.100, 103.86.99.100 # NordVPN DNS servers. Commented out because we're not using wg-quick
[Peer]
# XXXX.nordvpn.com -- Just a comment to remember which server we are targeting
PublicKey = NORDVPN_SERVER_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = NORDVPN_SERVER_IP:NORDVPN_SERVER_PORT
# /usr/lib/systemd/system/wireguard-nordvpn.service
[Unit]
Description=Nordvpn wireguard in netns
BindsTo=netns@nordvpn.service
After=netns@nordvpn.service network.target network-online.target
[Install]
WantedBy=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=yes
# By creating the wireguard interface in the normal namespace, it will know how to talk to the world even if it moves away.
ExecStart=/usr/bin/env ip link add wg-nordvpn0 type wireguard
# Move the wireguard interface to its own namespace
ExecStart=/usr/bin/env ip link set wg-nordvpn0 netns nordvpn
# Assign an address
ExecStart=/usr/bin/env ip -n nordvpn addr add LOCAL_ADDRESS_HERE_SOMETHING_LIKE_10.5.0.2_FOR_EXAMPLE/32 dev wg-nordvpn0
# Configure the wireguard interface so it knows its private key and peer
ExecStart=/usr/bin/env ip netns exec nordvpn wg setconf wg-nordvpn0 /etc/wireguard/wg-nordvpn0.conf
# Bring up the interface. If you get 'Address already in use' it might mean the port is bound
ExecStart=/usr/bin/env ip -n nordvpn link set wg-nordvpn0 up
# Setup default routing
ExecStart=/usr/bin/env ip -n nordvpn route add default dev wg-nordvpn0
# Stopping the vpn:
ExecStop=/usr/bin/env ip -n nordvpn link set wg-nordvpn0 down
ExecStop=/usr/bin/env ip -n nordvpn link delete wg-nordvpn0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment