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

139 lines
3.1 KiB
Plaintext
Executable File

#!/usr/bin/env nix-shell
#!nix-shell -i bash -p coreutils-full -p sane-scripts.ip-check
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 [COMMAND ARGS ...]"
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
id=
fwmark=
prioMain=
prioFwMark=
bridgeDevice=
dns=()
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:
# - id
# - fwmark
# - prioMain
# - prioFwMark
# - bridgeDevice
# - dns
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() {
if [ -n "$region" ] || [ "$region" = "default" ]; then
debug "canonicalizing region to $defaultVpn"
region="$defaultVpn"
fi
}
vpnToggle() {
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 " bridgeDevice='$bridgeDevice'"
debug " dns='$dns'"
# TODO: switch to bwrap, or `sane-sandboxed`!
firejail --noprofile --net="$bridgeDevice" --dns="$dns" "$@"
}
usage() {
rc="$1"
msg="$2"
test -n "$msg" && echo "$msg"
usageDescription
echo ""
echo "regions:"
echo "$vpns"
test -n "$rc" && exit "$rc"
}
parseCli() {
_oper="$1"
shift
region="$1"
shift
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 "$@"