nix-files/pkgs/additional/sane-scripts/src/sane-vpn

155 lines
3.7 KiB
Plaintext
Executable File

#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full -p sane-scripts.ip-check
set -e
usageDescription() {
echo "sane-vpn: tool to route all system internet traffic through some VPN, or just one application's"
echo 'and, thanks to cap_net_admin, we can do all this without superuser!'
echo "however, systemd --user has poor support for capabilities, hence this here is a bespoke script instead of a service"
echo ""
echo "usage:"
echo "sane-vpn up [REGION]"
echo "sane-vpn down [REGION]"
echo "sane-vpn do [REGION [COMMAND ...] ]"
echo "sane-vpn help"
}
## GLOBALS, POPULATED LATER:
# region, populated from CLI: should be e.g. `us` or `ukr`
region=
# vpn names: populated from ~/.config/sane-vpn
vpns=()
defaultVpn=
# loaded from a specific ~/.config/sane-vpn/vpns profile
addrV4=
dns=()
fwmark=
id=
name=
priorityFwMark=
priorityMain=
debug() {
if [ -n "$SANE_VPN_DEBUG" ]; then
printf "%s\n" "$@"
fi
}
# load VPN names from disk
getVpns() {
vpns=($(ls ~/.config/sane-vpn/vpns))
defaultVpn="$(cat ~/.config/sane-vpn/default)"
debug "default vpn: $defaultVpn"
}
# load a specific VPN profile, `"$1"`
sourceVpn() {
# populates: variables declared above
debug "sourcing: ~/.config/sane-vpn/vpns/$1"
# TODO: don't blindly source this, but parse explicitly as `K=V`
source ~/.config/sane-vpn/vpns/$1
}
canonicalizeRegion() {
debug "region before canonicalization: '$region'"
for v in "${vpns[@]}"; do
if [ "ovpnd-$region" = "$v" ]; then
debug "canonicalizing shorthand ovpnd- region to '$v'"
region="$v"
fi
done
if [ -z "$region" ] || [ "$region" = "default" ]; then
debug "canonicalizing default region to '$defaultVpn'"
region="$defaultVpn"
fi
debug "region after canonicalization: '$region'"
}
vpnToggle() {
local verb="$1"
debug "vpnToggle with:"
debug " id='$id'"
debug " fwmark='$fwmark'"
debug " priorityMain='$priorityMain'"
debug " priorityFwMark='$priorityFwMark'"
echo before: $(sane-ip-check --no-upnp)
# first, allow all non-default routes (prefix-length != 0) a chance to route the packet.
# - this allows the wireguard tunnel itself to pass traffic via our LAN gateway.
# - incidentally, it allows traffic to LAN devices and other machine-local or virtual networks.
ip rule "$verb" from all lookup main suppress_prefixlength 0 priority "$priorityMain"
# if packet hasn't gone through the wg device yet (fwmark), then move it to the table which will cause it to.
ip rule "$verb" not from all fwmark "$fwmark" lookup "$id" priority "$priorityFwMark"
echo after: $(sane-ip-check --no-upnp)
}
vpnDo() {
debug "vpnDo with:"
debug " name='$name'"
debug " addrV4='$addrV4'"
debug " dns='$dns'"
local cmd=("$@")
if [ ${#cmd} -eq 0 ]; then
cmd=(/bin/sh)
fi
debug "cmd: ${cmd[*]}"
# sanebox --sanebox-method pastaonly --sanebox-net-dev "$name" --sanebox-net-gateway "$addrV4" --sanebox-dns "$dns" "${cmd[@]}"
sanebox --sanebox-method bwrap --sanebox-keep-namespace all --sanebox-path / --sanebox-no-portal \
--sanebox-net-dev "$name" --sanebox-net-gateway "$addrV4" --sanebox-dns "$dns" \
"${cmd[@]}"
}
usage() {
local rc="$1"
local msg="$2"
test -n "$msg" && echo "$msg"
usageDescription
echo ""
echo "regions:"
for v in "${vpns[@]}"; do
echo "- $v"
done
test -n "$rc" && exit "$rc"
}
parseCli() {
local oper="$1"
shift
region="$1"
shift || true
getVpns
canonicalizeRegion
sourceVpn "$region"
case $oper in
(up)
vpnToggle add
;;
(down)
vpnToggle del
;;
(do)
vpnDo "$@"
;;
(--help|help|"")
usage 0
;;
(*)
usage 1 "invalid operation '$oper'"
;;
esac
}
parseCli "$@"