Help needed. Read this first
After doing all the steps described below, I can’t access the internet. The packets stop at sys-net:vif* and aren’t getting forwarded to ens6 (confirmed with wireshark). If in 4.15 I choose to leave the interfaces at their default values and skip 4.16, then zapret only applies in sys-net, but not in connected qubes. Please help me resolve this issue. (For the record I’m using tpws in transparent mode)
I plan to turn this post into a guide when the issue is solved, which is why it’s formatted like that
Below is the output of different commands when the internet is broken:
nft list tables
table ip qubes
table ip6 qubes
table ip qubes-firewall
table ip6 qubes-firewall
table inet zapret
table inet qubes-nat-accel
nft list table ip qubes
table ip qubes {
set downstream {
type ipv4_addr
elements = { 10.138.39.29 }
}
set allowed {
type ifname . ipv4_addr
elements = { "vif162.0" . 10.138.39.29 }
}
chain prerouting {
type filter hook prerouting priority raw; policy accept;
iifgroup 2 goto antispoof
ip saddr @downstream counter packets 0 bytes 0 drop
}
chain antispoof {
iifname . ip saddr @allowed accept
counter packets 0 bytes 0 drop
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifgroup 2 accept
oif "lo" accept
masquerade
}
chain input {
type filter hook input priority filter; policy drop;
jump custom-input
ct state invalid counter packets 64 bytes 5185 drop
iifgroup 2 udp dport 68 counter packets 0 bytes 0 drop
ct state established,related accept
iifgroup 2 meta l4proto icmp accept
iif "lo" accept
iifgroup 2 counter packets 0 bytes 0 reject with icmp host-prohibited
counter packets 755 bytes 88626
}
chain forward {
type filter hook forward priority filter; policy accept;
jump custom-forward
ct state invalid counter packets 49 bytes 3136 drop
ct state established,related accept
oifgroup 2 counter packets 0 bytes 0 drop
}
chain custom-input {
}
chain custom-forward {
}
chain dnat-dns {
type nat hook prerouting priority dstnat; policy accept;
ip daddr 10.139.1.1 udp dport 53 dnat to 8.8.8.8
ip daddr 10.139.1.1 tcp dport 53 dnat to 8.8.8.8
ip daddr 10.139.1.2 udp dport 53 dnat to 8.8.8.8
ip daddr 10.139.1.2 tcp dport 53 dnat to 8.8.8.8
}
}
nft list table ip6 qubes
table ip6 qubes {
set downstream {
type ipv6_addr
}
set allowed {
type ifname . ipv6_addr
}
chain antispoof {
iifname . ip6 saddr @allowed accept
counter packets 70 bytes 4596 drop
}
chain prerouting {
type filter hook prerouting priority raw; policy accept;
iifgroup 2 goto antispoof
ip6 saddr @downstream counter packets 0 bytes 0 drop
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifgroup 2 accept
oif "lo" accept
masquerade
}
chain _icmpv6 {
meta l4proto != ipv6-icmp counter packets 0 bytes 0 reject with icmpv6 admin-prohibited
icmpv6 type { nd-router-advert, nd-redirect } counter packets 0 bytes 0 drop
accept
}
chain input {
type filter hook input priority filter; policy drop;
jump custom-input
ct state invalid counter packets 0 bytes 0 drop
ct state established,related accept
iifgroup 2 goto _icmpv6
iif "lo" accept
ip6 saddr fe80::/64 ip6 daddr fe80::/64 udp dport 546 accept
meta l4proto ipv6-icmp accept
counter packets 0 bytes 0
}
chain forward {
type filter hook forward priority filter; policy accept;
jump custom-forward
ct state invalid counter packets 0 bytes 0 drop
ct state established,related accept
oifgroup 2 counter packets 0 bytes 0 drop
}
chain custom-input {
}
chain custom-forward {
}
}
nft list table ip qubes-firewall
table ip qubes-firewall {
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname != "vif*" accept
ip saddr 10.138.39.29 jump qbs-10-138-39-29
ip saddr 10.138.5.194 jump qbs-10-138-5-194
}
chain prerouting {
type filter hook prerouting priority raw; policy accept;
iifname != "vif*" ip saddr 10.138.39.29 drop
}
chain postrouting {
type filter hook postrouting priority raw; policy accept;
oifname != "vif*" ip daddr 10.138.39.29 drop
}
chain qbs-10-138-39-29 {
accept
reject with icmp admin-prohibited
}
chain qbs-10-138-5-194 {
accept
reject with icmp admin-prohibited
}
}
nft list table ip6 qubes-firewall
table ip6 qubes-firewall {
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname != "vif*" accept
}
chain prerouting {
type filter hook prerouting priority raw; policy accept;
}
chain postrouting {
type filter hook postrouting priority raw; policy accept;
}
}
nft list table inet zapret
table inet zapret {
set zapret {
type ipv4_addr
policy memory
size 522288
flags interval
auto-merge
}
set ipban {
type ipv4_addr
policy memory
size 522288
flags interval
auto-merge
}
set nozapret {
type ipv4_addr
policy memory
size 65536
flags interval
auto-merge
elements = { 10.0.0.0/8, 127.0.0.0/8,
169.254.0.0/16, 172.16.0.0/12,
192.168.0.0/16 }
}
set lanif {
type ifname
elements = { "vif162.0" }
}
set wanif {
type ifname
elements = { "ens6" }
}
set wanif6 {
type ifname
}
map link_local {
type ifname : ipv6_addr
}
chain dnat_output {
type nat hook output priority dstnat - 1; policy accept;
meta skuid != 981 oifname @wanif tcp dport { 80, 443 } ip daddr != @nozapret ip daddr != @ipban dnat ip to 127.0.0.127:988
}
chain dnat_pre {
type nat hook prerouting priority dstnat - 1; policy accept;
iifname @lanif tcp dport { 80, 443 } ip daddr != @nozapret ip daddr != @ipban dnat ip to 127.0.0.127:988
}
chain forward {
type filter hook forward priority filter - 1; policy accept;
}
chain input {
type filter hook input priority filter - 1; policy accept;
iif != "lo" jump localnet_protect
}
chain flow_offload {
}
chain localnet_protect {
ip daddr 127.0.0.127 return comment "route_localnet allow access to tpws"
ip daddr 127.0.0.0/8 drop comment "route_localnet remote access protection"
}
chain postrouting {
}
chain postrouting_hook {
type filter hook postrouting priority srcnat - 1; policy accept;
meta mark & 0x40000000 == 0x00000000 jump postrouting
}
chain postnat {
}
chain postnat_hook {
type filter hook postrouting priority srcnat + 1; policy accept;
meta mark & 0x40000000 == 0x00000000 jump postnat
}
chain prerouting {
type filter hook prerouting priority dstnat + 1; policy accept;
icmp type time-exceeded ct state invalid drop
icmp type time-exceeded ct mark & 0x40000000 != 0x00000000 drop comment "nfqws related : prevent ttl expired socket errors"
}
chain prenat {
type filter hook prerouting priority dstnat - 1; policy accept;
}
chain predefrag {
type filter hook output priority -401; policy accept;
meta mark & 0x40000000 != 0x00000000 jump predefrag_nfqws comment "nfqws generated : avoid drop by INVALID conntrack state"
}
chain predefrag_nfqws {
meta mark & 0x20000000 != 0x00000000 notrack comment "postnat traffic"
ip frag-off & 0x1fff != 0x0 notrack comment "ipfrag"
exthdr frag exists notrack comment "ipfrag"
tcp flags ! syn,rst,ack notrack comment "datanoack"
}
}
nft list table inet qubes-nat-accel
table inet qubes-nat-accel {
flowtable qubes-accel {
hook ingress priority filter
devices = { ens6, lo, vif162.0 }
}
chain qubes-accel {
type filter hook forward priority filter + 5; policy accept;
meta l4proto { tcp, udp } iifgroup 2 oifgroup 1 flow add @qubes-accel
counter packets 12281 bytes 8701803
}
}
And when the internet works, but zapret does not apply:
nft list table inet zapret
table inet zapret {
set zapret {
type ipv4_addr
policy memory
size 522288
flags interval
auto-merge
}
set ipban {
type ipv4_addr
policy memory
size 522288
flags interval
auto-merge
}
set nozapret {
type ipv4_addr
policy memory
size 65536
flags interval
auto-merge
elements = { 10.0.0.0/8, 127.0.0.0/8,
169.254.0.0/16, 172.16.0.0/12,
192.168.0.0/16 }
}
set lanif {
type ifname
}
set wanif {
type ifname
}
set wanif6 {
type ifname
}
map link_local {
type ifname : ipv6_addr
}
chain dnat_output {
type nat hook output priority dstnat - 1; policy accept;
meta skuid != 981 tcp dport { 80, 443 } ip daddr != @nozapret ip daddr != @ipban dnat ip to 127.0.0.127:988
}
chain dnat_pre {
type nat hook prerouting priority dstnat - 1; policy accept;
iifname @lanif tcp dport { 80, 443 } ip daddr != @nozapret ip daddr != @ipban dnat ip to 127.0.0.127:988
}
chain forward {
type filter hook forward priority filter - 1; policy accept;
}
chain input {
type filter hook input priority filter - 1; policy accept;
iif != "lo" jump localnet_protect
}
chain flow_offload {
}
chain localnet_protect {
ip daddr 127.0.0.127 return comment "route_localnet allow access to tpws"
ip daddr 127.0.0.0/8 drop comment "route_localnet remote access protection"
}
chain postrouting {
}
chain postrouting_hook {
type filter hook postrouting priority srcnat - 1; policy accept;
meta mark & 0x40000000 == 0x00000000 jump postrouting
}
chain postnat {
}
chain postnat_hook {
type filter hook postrouting priority srcnat + 1; policy accept;
meta mark & 0x40000000 == 0x00000000 jump postnat
}
chain prerouting {
type filter hook prerouting priority dstnat + 1; policy accept;
icmp type time-exceeded ct state invalid drop
icmp type time-exceeded ct mark & 0x40000000 != 0x00000000 drop comment "nfqws related : prevent ttl expired socket errors"
}
chain prenat {
type filter hook prerouting priority dstnat - 1; policy accept;
}
chain predefrag {
type filter hook output priority -401; policy accept;
meta mark & 0x40000000 != 0x00000000 jump predefrag_nfqws comment "nfqws generated : avoid drop by INVALID conntrack state"
}
chain predefrag_nfqws {
meta mark & 0x20000000 != 0x00000000 notrack comment "postnat traffic"
ip frag-off & 0x1fff != 0x0 notrack comment "ipfrag"
exthdr frag exists notrack comment "ipfrag"
tcp flags ! syn,rst,ack notrack comment "datanoack"
}
}
The guide that needs fixing:
This is a guide for using [zapret | github.com] together with DNSCrypt on QubesOS.
Notes
- This guide involves giving a TemplateVM (
sys-net-tmpl) network access. This means that the TemplateVM should be considered compromised, which is fine, as the only qube based on the template (sys-net) is untrusted - We have to run both
zapretandblockcheckin a PCI-passthrough qubes, because QubesOS’ networking interferes with both - This guide assumes that
sys-netis not disposable
1. Installing the necessary packages
- Create a new TemplateVM using the “Create New Qube” tool called
sys-net-tmpl(red) (based on the same template assys-net, in my case:fedora-41-xfce) - Start
sys-net-tmpl sys-net-tmpl:sudo dnf install nslookupsys-net-tmpl:sudo dnf install dnscrypt-proxysys-net-tmpl: Configurednscrypt-proxyif needed in/etc/dnscrypt-proxy/dnscrypt-proxy.toml(see [Dnscrypt-proxy#Configuration | wiki.archlinux.org] for details) (this is a temporary configuration that will only be used in the blockcheck)
3. Downloading zapret
- Download the latest tarball from [zapret releases | github.com]
- Copy it using
qvm-copytosys-net-tmpl sys-net-tmpl: Unpack the tarball
4. Installing zapret
- Shut down
sys-net-tmpl - Set the NetVM of
sys-firewall(and all other qubes connected tosys-net) to (none) - Shut down
sys-net - Change the virtualization mode of
sys-net-tmpltoHVM - Add your ethernet card to
sys-net-tmpl - Start
sys-net-tmpl sys-net-tmpl:sudo systemctl start dnscrypt-proxysys-net-tmpl:sudo touch /var/run/qubes-service/network-managersys-net-tmpl:sudo systemctl start NetworkManager- If your network doesn’t use DHCP, you’ll have to set the ip manually here
- Wait for NetworkManager to connect
sys-net-tmpl: Clear the contents of/etc/resolv.confand addnameserver 127.0.0.1sys-net-tmpl:sudo systemctl restart systemd-resolvedsys-net-tmpl: Go tozapret’s directory and run./install_bin.shand then./blockcheck.sh(see [zapret docs | github.com] for details)sys-net-tmpl:./install_easy.sh- Answer yes when prompted to copy to
/opt/zapret - If you enable
tpwsenable it with “transparent mode” - Set the LAN interface to
lo(we’ll change it later) - Set the WAN interface to your actual ethernet card
- Answer yes when prompted to copy to
- Edit the zapret config in
/opt/zapret/config, findIFACE_LAN=loand change it toIFACE_LAN="$(ls /sys/class/net/ | grep vif* | tr '\n' ' ')" sys-net-tmpl: Test the DPI bypass bycurling some blocked websites- Shut down
sys-net-tmpl
5. Setting up sys-net
- Change the template of
sys-nettosys-net-tmpl - Start
sys-net - Revert the NetVM of all qubes you changed in 4.2 to
sys-net
6. Setting up encrypted DNS
This part is based on [this guide | forum.qubes-os.org] by qubist
- Install the
fedora-<latest>-minimalTemplateVM (from now on called justfedora-minimal, see [QubesOS docs | qubes-os.org] for details) - Create a new TemplateVM using the “Create New Qube” tool called
sys-dns-tmpl(yellow), cloned fromfedora-minimal - Start
sys-dns-tmpl dom0:qvm-run -u root sys-dns-tmpl xtermsys-dns-tmpl:dnf install qubes-core-agent-networking dnscrypt-proxy vim-minimal iptables-nft sysctlsys-dns-tmpl:systemctl disable dnscrypt-proxy- Create the
dnscryptuser and group: (sys-dns-tmpl)
groupadd --system dnscrypt
useradd --system --home /run/dnscrypt-proxy --shell /bin/false --gid dnscrypt dnscrypt
usermod --lock dnscrypt
sys-dns-tmpl:mkdir -p /run/dnscrypt-proxy- Set proper ownership and permissions: (
sys-dns-tmpl)
chown dnscrypt:dnscrypt /run/dnscrypt-proxy
chmod go-rwx /run/dnscrypt-proxy
chown -R dnscrypt:dnscrypt /etc/dnscrypt-proxy
chmod -R go-rwx /etc/dnscrypt-proxy
sys-dns-tmpl: Edit/etc/dnscrypt-proxy/dnscrypt-proxy.tomland setuser_name = 'dnscrypt'- Shut down
sys-dns-tmpl - Create the following VM:
- Name:
sys-dns-dvm(yellow) - Type: “AppVM”
- Template:
sys-dns-tmpl - Settings > Advanced > Disposable Template: On
- Name:
- Start
sys-dns-dvm dom0:qvm-run -u root sys-dns-dvm xtermsys-dns-dvm:mv /etc/dnscrypt-proxy /rw/sys-dns-dvm: Create/rw/config/setup-dns.sh
File /rw/config/setup-dns.sh
nft='/usr/sbin/nft'
# allow redirects to localhost
/usr/sbin/sysctl -w net.ipv4.conf.all.route_localnet=1
"${nft}" add rule ip qubes custom-input meta l4proto { tcp, udp } iifgroup 2 ip daddr 127.0.0.1 th dport 53 accept
# block connections to other DNS servers
"${nft}" add rule ip qubes custom-forward meta l4proto { tcp, udp } iifgroup 2 ip daddr != 127.0.0.1 th dport 53 drop
"${nft}" flush chain ip qubes dnat-dns
"${nft}" add rule ip qubes dnat-dns meta l4proto { tcp, udp } th dport 53 counter dnat to 127.0.0.1
echo 'nameserver 127.0.0.1' > /etc/resolv.conf
# https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Installation-linux
# https://wiki.archlinux.org/title/Dnscrypt-proxy#Enable_EDNS0
echo 'options edns0' >> /etc/resolv.conf
ln -s /rw/dnscrypt-proxy /etc/dnscrypt-proxy
/usr/bin/systemctl start dnscrypt-proxy.service
sys-dns-dvm: Create/rw/config/watch-dns.sh
File /rw/config/watch-dns.sh
while true; do
if cat /etc/resolv.conf | grep 127.0.0.1 > /dev/null; then
sleep 1
else
sleep 5
/rw/config/setup-dns.sh
fi
done
sys-dns-dvm: Add the following to/rw/config/rc.local
File /rw/config/rc.local
/rw/config/watch-dns.sh &
sys-dns-dvm:chmod +x /rw/config/*.sh- Shut down
sys-dns-dvm - Start
sys-dns-tmpl dom0:qvm-run -u root sys-dns-tmpl xtermsys-dns-tmpl:rm -rf /etc/dnscrypt-proxy- Shut down
sys-dns-tmpl - Create the following VM:
- Name:
sys-dns(yellow) - Type: “DisposableVM”
- Template:
sys-dns-dvm - Networking:
sys-net - Settings > Basic > Start qube automatically on boot: On
- Settings > Advanced > Provides network: On
- Name:
- Start
sys-dns - Change the NetVM of
sys-firewalltosys-dns