2017-01-02 05:33:43 +01:00
#!/bin/bash
#
2017-01-10 06:36:19 +01:00
# Copyright (C) 2016-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
2017-01-02 05:33:43 +01:00
#
set -e -o pipefail
shopt -s extglob
2017-02-05 23:05:12 +01:00
export LC_ALL = C
2017-01-02 05:33:43 +01:00
SELF = " $( readlink -f " ${ BASH_SOURCE [0] } " ) "
export PATH = " ${ SELF %/* } : $PATH "
WG_CONFIG = ""
INTERFACE = ""
ADDRESSES = ( )
2017-04-24 05:01:16 +02:00
MTU = ""
2017-01-02 05:33:43 +01:00
PRE_UP = ""
POST_UP = ""
PRE_DOWN = ""
POST_DOWN = ""
SAVE_CONFIG = 0
CONFIG_FILE = ""
PROGRAM = " ${ 0 ##*/ } "
ARGS = ( " $@ " )
parse_options( ) {
local interface_section = 0 line key value
CONFIG_FILE = " $1 "
[ [ $CONFIG_FILE = ~ ^[ a-zA-Z0-9_= +.-] { 1,16} $ ] ] && CONFIG_FILE = " /etc/wireguard/ $CONFIG_FILE .conf "
[ [ -e $CONFIG_FILE ] ] || die " \` $CONFIG_FILE ' does not exist "
[ [ $CONFIG_FILE = ~ /?( [ a-zA-Z0-9_= +.-] { 1,16} ) \. conf$ ] ] || die "The config file must be a valid interface name, followed by .conf"
2017-01-05 19:57:50 +01:00
( ( ( $( stat -c '%#a' " $CONFIG_FILE " ) & 0007) = = 0) ) || echo " Warning: \` $CONFIG_FILE ' is world accessible " >& 2
2017-01-02 05:33:43 +01:00
INTERFACE = " ${ BASH_REMATCH [1] } "
shopt -s nocasematch
2017-02-22 20:23:00 +01:00
while read -r line || [ [ -n $line ] ] ; do
2017-01-02 05:33:43 +01:00
key = " ${ line %%=* } " ; key = " ${ key ##*( ) } " ; key = " ${ key %%*( ) } "
value = " ${ line #*= } " ; value = " ${ value ##*( ) } " ; value = " ${ value %%*( ) } "
[ [ $key = = "[" * ] ] && interface_section = 0
[ [ $key = = "[Interface]" ] ] && interface_section = 1
if [ [ $interface_section -eq 1 ] ] ; then
case " $key " in
Address) ADDRESSES += ( ${ value //,/ } ) ; continue ; ;
2017-04-24 05:01:16 +02:00
MTU) MTU = " $value " ; continue ; ;
2017-01-02 05:33:43 +01:00
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
2017-06-27 22:18:13 +02:00
true ) printf -v " $1 " 1 ; ;
false ) printf -v " $1 " 0 ; ;
2017-01-02 05:33:43 +01:00
*) die " \` $2 ' is neither true nor false "
esac
}
cmd( ) {
echo " [#] $* " >& 2
" $@ "
}
die( ) {
echo " $PROGRAM : $* " >& 2
exit 1
}
auto_su( ) {
[ [ $UID = = 0 ] ] || exec sudo -p " $PROGRAM must be run as root. Please enter the password for %u to continue: " " $SELF " " ${ ARGS [@] } "
}
add_if( ) {
cmd ip link add " $INTERFACE " type wireguard
}
del_if( ) {
2017-03-23 15:44:10 +01:00
local fwmark
fwmark = " $( wg show " $INTERFACE " fwmark) "
2017-02-22 21:45:03 +01:00
DEFAULT_TABLE = 0
2017-03-23 15:44:10 +01:00
[ [ $fwmark != off ] ] && DEFAULT_TABLE = $(( fwmark ))
2017-01-24 17:43:35 +01:00
if [ [ $DEFAULT_TABLE -ne 0 ] ] ; then
2017-03-16 23:57:55 +01:00
while [ [ $( ip -4 rule show) = = *" lookup $DEFAULT_TABLE " * ] ] ; do
2017-01-24 17:43:35 +01:00
cmd ip -4 rule delete table $DEFAULT_TABLE
done
2017-03-16 23:57:55 +01:00
while [ [ $( ip -4 rule show) = = *"from all lookup main suppress_prefixlength 0" * ] ] ; do
cmd ip -4 rule delete table main suppress_prefixlength 0
done
while [ [ $( ip -6 rule show) = = *" lookup $DEFAULT_TABLE " * ] ] ; do
2017-01-24 17:43:35 +01:00
cmd ip -6 rule delete table $DEFAULT_TABLE
2017-03-16 23:57:55 +01:00
done
while [ [ $( ip -6 rule show) = = *"from all lookup main suppress_prefixlength 0" * ] ] ; do
cmd ip -6 rule delete table main suppress_prefixlength 0
2017-01-24 05:28:03 +01:00
done
2017-01-02 05:33:43 +01:00
fi
cmd ip link delete dev " $INTERFACE "
}
up_if( ) {
cmd ip link set " $INTERFACE " up
}
add_addr( ) {
cmd ip address add " $1 " dev " $INTERFACE "
}
2017-04-24 05:01:16 +02:00
set_mtu( ) {
local mtu = 0 endpoint output
if [ [ -n $MTU ] ] ; then
cmd ip link set mtu " $MTU " dev " $INTERFACE "
return
fi
while read -r _ endpoint; do
2017-06-23 14:48:03 +02:00
[ [ $endpoint = ~ ^\[ ?( [ a-z0-9:.] +) \] ?:[ 0-9] +$ ] ] || continue
2017-04-24 05:01:16 +02:00
output = " $( ip route get " ${ BASH_REMATCH [1] } " || true ) "
[ [ ( $output = ~ mtu\ ( [ 0-9] +) || ( $output = ~ dev\ ( [ ^ ] +) && $( ip link show dev " ${ BASH_REMATCH [1] } " ) = ~ mtu\ ( [ 0-9] +) ) ) && ${ BASH_REMATCH [1] } -gt $mtu ] ] && mtu = " ${ BASH_REMATCH [1] } "
done < <( wg show " $INTERFACE " endpoints)
if [ [ $mtu -eq 0 ] ] ; then
read -r output < <( ip route show default || true ) || true
[ [ ( $output = ~ mtu\ ( [ 0-9] +) || ( $output = ~ dev\ ( [ ^ ] +) && $( ip link show dev " ${ BASH_REMATCH [1] } " ) = ~ mtu\ ( [ 0-9] +) ) ) && ${ BASH_REMATCH [1] } -gt $mtu ] ] && mtu = " ${ BASH_REMATCH [1] } "
fi
[ [ $mtu -gt 0 ] ] || mtu = 1500
cmd ip link set mtu $(( mtu - 80 )) dev " $INTERFACE "
}
2017-01-02 05:33:43 +01:00
add_route( ) {
2017-06-12 00:20:31 +02:00
if [ [ $1 = = 0.0.0.0/0 || $1 = ~ ^[ 0:] +/0$ ] ] ; then
2017-01-02 05:33:43 +01:00
add_default " $1 "
else
cmd ip route add " $1 " dev " $INTERFACE "
fi
}
2017-01-24 05:28:03 +01:00
DEFAULT_TABLE =
2017-01-02 05:33:43 +01:00
add_default( ) {
2017-01-24 05:28:03 +01:00
if [ [ -z $DEFAULT_TABLE ] ] ; then
DEFAULT_TABLE = 51820
2017-06-11 23:39:17 +02:00
while [ [ -n $( ip -4 route show table $DEFAULT_TABLE ) || -n $( ip -6 route show table $DEFAULT_TABLE ) ] ] ; do
( ( DEFAULT_TABLE++) )
done
2017-01-24 05:28:03 +01:00
fi
2017-07-24 16:08:42 +02:00
local proto = -4
[ [ $1 = = *:* ] ] && proto = -6
2017-01-24 17:43:35 +01:00
cmd wg set " $INTERFACE " fwmark $DEFAULT_TABLE
2017-07-24 16:08:42 +02:00
cmd ip $proto route add " $1 " dev " $INTERFACE " table $DEFAULT_TABLE
2017-01-24 17:43:35 +01:00
cmd ip $proto rule add not fwmark $DEFAULT_TABLE table $DEFAULT_TABLE
cmd ip $proto rule add table main suppress_prefixlength 0
2017-03-23 15:44:10 +01:00
local key value
while read -r key _ value; do
2017-01-24 17:43:35 +01:00
[ [ $value -eq 1 ] ] && sysctl -q " $key =2 "
done < <( sysctl -a -r 'net\.ipv4.conf\..+\.rp_filter' )
return 0
2017-01-02 05:33:43 +01:00
}
set_config( ) {
cmd wg setconf " $INTERFACE " <( echo " $WG_CONFIG " )
}
save_config( ) {
local old_umask new_config current_config address
[ [ $( 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
2017-04-24 05:01:16 +02:00
[ [ -n $MTU && $( ip link show dev " $INTERFACE " ) = ~ mtu\ ( [ 0-9] +) ] ] && new_config += " MTU = ${ BASH_REMATCH [1] } " $'\n'
2017-01-02 05:33:43 +01:00
[ [ $SAVE_CONFIG -eq 0 ] ] || new_config += $'SaveConfig = true\n'
[ [ -z $PRE_UP ] ] || new_config += " PreUp = $PRE_UP " $'\n'
[ [ -z $POST_UP ] ] || new_config += " PostUp = $POST_UP " $'\n'
[ [ -z $PRE_DOWN ] ] || new_config += " PreDown = $PRE_DOWN " $'\n'
[ [ -z $POST_DOWN ] ] || new_config += " PostDown = $POST_DOWN " $'\n'
old_umask = " $( umask ) "
umask 077
current_config = " $( cmd wg showconf " $INTERFACE " ) "
2017-03-23 15:44:10 +01:00
trap 'rm -f "$CONFIG_FILE.tmp"; exit' INT TERM EXIT
2017-01-02 05:33:43 +01:00
echo " ${ current_config / \[ Interface \] $'\n' / $new_config } " > " $CONFIG_FILE .tmp " || die "Could not write configuration file"
mv " $CONFIG_FILE .tmp " " $CONFIG_FILE " || die "Could not move configuration file"
trap - INT TERM EXIT
umask " $old_umask "
}
execute_hook( ) {
[ [ -n $1 ] ] || return 0
local hook = " ${ 1 //%i/ $INTERFACE } "
echo " [#] $hook " >& 2
( eval " $hook " )
}
cmd_usage( ) {
cat >& 2 <<-_EOF
Usage: $PROGRAM [ up | down ] [ 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.
2017-07-20 06:29:14 +02:00
- MTU: an optional MTU for the interface; if unspecified, auto-calculated.
2017-01-02 05:33:43 +01:00
- 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.
2017-07-20 06:29:14 +02:00
See wg-quick( 8) for more info and examples.
2017-01-02 05:33:43 +01:00
_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_hook " $PRE_UP "
add_if
set_config
for i in " ${ ADDRESSES [@] } " ; do
add_addr " $i "
done
2017-04-24 05:01:16 +02:00
set_mtu
2017-01-02 05:33:43 +01:00
up_if
2017-07-24 23:22:10 +02:00
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
2017-01-02 05:33:43 +01:00
[ [ $( ip route get " $i " 2>/dev/null) = = *dev\ $INTERFACE \ * ] ] || add_route " $i "
done
execute_hook " $POST_UP "
trap - INT TERM EXIT
}
cmd_down( ) {
[ [ -n $( ip link show dev " $INTERFACE " type wireguard 2>/dev/null) ] ] || die " \` $INTERFACE ' is not a WireGuard interface "
execute_hook " $PRE_DOWN "
[ [ $SAVE_CONFIG -eq 0 ] ] || save_config
del_if
execute_hook " $POST_DOWN "
}
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
else
cmd_usage
exit 1
fi
exit 0