Skip to content

Instantly share code, notes, and snippets.

@neowutran
Forked from fepitre/qvm-portfwd-iptables
Last active December 10, 2023 13:32
Show Gist options
  • Save neowutran/e93ce542ba1e94a5ecbf1a38eef85485 to your computer and use it in GitHub Desktop.
Save neowutran/e93ce542ba1e94a5ecbf1a38eef85485 to your computer and use it in GitHub Desktop.
Qubes-os port forwarding to allow external connections
#!/bin/bash
# Neowutran <github@neowutran.ovh>
# Adapted previous work to support QubesOS v4.2
# Adapted from previous work:
# - https://gist.github.com/fepitre/941d7161ae1150d90e15f778027e3248
# - https://gist.github.com/daktak/f887352d564b54f9e529404cc0eb60d5
# - https://gist.github.com/jpouellet/d8cd0eb8589a5b9bf0c53a28fc530369
# - https://gist.github.com/Joeviocoe/6c4dc0c283f6d6c5b1a3f5af8793292b
[ "$DEBUG" = 1 ] && set -x
ip() {
qvm-prefs -g -- "$1" ip
}
netvm() {
qvm-prefs -g -- "$1" netvm
}
forward() {
local action="$1"
local from_qube="$2"
local to_qube="$3"
local port="$4"
local proto="$5"
local persistent="$6"
local iface
local from_ip
local to_ip
local nft_cmd
local nft_handle
# TODO: Handle multiple interfaces in sys-net. It currently catches only the first physical interface which is UP.
iface=$(qvm-run -p -u root "$from_qube" "ip link | grep -E '^[0-9]' | grep -E 'state UP' | cut -d ':' -f 2 | cut -d ' ' -f 2 | grep -vE '^(vif|lo)' | head -1")
#from_ip=$(qvm-run -p -u root "$from_qube" "hostname -I | cut -d ' ' -f 1")
from_ip=$(qvm-run -p -u root "$from_qube" "ip addr show dev $iface | grep -Eo 'inet [0-9]+(\.[0-9]+){3}' | cut -d ' ' -f 2 | head -1")
to_ip=$(ip "$to_qube")
if [ "x$from_ip" = "xNone" ]; then
local from_ip=
fi
if [[ "$action" = "clear" ]]; then
echo "$from_qube: Clearing Port Forwarding from $from_qube iptables" >&2
qvm-run -p -u root "$from_qube" "iptables-save | grep -v 'PortFwd $from_qube' | iptables-restore"
nft_cmd="nft list table ip qubes-firewall -a | tr -d '\"' | grep 'iifname $iface accept # handle' | awk '{print \$NF}'"
nft_handle=$(qvm-run -p -u root "$from_qube" "$nft_cmd")
if [[ $nft_handle =~ ^[0-9]+$ ]]; then
qvm-run -p -u root "$from_qube" "nft delete rule ip qubes-firewall forward handle $nft_handle"
fi
qvm-run -p -u root "$from_qube" "sed -i '/PortFwd $from_qube $to_qube:$proto$port/d' /rw/config/rc.local"
qvm-run -p -u root "$from_qube" "sed -i '/PortFwd $from_qube $to_qube:$proto$port/d' /rw/config/rc.local"
if ! qvm-run -p -u root "$from_qube" "grep -q 'PortFwd' /rw/config/rc.local"; then
qvm-run -p -u root "$from_qube" "sed -i '/nft add rule ip qubes-firewall forward meta iifname $iface accept/d' /rw/config/rc.local"
fi
else
echo "$from_qube: Forwarding on $iface port $port to $to_qube ($from_ip -> $to_ip)" >&2
forward_rule1="nft -- create chain ip qubes-firewall custom-prerouting { type nat hook prerouting priority dstnat -1 \; }"
forward_rule2="nft add rule ip qubes-firewall custom-prerouting iifname $iface ip daddr $from_ip $proto dport $port counter dnat to $to_ip "
forward_rule2="$forward_rule2 comment \\\"PortFwd $from_qube $to_qube:$proto$port \\\" "
forward_rule3="nft add rule ip qubes custom-forward iifname $iface ip daddr $to_ip $proto dport $port ct state new counter accept "
forward_rule3="$forward_rule3 comment \\\"PortFwd $from_qube $to_qube:$proto$port\\\" "
qvm-run -p -u root "$from_qube" "iptables-nft-save | grep -v 'PortFwd $from_qube $to_qube:$proto$port' | iptables-nft-restore"
qvm-run -p -u root "$from_qube" "$forward_rule1"
qvm-run -p -u root "$from_qube" "$forward_rule2"
qvm-run -p -u root "$from_qube" "$forward_rule3"
if [ "$persistent" = 1 ]; then
qvm-run -p -u root "$from_qube" "echo $forward_rule1 >> /rw/config/rc.local"
qvm-run -p -u root "$from_qube" "echo $forward_rule2 >> /rw/config/rc.local"
qvm-run -p -u root "$from_qube" "echo $forward_rule3 >> /rw/config/rc.local"
if ! qvm-run -p -u root "$from_qube" "grep -q 'nft add rule ip qubes-firewall forward meta iifname $iface accept' /rw/config/rc.local"; then
# Ensure rc.local is executable
qvm-run -p -u root "$from_qube" "chmod +x /rw/config/rc.local"
fi
fi
fi
}
input() {
local action="$1"
local qube="$2"
local port="$3"
local proto="$4"
local persistent="$5"
if [[ "$action" = "clear" ]]; then
echo "$qube: Clearing Port Forwarding from $qube iptables" >&2
qvm-run -p -u root "$qube" "iptables-save | grep -v 'PortFwd $qube' | iptables-restore"
qvm-run -p -u root "$qube" "sed -i '/PortFwd $qube:$proto$port/d' /rw/config/rc.local"
else
echo "$qube: Allowing input to port $port" >&2
qvm-run -p -u root "$qube" "iptables-save | grep -v 'PortFwd $qube:$proto$port' | iptables-restore"
input_rule="nft add rule ip qubes custom-input $proto dport $port ct state new counter accept comment \\\"PortFwd $qube:$proto$port\\\" "
qvm-run -p -u root "$qube" "$input_rule"
if [ "$persistent" = 1 ]; then
qvm-run -p -u root "$qube" "echo $input_rule >> /rw/config/rc.local"
# Ensure rc.local is executable
qvm-run -p -u root "$qube" "chmod +x /rw/config/rc.local"
fi
fi
}
recurse_netvms() {
local action="$1"
local this_qube="$2"
local port="$3"
local proto="$4"
local persistent="$5"
local outer_dom
outer_dom=$(netvm "$this_qube")
if [[ -n "$outer_dom" && "$outer_dom" != "None" ]]; then
forward "$action" "$outer_dom" "$this_qube" "$port" "$proto" "$persistent"
recurse_netvms "$action" "$outer_dom" "$port" "$proto" "$persistent"
fi
}
usage() {
echo "Usage: ${0##*/} --action ACTION --qube QUBE --port PORT --proto PROTO --persistent" >&2
echo "" >&2
echo "Exemple: " >&2
echo " -> ${0##*/} --action create --qube work --port 22" >&2
echo " -> ${0##*/} --action create --qube work --port 444 --proto udp --persistent" >&2
echo " -> ${0##*/} --action clear --qube work --port 22" >&2
echo " -> ${0##*/} --action clear --qube work --port 444 --proto udp" >&2
echo "" >&2
echo "Default value for PROTO is 'tcp' and iptables are not persistent"
exit 1
}
if ! OPTS=$(getopt -o a:q:p:n:s --long action:,qube:,port:,proto:,persistent -n "$0" -- "$@"); then
echo "An error occurred while parsing options." >&2
exit 1
fi
eval set -- "$OPTS"
while [[ $# -gt 0 ]]; do
case "$1" in
-a | --action ) ACTION="$2"; shift ;;
-q | --qube ) QUBE="$2"; shift ;;
-p | --port ) PORT="$2"; shift ;;
-n | --proto ) PROTO="$2"; shift ;;
-s | --persistent ) PERSISTENT=1; shift ;;
esac
shift
done
if [ -z "$PROTO" ]; then
PROTO="tcp"
fi
if { [ "$ACTION" != "create" ] || [ "$ACTION" == "clear" ]; } && { [ -z "$QUBE" ] || [ -z "$PORT" ]; }; then
usage
fi
if ! qvm-check "$QUBE" > /dev/null 2>&1; then
echo "Qube '$QUBE' not found." >&2
exit 1
fi
input "$ACTION" "$QUBE" "$PORT" "$PROTO" "$PERSISTENT"
recurse_netvms "$ACTION" "$QUBE" "$PORT" "$PROTO" "$PERSISTENT"
@IOZZYS
Copy link

IOZZYS commented Aug 24, 2023

Time to rip my hair out as the ultimate noob that is going to try and port this to Qubes 4.2

@neowutran
Copy link
Author

neowutran commented Aug 24, 2023

my mistake, didn't checked and copied the wrong things ^^', i updated this script in one of my computer to make it work with Qubes 4.2. Will fix this git in one or two days with the correct thing

@IOZZYS
Copy link

IOZZYS commented Aug 25, 2023

Thank you so much, you're amazing!
I will try to add interface options and share my work!

@imme-emosol
Copy link

I couldn't test, because I'm not on 4.2 // missing the table 'qubes' , but perhaps this revision works : https://gist.github.com/imme-emosol/840ca36b4f098ce3431db43f08288aaa .

@neowutran
Copy link
Author

( updated my version, should work correctly on 4.2, but will not work for previous version )

@IOZZYS
Copy link

IOZZYS commented Dec 9, 2023

@neowutran Added "| grep -E '^[0-9]' | grep -E 'state UP' | cut -d ':' -f 2 |" in line 37 to ensure only interfaces that are UP are chosen for now.
@imme-emosol I remember trying your version but I don´t remember what went wrong, maybe I'll check again in a sec.

@neowutran
Copy link
Author

thanks, updated with your suggestion

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