[Qubes OS 4.2] Easily NAT qubes port to external network

Hello,

I made a script to ease the work of doing a NAT to expose a qube port to the physical network interface. I wrote a blog post about it. I often need to do that when developing.

Source code available on sourcehut: ~solene/qubes-os-nat - sourcehut git

It takes 2 mandatory parameters and 1 optional parameter:

  1. qube name
  2. port number
  3. protocol, either tcp or udp, tcp by default

The script will crawl the qube netVm up to the last netvm holding the physical interface, even if it’s not named sys-net, and establish the port redirection on all netvm in the path.

The script does not alter qvm-firewall rules, so if you blocked all ports, the redirection will not bypass the firewall. This is on purpose.

The redirection is temporary, if you reboot a qube in the network path or the qube itself, the redirection will stop working. If you want a permanent redirection, you should script your qubes to do so.

Installation

A simple method to copy the script to dom0 is to run this command on dom0:

qvm-run --pass-io sys-firewall "curl https://git.sr.ht/~solene/qubes-os-nat/blob/master/nat.sh" > nat.sh
chmod +x nat.sh

You can put the script wherever you prefer.

Usage

If you want to expose port TCP 8080 of a qube named Server type:

./nat.sh Server 8080 tcp

It also works for UDP, replace tcp with udp in the command line.

Limitations

Currently, there is no way to undo added rules, this is not a huge issue as rebooting remove the rules, but it’s not nice. The rules are added at the top of the related chains, so if you run the script to forward the same port you used before to another qube, it will have a higer priority over the older rules. :+1:

9 Likes

[irrelevant comment retracted]

1 Like

Cool!

I have a follow-up question.

How do I only allow TCP while denying all access to UDP and SSH? I know this can be done on the LAN in routers’ policies, but I also believe in layering security so I would like the redundancy across all entry points on the network such as the entire OS of Qubes and/or individual VMs to have that restriction in the sys-firewall policy as well.

Is there a guide for that too already, and if so where?

Your question seems to be unrelated to this topic.
Read this:

2 Likes

Okay thanks

thanks!

TLDR; Qubes OS blocks all incoming ports by default, on every qubes.

1 Like

Hi Solene,

thank you much for this script.

Unfortunately, I’m still on 4.1 and nft setup seems to be a little different. I have a vague idea how nft works and understand that there is no “qubes” table as in your script, but a “qubes-firewall” table and also a “nat” table, on both sys-firewall and on sys-net. Can I just replace “qubes” in the scripts with “qubes-firewall”, or is that a bad idea?

For Qubes OS 4.1 you need to use iptables instead of nftables.
The script is using nftables firewall rules described in this guide for Qubes OS 4.2:
Firewall | Qubes OS
You can modify the script to use corresponding iptables rules that you can see in the same guide for Qubes OS 4.1:
Firewall 4.1 | Qubes OS

Yes, thank you.

Strangely, I have nft, I must have already upgraded something, but I definitely have not 4.2 already.

Found this this in another thread, it’s exactly my situation.

So, I replaced the tables and chains in Solene’s script as showed below, and that works. I do not understand the setup in detail and hope I haven’t compromised security by doing so. It’s for development in WLAN anyway, but that doesn’t mean much.

Edit: removed the script since it’s been nonsense according to comments below

In Qubes OS 4.1 nftables is only used for Qubes Firewall rules (the firewall rules that you define in qube’s Settings → Firewall tab), the firewall rules for the qube itself are defined using iptables.

1 Like

Arrggh… so both are in use. Ok, I didn’t expect this. Thanks a lot!

In fact, only nftables is used, the iptables command is basically a wrapper that is converting the iptables rules to the nftables rules and adds them to the nftables tables.
But the system rules are still managed using iptables command and have different table names etc compared to the Qubes OS 4.2.

1 Like

the script looks for the netvm tree from the qube with the service you want to expose and will open the port in the firewall for its netvm.

Then, for each netvm up to the one which has no netvm (like sys-net or sys-usb), it redirects the traffic on that port to the VM below.

1 Like

Hi, I’ve tried your script, but it does not work :confused:
In the execution i see that the rules are applied correctly.

But if I run a webserver (python -m http.server) on the qube and I run nat.sh <dest> 8000 tcp, I cannot browse to this website using another qube.

How can I see if all the ports are blocked ? Do you mean inbound ?

Thanks !

This script is for providing access to the server running inside the qube from the network outside of Qubes OS (e.g. from the LAN).
For example, if your server is running in qubeA in this setup:
qubeA ↔ sys-firewall ↔ sys-net
And you run the script in dom0, then you’ll be able to access the server running in qubeA from the different machine connected to the same LAN as sys-net.

If you want to enable networking between two qubes then use this guide:
Firewall | Qubes OS
Or if you just want to access the server without full networking then you can use RPC and follow this guide:
Firewall | Qubes OS

1 Like

Thanks. I was working, but the service I exposed was only for other devices in the LAN, and I was checking on another qube. I thought the connecion would have gone from the browser up to the sys-net qubes, and back to the qubes where I listen…

How can I modify this script so that it’s also available to other qubes (behind the same firewall-vm) ?

You can use the firewall rules from guide for this:
Firewall | Qubes OS

so if my external machine is able to connect to my home network and is assigned an ip address on that subnet. how will that subnet communicate with my qube that is on the qubes os subnet? Sys-net handles this translation? no ip routes to add?

Hi Solene, Thanks for this script. Your work makes qubes better for us all!

I am encountering an issue running the script, and would greatly appreciate if you could take a look for me


[user@dom0 ~]$ /home/user/Documents/scripts/nat.sh debian-gaming-dvm 47989 tcp
qube debian-gaming-dvm is not running
[user@dom0 ~]$ /home/user/Documents/scripts/nat.sh debian-gaming-dvm 47989 tcp
qube is running 
Qube debian-gaming-dvm has sys-firewall for netvm
sys-firewall -> debian-gaming-dvm: [OK]
Qube sys-firewall has sys-net for netvm
sys-net -> sys-firewall: [OK]
Qube sys-net has - for netvm
2025-04-25 18:30:43.774 qrexec-client[11634]: qrexec-daemon-common.c:232:connect_unix_socket: connect /var/run/qubes/qrexec.debian-gaming-dvm: No such file or directory
2025-04-25 18:30:43.774 qrexec-client[11634]: qrexec-daemon-common.c:29:negotiate_connection_params: write daemon: Bad file descriptor
[user@dom0 ~]$

Is this because I don’t have qubes tools installed in the standalone hvm?

This is for a sunshine server I’m trying to access with moonlight over the LAN, as well as in a seperate qube (I know you’ve used the tool so I thought I’d mention)

Any thoughts appreciated, cheers

hi, if you can’t use qvm-run for the qube, the NAT is ready but you will need to ensure the port is opened in that qube, that’s the only thing qvm-run is doing in the qube: opening the port.

1 Like