Skip to content

Instantly share code, notes, and snippets.

@fepitre
Forked from Joeviocoe/qvm-portfwd-iptables
Last active February 1, 2024 12:49
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save fepitre/941d7161ae1150d90e15f778027e3248 to your computer and use it in GitHub Desktop.
Save fepitre/941d7161ae1150d90e15f778027e3248 to your computer and use it in GitHub Desktop.
Qubes-os port forwarding to allow external connections
#!/bin/bash
# Frédéric Pierret <frederic.pierret@qubes-os.org>
# Adapted from previous work:
# - 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.
iface=$(qvm-run -p -u root "$from_qube" "ifconfig | grep cast -B 1 --no-group-separator | grep -vE '^(vif|lo)' | grep -oE '^[^: ]+' | head -1")
from_ip=$(qvm-run -p -u root "$from_qube" "hostname -I | cut -d ' ' -f 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="iptables -t nat -A PREROUTING -i $iface -p $proto ${from_ip:+-d} $from_ip --dport $port -j DNAT --to-destination $to_ip -m comment --comment \"'PortFwd $from_qube>$to_qube:$proto$port'\""
forward_rule2="iptables -I FORWARD 2 -i $iface -p $proto ${to_ip:+-d} $to_ip --dport $port -m conntrack --ctstate NEW -j ACCEPT -m comment --comment \"'PortFwd $from_qube>$to_qube:$proto$port'\""
forward_rule3="nft add rule ip qubes-firewall forward meta iifname $iface accept"
qvm-run -p -u root "$from_qube" "iptables-save | grep -v 'PortFwd $from_qube>$to_qube:$proto$port' | iptables-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"
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
qvm-run -p -u root "$from_qube" "echo $forward_rule3 >> /rw/config/rc.local"
# 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="iptables -I INPUT 5 -p $proto --dport $port -m conntrack --ctstate NEW -j ACCEPT -m comment --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"
@imme-emosol
Copy link

#jfyi in https://forum.qubes-os.org/t/port-forwarding-to-allow-external-connections-qvm-portfwd-iptables/18870 ,
i have attempted to grasp the "essentials" of what this script does (minus the undo). But also:

  • replaced all short form options
  • renamed iface to incomingInterface
  • renamed from_ip to incomingPacketRecipient
  • renamed to_ip to outgoingPacketRecipient

What seems to be the essence is this:

iptables -I INPUT 5 -p $proto --dport $port -m conntrack --ctstate NEW -j ACCEPT -m comment --comment "'PortFwd $qube:$proto$port'"

And then traversing the net vms:

iptables -t nat -A PREROUTING -i $iface -p $proto ${from_ip:+-d} $from_ip --dport $port -j DNAT --to-destination $to_ip -m comment --comment "'PortFwd $from_qube>$to_qube:$proto$port'"
iptables -I FORWARD 2 -i $iface -p $proto ${to_ip:+-d} $to_ip --dport $port -m conntrack --ctstate NEW -j ACCEPT -m comment --comment "'PortFwd $from_qube>$to_qube:$proto$port'"
nft add rule ip qubes-firewall forward meta iifname $iface accept

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