Skip to content

Instantly share code, notes, and snippets.

Forked from radfish/README
Created May 29, 2023 06:35
Show Gist options
  • Save uhop/b02655265ca4872c394d197f0e496677 to your computer and use it in GitHub Desktop.
Save uhop/b02655265ca4872c394d197f0e496677 to your computer and use it in GitHub Desktop.
Route only Transmission through a VPN connection using your own VPN server
# The approach is to mark packets from a specific user,
# create a dedicated routing table with a default route
# through the VPN, and force all marked packets to be
# routed using that table.
# Sources:
# In this guide
# IP of VPN tunnel interface (tun0) on machine running transmission
# IP of external interface (eth0) on the VPN server
# add to /etc/iproute2/rt_tables:
200 transmission
#in /etc/openvpn/client.conf
script-security 2
up /etc/openvpn/client/
# See file in this dir: /etc/openvpn/client/
# creates fwmark rule and blackhole route to reject
# In /etc/iptables/iptables.rules
# This rule identifies packets from the user that we want to route through VPN
# NOTE: To add another user, the only necessary step is to add another rule like this
# NOTE: I think group won't work, because GID will match by the primary group of the
# user that executing the app, not by membership of that user in a group.
iptables -t mangle -A OUTPUT -m owner --uid-owner transmission -j MARK --set-mark 0x2
iptables -t nat -A POSTROUTING -o tunoak -j SNAT --to-source
# This rule rejects all pkts until the VPN starts up ( removes this rule),
# after vpn shuts down, the rejection is done by blackhole route (see
iptables -A OUTPUT -m mark --mark 0x2 -j REJECT
# In /etc/sysctl.d/net.conf:
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.tunoak.rp_filter = 0
# On VPN server:
iptables -t nat -A PREROUTING -i eth0 -p tcp -m tcp --dport 51413 -j DNAT --to-destination
iptables -t nat -A PREROUTING -i eth0 -p udp -m udp --dport 51413 -j DNAT --to-destination
# Main NAT rule: re-write source to the VPN server's external IP
iptables -t nat -A POSTROUTING -s -o eth0 -j SNAT --to-source
# You can use systemd's iptables.service to restore iptables automatically on
# boot, that you save with 'iptables-save > /etc/iptables/iptables.rules'
# Also, on VPN server, in /etc/openvpn/server/server.conf comment out this line
# to prevent openvpn server forcing the client to forward all traffic through the VPN:
;push "redirect-gateway def1 bypass-dhcp"
# Transmission /var/lib/transmission/.config/transmission-daemon/settings.json
# Must bind to the VPN tun interface, because otherwise transmission communication
# breaks across the NAT: netfilter # modifies the outgoing reply UDP packets
# with wrong source port, because # conntrack can't detect the connection,
# because it sees a source port from # an interface other than the tunnel
# interface (despite the packet reaching the destination (it reaches, but
# with the wrong source port, so transmission rejects it).
"bind-address-ipv4": "",
# not necessary, but just to keep things simple: disable IPv6 by binding it to localhost
"bind-address-ipv6": "::1",
# cp /{usr/lib,etc}/systemd/system/transmission.service to disable UPnP port mapping:
# Add --no-portmap
# Because of the firewalling, we don't have access to RPC from outside the box
# See transmission-rpc.service for creating SSH tunnel accessible to outside
Description=SSH Tunnel to transmission RPC port
# Create this user with: useradd -r -m -d /var/lib/transmission-rpc transmission-rpc
# Then login, create an SSH key (no passphrase), and authorize the key, and approve the host as known:
# sudo su - transmission-rpc
# ssh-keygen -t rsa
# cp ~/.ssh/ >> ~/.ssh/authorized_keys
# ssh localhost
ExecStart=/usr/bin/ssh -NT -L '*:9191:localhost:9091' localhost
#! /bin/bash
# Must be created in /etc/iproute2/rt_tables
# Must match the mark value saved in iptables.rules:
# (1) in the rules that mark packets by GUID
# -A OUTPUT -m owner --uid-owner $USERNAME -j MARK --set-xmark $MARK
# (2) in the rule that rejects all marked packets by default (see below)
# Gateway of VPN interface
if [[ `ip rule list | grep -c $MARK` == 0 ]]; then
ip rule add from all fwmark $MARK lookup $VPNTABLE
ip route replace default via $GATEWAYIP table $VPNTABLE
# when default route disappears, will fall back to this null route:
# NOTE: this is crucial, because iptables rejection rule is removed below,
# so when openvpn-client service is stopped, iptables no longer protects.
ip route append blackhole default table $VPNTABLE
ip route flush cache
# Remove the rejection rule
if iptables -C OUTPUT -m mark --mark $MARK -j REJECT
iptables -D OUTPUT -m mark --mark $MARK -j REJECT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment