Skip to content

Instantly share code, notes, and snippets.

@Joeviocoe
Forked from daktak/qvm-exposeip.sh
Last active June 16, 2021 11:40
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save Joeviocoe/6c4dc0c283f6d6c5b1a3f5af8793292b to your computer and use it in GitHub Desktop.
Save Joeviocoe/6c4dc0c283f6d6c5b1a3f5af8793292b to your computer and use it in GitHub Desktop.
Qubes-os port forwarding to allow external connections
#!/bin/sh
# Inspired by https://gist.github.com/daktak/f887352d564b54f9e529404cc0eb60d5
# Inspired by https://gist.github.com/jpouellet/d8cd0eb8589a5b9bf0c53a28fc530369
ip() { qvm-prefs -g -- "$1" ip; }
netvm() { qvm-prefs -g -- "$1" netvm; }
forward() {
local from_domain=$1
local to_domain=$2
local port=$3
local type=$4
local from_ip=$(ip "$from_domain")
local to_ip=$(ip "$to_domain")
local iface=$(qvm-run -p -u root "$from_domain" "ifconfig \
| grep cast -B 1 --no-group-separator | grep -vE '^(vif|lo)' | grep -oE '^[^: ]+' | head -1")
local from_ip=$(qvm-run -p -u root "$from_domain" "hostname -I | cut -d ' ' -f 1")
if [ X"$from_ip" = XNone ] ; then local from_ip= ; fi
if [[ $3 = "clear" && $4 = "all" ]]
then
echo "$from_domain: Clearing Port Forwarding from $1 iptables" >&2
qvm-run -p -u root "$from_domain" "iptables-save | grep -v 'PortFwd $1' | iptables-restore"
local nft_cmd="nft list table ip qubes-firewall -a | tr -d '\"' | grep 'iifname $iface accept # handle' | awk '{print \$NF}'"
local nft_handle=$(qvm-run -p -u root "$from_domain" "$nft_cmd")
if [[ $nft_handle =~ ^[0-9]+$ ]] ; then qvm-run -p -u root "$from_domain" "nft delete rule ip qubes-firewall forward handle $nft_handle" ; fi
else
echo "$from_domain: Forwarding on $iface port $port to $to_domain
($from_ip -> $to_ip)" >&2
qvm-run -p -u root "$from_domain" "iptables-save | grep -v 'PortFwd $1>$2:$4$3' | iptables-restore"
qvm-run -p -u root "$from_domain" "iptables -t nat -A PREROUTING -i $iface -p $type ${from_ip:+-d} $from_ip --dport $port -j DNAT --to-destination $to_ip \
-m comment --comment 'PortFwd $1>$2:$4$3'"
qvm-run -p -u root "$from_domain" "iptables -I FORWARD 2 -i $iface -p $type ${to_ip:+-d} $to_ip --dport $port -m conntrack --ctstate NEW -j ACCEPT \
-m comment --comment 'PortFwd $1>$2:$4$3'"
qvm-run -p -u root "$from_domain" "nft add rule ip qubes-firewall forward meta iifname $iface accept"
fi
}
input() {
local domain=$1
local port=$2
local type=$3
if [[ $2 = "clear" && $3 = "all" ]]
then
echo "$domain: Clearing Port Forwarding from $1 iptables" >&2
qvm-run -p -u root "$domain" "iptables-save | grep -v 'PortFwd $1' | iptables-restore"
else
echo "$domain: Allowing input to port $port" >&2
qvm-run -p -u root "$domain" "iptables-save | grep -v 'PortFwd $1:$3$2' | iptables-restore"
qvm-run -p -u root "$domain" "iptables -I INPUT 5 -p $type --dport $port -m conntrack --ctstate NEW -j ACCEPT \
-m comment --comment 'PortFwd $1:$3$2'"
fi
}
recurse_netvms() {
local this_dom=$1
local port=$2
local type=$3
local outer_dom=$(netvm "$this_dom")
if [[ -n "$outer_dom" && "$outer_dom" != "None" ]]; then
forward "$outer_dom" "$this_dom" "$port" "$type"
recurse_netvms "$outer_dom" "$port" "$type"
fi
}
usage() {
echo "Usage: ${0##*/} <vm> <port> <proto> | <vm> clear all" >&2
exit 1
}
[ $# -eq 3 ] || usage
input "$1" "$2" "$3"
recurse_netvms "$1" "$2" "$3"
@Joeviocoe
Copy link
Author

Joeviocoe commented Feb 10, 2018

Updated version for Qubes 4.0 (RC4 tested)
Usage: qvm-portfwd <vm> <port> <proto> | <vm> clear all
Example: qvm-portfwd webserv 8888 tcp

Command line specify the "VM, Port and Protocol"... or just "VM clear all" to undo previous.
Script will recursively configure iptables/nft for all proxyVMs in use.
Now uses comments on iptables to remove previous entries (no duplicates)

Works with Fedora 25/26 which uses nft rules along with iptables
Works with Debian 8/9 too

@jpouellet
Copy link

[[ ... =~ .. ]] is a bashism, but you're using /bin/sh (which is not guaranteed to be bash)

Also, for safety and portability I'd suggest:

  • [ X"$foo" = Xbar ] or [ bar = "$foo" ] instead of [[ $foo = bar ]]
  • [ ... -a ... ] / [ ... -o ... ] instead of [[ ... && ... ]] / [[ ... || ...]].

Otherwise nice, and your script is probably better than mine. 🍻

@jpouellet
Copy link

jpouellet commented Jun 25, 2018

Also, I don't like how you're reaching into a VM to grab nft_handle, processing the (untrusted!) result in dom0, and then using the result to decide whether to issue another command. A safer way would be to issue only one command which does the checking and conditional command entirely on the VM, discarding any output back to dom0. Might be over-paranoid, but then again, we had shellshock.

... | grep -qx '[0-9]+' && ( ... ) in the command passed to the VM should do it and remove the bashism at the same time.

@AlphaMufasaOmega
Copy link

AlphaMufasaOmega commented Sep 12, 2018

how do i use/install this on Qubes 4.0 I need to port forward correctly on Qubes 4.0 and and also I have a router so kinda confusing... any help is appreciated! thanks

@w1k1n9cc
Copy link

Simpy put it in ~/bin in dom0 or any other directory in your PATH and use it. Here is an example how I use it for samba:

#!/bin/sh

# Configure ports for samba server
qvm-portfwd server 445 tcp
qvm-portfwd server 445 udp
qvm-portfwd server 139 tcp
qvm-portfwd server 138 udp
qvm-portfwd server 137 udp

# and make conncections between VMS
qvm-portfwd server 445 tcp work
qvm-portfwd server 445 udp work
qvm-portfwd server 139 tcp work
qvm-portfwd server 138 udp work
qvm-portfwd server 137 udp work

I hope that I didn't make any changes. But I don't think so.

@AlphaMufasaOmega
Copy link

AlphaMufasaOmega commented Sep 17, 2018

this sounds really stupid but what is the exact command to do that?

`
qvm-portfwd PTFAssaultMachine 443 tcp

isn't working lol i can get the file into dom0 but it wont recognize the command

for the command

 sudo nano /rw/config/rc.local

#!/bin/sh


####################
# My service routing

# Create a new firewall natting chain for my service
if iptables -t nat -N MY-HTTPS; then

# Add a natting rule if it did not exit (to avoid cluter if script executed multiple times)
  iptables -t nat -A MY-HTTPS -j DNAT --to-destination 10.137.1.x

fi


# If no prerouting rule exist for my service
if ! iptables -t nat -n -L PREROUTING | grep --quiet MY-HTTPS; then

# add a natting rule for the traffic (same reason)
  iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -d 192.168.0.x -j MY-HTTPS
fi


######################
# My service filtering

# Create a new firewall filtering chain for my service
if iptables -N MY-HTTPS; then

# Add a filtering rule if it did not exit (to avoid cluter if script executed multiple times)
  iptables -A MY-HTTPS -s 192.168.x.0/24 -j ACCEPT

fi

# If no forward rule exist for my service
if ! iptables -n -L FORWARD | grep --quiet MY-HTTPS; then

# add a forward rule for the traffic (same reason)
  iptables -I FORWARD 2 -d 10.137.1.x -p tcp --dport 443 -m conntrack --ctstate NEW -j MY-HTTPS

fi

    Note: Again in R4 the following needs to be added:

#############
# In Qubes R4

# If not already present
if nft -nn list table ip qubes-firewall | grep "tcp dport 443 ct state new"; then

# Add a filtering rule
  nft add rule ip qubes-firewall forward meta iifname eth0 ip daddr 10.137.0.x tcp dport 443 ct state new counter accept

fi

Finally make this file executable, so it runs at each boot

sudo chmod +x /rw/config/rc.local 

what is MY-HTTPS referring to? it is confusing me lol its on

https://www.qubes-os.org/doc/firewall/#port-forwarding-to-a-qube-from-the-outside-world

also what does this mean for me(at the bottom of the same page mentioned):

Where to put firewall rules (R4.0)

Implicit in the above example scripts, but worth calling attention to: for all qubes except AppVMs supplying networking, iptables commands should be added to the /rw/config/rc.local script. For AppVMs supplying networking (sys-firewall inclusive), iptables commands should be added to /rw/config/qubes-firewall-user-script.

if i have a basic configuration with app machines : firewall sys-net and PTFAssaultMachine with all three being App machines, also i have a WiFi router and I am wondering if that matters

@npodonnell
Copy link

This is awesome! - worked like a charm.

Thanks

@thavlik
Copy link

thavlik commented Jun 5, 2019

This works well for me. As the instructions said, put it in dom0's ~/bin directory. I used the following instructions:
https://www.qubes-os.org/doc/copy-from-dom0/

From my debian-9 vm, it was:

qvm-run --pass-io debian-9 'cat /home/user/Downloads/qvm-portfwd' > ~/bin/qvm-portfwd
chmod +x ~/bin/qvm-portfwd

Good luck to all others.

@Joeviocoe
Copy link
Author

[[ ... =~ .. ]] is a bashism, but you're using /bin/sh (which is not guaranteed to be bash)

Also, for safety and portability I'd suggest:

* `[ X"$foo" = Xbar ]` or `[ bar = "$foo" ]` instead of `[[ $foo = bar ]]`

* `[ ... -a ... ]` / `[ ... -o ... ]` instead of `[[ ... && ... ]]` / `[[ ... || ...]]`.

Otherwise nice, and your script is probably better than mine. beers
Also, I don't like how you're reaching into a VM to grab nft_handle, processing the (untrusted!) result in dom0....

Thanks. I haven't had time to implement these changes but they are good changes to make.

Honestly, I use a new script that is more in line with Qubism (not Picasso). Everything I need for port fowarding is TCP based... so this script is much safer and uses socat over Qubes own RPC. It connects the net-vm directly to the app-vm without the need for either NFT or IPtables on a proxy-vm/firewall.

It requires some 'install' commands outside/prior to the script... but that could be scripted as well.
https://gist.github.com/Joeviocoe/90ec9fd9a0769b4671a8ae9c87584187

@100111001
Copy link

Dear @Joeviocoe are the changes applied persistent upon next reboot or even beyond that?

@schichtnudelauflauf
Copy link

Why this double fw thing tho?
nf and ip tables

@fepitre
Copy link

fepitre commented Dec 22, 2019

In case someone wants to test, I improved a little bit the current version (https://gist.github.com/fepitre/941d7161ae1150d90e15f778027e3248), notably by adding option for persistence. I'll do some review again (and from others I hope) and we will probably refactor it a little bit again for putting it in @QubesOS-contrib

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment