Route qube traffic transparently through a proxy qube (Qubes R4.1 and R4.2)

Introduction

This guide describes setting up a sys-proxy qube, which will transparently route the traffic of qubes using it as their networking qube through a SOCKS5, HTTP(S) or other supported proxy.

Requirements

  1. Qubes OS R4.1 or R4.2
  2. A proxy server (SOCKS5, HTTP(S), Shadowsocks)

Setup

Setting up the template

We’ll be using a minimal Fedora template. To install it, run in dom0:
sudo qubes-dom0-update qubes-template-fedora-38-minimal

After it has finished installing, open the Qubes update tool in dom0, enable Enable updates for qubes without known available updates, check fedora-38-minimal and hit next, wait for it to finish updateing.

Open Qube Manager, find fedora-38-minimal, right-click it, choose Clone qube, name it fedora-38-minimal-proxy and hit OK.

Open a terminal window in dom0, open up a root terminal in the cloned template:
qvm-run -u root fedora-38-minimal-proxy xterm

Install the necessary software in the template by running in the opened terminal window of the template:

dnf install qubes-core-agent-networking iproute clash dnscrypt-proxy
systemctl disable dnscrypt-proxy
poweroff

Creating sys-proxy qube

Open the Qube creation tool and configure the qube as follows:

  • Name: sys-proxy
  • Type: AppVM
  • Template: fedora-38-minimal-proxy
  • Networking: this is for you to decide, perhaps you want to use a VPN qube, default is sys-firewall

Tick Launch settings after creation and hit OK
Select tab Advanced, tick Provides network
click Apply and then OK

Launch a terminal in dom0 and run:

qvm-firewall sys-proxy del --rule-no 0
qvm-firewall sys-proxy add drop
qvm-firewall sys-proxy add --before 0 drop proto=icmp
qvm-firewall sys-proxy add --before 0 drop specialtarget=dns
qvm-firewall sys-proxy add --before 0 accept PROXY_IP
qvm-firewall sys-proxy

replace PROXY_IP with your proxy’s IP
last command should show accept → drop DNS → drop ICMP → drop

Configuring sys-proxy qube

Open sys-proxy’s terminal by running in dom0:
qvm-run -u root sys-proxy xterm

In sys-proxy’s terminal run: mkdir -p /rw/proxy/dns /rw/proxy/clash

Edit /rw/proxy/dns/dnscrypt-proxy.toml in sys-proxy and add:

listen_addresses = ['127.0.0.1:5353']
max_clients = 250
proxy = 'socks5://127.0.0.1:7891'
timeout = 5000
keepalive = 30
ignore_system_dns = true
netprobe_timeout = 0
cache = true
[static]
  [static.quad9_doh]
    stamp = 'sdns://AgMAAAAAAAAABzkuOS45LjkgKhX11qy258CQGt5Ou8dDsszUiQMrRuFkLwaTaDABJYoSZG5zOS5xdWFkOS5uZXQ6NDQzCi9kbnMtcXVlcnk'
  [static.mullvad_doh]
    stamp = 'sdns://AgcAAAAAAAAAAAAPZG9oLm11bGx2YWQubmV0Ci9kbnMtcXVlcnk'

Edit /rw/proxy/clash/config.yaml in sys-proxy and add:

socks-port: 7891
redir-port: 7892

mode: rule

allow-lan: true
bind-address: '*'

dns:
  enable: false

proxies:
  - name: "socks_proxy"
    type: socks5
    server: PROXY_IP
    port: 1080
    # username: username
    # password: password
  # - name: "http_proxy"
  #   type: http
  #   server: PROXY_IP
  #   port: 80
  #   # username: username
  #   # password: password
  #   # tls: true # https
  #   # skip-cert-verify: true

rules:
  - MATCH,socks_proxy # or http_proxy

Replace PROXY_IP and with your proxy’s IP, modify proxy settings as needed

For Qubes R4.1

Edit /rw/config/rc.local in sys-proxy and add:

sysctl -w net.ipv4.conf.all.route_localnet=1
iptables -I FORWARD -o eth0 -j DROP
iptables -I FORWARD -i eth0 -j DROP
ip6tables -I FORWARD -o eth0 -j DROP
ip6tables -I FORWARD -i eth0 -j DROP
iptables -P OUTPUT DROP
iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -d PROXY_IP -j ACCEPT
iptables -t nat -F PR-QBS
iptables -t nat -A PR-QBS -d 10.139.1.1 -p udp --dport 53 -j DNAT --to 127.0.0.1:5353
iptables -t nat -A PR-QBS -d 10.139.1.1 -p tcp --dport 53 -j DNAT --to 127.0.0.1:5353
iptables -t nat -A PR-QBS -d 10.139.1.2 -p udp --dport 53 -j DNAT --to 127.0.0.1:5353
iptables -t nat -A PR-QBS -d 10.139.1.2 -p tcp --dport 53 -j DNAT --to 127.0.0.1:5353
iptables -t nat -A PREROUTING -i vif+ -p udp -j REDIRECT --to-ports 7892
iptables -t nat -A PREROUTING -i vif+ -p tcp -j REDIRECT --to-ports 7892
iptables -I INPUT -i vif+ -p tcp --dport 7892 -j ACCEPT
iptables -I INPUT -i vif+ -p udp --dport 7892 -j ACCEPT
iptables -I INPUT -i vif+ -p tcp --dport 5353 -j ACCEPT
iptables -I INPUT -i vif+ -p udp --dport 5353 -j ACCEPT
clash -d /rw/proxy/clash >/dev/null 2>&1 &
sleep 0.5
dnscrypt-proxy -config /rw/proxy/dns/dnscrypt-proxy.toml >/dev/null 2>&1 &

Replace PROXY_IP and with your proxy’s IP

For Qubes R4.2

Edit /rw/config/rc.local in sys-proxy and add:

sysctl -w net.ipv4.conf.all.route_localnet=1
nft 'add rule ip qubes custom-forward oifname "eth0" drop'
nft 'add rule ip6 qubes custom-forward oifname "eth0" drop'
nft 'add rule ip qubes custom-forward iifname "eth0" drop'
nft 'add rule ip6 qubes custom-forward iifname "eth0" drop'
nft flush chain ip qubes dnat-dns
nft 'add rule ip qubes dnat-dns ip daddr 10.139.1.1 udp dport 53 dnat to 127.0.0.1:5353'
nft 'add rule ip qubes dnat-dns ip daddr 10.139.1.1 tcp dport 53 dnat to 127.0.0.1:5353'
nft 'add rule ip qubes dnat-dns ip daddr 10.139.1.2 udp dport 53 dnat to 127.0.0.1:5353'
nft 'add rule ip qubes dnat-dns ip daddr 10.139.1.2 tcp dport 53 dnat to 127.0.0.1:5353'
nft 'add rule ip qubes custom-input iifname "vif*" tcp dport 7892 accept'
nft 'add rule ip qubes custom-input iifname "vif*" udp dport 7892 accept'
nft 'add rule ip qubes custom-input iifname "vif*" tcp dport 5353 accept'
nft 'add rule ip qubes custom-input iifname "vif*" udp dport 5353 accept'
nft 'add chain ip qubes redir { type nat hook prerouting priority -99 ; policy accept; }'
nft 'add rule ip qubes redir iifname "vif*" ip protocol udp redirect to :7892'
nft 'add rule ip qubes redir iifname "vif*" ip protocol tcp redirect to :7892'
nft 'add chain ip qubes output { type filter hook output priority filter ; policy drop; }'
nft 'add rule ip qubes output ct state related,established accept'
nft 'add rule ip qubes output oifname "lo" accept'
nft 'add rule ip qubes output ip daddr PROXY_IP accept'
clash -d /rw/proxy/clash >/dev/null 2>&1 &
sleep 0.5
dnscrypt-proxy -config /rw/proxy/dns/dnscrypt-proxy.toml >/dev/null 2>&1 &

Replace PROXY_IP and with your proxy’s IP

End of Qubes version specific instructions

Download Country.mmdb from here in another qube, move it to sys-proxy’s /rw/proxy/clash directory.
It doesn’t actually get used, but the proxy client refuses to start without it, meaning it doesn’t have to be kept up-to-date.

Last but not least, restart sys-proxy!

Using sys-proxy

After having restarted sys-proxy, you can create a new qube that uses sys-proxy as its networking qube.
To test if it’s indeed working, in the new qube curl https://ip.me should show the proxy’s IP address.

Notes

  • clash, the proxy client used, also supports other proxy protocols such as Shadowsocks in addition to SOCKS5 and HTTP(S), see their documentation for more details.
  • dnscrypt-proxy, the DNS client used, is being used with a minimal configuration that has two preconfigured DNS servers: Quad9’s and Mullvad’s DoH servers. You can also configure sources for lists of DNS servers, but make sure to preserve the custom configuration in our dnscrypt-proxy.toml file (before [static]). See their documentation and example config file for details.
  • It should be leak-proof, since qvm-firewall is used to block all traffic of all kinds from sys-proxy to any non-proxy IPs.
8 Likes

Thanks for the helpful post! I have mine working pretty well now. I have some recommended fixes to the article, and some comments:

  1. Regarding the Country.mmdb, it’s mentioned later in the article. I was having issues getting the file there by following the steps in that order. I recommend adding the Country.mmdb before making the qvm-firewall changes. To pull the file over you’ll either need DNS to resolve or you’ll have to pull the file over from another Qube. To manually fix it with DNS I temporarily removed the rule:
    Check the rules, find the specialtarget=dns:
    qvm-firewall sys-proxy
    Delete the specific rule:
    qvm-firewall sys-proxy del --rule-no X
    After adding the file you can add the original rule back.

  2. I’m running Qubes R4.2 and I noticed I also had to disable resolved in Fedora 38:
    systemctl disable systemd-resolved
    systemctl stop systemd-resolved

  3. I had some issues with the Fedora 38 Minimal, so I went with the full Fedora 38 until I can figure it out. I wasn’t getting any DNS resolution in there and was having issues adding packages without DNS to troubleshoot.

  4. Hoping to find a solution to add and reference a list of socks proxy IPs and http proxy IPs. Is there a convenient way to script this both for the qvm-firewall rules and within the qube for clash and dnscrypt-proxy?

1 Like

Thank you for your guide. I’m trying to replicate your solution but ran into a problem. I am using qubes 4.1. During the cube cofiguration phase, I need to make changes to dnscrypt-proxy.toml and clash config.yaml, but the dns\clash folders are empty. What can this be related to?

Thanks for guide ! Proxy qube is heavily based.

1 Like

Thank you for your guide. I’m trying to replicate your solution but ran into a problem. I am using qubes 4.1. During the cube cofiguration phase, I need to make changes to dnscrypt-proxy.toml and clash config.yaml, but the dns\clash folders are empty. What can this be related to?

If you add a file there and restart it, if it disappears you’re probably running a disposable appVM. Might need to recreate it as a new Qube.

1 Like

I tried to implement this using the standard fedora template. Is it mandatory to use a minimal template?

I tried to implement this using the standard fedora template. Is it mandatory to use a minimal template?

Personally I ended up having to use the standard template for other issues and that worked for me but it has a larger footprint, so I’d like to redo it if I can solve my other issues.

Thank you for your guide. I’m trying to replicate your solution but ran into a problem. I am using qubes 4.1. During the cube cofiguration phase, I need to make changes to dnscrypt-proxy.toml and clash config.yaml, but the dns\clash folders are empty. What can this be related to?

I re-read this and I think I misunderstood you the first time, so in the instructions you create the dns and clash directories with the mkdir -p command, so they will be empty. You then create the dnscrypt-proxy.toml and config.yaml files in the respective directories and copy in the information to it and edit as necessary.

1 Like

I created dnscrypt-proxy.toml and modified rc.local. I added there only the data you specified in the first post. Next I created config.yaml. In it I added your data changing only the data of my socks 5 proxy (proxy ip:13786:login:pass). Then I repeated the commands from the original topic in the console and got an error. I tested my proxy, it is definitely working. What am I doing wrong?

Looks like you have an issue there with your clash configuration.

1 Like

I fixed the error in clash configuration, I still don’t have it working the same way. Which way should I go?

screenshots







This text will be hidden

In your rc.local (second screenshot) at the bottom it is launching clash and dnscrypt for you when you restart the Qube in the instructions. Have you tried curling ip.me like it mentions in the guide? You can also check your running processes to see that it’s running already.

Also are you sure the proxy you added to the clash config is a socks5 proxy? If you’re using an http proxy, be sure to configure the appropriate type.

1 Like

yes, the test cube uses socks5. I tried a few more times to get success. it helped me to comment out :
#clash -d /rw/proxy/clash >/dev/null 2>&1 &
sleep 0.5
#dnscrypt-proxy -config /rw/proxy/dns/dnscrypt-proxy.toml >/dev/null 2>&1 &”
I don’t know much about network configuration. How will my action affect the operation of the cube?

Well if you comment that out it’s not going to start automatically, but helps when you’re manually trying to run it for testing when it was already running. Don’t forget to uncomment it when you’re done testing.

If you notice in some of your screenshots it’s saying there is a FATAL error and other server errors because you already have dnscrypt and clash running (due to the autostart).

If you get those bind errors where it says ‘address already in use’. You can kill the process:
ps aux | grep dnscrypt
ps aux | grep clash

That will give you the id of the process if it’s running and then kill it:
kill -9 $id

Then you can rerun the clash or dnscrypt command again manually. Again once you have it tuned up and finished testing you’ll want to uncomment the clash and dnscrypt commands in the rc.local config so that it autostarts when you start the qube.

As for the network configuration I’m not sure what you mean. If you want another qube to use sys-proxy you have to specify that in the qube config by pointing a qube at sys-proxy. Does the curl command give you a different IP than your home IP? It should give the IP of the socks proxy so that you confirm that the proxy is working.

1 Like

thanks) Thanks to your advice I figured out how it works.

1 Like

Thank you for the instructions! Worked great for me. To be honest, this is the first time I see clash and the non-Chinese info is very limited. According to the English doc, there is a fallback/routing set up option, but it is implemented in the “premium edition”. I would like some automatic fallback for my shadowsocks, is there any open-source alternative available? I’ve read about tun2sock, but couldn’t get it working properly, is it the right place to look?

I have a question to ask: for a SOCKS5 proxy that is a rotating proxy, as well as a SOCKS5 proxy whose address is a domain name, does the firewall function, and is the guide applicable?