139 lines
3.1 KiB
Plaintext
Executable File
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 "$@"
|