wg: add wg-quick

This is based on wg-config, but is even easier to use, and now makes
our full tools suite.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2017-01-02 05:33:43 +01:00
parent bf158a73fe
commit e975597e72
8 changed files with 430 additions and 337 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -7,6 +7,7 @@ BASHCOMPDIR ?= $(PREFIX)/share/bash-completion/completions
RUNSTATEDIR ?= /var/run RUNSTATEDIR ?= /var/run
PKG_CONFIG ?= pkg-config PKG_CONFIG ?= pkg-config
WITH_BASHCOMPLETION ?= yes WITH_BASHCOMPLETION ?= yes
WITH_WGQUICK ?= yes
CFLAGS ?= -O3 CFLAGS ?= -O3
CFLAGS += -std=gnu11 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)$(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" @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_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 check: clean
CFLAGS=-g scan-build --view --keep-going $(MAKE) wg CFLAGS=-g scan-build --view --keep-going $(MAKE) wg

View file

@ -0,0 +1,20 @@
# Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. 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

194
src/wg-quick.8 Normal file
View file

@ -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 .

209
src/wg-quick.bash Executable file
View file

@ -0,0 +1,209 @@
#!/bin/bash
#
# Copyright (c) 2016 Jason A. Donenfeld <Jason@zx2c4.com>. 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

View file

@ -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 private key, and usually transmitted out of band to the author of the
configuration file. Required. configuration file. Required.
.IP \(bu .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 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 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 \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 .IP \(bu
Endpoint \(em an endpoint IP or hostname, followed by a colon, and then a 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 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 .SH CONFIGURATION FILE FORMAT EXAMPLE
This example may be used as a model for writing configuration files. This example may be used as a model for writing configuration files.
Note that not all keys are required.
[Interface] [Interface]
.br .br