[Needs Testing] Transparent Proxy Qube with sing-box


Transparent Proxy Qube with sing-box
Quick Start Guide

dom0 terminal:

qvm-create sys-proxy --class AppVM --label blue
qvm-prefs sys-proxy provides_network true
qvm-prefs sys-proxy autostart true
qvm-start sys-proxy
qrexec-client -W -d sys-proxy user:'sh <(curl --proto "=https" -tlsv1.2 -SfL https://raw.githubusercontent.com/glockmane/qubes-proxy/refs/heads/main/install.sh)'

sys-proxy terminal:
Configure Proxy:

sudo nano /rw/bind-dirs/etc/sing-box/config.json

Configuration example:

Reboot and check sing-box status:

reboot
sudo journalctl -ft sing-box

Based on the following guide and install script, but translated from iptables to nftables:

Link to my fork:

Like this?

nft add table inet filter
nft add chain inet filter forward { type filter hook forward priority 0 \; policy drop \; }

nft delete rule inet filter input drop

nft list chain inet filter input | grep -q 'iifname "vif+" icmp accept'
if [ $? -eq 0 ]; then
  nft delete rule inet filter input iifname "vif+" ip protocol icmp accept
fi

nft list chain inet filter input | grep -q 'iifname "vif+" reject with icmp host-prohibited'
if [ $? -eq 0 ]; then
  nft delete rule inet filter input iifname "vif+" reject with icmp host-prohibited
fi

Thank you, I’ll give it a try!

Made a few adjustments and finally the installation works! Sing-Box runs, but a connected qube has no internet…

Is anyone willing to test? Just a few install steps, updated the first post!

I’m trapped, guess something is wrong or missing… Maybe a simple thing… Is anyone willing to help me? I will return the favor in form of a donation!

@kenosen

is it really…

table inet filter

…or table ip filter like the rest of my rules?

If inet from your example is correct, should I change then everything else also to inet or should I also do a nft add table ip filter?

Or even table ip qubes?

Edit:

After reading through the forum I assume the table has to be just “qubes”… Changed the script but still no internet on a connected app qube :frowning:

You can just add this firewall rule in /rw/config/rc.local in sys-proxy qube:

nft add rule ip qubes custom-input iifname singtun0 ip saddr 10.139.1.0/30 accept

And use this inbound tun in your sing-box config:

  "inbounds": [
    {
      "type": "tun",
      "tag": "tun-in",
      "interface_name": "singtun0",
      "inet4_address": "10.139.1.0/30",
      "strict_route": true,
      "auto_route": true,
      "inet4_route_exclude_address": [
        "10.137.0.0/16",
        "10.138.0.0/16"
      ],
      "inet6_route_exclude_address": [
        "fd09:24ef:4179::a89:0/112",
        "fd09:24ef:4179::a8a:0/112"
      ],
      "sniff": true
    }
  ],

Only this and nothing more?

Yes.

UPD:
On second thought, you can add killswitch and MTU fix rules as well in the /rw/config/rc.local:

nft add rule ip qubes custom-forward oifname eth0 counter drop
nft add rule ip6 qubes custom-forward oifname eth0 counter drop
nft add rule ip qubes custom-forward tcp flags syn / syn,rst tcp option maxseg size set rt mtu

So the whole restrict-firewall part is obsolete then?

I’m not sure what’s the point of dropping ICMP and some specific invalid connections, but other rules are not needed.

Okay, changed the install.sh to add your rules to /rw/config/rc.local and changed “inbounds” in sing-box config but it seems sys-proxy outgoing connection doesn’t work… From proxy qube I can ping websites successfully but curl the same website gives no output…

Maybe your outbound is not working?
Set inbound to proxy instead of tun e.g.:

  "inbounds": [
     {
       "type": "socks",
       "tag": "socks-in",
       "listen": "127.0.0.1",
       "listen_port": 2080
     }
  ],

Then check the outbound connection using curl in sys-proxy:

curl -x socks5h://127.0.0.1:2080 9.9.9.9
curl -x socks5h://127.0.0.1:2080 debian.org

Also you can’t ping through proxy since it’s not routing ICMP.

But after a while I get frequent ping responses when pinging website from sys-proxy…?

What latency do you see in the ping output?
Does it look like a real latency or are the values <1ms? That’d indicate that the ping response is coming from local process in VM, e.g. from sing-box itself instead of the remote host.

Takes a few minutes until the ping responses show up, but then all under 1ms so I guess you are right…

Try this test setup with socks from sys-whonix as outbound:
Set sys-proxy net qube to sys-whonix.
Run these commands in sys-proxy to only allow the connection to the socks proxy in sys-whonix:

sudo nft add chain ip qubes output '{ type filter hook output priority 0; policy accept; }'
sudo nft insert rule ip qubes output ct state established,related accept
sudo nft add rule ip qubes output oifname eth0 ip daddr 10.152.152.10 meta l4proto tcp tcp dport 9153 accept
sudo nft add rule ip qubes output oifname eth0 drop

Allow sing-box traffic:

sudo nft add rule ip qubes custom-input iifname singtun0 ip saddr 10.139.1.0/30 accept

Create new sing-box config whonix.json:

{
  "log": {
    "disabled": false,
    "level": "info",
    "output": "",
    "timestamp": true
  },
  "dns": {
    "servers": [
      {
        "tag": "remote",
        "address": "tls://1.1.1.1"
      },
      {
        "tag": "local",
        "address": "local",
        "detour": "direct"
      }
    ],
    "strategy": "prefer_ipv4"
  },
  "inbounds": [
    {
      "type": "socks",
      "tag": "socks-in",
      "listen": "127.0.0.1",
      "listen_port": 2080
    },
    {
      "type": "tun",
      "tag": "tun-in",
      "interface_name": "singtun0",
      "inet4_address": "10.139.1.0/30",
      "strict_route": true,
      "auto_route": true,
      "inet4_route_exclude_address": [
        "10.137.0.0/16",
        "10.138.0.0/16"
      ],
      "inet6_route_exclude_address": [
        "fd09:24ef:4179::a89:0/112",
        "fd09:24ef:4179::a8a:0/112"
      ],
      "sniff": true
    }
  ],
  "outbounds": [
    {
      "tag": "proxy",
      "type": "socks",
      "server": "10.152.152.10",
      "server_port": 9153,
      "version": "5",
      "username": "sing-box",
      "password": "RandomPassword",
      "network": "tcp"
      #"udp_over_tcp": false
    },
    {
      "type": "direct",
      "tag": "direct"
    },
    {
      "type": "block",
      "tag": "block"
    },
    {
      "type": "dns",
      "tag": "dns-out"
    }
  ],
  "route": {
    "geosite": {
      "path": "/usr/local/share/sing-box/geosite.db"
    },
    "geoip": {
      "path": "/usr/local/share/sing-box/geoip.db"
    },
    "rules": [
      {
        "protocol": "dns",
        "outbound": "dns-out"
      },
      {
        "geosite": "category-ads-all",
        "outbound": "block"
      },
      {
        "geosite": "cn",
        "geoip": "cn",
        "outbound": "direct"
      },
      {
        "geosite": "private",
        "geoip": "private",
        "outbound": "direct"
      }
    ],
    "auto_detect_interface": true
  }
}

Stop the sing-box service if it’s running:

sudo systemctl stop sing-box

Run sing-box manually in the terminal to see the log output:

sudo sing-box run -c whonix.json

Test the connection in sys-proxy:

curl https://ip.me
curl -x socks5h://127.0.0.1:2080 https://ip.me

Okay, tried and both curl commands give me a ip as output! Does this mean it is working and the problem is with my proxy?

Yes.
Use curl in some qube to test your proxy first e.g.:

curl -U user:password -x socks5h://1.2.3.4:12345 https://ip.me

Gives me a ipv6 address as output