Skip to content

Instantly share code, notes, and snippets.

@manicminer
Created April 13, 2026 21:11
Show Gist options
  • Select an option

  • Save manicminer/ba03fb8264399ca47e11d7e2dc979528 to your computer and use it in GitHub Desktop.

Select an option

Save manicminer/ba03fb8264399ca47e11d7e2dc979528 to your computer and use it in GitHub Desktop.
Script to pass through OpenVPN(3) routes from a linux guest VM to your linux host. Tested with openSUSE Tumbleweed host and Ubuntu24 guest (uses systemd-resolved on the host and requires dnsmasq on the guest)
#!/bin/bash
gateway="ubuntu24.virt"
gateway_addr="$(dig +short "${gateway}" a)"
vpn_device=tun0
vpn_user=tom
resolve_domains="~azure.net ~azurecr.io ~defra.cloud ~service-now.com ~windows.net"
resolved_config="/etc/systemd/dns-delegate.d/openvpn3.dns-delegate"
routes_tmpfile="/tmp/openvpn3-routes"
additional_routes=()
if [[ "${gateway_addr}" == "" ]]; then
echo "Gateway host ${gateway} cannot be resolved" >&2
exit 1
fi
cmd=
while getopts "de" OPT; do
case "${OPT}" in
d)
cmd=disable
;;
e)
cmd=enable
;;
esac
done
mkdir -p "$(dirname "${resolved_config}")"
do_disable_gateway() {
ssh "${gateway}" iptables -v -t nat -F POSTROUTING
ssh "${gateway}" sysctl net.ipv4.ip_forward=0
}
do_disable_local() {
rm -f "${resolved_config}"
systemctl -v reload systemd-resolved
if [[ -r "${routes_tmpfile}" ]]; then
count=0
routes="$(ip -f inet route)"
while read cidr; do
if echo "${routes}" | grep -q "${cidr}"; then
ip -f inet route del "${cidr}"
((++count))
fi
done < <(cat "${routes_tmpfile}" | awk '{print $1}')
echo "Removed ${count} routes"
fi
}
do_disable() {
do_disable_gateway
do_disable_local
}
do_enable() {
do_disable
ssh "${gateway}" sysctl net.ipv4.ip_forward=1
ssh "${gateway}" iptables -v -t nat -A POSTROUTING -o ${vpn_device} -j MASQUERADE
if (( ${#additional_routes[@]} > 0 )); then
gateway_routes="$(ssh "${gateway}" ip -f inet route)"
for cidr in "${additional_routes[@]}"; do
echo "${gateway_routes}" | grep -q "${cidr%/32}" && ssh "${gateway}" ip -f inet route del "${cidr}"
ssh "${gateway}" ip route add "${cidr}" dev "${vpn_device}"
done
fi
vpn_routes="$(ssh "${gateway}" ip -f inet route | grep "dev ${vpn_device}")"
if [[ "${vpn_routes}" != "" ]]; then
count=0
routes="$(ip -f inet route)"
while read cidr; do
echo "${routes}" | grep -q "${cidr}" && ip -f inet route del "${cidr}"
ip -f inet route add "${cidr}" via "${gateway_addr}"
((++count))
done < <(echo "${vpn_routes}" | tee "${routes_tmpfile}" | awk '{print $1}')
echo "Added ${count} routes"
fi
cat >"${resolved_config}" <<EOF
[Delegate]
DNS=${gateway_addr}
Domains=${resolve_domains}
EOF
systemctl -v reload systemd-resolved
}
if ! nc -z "${gateway}" 22 2>/dev/null; then
echo "Gateway host ${gateway} is unreachable, disabling" >&2
do_disable_local
exit
fi
case "${cmd}" in
enable)
echo "Enabling" >&2
do_enable
;;
disable)
echo "Disabling" >&2
do_disable
;;
*)
vpn_status="$(ssh "${gateway}" sudo -u "${vpn_user}" openvpn3 sessions-list)"
echo "${vpn_status}"
if [[ "${vpn_status}" =~ "No sessions available" ]]; then
echo "VPN down, disabling" >&2
do_disable
elif [[ "${vpn_status}" =~ "Connection, Client connected" ]]; then
echo "VPN is up, enabling" >&2
do_enable
else
echo "VPN state unknown, disabling" >&2
do_disable
fi
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment