[guide] how-to setup a sys-dns qube

I used to run dnscrypt-proxy inside of sys-net to encrypt and secure dns-requests. Meanwhile I moved the service to a separate sys-dns and would like to share the setup with the community. Prerequisite is a fedora-36-minimal and fedora-36-minimal-dvm with dnscrypt-proxy installed and disabled.

[user@dom0 ~]$ qvm-clone fedora-36-minimal-dvm fedora-36-minimal-dns
[user@dom0 ~]$ qvm-create -C DispVM --template fedora-36-minimal-dns --label orange sys-dns
[user@dom0 ~]$ qvm-prefs sys-dns netvm sys-net
[user@dom0 ~]$ qvm-prefs sys-dns autostart true
[user@dom0 ~]$ qvm-prefs sys-dns provides_network true
[user@dom0 ~]$ qvm-run -u root fedora-36-minimal-dns xterm

Change /rw/config/rc.local in fedora-36-minimal-dns as follows:

[user@fedora-36-minimal-dns]~% cat /rw/config/rc.local
#!/bin/sh

# This script will be executed at every VM startup, you can place your own
# custom commands here. This includes overriding some configuration in /etc,
# starting services etc.

# Example for overriding the whole CUPS configuration:
#  rm -rf /etc/cups
#  ln -s /rw/config/cups /etc/cups
#  systemctl --no-block restart cups

# allow redirects to localhost
/usr/sbin/sysctl -w net.ipv4.conf.all.route_localnet=1
/usr/sbin/iptables -I INPUT -i vif+ -p tcp --dport 53 -d 127.0.0.1 -j ACCEPT
/usr/sbin/iptables -I INPUT -i vif+ -p udp --dport 53 -d 127.0.0.1 -j ACCEPT

# redirect dns-requests to localhost
/usr/sbin/iptables -t nat -F PR-QBS
/usr/sbin/iptables -t nat -A PR-QBS -d 10.139.1.1/32 -p udp -m udp --dport 53 -j DNAT --to-destination 127.0.0.1
/usr/sbin/iptables -t nat -A PR-QBS -d 10.139.1.1/32 -p tcp -m tcp --dport 53 -j DNAT --to-destination 127.0.0.1
/usr/sbin/iptables -t nat -A PR-QBS -d 10.139.1.2/32 -p udp -m udp --dport 53 -j DNAT --to-destination 127.0.0.1
/usr/sbin/iptables -t nat -A PR-QBS -d 10.139.1.2/32 -p tcp -m tcp --dport 53 -j DNAT --to-destination 127.0.0.1

# set /etc/resolv.conf and start dnscrypt-proxy
echo "nameserver 127.0.0.1" > /etc/resolv.conf
/usr/bin/systemctl start dnscrypt-proxy.service

If you want to configure dnscrypt-proxy the easiest way to achieve persitance is doing that in the template:

[user@dom0 ~]$ qvm-run -u root fedora-36-minimal xterm

From my point of view the most interesting settings are located in the following files:

[user@fedora-36-minimal]~% nano /etc/dnscrypt-proxy/dnscrypt-proxy.toml 
[user@fedora-36-minimal]~% nano /etc/dnscrypt-proxy/captive-portals.txt 
[user@fedora-36-minimal]~% nano /etc/dnscrypt-proxy/cloaking-rules.txt

I.e. you need to have

listen_addresses = ['127.0.0.1:53']

set in /etc/dnscrypt-proxy/dnscrypt-proxy.toml. I disabled systemd-resolved in the template, it might be possible to deinstall it. Actually I like systemd but sometimes systemd (and others like NetworkManager) do stuff in the background which I do not fully understand.

After setting everything up to your needs fedora-36-minimal-dns and fedora-36-minimal have to be shutdown. Then start sys-dns and point sys-firewall to sys-dns:

[user@dom0 ~]$ qvm-shutdown fedora-36-minimal
[user@dom0 ~]$ qvm-shutdown fedora-36-minimal-dns
[user@dom0 ~]$ qvm-start sys-dns
[user@dom0 ~]$ qvm-prefs sys-firewall netvm sys-dns
11 Likes

Thank you for sharing this info.

I am looking for a way to use DNScrypt for my non-whonix qubes as well. However, I am not using minimal templates but the default ones. Could you kindly share what I need to do?

I notice you don’t add any firewall rules to block non-local port 53 traffic from/to “client” qubes (those which will use sys-net/firewall and hence sys-dns in turn). Doesn’t that leave an open door for a qube to still try to access a DNS server directly, not going through dns-proxy?

Also, is there any particular reason to use iptables instead of nftables? Doesn’t Qubes OS use nftables?

minimal templates are lightweight versions of their standard template counterparts.

This means that this same setup will most likely work on your existing templates, you’ll just have to install the required packages and use the template names that apply to your case (instead of fedora-36-minimal you may have to use fedora-36).

You can read more about minimal templates here: Minimal templates | Qubes OS

Yes, I know what minimal templates are. I am just not using them. Thanks for the info.

What about the other questions?

The answer to your question is in the explanation I provided.

It doesn’t answer my firewall related questions. Maybe you didn’t notice them, did you?

I finally found the time to try this and I followed strictly the steps. Unfortunately, dnscrypt-proxy doesn’t seem to work as expected.

What happens in sys-dns is: systemctl status dnscrypt-proxy shows that the service starts successfully but for some reason it cannot connect to the network. The last message in the journal is (skipping the timestamps):

... sys-dns dnscrypt-proxy[444]: ... [NOTICE] Network not available yet -- waiting...

and it remains like this forever. The Internet connection is definitely fine - in sys-net I can ping and so on. FWIW, all my config files in /etc/dnscrypt-proxy are taken from another (physical) machine running Linux where they work fine. In case that might be interesting regarding the current issue, in dnscrypt-proxy.toml I have netprobe_timeout = -1.

Another thing I notice in sys-dns: Although my /rw/config/rc.local is identical to the example given in the OP, the file /etc/resolv.conf does not exist, i.e. is not auto-created as expected.

I tried restarting all the network related sys-* qubes but the result is always the same as described above.

How can I make this work please?

the file /etc/resolv.conf does not exist

More specifically:

# ls -l /etc/resolv*
lrwxrwxrwx 1 root root 39 Jan 11 15:48 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf

and the target seems to be non-existing.

Wild guess - did you try to enable systemd rc-local.service instead or to create the file manually?

Wild guess - did you try to enable systemd rc-local.service instead or to create the file manually?

No. I have only followed the exacts steps listed in the OP.

Some more things I notice:

Looking at the iptables rules given in this guide, I see they relate to 10.139.1.x addresses. In Qubes VM manager, column IP address, I see no qubes with IP addresses in the 10.139.1.x range. I also see that command iptables does not exist in fedora-36-minimal.

Based on all this, my questions are:

  • What is PR-QBS? (I can’t find any documentation about it)
  • What is IP address range 10.139.1.x and why is it used in a firewall rule when there is no actual host (qube) with such IP address?
  • What to do regarding missing iptables command?
  • Should the more modern nftables be used instead?
  • What should be the actual firewall rules? (considering the everything above)
  • Does this guide need an update? If yes, please someone provide the actual steps that work.

I still hope to make this work, so I kindly ask for help again.

I’m not familiar with that guide, but I will take a look tomorrow.

In the meantime -

Qubes needs some mechanism to allow networking to work in a flexible
environment.
PR-QBS is a chain in the nat table which allows DNS traffic to
propagate up Qubes networking until it reaches sys-net, whatever that
networking looks like and however the user changes it
.
10.139.1.x are placeholders used in resolv.conf in the originating qube.
When the upstream qube sees DNS traffic to those addresses it forwards it
upstream.
This continues until it reaches sys-net, where the traffic is forwarded
to the actual DNS servers used by sys-net. Responses are returned down
the network to the originating qube.

You can interrupt this flow at any stage, by changing the rules in the
PR-QBS chain.

Unless someone else jumps in I’ll comment tomorrow.

Of course.
But, it probably is already, because most use of iptables now is not
iptables-legacy but iptables-nft (nf_tables). It is a bridge from
familiar iptables commands to the nftables api.
Check what you are using with iptables -V

Probably. Once you are clear on what is wrong, perhaps you can provide
the update.

I never presume to speak for the Qubes team. When I comment in the Forum or in the mailing lists I speak for myself.
1 Like

I’m not familiar with that guide, but I will take a look tomorrow.

Great! Thank you!

PR-QBS is a chain in the nat table which allows DNS traffic to
propagate up Qubes networking until it reaches sys-net, whatever that
networking looks like and however the user changes it
.

Where did you learn about all this? How can I educate myself?

10.139.1.x are placeholders used in resolv.conf in the originating qube.

But how do those relate to the actual IP addresses of the qubes as seen in Qubes manager? The later are quite different.

This continues until it reaches sys-net, where the traffic is forwarded
to the actual DNS servers used by sys-net. Responses are returned down
the network to the originating qube.

You can interrupt this flow at any stage, by changing the rules in the
PR-QBS chain.

Doesn’t that make sys-firewall, being a/the dedicated firewall, the proper place to do that? What is the benefit of having a whole separate sys-dns qube just for DNS compared to simply installing dnscrypt-proxy in sys-firewall (or sys-net) itself?

I may be missing something but considering minimalism and simplicity as a security principle, as well as the (always) limited system resources, is it not an overkill to have whole 3 VMs chained one after another just to connect to the Internet?

Check what you are using with iptables -V

As I mentioned, the command iptables does not exist in fedora-36-minimal. I have no idea which package to install and in which particular qube in order to have it in sys-dns. Assuming that the minimal template was created by experts with the intention to be minimal, I suppose it is a deliberate choice not to include iptables in it. IOW, I wonder if it is appropriate to even consider installing anything additional to the intentionally minimal system. So, this is kind of confusing. I will wait for your clarifications.

Once you are clear on what is wrong, perhaps you can provide the update.

Of course, I would be glad to help if I can. However, although I run dnscrypt-proxy on my (non Qubes OS) Linux systems, I don’t consider myself a network expert. Perhaps that would better be done by someone who has deeper knowledge about Qubes OS and could answer questions which may arise later. It may be best to simply have dnscrypt-proxy “out of the box” in Qubes OS as default DNS system for the non-anonymous networking. Then, one can simply customize the config files in /etc/dnscrypt-proxy. I don’t know if that has been suggested or considered but IMO it would fit quite well the overall philosophy of Qubes OS.

Looking forward to your further comments.
Thank you.

Dear @unman,

Did you have the time to look into this?
I really hope you can help.
Thanks.

After finding some problems, I made it work.
I am working on an updated guide. Coming soon.

2 Likes

Looking forward to it.

The firewall documentation says:

“Qubes does not support running any networking services (e.g. VPN, local DNS server, IPS, …) directly in a qube that is used to run the Qubes firewall service (usually sys-firewall) for good reasons. In particular, if you want to ensure proper functioning of the Qubes firewall, you should not tinker with iptables or nftables rules in such qubes.”

which sounds confusing because:

  • sys-firewall is not a service, it is a VM
  • the “good reasons” have not been explicitly clarified
  • it seems impossible “not to tinker” with iptables or nftables rules if one wants to configure a qube running a DNS because those rules are necessary for proper packet routing

IOW, even if we deploy the network infrastructure proposed in the doc:

sys-net <--> sys-firewall-1 <--> network service qube <--> sys-firewall-2 <--> [client qubes]

we still need firewall rules in the network service qube (which will run the dnscrypt-proxy service). Does this remove the need for sys-firewall-2 which only direct DNS traffic to the network service qube? The doc says:

"The sys-firewall-2 proxy ensures that:

  1. Firewall changes done in the network service qube cannot render the Qubes firewall ineffective.
  2. Changes to the Qubes firewall by the Qubes maintainers cannot lead to unwanted information leakage in combination with user rules deployed in the network service qube.
  3. A compromise of the network service qube does not compromise the Qubes firewall."

Re. 1: Even without sys-firewall-2, the Qubes firewall (sys-firewall-1) is still separate from the network service qube. So, it is not clear how exactly sys-firewall-2 ensures anything in regards to that.

Re. 2: That highly depends on the actual changes and the actual user rules. Example: Suppose the developers (deliberately or by mistake) switch the default policy of the FORWARD chain from DROP to ACCEPT. Then maybe the network service qube (not having any firewall rules, as both advised and impossible) can forward traffic through sys-firewall-1 and sys-firewall-2 can do nothing about it.

Re. 3: Just like in 1, sys-firewall-2 has nothing to do with that.

Regardless of the above confusion in documentation, one can assume that “good reasons” means improved security through additional isolation of firewall stuff from DNS. Having a second firewall between the network service qube and the client cubes can reduce the possibility of leakage from client qubes to the Internet. It also creates another possibility (more on that below).

The other confusion is that Qubes OS still uses the legacy iptables, making us dependent on it through the package qubes-core-agent-networking. To make things even more complicated, Qubes OS mixes that with nftables, making the whole thing very difficult to understand and manage. In my trials, I found it sufficient to use only iptables rules, as explained below.

So, the goal is to deploy the following network infrastructure:

[network uplink]
 └── sys-net
     └── sys-firewall
         ├── sys-dns
         │   └── sys-wall
         │       ├── qube-1
         │       ├── qube-2
         │       ├── [...]
         │       └── qube-n
         └── sys-whonix
             └── [anonymized-qubes]

Preparation

Install fedora-37-minimal template and update it.

In dom0:

sudo qubes-dom0-update qubes-template-fedora-37-minimal
sudo qubesctl --show-output --skip-dom0 --targets fedora-37-minimal state.sls update.qubes-vm

Create a minimal disposable sys-dns qube:

Preserve the original template as an untouched starting point for other qubes.

In dom0:

qvm-shutdown fedora-37-minimal
qvm-clone fedora-37-minimal f37-m-net

Install software in the cloned template

In dom0:

qvm-run -u root f37-m-net xterm

As per docs, qubes-core-agent-networking seems necessary for anything network related:

In f37-m-net:

dnf install qubes-core-agent-networking dnscrypt-proxy vim-minimal
systemctl disable dnscrypt-proxy

Customize files in /etc/dnscrypt-proxy.

Create user and group, so the service does not run as root:

groupadd --system dnscrypt
useradd --system --home /run/dnscrypt-proxy --shell /bin/false --gid dnscrypt dnscrypt
usermod --lock dnscrypt

This directory should be the same as the one used in subsections of section [sources] in /etc/dnscrypt-proxy/dnscrypt-proxy.toml. I am using a subdir of /run in order to hopefully have cache in RAM (/run is a tmpfs mount):

mkdir -p /run/dnscrypt-proxy

Set proper ownership and permissions:

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

Use the same user in /etc/dnscrypt-proxy/dnscrypt-proxy.toml:

user_name = 'dnscrypt'

Create a disposable DNS template:

In dom0:

qvm-shutdown f37-m-net
qvm-create -C AppVM --template f37-m-net --label red f37-m-dns-dvm
qvm-prefs f37-m-dns-dvm template_for_dispvms True
qvm-create -C DispVM --template f37-m-dns-dvm --label orange sys-dns
qvm-run -u root f37-m-dns-dvm xterm

In f37-m-dns-dvm move the config files to /rw dir to make it specific to the disposable qube only:

mv /etc/dnscrypt-proxy /rw/

In /rw/config/rc.local:

#!/bin/sh

# This script will be executed at every VM startup, you can place your own
# custom commands here. This includes overriding some configuration in /etc,
# starting services etc.

ipt='/usr/sbin/iptables'

# allow redirects to localhost
/usr/sbin/sysctl -w net.ipv4.conf.all.route_localnet=1
"${ipt}" -I INPUT -i vif+ -p tcp --dport 53 -d 127.0.0.1 -j ACCEPT
"${ipt}" -I INPUT -i vif+ -p udp --dport 53 -d 127.0.0.1 -j ACCEPT

# block connections to other DNS servers
"${ipt}" -I FORWARD -i vif+ -p tcp --dport 53 ! -d 127.0.0.1 -j DROP
"${ipt}" -I FORWARD -i vif+ -p udp --dport 53 ! -d 127.0.0.1 -j DROP

"${ipt}" -t nat -F PR-QBS
"${ipt}" -t nat -A PR-QBS -p udp --dport 53 -j DNAT --to-destination 127.0.0.1
"${ipt}" -t nat -A PR-QBS -p tcp --dport 53 -j DNAT --to-destination 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

In dom0:

qvm-run -u root f37-m-net xterm

In f37-m-net:

rm -rf /etc/dnscrypt-proxy

Test if the service works:

In dom0:

qvm-shutdown f37-m-dns-dvm f37-m-net
qvm-run -u root sys-dns xterm

In sys-dns:

systemctl status dnscrypt-proxy

The above should show that the service is active and running.

Optional (for more details):

systemctl restart dnscrypt-proxy; journalctl --output=short-monotonic -f -u dnscrypt-proxy

after completion of the start process the journal shows something like:

[...]
[ 5611.615623] sys-dns dnscrypt-proxy[2951]: [2023-01-22 21:50:02] [NOTICE] -   519ms dnswarden-uncensor-dc-swiss
[ 5611.615696] sys-dns dnscrypt-proxy[2951]: [2023-01-22 21:50:02] [NOTICE] -   584ms pryv8boi
[ 5611.615780] sys-dns dnscrypt-proxy[2951]: [2023-01-22 21:50:02] [NOTICE] -   760ms altername
[ 5611.615845] sys-dns dnscrypt-proxy[2951]: [2023-01-22 21:50:02] [NOTICE] Server with the lowest initial latency: scaleway-ams (rtt: 89ms)
[ 5611.615944] sys-dns dnscrypt-proxy[2951]: [2023-01-22 21:50:02] [NOTICE] dnscrypt-proxy is ready - live servers: 33

List processes running as user dnscrypt:

In sys-dns:

ps -U dnscrypt -u dnscrypt u
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
dnscrypt    2951  0.2  2.5 1301200 100776 ?      Ssl  21:49   0:01 /usr/bin/dnscrypt-proxy -config /etc/dnscrypt-proxy/dnscrypt-proxy.toml -child

Create and configure the new minimal disposable firewall sys-wall:

We have already installed qubes-core-agent-networking in f37-m-net, so we simply use that template.

qvm-create -C AppVM --template f37-m-net --label red f37-m-firewall-dvm
qvm-prefs f37-m-firewall-dvm template_for_dispvms True
qvm-create -C DispVM --template f37-m-firewall-dvm --label green sys-wall

Configure the network structure based on the initial diagram.

In dom0:

qvm-prefs sys-dns netvm sys-firewall
qvm-prefs sys-dns autostart true
qvm-prefs sys-dns provides_network true
qvm-prefs sys-wall netvm sys-dns
qvm-prefs sys-wall provides_network true

Configure the firewall rules:

In dom0:

qvm-run -u root f37-m-firewall-dvm xterm

In f37-m-firewall-dvm edit /rw/config/rc.local

#!/bin/sh

# This script will be executed at every VM startup, you can place your own
# custom commands here. This includes overriding some configuration in /etc,
# starting services etc.

ipt='/usr/sbin/iptables'

# redirect all dns-requests to sys-dns
"${ipt}" -t nat -F PR-QBS
"${ipt}" -t nat -A PR-QBS -d 10.139.1.1/32 -p udp --dport 53 -j DNAT --to-destination 10.138.26.87
"${ipt}" -t nat -A PR-QBS -d 10.139.1.1/32 -p tcp --dport 53 -j DNAT --to-destination 10.138.26.87
"${ipt}" -t nat -A PR-QBS -d 10.139.1.2/32 -p udp --dport 53 -j DNAT --to-destination 10.138.26.87
"${ipt}" -t nat -A PR-QBS -d 10.139.1.2/32 -p tcp --dport 53 -j DNAT --to-destination 10.138.26.87

# block connections to other DNS servers
"${ipt}" -t nat -A PR-QBS -p udp --dport 53 -j DNAT --to-destination 0.0.0.0
"${ipt}" -t nat -A PR-QBS -p tcp --dport 53 -j DNAT --to-destination 0.0.0.0

Set sys-wall as NetVM for a qube and test in a terminal in that qube:

[user@disp1463 ~]$ host gnu.org
gnu.org has address 209.51.188.116
gnu.org has IPv6 address 2001:470:142:5::116
gnu.org mail is handled by 10 eggs.gnu.org.
[user@disp1463 ~]$ host google-analytics.com
google-analytics.com host information "This query has been locally blocked" "by dnscrypt-proxy"
google-analytics.com host information "This query has been locally blocked" "by dnscrypt-proxy"
google-analytics.com host information "This query has been locally blocked" "by dnscrypt-proxy"

That’s because my blocked_names_file has a line blocking the last host. This confirms the configuration is working.

Test if it is possible to circumvent the blockage and use another DNS server (8.8.8.8):

[user@disp1463 ~]$ host google-analytics.com 8.8.8.8
;; connection timed out; no servers could be reached

It seems sys-wall woks as expected too.

Finally, set sys-wall as NetVM for all qubes which should use DNScrypt.

TODO:

  • DNScrypt-proxy allows name (un)blocking. Using an dedicated combination of sys-dns and sys-wall for a qube it is possible to block everything and allow only one domain (or a set of them), e.g. *.mybank.com. With proper scripting and UI, this may be a possible solution to a problem discussed long time ago.

  • Another thing is getting rid of iptables and using nftables. This seems something developers need to do in relation to qubes-core-agent-networking. Has it been reported and/or considered?

  • Ideally, dnscrypt-proxy should be integrated in Qubes OS out of the box.

Comments, suggestions and corrections are very welcome. I don’t pretend to be an expert, just sharing what worked for me.

P.S. Sorry for the huge delay. I had some troubles and this whole thing took longer than expected. Still, better late than never, I hope.

6 Likes

@qubist Thank you for sharing, it works beautifully!

What memory parameters you can suggest in order to use the limited RAM more frugally?

I don’t understand why do we need sys-wall when you yourself said that sys-firewall-2 makes no sense.

Walk me through these lines please.
Do I understand it correctly that 10.139.1.1/32 and 10.139.1.2/32 are addresses of the qubes dns server? If so, why there are two of them?

Is assigned by qubes dhcp server sys-dns ip address always static?

What does PR-QBS chain exactly do? Does it forward all packets on port 53 in all qubes to the 10.139.1.1-10.139.1.2? And in your example it further redirects them to sys-dns ip?


wtf, why is unman’s post listed as my parent post?
well, whatever, he has some interesting things in his post related to my question

When the upstream qube sees DNS traffic to those addresses it forwards it upstream.

This is all fine and dandy, but how does this works exactly?
In my firewall vm I have the following iptables configuration:

  1. filter
Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  any    any     anywhere             anywhere             state INVALID
    0     0 DROP       udp  --  vif+   any     anywhere             anywhere             udp dpt:bootpc
   16  1582 ACCEPT     all  --  any    any     anywhere             anywhere             ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     icmp --  vif+   any     anywhere             anywhere            
    4   208 ACCEPT     all  --  lo     any     anywhere             anywhere            
    0     0 REJECT     all  --  vif+   any     anywhere             anywhere             reject-with icmp-host-prohibited
    0     0 DROP       all  --  any    any     anywhere             anywhere            

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  any    any     anywhere             anywhere             state INVALID
2241K 2453M ACCEPT     all  --  any    any     anywhere             anywhere             ctstate RELATED,ESTABLISHED
  653  160K QBS-FORWARD  all  --  any    any     anywhere             anywhere            
    0     0 DROP       all  --  vif+   vif+    anywhere             anywhere            
  653  160K ACCEPT     all  --  vif+   any     anywhere             anywhere            
    0     0 DROP       all  --  any    any     anywhere             anywhere            

Chain OUTPUT (policy ACCEPT 20 packets, 1202 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain QBS-FORWARD (1 references)
 pkts bytes target     prot opt in     out     source               destination         

  1. mangle
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
2244K 2456M QBS-POSTROUTING  all  --  any    any     anywhere             anywhere            

Chain QBS-POSTROUTING (1 references)
 pkts bytes target     prot opt in     out     source               destination         

  1. nat
Chain PREROUTING (policy ACCEPT 115 packets, 79516 bytes)
 pkts bytes target     prot opt in     out     source               destination         
  415  102K PR-QBS     all  --  any    any     anywhere             anywhere            
  115 79516 PR-QBS-SERVICES  all  --  any    any     anywhere             anywhere            

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 16 packets, 1042 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  any    vif+    anywhere             anywhere            
    4   208 ACCEPT     all  --  any    lo      anywhere             anywhere            
  425  103K MASQUERADE  all  --  any    any     anywhere             anywhere            

Chain PR-QBS (1 references)
 pkts bytes target     prot opt in     out     source               destination         
  300 22677 DNAT       udp  --  any    any     anywhere             10.139.1.1           udp dpt:domain to:10.139.1.1
    0     0 DNAT       tcp  --  any    any     anywhere             10.139.1.1           tcp dpt:domain to:10.139.1.1
    0     0 DNAT       udp  --  any    any     anywhere             10.139.1.2           udp dpt:domain to:10.139.1.2
    0     0 DNAT       tcp  --  any    any     anywhere             10.139.1.2           tcp dpt:domain to:10.139.1.2

Chain PR-QBS-SERVICES (1 references)
 pkts bytes target     prot opt in     out     source               destination         

  1. raw
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  !vif+  any     10.137.0.36          anywhere            
    0     0 DROP       all  --  vif7.0 any    !10.137.0.36          anywhere            
    0     0 DROP       all  --  !vif+  any     10.138.16.52         anywhere            
    0     0 DROP       all  --  vif6.0 any    !10.138.16.52         anywhere            
2245K 2457M QBS-PREROUTING  all  --  any    any     anywhere             anywhere            

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain QBS-PREROUTING (1 references)
 pkts bytes target     prot opt in     out     source               destination         

  1. security
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

The only substantial part related to PR-QBS is in the nat table and it is just a tautology that forwards to 10.139.1.1 if the destination is 10.139.1.1

I don’t see how it “forwards it upstream”

@qubean

What memory parameters you can suggest in order to use the limited RAM more frugally?

The minimum which doesn’t cause swapping.