mirror of https://git.zx2c4.com/wireguard-rs
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
322 lines
9.6 KiB
322 lines
9.6 KiB
#!/bin/bash |
|
# SPDX-License-Identifier: GPL-2.0 |
|
# |
|
# Copyright (C) 2015-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. |
|
# |
|
# Modified by Jake McGinty <me@jake.su> for the userspace macOS implementation. |
|
# |
|
|
|
set -e -o pipefail |
|
shopt -s extglob |
|
export LC_ALL=C |
|
|
|
SELF="$(greadlink -f "${BASH_SOURCE[0]}")" |
|
export PATH="${SELF%/*}:$PATH" |
|
|
|
WG_CONFIG="" |
|
INTERFACE="" |
|
ADDRESSES=( ) |
|
MTU="" |
|
DNS=( ) |
|
TABLE="" |
|
PRE_UP=( ) |
|
POST_UP=( ) |
|
PRE_DOWN=( ) |
|
POST_DOWN=( ) |
|
SAVE_CONFIG=0 |
|
CONFIG_FILE="" |
|
PROGRAM="${0##*/}" |
|
ARGS=( "$@" ) |
|
|
|
WGRS_PID=0 |
|
|
|
parse_options() { |
|
local interface_section=0 line key value |
|
CONFIG_FILE="$1" |
|
[[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]] && CONFIG_FILE="/etc/wireguard/$CONFIG_FILE.conf" |
|
[[ -e $CONFIG_FILE ]] || die "\`$CONFIG_FILE' does not exist" |
|
[[ $CONFIG_FILE =~ /?([a-zA-Z0-9_=+.-]{1,15})\.conf$ ]] || die "The config file must be a valid interface name, followed by .conf" |
|
CONFIG_FILE="$(greadlink -f "$CONFIG_FILE")" |
|
((($(gstat -c '0%#a' "$CONFIG_FILE") & $(gstat -c '0%#a' "${CONFIG_FILE%/*}") & 0007) == 0)) || echo "Warning: \`$CONFIG_FILE' is world accessible" >&2 |
|
INTERFACE="${BASH_REMATCH[1]}" |
|
shopt -s nocasematch |
|
while read -r line || [[ -n $line ]]; do |
|
key="${line%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}" |
|
value="${line#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}" |
|
[[ $key == "["* ]] && interface_section=0 |
|
[[ $key == "[Interface]" ]] && interface_section=1 |
|
if [[ $interface_section -eq 1 ]]; then |
|
case "$key" in |
|
Address) ADDRESSES+=( ${value//,/ } ); continue ;; |
|
MTU) MTU="$value"; continue ;; |
|
DNS) DNS+=( ${value//,/ } ); continue ;; |
|
Table) TABLE="$value"; continue ;; |
|
PreUp) PRE_UP+=( "$value" ); continue ;; |
|
PreDown) PRE_DOWN+=( "$value" ); continue ;; |
|
PostUp) POST_UP+=( "$value" ); continue ;; |
|
PostDown) POST_DOWN+=( "$value" ); continue ;; |
|
SaveConfig) read_bool SAVE_CONFIG "$value"; continue ;; |
|
esac |
|
fi |
|
WG_CONFIG+="$line"$'\n' |
|
done < "$CONFIG_FILE" |
|
shopt -u nocasematch |
|
} |
|
|
|
read_bool() { |
|
case "$2" in |
|
true) printf -v "$1" 1 ;; |
|
false) printf -v "$1" 0 ;; |
|
*) die "\`$2' is neither true nor false" |
|
esac |
|
} |
|
|
|
cmd() { |
|
echo "[#] $*" >&2 |
|
"$@" |
|
} |
|
|
|
die() { |
|
echo "$PROGRAM: $*" >&2 |
|
exit 1 |
|
} |
|
|
|
auto_su() { |
|
[[ $UID == 0 ]] || exec sudo -E -p "$PROGRAM must be run as root. Please enter the password for %u to continue: " "$SELF" "${ARGS[@]}" |
|
} |
|
|
|
add_if() { |
|
cmd wireguard-rs "$INTERFACE" |
|
sleep 1 |
|
} |
|
|
|
del_if() { |
|
for i in $(while read -r _ i; do for i in $i; do [[ $i =~ ^\[?[0-9a-z:.]+\]?:[0-9]+$ ]] && echo "$i"; done; done < <(wg show "$INTERFACE" endpoints) | sort -nr -k 2 -t /); do |
|
echo "removing route for endpoint $i" |
|
if [[ $i =~ ^\[([a-z0-9:.]+)\]:[0-9]+$ ]]; then |
|
netstat -rn | grep "${BASH_REMATCH[1]}" > /dev/null && \ |
|
cmd route delete -inet6 -host "${BASH_REMATCH[1]}" # delete any old route for endpoint |
|
elif [[ $i =~ ^([0-9.]+):[0-9]+$ ]]; then |
|
netstat -rn | grep "${BASH_REMATCH[1]}" > /dev/null && \ |
|
cmd route delete -host "${BASH_REMATCH[1]}" # delete any old route for endpoint |
|
fi |
|
done |
|
cmd rm -f "/var/run/wireguard/$INTERFACE.sock" |
|
} |
|
|
|
up_if() { |
|
return |
|
} |
|
|
|
add_addr() { |
|
gateway=${1%%/*} |
|
if [[ $1 == *:* ]]; then |
|
cmd ifconfig "$INTERFACE" inet6 add "$1" |
|
else |
|
cmd ifconfig "$INTERFACE" inet add "$1" "$gateway" |
|
fi |
|
} |
|
|
|
set_mtu() { |
|
if [[ -n $MTU ]]; then |
|
cmd ifconfig "$INTERFACE" mtu "$MTU" |
|
return |
|
fi |
|
# TODO read internet adapter's mtu |
|
cmd ifconfig "$INTERFACE" mtu 1420 |
|
} |
|
|
|
HAVE_SET_DNS=0 |
|
# TODO: this only works with the Wi-Fi adapter right now. |
|
set_dns() { |
|
[[ ${#DNS[@]} -gt 0 ]] || return 0 |
|
cmd networksetup -setdnsservers Wi-Fi empty |
|
cmd networksetup -setdnsservers Wi-Fi "${DNS[@]}" |
|
HAVE_SET_DNS=1 |
|
} |
|
|
|
unset_dns() { |
|
[[ ${#DNS[@]} -gt 0 ]] || return 0 |
|
cmd networksetup -setdnsservers Wi-Fi empty |
|
} |
|
|
|
add_route() { |
|
ip=$(ifconfig | grep -A 1 $INTERFACE | tail -1 | cut -d ' ' -f 2) |
|
|
|
[[ $TABLE != off ]] || return 0 |
|
|
|
if [[ -n $TABLE && $TABLE != auto ]]; then |
|
echo "add to table not supported" |
|
elif [[ $1 == ::/0 ]]; then # TODO be better at recognizing all ipv6 default route strings |
|
add_default_ipv6 "$1" |
|
elif [[ $1 == */0 ]]; then |
|
add_default_ipv4 "$1" |
|
else |
|
cmd route add "$1" "$ip" |
|
fi |
|
} |
|
|
|
add_endpoint_passthroughs() { |
|
gateway=$(route -n get default | grep gateway | awk '{print $2}') |
|
gateway6=$(route -n get -inet6 default | grep gateway | awk '{print $2}') |
|
for i in $(while read -r _ i; do for i in $i; do [[ $i =~ ^\[?[0-9a-z:.]+\]?:[0-9]+$ ]] && echo "$i"; done; done < <(wg show "$INTERFACE" endpoints) | sort -nr -k 2 -t /); do |
|
echo "adding endpoint passthrough for $i" |
|
if [[ $i =~ ^\[([a-z0-9:.]+)\]:[0-9]+$ ]]; then |
|
netstat -rn | grep "${BASH_REMATCH[1]}" > /dev/null && \ |
|
cmd route delete -inet6 -host "${BASH_REMATCH[1]}" # delete any old route for endpoint |
|
cmd route add -inet6 -host "${BASH_REMATCH[1]}" "$gateway6" |
|
elif [[ $i =~ ^([0-9.]+):[0-9]+$ ]]; then |
|
netstat -rn | grep "${BASH_REMATCH[1]}" > /dev/null && \ |
|
cmd route delete -host "${BASH_REMATCH[1]}" # delete any old route for endpoint |
|
cmd route add -host "${BASH_REMATCH[1]}" "$gateway" |
|
fi |
|
done |
|
} |
|
|
|
add_default_ipv4() { |
|
ip=$(ifconfig $INTERFACE | grep "inet " | tail -1 | cut -d ' ' -f 2) # this doesn't play well with multiple assigned IPs |
|
cmd route add 0.0.0.0/1 $ip |
|
cmd route add 128.0.0.0/1 $ip |
|
return 0 |
|
} |
|
|
|
add_default_ipv6() { |
|
ip=$(ifconfig $INTERFACE | grep "inet6 " | tail -1 | cut -d ' ' -f 2) # this doesn't play well with multiple assigned IPs |
|
cmd route add -inet6 0000::/1 $ip |
|
cmd route add -inet6 8000::/1 $ip |
|
return 0 |
|
} |
|
|
|
set_config() { |
|
cmd wg setconf "$INTERFACE" <(echo "$WG_CONFIG") |
|
} |
|
|
|
save_config() { |
|
local old_umask new_config current_config address cmd |
|
[[ $(ip -all -brief address show dev "$INTERFACE") =~ ^$INTERFACE\ +\ [A-Z]+\ +(.+)$ ]] || true |
|
new_config=$'[Interface]\n' |
|
for address in ${BASH_REMATCH[1]}; do |
|
new_config+="Address = $address"$'\n' |
|
done |
|
while read -r address; do |
|
[[ $address =~ ^nameserver\ ([a-zA-Z0-9_=+:%.-]+)$ ]] && new_config+="DNS = ${BASH_REMATCH[1]}"$'\n' |
|
done < <(resolvconf -l "tun.$INTERFACE" 2>/dev/null) |
|
[[ -n $MTU && $(ip link show dev "$INTERFACE") =~ mtu\ ([0-9]+) ]] && new_config+="MTU = ${BASH_REMATCH[1]}"$'\n' |
|
[[ -n $TABLE ]] && new_config+="Table = $TABLE"$'\n' |
|
[[ $SAVE_CONFIG -eq 0 ]] || new_config+=$'SaveConfig = true\n' |
|
for cmd in "${PRE_UP[@]}"; do |
|
new_config+="PreUp = $cmd"$'\n' |
|
done |
|
for cmd in "${POST_UP[@]}"; do |
|
new_config+="PostUp = $cmd"$'\n' |
|
done |
|
for cmd in "${PRE_DOWN[@]}"; do |
|
new_config+="PreDown = $cmd"$'\n' |
|
done |
|
for cmd in "${POST_DOWN[@]}"; do |
|
new_config+="PostDown = $cmd"$'\n' |
|
done |
|
old_umask="$(umask)" |
|
umask 077 |
|
current_config="$(cmd wg showconf "$INTERFACE")" |
|
trap 'rm -f "$CONFIG_FILE.tmp"; exit' INT TERM EXIT |
|
echo "${current_config/\[Interface\]$'\n'/$new_config}" > "$CONFIG_FILE.tmp" || die "Could not write configuration file" |
|
sync "$CONFIG_FILE.tmp" |
|
mv "$CONFIG_FILE.tmp" "$CONFIG_FILE" || die "Could not move configuration file" |
|
trap - INT TERM EXIT |
|
umask "$old_umask" |
|
} |
|
|
|
execute_hooks() { |
|
local hook |
|
for hook in "$@"; do |
|
hook="${hook//%i/$INTERFACE}" |
|
echo "[#] $hook" >&2 |
|
(eval "$hook") |
|
done |
|
} |
|
|
|
cmd_usage() { |
|
cat >&2 <<-_EOF |
|
Usage: $PROGRAM [ up | down | save ] [ CONFIG_FILE | INTERFACE ] |
|
|
|
CONFIG_FILE is a configuration file, whose filename is the interface name |
|
followed by \`.conf'. Otherwise, INTERFACE is an interface name, with |
|
configuration found at /etc/wireguard/INTERFACE.conf. It is to be readable |
|
by wg(8)'s \`setconf' sub-command, with the exception of the following additions |
|
to the [Interface] section, which are handled by $PROGRAM: |
|
|
|
- Address: may be specified one or more times and contains one or more |
|
IP addresses (with an optional CIDR mask) to be set for the interface. |
|
- DNS: an optional DNS server to use while the device is up. |
|
- MTU: an optional MTU for the interface; if unspecified, auto-calculated. |
|
- Table: an optional routing table to which routes will be added; if |
|
unspecified or \`auto', the default table is used. If \`off', no routes |
|
are added. |
|
- PreUp, PostUp, PreDown, PostDown: script snippets which will be executed |
|
by bash(1) at the corresponding phases of the link, most commonly used |
|
to configure DNS. The string \`%i' is expanded to INTERFACE. |
|
- SaveConfig: if set to \`true', the configuration is saved from the current |
|
state of the interface upon shutdown. |
|
|
|
See wg-quick(8) for more info and examples. |
|
_EOF |
|
} |
|
|
|
cmd_up() { |
|
local i |
|
[[ -z $(ip link show dev "$INTERFACE" 2>/dev/null) ]] || die "\`$INTERFACE' already exists" |
|
trap 'del_if; exit' INT TERM EXIT |
|
execute_hooks "${PRE_UP[@]}" |
|
add_if |
|
set_config |
|
for i in "${ADDRESSES[@]}"; do |
|
add_addr "$i" |
|
done |
|
set_mtu |
|
up_if |
|
set_dns |
|
for i in $(while read -r _ i; do for i in $i; do [[ $i =~ ^[0-9a-z:.]+/[0-9]+$ ]] && echo "$i"; done; done < <(wg show "$INTERFACE" allowed-ips) | sort -nr -k 2 -t /); do |
|
add_route "$i" |
|
done |
|
add_endpoint_passthroughs |
|
execute_hooks "${POST_UP[@]}" |
|
trap - INT TERM EXIT |
|
} |
|
|
|
cmd_down() { |
|
[[ " $(wg show interfaces) " == *" $INTERFACE "* ]] || die "\`$INTERFACE' is not a WireGuard interface" |
|
execute_hooks "${PRE_DOWN[@]}" |
|
[[ $SAVE_CONFIG -eq 0 ]] || save_config |
|
del_if |
|
unset_dns |
|
execute_hooks "${POST_DOWN[@]}" |
|
} |
|
|
|
cmd_save() { |
|
[[ " $(wg show interfaces) " == *" $INTERFACE "* ]] || die "\`$INTERFACE' is not a WireGuard interface" |
|
save_config |
|
} |
|
|
|
# ~~ function override insertion point ~~ |
|
|
|
if [[ $# -eq 1 && ( $1 == --help || $1 == -h || $1 == help ) ]]; then |
|
cmd_usage |
|
elif [[ $# -eq 2 && $1 == up ]]; then |
|
auto_su |
|
parse_options "$2" |
|
cmd_up |
|
elif [[ $# -eq 2 && $1 == down ]]; then |
|
auto_su |
|
parse_options "$2" |
|
cmd_down |
|
elif [[ $# -eq 2 && $1 == save ]]; then |
|
auto_su |
|
parse_options "$2" |
|
cmd_save |
|
else |
|
cmd_usage |
|
exit 1 |
|
fi |
|
|
|
exit 0
|
|
|