The problem
I am using a (paid) VPN, namely, ProtonVPN. The goal is to be able to torrent, using (in this specific case) the qBittorrent client. As a “convenience” feature, I’d like to have the tunnel be used only by programs which “need it”, and use the usual interface for the rest of my activities.
A possible solution
As I am not very knowledgeable about networking, but still willing to learn, I found a nice middle ground: Vopono. This is basically a wrapper around some common commercial VPNs, which automates the creation of routes/filter/communication with the VPN server, the creation of a network namespace, and starts programs within the namespace. You can see below the entire list of commands it runs, as far as I can tell it’s pretty reasonable. The server does port-forwarding, and correctly communicates the port that I can use on my side. The script that I actually run, start-all.sh, is just a small wrapper, starting a few services, and starting qbittorrent-now --torrenting-port=<the port given by the server>. It also starts an instance of firefox in the same network namespace, so that I can “monitor” those services/clients.
Result
I can access the web interface of qBittorrent from the firefox instance. However, this specific torrent client can “choose” which interface to use, and:
- it fails to download/seed anything if it is restricted to the interface
vo_pr_nl - on the other hand, if allowed to use any interface, it does download/seed correctly … but it does so regardless of the torrenting port (that is, it seems to also be able to comunicate via other ports than the one given by the server for port-forwarding).
- Nonetheless, qBittorrent’s logs indicate that in both cases, my IP (real) address is not leaking.
- For everything else, the VPN is working as expected: from the firefox instance, all the usual tests (DNS leaks, IP checks …) correctly fail to identify my real location/IP and so on, and applications started outside of the network namespace correctly use the usual interface.
At this point, I’m not really sure if the network namespace does not act like a sandbox /at all/ and the torrent client manages to completely bypass the tunnel, or if the port-forwarding does not work as I thought …
Actual question
Is there a simple explanation to the observed behaviour of the torrent client running in the network namespace ? Otherwise, is there any tool that I could use to be completely sure that the traffic is hidden from my ISP (e.g., to look at all the “reasonable” packets going out of every network interface, virtual or not …) ? I’m not (yet) looking at solutions to the initial problem (as in the worst case, I could just route everything through the VPN rather than try to run only specific applications …), and this is more a “help me understand what is going on” than a “fix my problem” kind of question. I basically knew very little about the ip/nft commands before setting this up, I’ve read quite a lot of doc about those so I have at least a vague understanding of what every command below does, and learnt quite a lot in the process, but it is not enough to be fully sure that the torrent client does not leak anything.
Relevant logs and extra info
wlp3s0is my usual WiFi interface- I use NetworkManager, but I’m pretty sure it makes no difference here
- As far as I can tell, I have all the relevant kernel modules
- My
/etc/openvpn/openvpn.confis empty, the relevant configuration is done via vopono, using the usualovpnfiles given by ProtonVPN. - This is ProtonVPN-specific, but I am correctly using the
+pmpversion of the username to enable torrenting.
ip addr
# Interface: wlp3s0
# Using config dir from $HOME config: /home/numbra/.config
# Existing namespaces: []
ip netns add vo_pr_nl
# Created new network namespace: vo_pr_nl
# Existing interfaces:
# Assigned IPs: []
ip netns exec vo_pr_nl ip addr add 127.0.0.1/8 dev lo
ip netns exec vo_pr_nl ip link set lo up
# Detected NetworkManager running
# NetworkManager detected, adding vo_pr_nl_d to unmanaged devices
# Creating new NetworkManager config file: /etc/NetworkManager/conf.d/unmanaged.conf
nmcli connection reload
# firewalld not detected running
ip link add vo_pr_nl_d type veth peer name vo_pr_nl_s
ip link set vo_pr_nl_d up
ip link set vo_pr_nl_s netns vo_pr_nl up
ip addr add 10.200.1.1/24 dev vo_pr_nl_d
ip netns exec vo_pr_nl ip addr add 10.200.1.2/24 dev vo_pr_nl_s
ip netns exec vo_pr_nl ip route add default via 10.200.1.1 dev vo_pr_nl_s
# IP address of namespace as seen from host: 10.200.1.2
# IP address of host as seen from namespace: 10.200.1.1
nft add table inet vopono_nat
nft add chain inet vopono_nat postrouting { type nat hook postrouting priority 100 ; }
nft add rule inet vopono_nat postrouting oifname wlp3s0 ip saddr 10.200.1.0/24 counter masquerade
nft add table inet vopono_bridge
nft add chain inet vopono_bridge forward { type filter hook forward priority -10 ; }
nft add rule inet vopono_bridge forward iifname vo_pr_nl_d oifname wlp3s0 counter accept
nft add rule inet vopono_bridge forward oifname vo_pr_nl_d iifname wlp3s0 counter accept
sysctl -q net.ipv4.ip_forward=1
# Using config dir from $HOME config: /home/numbra/.config
# Chosen config: /home/numbra/.config/vopono/proton/openvpn/netherlands-nl-ch_02.ovpn
# Using config dir from $HOME config: /home/numbra/.config
# Read auth file: /home/numbra/.config/vopono/proton/openvpn/auth.txt
# Setting namespace vo_pr_nl DNS server to 8.8.8.8
# Using config dir from $HOME config: /home/numbra/.config
# Launching OpenVPN...
# Detected IPv6 enabled in /sys/module/ipv6/parameters/disable
# Found remotes: [Remote { host: IPv4(<SERVER-IP-HIGH-24-BITS>.199), port: 443, protocol: TCP } [...]]
ip netns exec vo_pr_nl openvpn --config /home/numbra/.config/vopono/proton/openvpn/netherlands-nl-ch_02.ovpn --machine-readable-output --log /home/numbra/.config/vopono/logs/vo_pr_nl_openvpn.log --auth-user-pass /home/numbra/.config/vopo
1744825315.559253 1 OpenVPN 2.6.12 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [MH/PKTINFO] [AEAD]
1744825315.559258 1 library versions: OpenSSL 3.3.3 11 Feb 2025, LZO 2.10
1744825315.560127 1 TCP/UDP: Preserving recently used remote address: [AF_INET]<SERVER-IP-HIGH-24-BITS>.96:7770
1744825315.560141 1 Attempting to establish TCP connection with [AF_INET]<SERVER-IP-HIGH-24-BITS>.96:7770
1744825315.662375 1 TCP connection established with [AF_INET]<SERVER-IP-HIGH-24-BITS>.96:7770
1744825315.662391 1 TCPv4_CLIENT link local: (not bound)
1744825315.662394 1 TCPv4_CLIENT link remote: [AF_INET]<SERVER-IP-HIGH-24-BITS>.96:7770
1744825315.766908 40 WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
1744825316.074124 1 [node-nl-209.protonvpn.net] Peer Connection Initiated with [AF_INET]<SERVER-IP-HIGH-24-BITS>.96:7770
1744825317.200945 1 TUN/TAP device tun0 opened
1744825317.200966 1 net_iface_mtu_set: mtu 1500 for tun0
1744825317.200984 1 net_iface_up: set tun0 up
1744825317.201080 1 net_addr_v4_add: 10.98.0.18/16 dev tun0
1744825317.201255 1 Initialization Sequence Completed
# Setting OpenVPN killswitch....
ip netns exec vo_pr_nl nft add table inet vo_pr_nl
ip netns exec vo_pr_nl nft add chain inet vo_pr_nl input { type filter hook input priority 100 ; policy drop; }
ip netns exec vo_pr_nl nft add chain inet vo_pr_nl forward { type filter hook forward priority 100 ; policy drop; }
ip netns exec vo_pr_nl nft add chain inet vo_pr_nl output { type filter hook output priority 100 ; policy drop; }
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl input ct state related,established counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl input iifname "lo" counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl input iifname "tun*" counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output oifname "lo" counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.199 tcp dport 443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.103 tcp dport 7770 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.236 tcp dport 7770 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.198 tcp dport 443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.96 tcp dport 7770 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.199 tcp dport 7770 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.91 tcp dport 443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.236 tcp dport 443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.91 tcp dport 8443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.91 tcp dport 7770 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.198 tcp dport 8443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.96 tcp dport 443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.96 tcp dport 8443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.103 tcp dport 8443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.197 tcp dport 7770 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.197 tcp dport 443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.197 tcp dport 8443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.103 tcp dport 443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.198 tcp dport 7770 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.199 tcp dport 8443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output ip daddr <SERVER-IP-HIGH-24-BITS>.236 tcp dport 8443 counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output oifname "tun*" counter accept
ip netns exec vo_pr_nl nft add rule inet vo_pr_nl output counter reject with icmp type net-unreachable
# Checking that OpenVPN is running in namespace: vo_pr_nl
# Will use Some(ProtonVPN) as provider for port forwarding
ip netns exec vo_pr_nl nft insert rule inet vo_pr_nl output ip daddr 10.2.0.1 counter accept
ip netns exec vo_pr_nl natpmpc -g 10.2.0.1
ip netns exec vo_pr_nl natpmpc -a 1 0 udp 60 -g 10.2.0.1
ip netns exec vo_pr_nl natpmpc -a 1 0 tcp 60 -g 10.2.0.1
# ProtonVPN forwarded local port: 39930
ip netns exec vo_pr_nl nft add table inet vo_pr_nl
ip netns exec vo_pr_nl nft add chain inet vo_pr_nl input { type filter hook input priority 100 ; }
ip netns exec vo_pr_nl nft insert rule inet vo_pr_nl input tcp dport 39930 counter accept
ip netns exec vo_pr_nl nft add chain inet vo_pr_nl output { type filter hook output priority 100 ; }
ip netns exec vo_pr_nl nft insert rule inet vo_pr_nl output tcp sport 39930 counter accept
# Using config dir from $HOME config: /home/numbra/.config
# Writing lockfile: /home/numbra/.config/vopono/locks/vo_pr_nl
# Lockfile written: /home/numbra/.config/vopono/locks/vo_pr_nl/8650
# Using config dir from $HOME config: /home/numbra/.config
ip netns exec vo_pr_nl sudo --preserve-env --user numbra sudo -E ./start-all.sh > /tmp/vopono.log
# Application sudo -E ./start-all.sh > /tmp/vopono.log launched in network namespace vo_pr_nl with pid 8756
# Port Forwarding on port 39930
# Password:
# Starting qBittorrent, listening on port 39930 ...