diff --git a/contrib/wg-config/Makefile b/contrib/wg-config/Makefile deleted file mode 100644 index 4fa264c..0000000 --- a/contrib/wg-config/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -PREFIX ?= /usr -DESTDIR ?= -SBINDIR ?= $(PREFIX)/sbin - -all: - @echo "This is a shell script, so there is nothing to do. Try \"make install\" instead." - -install: - @install -v -m0755 -D -t$(DESTDIR)$(SBINDIR) wg-config - -.PHONY: all install diff --git a/contrib/wg-config/README b/contrib/wg-config/README deleted file mode 100644 index 93da029..0000000 --- a/contrib/wg-config/README +++ /dev/null @@ -1,140 +0,0 @@ -== Installation == - - # make install - -== Usage == - -wg-config is a very simple utility for adding and configuring WireGuard -interfaces using ip(8) and wg(8). - -Usage: wg-config [ add | del ] INTERFACE [arguments...] - - wg-config add INTERFACE --config=CONFIG_FILE [--address=ADDRESS/CIDR...] - [--route=ROUTE/CIDR...] [--no-auto-route-from-allowed-ips] - [--env-file=ENV_FILE] - - The add subcommand adds a new WireGuard interface, INTERFACE, replacing - any existing interfaces of the same name. The --config argument is - required, and its argument is passed to wg(8)'s setconf subcommand. The - --address argument(s) is recommended for this utility to be useful. The - --route argument is purely optional, as by default this utility will - automatically add routes implied by --address and as implied by the - allowed-ip entries inside the --config file. To disable this automatic - route adding, you may use the option entitled --no-auto-route-from-allowed-ips. - - wg-config del INTERFACE [--config=CONFIG_FILE_TO_SAVE] [--env-file=ENV_FILE] - - The del subcommand removes an existing WireGuard interface. If the - optional --config is specified, then the existing configuration is - written out to the file specified, via wg(8)'s showconf subcommand. - -Both `add' and del' take the --env-file=ENV_FILE option. If specified, -the contents of ENV_FILE are imported into wg-config. This can be used to -set variables in a file, instead of needing to pass them on the command -line. The following table shows the relation between the command line -options described above, and variables that may be declared in ENV_FILE: - - --address=A, --address=B, --address=C ADDRESSES=( "A" "B" "C" ) - --route=A, --route=B, --route=C ADDITIONAL_ROUTES=( "A" "B" "C" ) - --config-file=F CONFIG_FILE="F" - echo C > /tmp/F, --config-file=/tmp/F CONFIG_FILE_CONTENTS="C" - --no-auto-route-from-allowed-ips AUTO_ROUTE=0 - -Additionally, ENV_FILE may define the bash functions pre_add, post_add, -pre_del, and post_del, which will be called at their respective times. - -== Basic Example == - -This basic example might be used by a server. - -/etc/wireguard/wg-server.conf: - - [Interface] - PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk= - ListenPort = 41414 - - [Peer] - PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg= - AllowedIPs = 10.192.122.3/32, 10.192.124.1/24 - - [Peer] - PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0= - AllowedIPs = 10.192.122.4/32, 192.168.0.0/16 - - [Peer] - PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA= - AllowedIPs = 10.10.10.230/32 - -/etc/wireguard/wg-server.env: - - CONFIG_FILE="$(dirname "${BASH_SOURCE[0]}")/wg-server.conf" - ADDRESSES=( 10.192.122.1/34 10.10.0.1/16 ) - -Run at startup: -# wg-config add wgserver0 --env-file=/etc/wireguard/wg-server.env -Run at shutdown: -# wg-config del wgserver0 --env-file=/etc/wireguard/wg-server.env - -== Single File Advanced Example == - -This type of configuration might be desirable for a personal access gateway -VPN, connecting to a server like in the example above. - -/etc/wireguard/wg-vpn-gateway.env: - - CONFIG_FILE_CONTENTS=" - [Interface] - PrivateKey = 6JiA3fa+NG+x5m6aq7+lxlVaVqVf1mxK6/pDOZdNuXc= - - [Peer] - PublicKey = 6NagfTu+s8+TkEKpxX7pNjJuTf4zYtoJme7iQFYIw0A= - AllowedIPs = 0.0.0.0/0 - Endpoint = demo.wireguard.io:29912 - " - - ADDRESSES=( 10.200.100.2/32 ) - - post_add() { - printf 'nameserver 10.200.100.1' | cmd resolvconf -a "$INTERFACE" -m 0 - } - post_del() { - cmd resolvconf -d "$INTERFACE" - } - -Run to flip on the VPN: -# wg-config add wgvpn0 --env-file=/etc/wireguard/wg-vpn-gateway.env -Run to flip off the VPN: -# wg-config del wgvpn0 --env-file=/etc/wireguard/wg-vpn-gateway.env - -== Advanced Example == - -This achieves the same as the above, but with an external file. It only sets the -configuration file when the subcommand is add, to prevent it from being overwritten. -The above is much simpler and probably preferred, but this example shows how powerful -the tool can be. - -/etc/wireguard/wg-vpn-gateway.conf: - - [Interface] - PrivateKey = 6JiA3fa+NG+x5m6aq7+lxlVaVqVf1mxK6/pDOZdNuXc= - - [Peer] - PublicKey = 6NagfTu+s8+TkEKpxX7pNjJuTf4zYtoJme7iQFYIw0A= - AllowedIPs = 0.0.0.0/0 - Endpoint = demo.wireguard.io:29912 - -/etc/wireguard/wg-vpn-gateway.env: - - [[ $SUBCOMMAND == add ]] && CONFIG_FILE="$(dirname "${BASH_SOURCE[0]}")/demo-vpn.conf" || true - ADDRESSES=( 10.200.100.2/32 ) - post_add() { - printf 'nameserver 10.200.100.1' | cmd resolvconf -a "$INTERFACE" -m 0 - } - post_del() { - cmd resolvconf -d "$INTERFACE" - } - -Run to flip on the VPN: -# wg-config add wgvpn0 --env-file=/etc/wireguard/wg-vpn-gateway.env -The config file is not overwritten on shutdown, due to the conditional in the env file: -# wg-config del wgvpn0 --env-file=/etc/wireguard/wg-vpn-gateway.env diff --git a/contrib/wg-config/wg-config b/contrib/wg-config/wg-config deleted file mode 100755 index 8d8d4e7..0000000 --- a/contrib/wg-config/wg-config +++ /dev/null @@ -1,183 +0,0 @@ -#!/bin/bash -set -e -o pipefail - -SELF="$(readlink -f "${BASH_SOURCE[0]}")" -export PATH="${SELF%/*}:$PATH" - -cmd() { - echo "[#] $*" >&2 - "$@" -} - -auto_su() { - [[ $UID == 0 ]] || exec sudo -p "$PROGRAM must be run as root. Please enter the password for %u to continue: " "$SELF" "${ARGS[@]}" -} - -unwind() { - set +e - [[ -n $INTERFACE && -n $(ip link show dev "$INTERFACE" type wireguard 2>/dev/null) ]] && del_if - exit -} - -add_if() { - ip link delete dev "$INTERFACE" 2>/dev/null || true - cmd ip link add "$INTERFACE" type wireguard -} - -del_if() { - [[ -n $(ip link show dev "$INTERFACE" type wireguard 2>/dev/null) ]] || { echo "$PROGRAM: \`$INTERFACE' is not a WireGuard interface" >&2; exit 1; } - if [[ $(ip route show table all) =~ .*\ dev\ $INTERFACE\ table\ ([0-9]+)\ .* ]]; then - cmd ip rule delete table ${BASH_REMATCH[1]} - fi - cmd ip link delete dev "$INTERFACE" -} - -up_if() { - cmd ip link set "$INTERFACE" up -} - -add_addr() { - cmd ip address add "$1" dev "$INTERFACE" -} - -add_route() { - if [[ $1 == 0.0.0.0/0 || $1 == ::/0 ]]; then - add_default "$1" - else - cmd ip route add "$1" dev "$INTERFACE" - fi -} - -add_default() { - [[ $(join <(wg show "$INTERFACE" allowed-ips) <(wg show "$INTERFACE" endpoints)) =~ .*\ ${1//./\\.}\ ([0-9.:a-f]+):[0-9]+$ ]] && local endpoint="${BASH_REMATCH[1]}" - [[ -n $endpoint ]] || return 0 - local table=51820 - while [[ -n $(ip route show table $table) ]]; do ((table++)); done - cmd ip route add "$1" dev "$INTERFACE" table $table - cmd ip rule add not to "$endpoint" table $table -} - -set_config() { - if [[ -n $CONFIG_FILE_CONTENTS ]]; then - cmd wg setconf "$INTERFACE" <(echo "$CONFIG_FILE_CONTENTS") - else - cmd wg setconf "$INTERFACE" "$CONFIG_FILE" - fi -} - -save_config() { - local old_umask="$(umask)" - umask 077 - cmd wg showconf "$INTERFACE" > "$CONFIG_FILE.tmp" || { rm -f "$CONFIG_FILE.tmp"; exit 1; } - mv "$CONFIG_FILE.tmp" "$CONFIG_FILE" || { rm -f "$CONFIG_FILE.tmp"; exit 1; } - umask "$old_umask" -} - -cmd_usage() { - cat >&2 <<-_EOF - Usage: $PROGRAM [ add | del ] INTERFACE [arguments...] - - $PROGRAM add INTERFACE --config=CONFIG_FILE [--address=ADDRESS/CIDR...] - [--route=ROUTE/CIDR...] [--no-auto-route-from-allowed-ips] - [--env-file=ENV_FILE] - - The add subcommand adds a new WireGuard interface, INTERFACE, replacing - any existing interfaces of the same name. The --config argument is - required, and its argument is passed to wg(8)'s setconf subcommand. The - --address argument(s) is recommended for this utility to be useful. The - --route argument is purely optional, as by default this utility will - automatically add routes implied by --address and as implied by the - allowed-ip entries inside the --config file. To disable this automatic - route adding, you may use the option entitled --no-auto-route-from-allowed-ips. - - $PROGRAM del INTERFACE [--config=CONFIG_FILE_TO_SAVE] [--env-file=ENV_FILE] - - The del subcommand removes an existing WireGuard interface. If the - optional --config is specified, then the existing configuration is - written out to the file specified, via wg(8)'s showconf subcommand. - - $PROGRAM help - - Show this message. - - Both \`add' and ``del' take the --env-file=ENV_FILE option. If specified, - the contents of ENV_FILE are imported into $PROGRAM. This can be used to - set variables in a file, instead of needing to pass them on the command - line. The following table shows the relation between the command line - options described above, and variables that may be declared in ENV_FILE: - - --address=A, --address=B, --address=C ADDRESSES=( "A" "B" "C" ) - --route=A, --route=B, --route=C ADDITIONAL_ROUTES=( "A" "B" "C" ) - --config-file=F CONFIG_FILE="F" - echo C > /tmp/F, --config-file=/tmp/F CONFIG_FILE_CONTENTS="C" - --no-auto-route-from-allowed-ips AUTO_ROUTE=0 - - Additionally, ENV_FILE may define the bash functions pre_add, post_add, - pre_del, and post_del, which will be called at their respective times. - _EOF -} - -cmd_add() { - local i - [[ -n $CONFIG_FILE || -n $CONFIG_FILE_CONTENTS ]] || { echo "$PROGRAM: --config is required for add subcommand" >&2; exit 1; } - auto_su - trap unwind INT TERM EXIT - [[ $(type -t pre_add) != function ]] || pre_add - add_if - set_config - for i in "${ADDRESSES[@]}"; do - add_addr "$i" - done - up_if - if [[ $AUTO_ROUTE -eq 1 ]]; then - for i in $(wg show "$INTERFACE" allowed-ips | grep -Po '(?<=[\t ])[0-9.:/a-f]+' | sort -nr -k 2 -t /); do - [[ $(ip route get "$i" 2>/dev/null) == *dev\ $INTERFACE\ * ]] || add_route "$i" - done - fi - for i in "${ADDITIONAL_ROUTES[@]}"; do - add_route "$i" - done - [[ $(type -t post_add) != function ]] || post_add - trap - INT TERM EXIT -} - -cmd_del() { - auto_su - [[ $(type -t pre_del) != function ]] || pre_del - [[ -n $CONFIG_FILE ]] && save_config - del_if - [[ $(type -t post_del) != function ]] || post_del -} - -declare INTERFACE="$2" -declare SUBCOMMAND="$1" -declare -a ADDRESSES -declare -a ADDITIONAL_ROUTES -declare AUTO_ROUTE=1 -declare CONFIG_FILE -declare CONFIG_FILE_CONTENTS -declare PROGRAM="${0##*/}" -declare -a ARGS=( "$@" ) - -[[ -n $INTERFACE && -n $SUBCOMMAND ]] || { cmd_usage; exit 1; } - -shift 2 - -for arg; do - case "$arg" in - --env-file=*) source "${arg#*=}" ;; - --config=*) CONFIG_FILE="${arg#*=}" ;; - --address=*) ADDRESSES+=( ${arg#*=} ) ;; - --route=*) ADDITIONAL_ROUTES+=( ${arg#*=} ) ;; - --no-auto-route-from-allowed-ips) AUTO_ROUTE=0 ;; - *) cmd_usage; exit 1 ;; - esac -done - -case "$SUBCOMMAND" in -add) cmd_add ;; -del) cmd_del ;; -*) cmd_usage; exit 1 ;; -esac - -exit 0 diff --git a/src/Makefile b/src/Makefile index 5b72879..cc11c9e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,6 +7,7 @@ BASHCOMPDIR ?= $(PREFIX)/share/bash-completion/completions RUNSTATEDIR ?= /var/run PKG_CONFIG ?= pkg-config WITH_BASHCOMPLETION ?= yes +WITH_WGQUICK ?= yes CFLAGS ?= -O3 CFLAGS += -std=gnu11 @@ -30,6 +31,9 @@ install: wg @install -v -d "$(DESTDIR)$(BINDIR)" && install -m 0755 -v wg "$(DESTDIR)$(BINDIR)/wg" @install -v -d "$(DESTDIR)$(MANDIR)/man8" && install -m 0644 -v wg.8 "$(DESTDIR)$(MANDIR)/man8/wg.8" @[ "$(WITH_BASHCOMPLETION)" = "yes" ] && install -v -d "$(BASHCOMPDIR)" && install -m 0644 -v completion/wg.bash-completion "$(DESTDIR)$(BASHCOMPDIR)/wg" + @[ "$(WITH_WGQUICK)" = "yes" ] && install -m 0755 -v wg-quick.bash "$(DESTDIR)$(BINDIR)/wg-quick" + @[ "$(WITH_WGQUICK)" = "yes" ] && install -m 0644 -v wg-quick.8 "$(DESTDIR)$(MANDIR)/man8/wg-quick.8" + @[ "$(WITH_WGQUICK)" = "yes" -a "$(WITH_BASHCOMPLETION)" = "yes" ] && install -m 0644 -v completion/wg-quick.bash-completion "$(DESTDIR)$(BASHCOMPDIR)/wg-quick" check: clean CFLAGS=-g scan-build --view --keep-going $(MAKE) wg diff --git a/src/completion/wg-quick.bash-completion b/src/completion/wg-quick.bash-completion new file mode 100644 index 0000000..cf3e9a0 --- /dev/null +++ b/src/completion/wg-quick.bash-completion @@ -0,0 +1,20 @@ +# Copyright (C) 2017 Jason A. Donenfeld . All Rights Reserved. + +_wg_quick_completion() { + local i + if [[ $COMP_CWORD -eq 1 ]]; then + COMPREPLY+=( $(compgen -W "up down" -- "${COMP_WORDS[1]}") ) + return + elif [[ $COMP_CWORD -eq 2 ]]; then + local old_glob="$(shopt -p nullglob)" + shopt -s nullglob + for i in /etc/wireguard/*.conf; do + i="${i##*/}"; i="${i%.conf}" + COMPREPLY+=( $(compgen -W "$i" -- "${COMP_WORDS[2]}") ) + done + eval "$old_glob" + COMPREPLY+=( $(compgen -f -X '!*.conf' -- "${COMP_WORDS[2]}") $(compgen -d -- "${COMP_WORDS[2]}") ) + fi +} + +complete -o filenames -o nosort -F _wg_quick_completion wg-quick diff --git a/src/wg-quick.8 b/src/wg-quick.8 new file mode 100644 index 0000000..2425926 --- /dev/null +++ b/src/wg-quick.8 @@ -0,0 +1,194 @@ +.TH WG-QUICK 8 "2016 January 1" ZX2C4 "WireGuard" + +.SH NAME +wg-quick - set up a WireGuard interface simply + +.SH SYNOPSIS +.B wg-quick +[ +.I up +| +.I down +] [ +.I CONFIG_FILE +| +.I INTERFACE +] + +.SH DESCRIPTION + +This is an extremely simple script for easily bringing up a WireGuard interface, +suitable for a few common use cases. + +Use \fIup\fP to add and set up an interface, and use \fIdown\fP to tear down and remove +an interface. Running \fIup\fP adds a WireGuard interface, brings up the interface with the +supplied IP addresses, sets up routes, and optionally runs pre/post up scripts. Running \fIdown\fP +optionally saves the current configuration, removes the WireGuard interface, and optionally +runs pre/post down scripts. + +\fICONFIG_FILE\fP is a configuration file, whose filename is the interface name +followed by `.conf'. Otherwise, \fIINTERFACE\fP is an interface name, with configuration +found at `/etc/wireguard/\fIINTERFACE\fP.conf'. + +Generally speaking, this utility is just a simple script that wraps invocations to +.BR wg (8) +and +.BR ip (8) +in order to set up a WireGuard interface. It is designed for users with simple +needs, and users with more advanced needs are highly encouraged to use a more +specific tool, a more complete network manager, or otherwise just use +.BR wg (8) +and +.BR ip (8), +as usual. + +.SH CONFIGURATION + +The configuration file adds a few extra configuration values to the format understood by +.BR wg (8) +in order to configure additional attribute of an interface. It handles the +values that it understands, and then it passes the remaining ones directly to +.BR wg (8) +for further processing. + +It infers all routes from the list of peers' allowed IPs, and automatically adds +them to the system routing table. If one of those routes is the default route +(0.0.0.0/0 or ::/0), then it uses +.BR ip-rule (8) +to handle overriding of the default gateway. + +The configuration file will be passed directly to \fBwg\fP(8)'s `setconf' +sub-command, with the exception of the following additions to the \fIInterface\fP section, +which are handled by this tool: + +.IP \(bu +Address \(em a comma-separated list of ip (v4 or v6) addresses (optionally with CIDR masks) +to be assigned to the interface. May be specified multiple times. +.IP \(bu +PreUp, PostUp, PreDown, PostDown \(em script snippets which will be executed by +.BR bash (1) +before/after setting up/tearing down the interface, most commonly used +to configure DNS. The special string `%i' is expanded to \fIINTERFACE\fP. +.IP \(bu +SaveConfig \(em if set to `true', the configuration is saved from the current state of the +interface upon shutdown. + +.P +Recommended \fIINTERFACE\fP names include `wg0' or `wgvpn0' or even `wgmgmtlan0'. +However, the number at the end is in fact optional, and really +any free-form string [a-zA-Z0-9_=+.-]{1,16} will work. So even interface names corresponding +to geographic locations would suffice, such as `cincinnati', `nyc', or `paris', if that's +somehow desirable. + +.SH EXAMPLES + +These examples draw on the same syntax found for +.BR wg (8), +and a more complete description may be found there. Bold lines below are for options that extend +.BR wg (8). + +The following might be used for connecting as a client to a VPN gateway for tunneling all +traffic: + + [Interface] +.br + \fBAddress = 10.200.100.8/24\fP +.br + \fBPostUp = echo nameserver 10.200.100.1 | resolvconf -a %i -m 0\fP +.br + \fBPostDown = resolvconf -d %i\fP +.br + PrivateKey = oK56DE9Ue9zK76rAc8pBl6opph+1v36lm7cXXsQKrQM= +.br + PresharedKey = /UwcSPg38hW/D9Y3tcS1FOV0K1wuURMbS0sesJEP5ak= +.br + +.br + [Peer] +.br + PublicKey = GtL7fZc/bLnqZldpVofMCD6hDjrK28SsdLxevJ+qtKU= +.br + AllowedIPs = 0.0.0.0/0 +.br + Endpoint = demo.wireguard.io:51820 +.br + +Notice that the `PostUp` and `PostDown` commands are used here to configure DNS using +.BR resolvconf (8), +which is one of the many options for DNS configuration. The `Address` field is added +here in order to set up the address for the interface. The peer's allowed IPs entry +implies that this interface should be configured as the default gateway, which this +script does. + +Here is a more complicated example, fit for usage on a server: + + [Interface] +.br + \fBAddress = 10.192.122.1/24\fP +.br + \fBAddress = 10.10.0.1/16\fP +.br + \fBSaveConfig = true\fP +.br + PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk= +.br + ListenPort = 41414 +.br + +.br + [Peer] +.br + PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg= +.br + AllowedIPs = 10.192.122.3/32, 10.192.124.1/24 +.br + +.br + [Peer] +.br + PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0= +.br + AllowedIPs = 10.192.122.4/32, 192.168.0.0/16 +.br + +.br + [Peer] +.br + PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA= +.br + AllowedIPs = 10.10.10.230/32 + +Notice the two `Address' lines at the top, and that `SaveConfig' is set to `true', indicating +that the configuration file should be saved on shutdown using the current status of the +interface. + +These configuration files may be placed in any directory, putting the desired interface name +in the filename: + +\fB # wg-quick up /path/to/wgnet0.conf\fP + +For convienence, if only an interface name is supplied, it automatically chooses a path in +`/etc/wireguard/': + +\fB # wg-quick up wgnet0\fP + +This will load the configuration file `/etc/wireguard/wgnet0.conf'. + +.SH SEE ALSO +.BR wg (8), +.BR ip (8), +.BR ip-link (8), +.BR ip-address (8), +.BR ip-route (8), +.BR ip-rule (8). + +.SH AUTHOR +.B wg-quick +was written by +.MT Jason@zx2c4.com +Jason A. Donenfeld +.ME . +For updates and more information, a project page is available on the +.UR https://\:www.wireguard.io/ +World Wide Web +.UE . diff --git a/src/wg-quick.bash b/src/wg-quick.bash new file mode 100755 index 0000000..e686d73 --- /dev/null +++ b/src/wg-quick.bash @@ -0,0 +1,209 @@ +#!/bin/bash +# +# Copyright (c) 2016 Jason A. Donenfeld . All Rights Reserved. +# + +set -e -o pipefail +shopt -s extglob + +SELF="$(readlink -f "${BASH_SOURCE[0]}")" +export PATH="${SELF%/*}:$PATH" + +WG_CONFIG="" +INTERFACE="" +ADDRESSES=( ) +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" + INTERFACE="${BASH_REMATCH[1]}" + shopt -s nocasematch + while read -r line; do + 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 ;; + 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() { + local -n out="$1" + case "$2" in + true) out=1 ;; + false) out=0 ;; + *) 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() { + if [[ $(ip route show table all) =~ .*\ dev\ $INTERFACE\ table\ ([0-9]+)\ .* ]]; then + cmd ip rule delete table "${BASH_REMATCH[1]}" + cmd ip rule delete table main suppress_prefixlength 0 2>/dev/null + fi + cmd ip link delete dev "$INTERFACE" +} + +up_if() { + cmd ip link set "$INTERFACE" up +} + +add_addr() { + cmd ip address add "$1" dev "$INTERFACE" +} + +add_route() { + if [[ $1 == 0.0.0.0/0 || $1 == ::/0 ]]; then + add_default "$1" + else + cmd ip route add "$1" dev "$INTERFACE" + fi +} + +add_default() { + [[ $(join <(wg show "$INTERFACE" allowed-ips) <(wg show "$INTERFACE" endpoints)) =~ .*\ ${1//./\\.}\ ([0-9.:a-f]+):[0-9]+$ ]] && local endpoint="${BASH_REMATCH[1]}" + [[ -n $endpoint ]] || return 0 + local table=51820 + while [[ -n $(ip route show table $table) ]]; do ((table++)); done + cmd ip route add "$1" dev "$INTERFACE" table $table + cmd ip rule add not to "$endpoint" table $table + cmd ip rule add table main suppress_prefixlength 0 +} + +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 + [[ $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")" + 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" + 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. + - 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_hook "$PRE_UP" + add_if + set_config + for i in "${ADDRESSES[@]}"; do + add_addr "$i" + done + up_if + for i in $(wg show "$INTERFACE" allowed-ips | grep -Po '(?<=[\t ])[0-9.:/a-f]+' | sort -nr -k 2 -t /); do + [[ $(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 diff --git a/src/wg.8 b/src/wg.8 index 18edba3..9322509 100644 --- a/src/wg.8 +++ b/src/wg.8 @@ -131,11 +131,12 @@ PublicKey \(em a base64 public key calculated by \fIwg pubkey\fP from a private key, and usually transmitted out of band to the author of the configuration file. Required. .IP \(bu -AllowedIPs \(em a comma-separated list of IP (v4 or v6) addresses with +AllowedIPs \(em a comma-separated list of ip (v4 or v6) addresses with CIDR masks from which this peer is allowed to send incoming traffic and to which outgoing traffic for this peer is directed. The catch-all \fI0.0.0.0/0\fP may be specified for matching all IPv4 addresses, and -\fI::/0\fP may be specified for matching all IPv6 addresses. Required. +\fI::/0\fP may be specified for matching all IPv6 addresses. May be specified +multiple times. Required. .IP \(bu Endpoint \(em an endpoint IP or hostname, followed by a colon, and then a port number. This endpoint will be updated automatically to the most recent @@ -152,7 +153,6 @@ when unspecified, this option is off. Most users will not need this. Optional. .SH CONFIGURATION FILE FORMAT EXAMPLE This example may be used as a model for writing configuration files. -Note that not all keys are required. [Interface] .br