xref: /dragonfly/etc/rc.d/wg (revision 212e48ce)
1451640b7SAaron LI#!/bin/sh
2451640b7SAaron LI#
3451640b7SAaron LI# Copyright (c) 2024 The DragonFly Project.  All rights reserved.
4451640b7SAaron LI#
5451640b7SAaron LI# This code is derived from software contributed to The DragonFly Project
6451640b7SAaron LI# by Aaron LI <aly@aaronly.me>
7451640b7SAaron LI#
8451640b7SAaron LI# Redistribution and use in source and binary forms, with or without
9451640b7SAaron LI# modification, are permitted provided that the following conditions
10451640b7SAaron LI# are met:
11451640b7SAaron LI#
12451640b7SAaron LI# 1. Redistributions of source code must retain the above copyright
13451640b7SAaron LI#    notice, this list of conditions and the following disclaimer.
14451640b7SAaron LI# 2. Redistributions in binary form must reproduce the above copyright
15451640b7SAaron LI#    notice, this list of conditions and the following disclaimer in
16451640b7SAaron LI#    the documentation and/or other materials provided with the
17451640b7SAaron LI#    distribution.
18451640b7SAaron LI# 3. Neither the name of The DragonFly Project nor the names of its
19451640b7SAaron LI#    contributors may be used to endorse or promote products derived
20451640b7SAaron LI#    from this software without specific, prior written permission.
21451640b7SAaron LI#
22451640b7SAaron LI# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23451640b7SAaron LI# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24451640b7SAaron LI# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25451640b7SAaron LI# FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
26451640b7SAaron LI# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27451640b7SAaron LI# INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28451640b7SAaron LI# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29451640b7SAaron LI# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30451640b7SAaron LI# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31451640b7SAaron LI# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32451640b7SAaron LI# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33451640b7SAaron LI# SUCH DAMAGE.
34451640b7SAaron LI#
35451640b7SAaron LI
36451640b7SAaron LI# PROVIDE: wg wireguard
37451640b7SAaron LI# REQUIRE: NETWORKING
38451640b7SAaron LI# BEFORE:  DAEMON
39451640b7SAaron LI
40451640b7SAaron LI# uncomment to show extra debug logs
41451640b7SAaron LI#WG_DEBUG=yes
42451640b7SAaron LI# uncomment to not actually execute the commands
43451640b7SAaron LI#WG_DRYRUN=yes
44451640b7SAaron LI
45451640b7SAaron LI. /etc/rc.subr
46451640b7SAaron LI
47451640b7SAaron LIname="wg"
48451640b7SAaron LIrcvar=$(set_rcvar)
49451640b7SAaron LIstart_cmd="${name}_start"
50451640b7SAaron LIstop_cmd="${name}_stop"
51451640b7SAaron LIstatus_cmd="${name}_status"
52451640b7SAaron LIextra_commands="status"
53451640b7SAaron LI
54451640b7SAaron LI# usage: wg_run cmd ...
55451640b7SAaron LIwg_run()
56451640b7SAaron LI{
57451640b7SAaron LI	if [ -n "${WG_DRYRUN}" ]; then
58451640b7SAaron LI		printf "[+] %s\n" "$*"
59451640b7SAaron LI		return
60451640b7SAaron LI	fi
61451640b7SAaron LI	debug "[+] $*"
62451640b7SAaron LI	"$@"
63451640b7SAaron LI}
64451640b7SAaron LI
65451640b7SAaron LI# similar to wg_run(), but exit if the command fails.
66451640b7SAaron LIwg_must_run()
67451640b7SAaron LI{
68451640b7SAaron LI	wg_run "$@"
69451640b7SAaron LI	local ret=$?
70451640b7SAaron LI	if [ ${ret} -ne 0 ]; then
71451640b7SAaron LI		err ${ret} "return code: ${ret}, command was: $*"
72451640b7SAaron LI	fi
73451640b7SAaron LI}
74451640b7SAaron LI
75451640b7SAaron LI# usage: wg_load_config <conffile>
76451640b7SAaron LIwg_load_config()
77451640b7SAaron LI{
78451640b7SAaron LI	local conffile=$1
79451640b7SAaron LI	local ifname=$(basename ${conffile} .conf)
80451640b7SAaron LI
81451640b7SAaron LI	debug "loading [${ifname}] configs from file: ${conffile}"
82451640b7SAaron LI	local configs=$(awk -v ifname="${ifname}" -v debugvar="${WG_DEBUG}" '
83451640b7SAaron LI	BEGIN {
84451640b7SAaron LI		SUBSEP = "_"
85451640b7SAaron LI
86451640b7SAaron LI		# conversion table for hex2dec()
87451640b7SAaron LI		xdigits = "0123456789abcdef"
88451640b7SAaron LI		for (i = 0; i < length(xdigits); i++) {
89451640b7SAaron LI			k = substr(xdigits, i + 1, 1)
90451640b7SAaron LI			decv[k] = i
91451640b7SAaron LI			decv[toupper(k)] = i
92451640b7SAaron LI		}
93451640b7SAaron LI	}
94451640b7SAaron LI
95451640b7SAaron LI	function debug(msg) {
96451640b7SAaron LI		if (!debugvar)
97451640b7SAaron LI			return
98451640b7SAaron LI		printf("wg [%s]: DEBUG: %s\n", ifname, msg) > "/dev/stderr"
99451640b7SAaron LI	}
100451640b7SAaron LI	function info(msg) {
101451640b7SAaron LI		printf("wg [%s]: INFO: %s\n", ifname, msg) > "/dev/stderr"
102451640b7SAaron LI	}
103451640b7SAaron LI	function warn(msg) {
104451640b7SAaron LI		printf("wg [%s]: WARNING: %s\n", ifname, msg) > "/dev/stderr"
105451640b7SAaron LI	}
106451640b7SAaron LI	function error(code, msg) {
107451640b7SAaron LI		printf("wg [%s]: ERROR: %s\n", ifname, msg) > "/dev/stderr"
108451640b7SAaron LI		exit code
109451640b7SAaron LI	}
110451640b7SAaron LI	function hex2dec(x,   v) {
111451640b7SAaron LI		# The 0x prefix is optional.
112451640b7SAaron LI		v = 0
113451640b7SAaron LI		for (i = 1; i <= length(x); i++)
114451640b7SAaron LI			v = 16 * v + decv[substr(x, i, 1)]
115451640b7SAaron LI		return v
116451640b7SAaron LI	}
117451640b7SAaron LI	function fix_integer(v) {
118451640b7SAaron LI		if (v == "off")
119451640b7SAaron LI			return 0
120451640b7SAaron LI		else if (v ~ /^0[xX][[:xdigit:]]+$/)
121451640b7SAaron LI			return hex2dec(v)
122451640b7SAaron LI		else
123451640b7SAaron LI			return v + 0
124451640b7SAaron LI	}
125451640b7SAaron LI	function fix_boolean(v) {
126451640b7SAaron LI		v = tolower(v)
127451640b7SAaron LI		if (v == "1" || v == "true" || v == "on" || v == "yes")
128451640b7SAaron LI			return "true"
129451640b7SAaron LI		else
130451640b7SAaron LI			return "false"
131451640b7SAaron LI	}
132451640b7SAaron LI	function fix_endpoint(v) {
133451640b7SAaron LI		if (v ~ /^\[/) {
134451640b7SAaron LI			# Assume IPv6: [ipv6]:port
135451640b7SAaron LI			sub(/\[/, "", v)
136451640b7SAaron LI			sub(/\]:/, " ", v)
137451640b7SAaron LI		} else {
138451640b7SAaron LI			# Assume IPv4 or domain: ipv4:port, domain:port
139451640b7SAaron LI			sub(/:/, " ", v)
140451640b7SAaron LI		}
141451640b7SAaron LI		return v
142451640b7SAaron LI	}
143451640b7SAaron LI	function fix_address(v,   n, a) {
144451640b7SAaron LI		# Comma-separated IPv4/IPv6, with optional CIDR masks
145451640b7SAaron LI		n = split(v, addrs, /[, ]+/)
146451640b7SAaron LI		v = ""
147451640b7SAaron LI		for (i = 1; i <= n; i++) {
148451640b7SAaron LI			a = addrs[i]
149451640b7SAaron LI			if (!index(a, "/")) {
150451640b7SAaron LI				if (index(a, ":"))
151451640b7SAaron LI					a = a "/128"
152451640b7SAaron LI				else
153451640b7SAaron LI					a = a "/32"
154451640b7SAaron LI			}
155451640b7SAaron LI			v = v " " a
156451640b7SAaron LI		}
157451640b7SAaron LI		return v
158451640b7SAaron LI	}
159451640b7SAaron LI	function fix_aips(v,   n) {
160451640b7SAaron LI		# Comma-separated IPv4/IPv6 with CIDR masks
161451640b7SAaron LI		n = split(v, aips, /[, ]+/)
162451640b7SAaron LI		v = ""
163451640b7SAaron LI		for (i = 1; i <= n; i++)
164451640b7SAaron LI			v = v " " aips[i]
165451640b7SAaron LI		return v
166451640b7SAaron LI	}
167451640b7SAaron LI	function trim(s) {
168451640b7SAaron LI		gsub(/^[ \t]+|[ \t]+$/, "", s)
169451640b7SAaron LI		return s
170451640b7SAaron LI	}
171451640b7SAaron LI	function quote(s) {
172451640b7SAaron LI		# NOTE: \047 is the single quote.
173451640b7SAaron LI		gsub(/\047/, "\047\\\047\047", s)
174f43226ddSAaron LI		return "\047" s "\047"
175451640b7SAaron LI	}
176451640b7SAaron LI
177451640b7SAaron LI	NF == 0 || $1 ~ /^[#;]/ {
178451640b7SAaron LI		next
179451640b7SAaron LI	}
180451640b7SAaron LI	$1 ~ /^\[/ {
181451640b7SAaron LI		section = tolower($1)
182451640b7SAaron LI		if (section == "[interface]") {
183451640b7SAaron LI			is_interface = 1
184451640b7SAaron LI			is_peer = 0
185451640b7SAaron LI		} else if (section == "[peer]") {
186451640b7SAaron LI			is_interface = 0
187451640b7SAaron LI			is_peer = 1
188451640b7SAaron LI			peer_count++
189451640b7SAaron LI		} else {
190451640b7SAaron LI			is_interface = 0
191451640b7SAaron LI			is_peer = 0
192451640b7SAaron LI			warn(sprintf("unknown section: %s", section))
193451640b7SAaron LI		}
194451640b7SAaron LI		next
195451640b7SAaron LI	}
196451640b7SAaron LI	!(is_interface || is_peer) {
197451640b7SAaron LI		warn(sprintf("skip unknown %s: %s", section, $0))
198451640b7SAaron LI		next
199451640b7SAaron LI	}
200451640b7SAaron LI	$0 !~ /^[ \t]*[[:alnum:]]+[ \t]*=[ \t]*[^ \t].*$/ {
201451640b7SAaron LI		warn(sprintf("skip invalid line: %s", $0))
202451640b7SAaron LI		next
203451640b7SAaron LI	}
204451640b7SAaron LI	{
205451640b7SAaron LI		match($0, /^[ \t]*[[:alnum:]]+[ \t]*=/)
206451640b7SAaron LI		key = trim(tolower(substr($0, 1, RLENGTH - 1)))
207451640b7SAaron LI		value = trim(substr($0, RLENGTH + 1))
208451640b7SAaron LI		if (key == "" || value == "")
209451640b7SAaron LI			error(1, "code bug") # already skipped; cannot happen
210451640b7SAaron LI
211451640b7SAaron LI		# Join split lines.
212451640b7SAaron LI		while (value ~ /\\$/) {
213451640b7SAaron LI			if ((getline vline) <= 0) {
214451640b7SAaron LI				warn(sprintf("incomplete value of |%s|: %s",
215451640b7SAaron LI					     key, value))
216451640b7SAaron LI				break
217451640b7SAaron LI			}
218451640b7SAaron LI			value = substr(value, 1, length(value) - 1)
219451640b7SAaron LI			value = value " " trim(vline)
220451640b7SAaron LI		}
221451640b7SAaron LI
222451640b7SAaron LI		if (is_interface) {
223451640b7SAaron LI			debug(sprintf("interface: |%s| = |%s|", key, value))
224451640b7SAaron LI			if (key == "description" || key == "privatekey" ||
225451640b7SAaron LI			    key == "listenport" || key == "mtu") {
226451640b7SAaron LI				interface[key] = value
227451640b7SAaron LI			} else if (key == "cookie" || key == "fwmark") {
228451640b7SAaron LI				key = "cookie"
229451640b7SAaron LI				interface[key] = fix_integer(value)
230451640b7SAaron LI			} else if (key == "address") {
231451640b7SAaron LI				old = interface[key]
232451640b7SAaron LI				interface[key] = old " " fix_address(value)
233451640b7SAaron LI			} else if (key == "preup" || key == "postup" ||
234451640b7SAaron LI				   key == "predown" || key == "postdown") {
235451640b7SAaron LI				gsub(/%i/, ifname, value)
236451640b7SAaron LI				n = ++interface[key "_count"]
237451640b7SAaron LI				interface[key n] = value
238451640b7SAaron LI			} else {
239451640b7SAaron LI				info(sprintf("ignore unsupported interface " \
240451640b7SAaron LI					     "config: %s = %s", key, value))
241451640b7SAaron LI				next
242451640b7SAaron LI			}
243451640b7SAaron LI		} else {
244451640b7SAaron LI			debug(sprintf("peer[%d]: |%s| = |%s|",
245451640b7SAaron LI				      peer_count, key, value))
246451640b7SAaron LI			if (key == "description" || key == "publickey" ||
247451640b7SAaron LI			    key == "presharedkey") {
248451640b7SAaron LI				peers[peer_count, key] = value
249451640b7SAaron LI			} else if (key == "endpoint") {
250451640b7SAaron LI				peers[peer_count, key] = fix_endpoint(value)
251451640b7SAaron LI			} else if (key == "allowedips") {
252451640b7SAaron LI				old = peers[peer_count, key]
253451640b7SAaron LI				peers[peer_count, key] = old " " fix_aips(value)
254451640b7SAaron LI			} else if (key == "persistentkeepalive") {
255451640b7SAaron LI				peers[peer_count, key] = fix_integer(value)
256451640b7SAaron LI			} else if (key == "enabled") {
257451640b7SAaron LI				peers[peer_count, key] = fix_boolean(value)
258451640b7SAaron LI			} else {
259451640b7SAaron LI				info(sprintf("ignore unsupported peer " \
260451640b7SAaron LI					     "config: %s = %s", key, value))
261451640b7SAaron LI				next
262451640b7SAaron LI			}
263451640b7SAaron LI		}
264451640b7SAaron LI	}
265451640b7SAaron LI
266451640b7SAaron LI	END {
267451640b7SAaron LI		for (key in interface)
268451640b7SAaron LI			printf("_wg_interface_%s=%s;\n",
269451640b7SAaron LI			       key, quote(interface[key]))
270451640b7SAaron LI
2719c070710SAaron LI		peer_count += 0  # fix empty value to be 0
272451640b7SAaron LI		printf("_wg_peer_count=%s;\n", quote(peer_count))
273451640b7SAaron LI		for (key in peers)
274451640b7SAaron LI			printf("_wg_peer%s=%s;\n", key, quote(peers[key]))
275451640b7SAaron LI	}' "${conffile}") || exit $?
276451640b7SAaron LI
277451640b7SAaron LI	local msg=$(printf "eval configs: {{{\n%s\n}}}\n" "${configs}")
278451640b7SAaron LI	debug "${msg}"
279451640b7SAaron LI
280451640b7SAaron LI	eval "${configs}"
281451640b7SAaron LI}
282451640b7SAaron LI
283451640b7SAaron LI# usage: wg_set_interface <ifname>
284451640b7SAaron LIwg_set_interface()
285451640b7SAaron LI{
286451640b7SAaron LI	local ifname=$1
287451640b7SAaron LI
288451640b7SAaron LI	local privkey=${_wg_interface_privatekey}
289451640b7SAaron LI	local port=${_wg_interface_listenport}
290451640b7SAaron LI	local cookie=${_wg_interface_cookie}
291451640b7SAaron LI
292451640b7SAaron LI	local args=
293451640b7SAaron LI	if [ -z "${privkey}" ]; then
294451640b7SAaron LI		err 1 "interface is missing the private key"
295451640b7SAaron LI	else
296451640b7SAaron LI		args="wgkey ${privkey}"
297451640b7SAaron LI	fi
298451640b7SAaron LI	if [ -n "${port}" ]; then
299451640b7SAaron LI		args="${args} wgport ${port}"
300451640b7SAaron LI	fi
301451640b7SAaron LI	if [ -n "${cookie}" ]; then
302451640b7SAaron LI		args="${args} wgcookie ${cookie}"
303451640b7SAaron LI	fi
304451640b7SAaron LI	wg_must_run ifconfig ${ifname} ${args}
305451640b7SAaron LI
306451640b7SAaron LI	local addrs=${_wg_interface_address}
307451640b7SAaron LI	local addr af
308451640b7SAaron LI	for addr in ${addrs}; do
309451640b7SAaron LI		case ${addr} in
310451640b7SAaron LI		*:*)
311451640b7SAaron LI			af=inet6
312451640b7SAaron LI			;;
313451640b7SAaron LI		*)
314451640b7SAaron LI			af=inet
315451640b7SAaron LI			;;
316451640b7SAaron LI		esac
317451640b7SAaron LI		wg_run ifconfig ${ifname} ${af} ${addr} alias
318451640b7SAaron LI	done
319451640b7SAaron LI
320451640b7SAaron LI	local descr=${_wg_interface_description}
321451640b7SAaron LI	if [ -n "${descr}" ]; then
322451640b7SAaron LI		wg_run ifconfig ${ifname} description "${descr}"
323451640b7SAaron LI	fi
324451640b7SAaron LI
325451640b7SAaron LI	local mtu=${_wg_interface_mtu}
326451640b7SAaron LI	if [ -n "${mtu}" ]; then
327451640b7SAaron LI		wg_run ifconfig ${ifname} mtu ${mtu}
328451640b7SAaron LI	fi
329451640b7SAaron LI}
330451640b7SAaron LI
331451640b7SAaron LI# usage: wg_set_peer <ifname> <peerid>
332451640b7SAaron LIwg_set_peer()
333451640b7SAaron LI{
334451640b7SAaron LI	local ifname=$1
335451640b7SAaron LI	local peerid=$2
336451640b7SAaron LI
337451640b7SAaron LI	local enabled
338451640b7SAaron LI	eval 'enabled="${_wg_'${peerid}'_enabled}"'
339451640b7SAaron LI	if [ "${enabled}" = "false" ]; then
340451640b7SAaron LI		info "peer [${peerid}] is disabled"
341451640b7SAaron LI		return
342451640b7SAaron LI	fi
343451640b7SAaron LI
344451640b7SAaron LI	local publickey
345451640b7SAaron LI	eval 'publickey="${_wg_'${peerid}'_publickey}"'
346451640b7SAaron LI	if [ -z "${publickey}" ]; then
347451640b7SAaron LI		warn "peer [${peerid}] is missing the public key"
348451640b7SAaron LI		return
349451640b7SAaron LI	fi
350451640b7SAaron LI	local cmd="ifconfig ${ifname} wgpeer ${publickey}"
351451640b7SAaron LI
352451640b7SAaron LI	local descr
353451640b7SAaron LI	eval 'descr="${_wg_'${peerid}'_description}"'
354451640b7SAaron LI	if [ -n "${descr}" ]; then
355451640b7SAaron LI		wg_run ${cmd} wgdescription "${descr}"
356451640b7SAaron LI	fi
357451640b7SAaron LI
358451640b7SAaron LI	local psk endpoint pka aips
359451640b7SAaron LI	eval 'psk="${_wg_'${peerid}'_presharedkey}"'
360451640b7SAaron LI	eval 'endpoint="${_wg_'${peerid}'_endpoint}"'
361451640b7SAaron LI	eval 'pka="${_wg_'${peerid}'_persistentkeepalive}"'
362451640b7SAaron LI	eval 'aips="${_wg_'${peerid}'_allowedips}"'
363451640b7SAaron LI
364451640b7SAaron LI	local args= aip
365451640b7SAaron LI	if [ -n "${psk}" ]; then
366451640b7SAaron LI		args="${args} wgpsk ${psk}"
367451640b7SAaron LI	fi
368451640b7SAaron LI	if [ -n "${endpoint}" ]; then
369451640b7SAaron LI		args="${args} wgendpoint ${endpoint}"
370451640b7SAaron LI	fi
371451640b7SAaron LI	if [ -n "${pka}" ]; then
372451640b7SAaron LI		args="${args} wgpka ${pka}"
373451640b7SAaron LI	fi
374451640b7SAaron LI	# All allowed IPs must be configured at once.
375451640b7SAaron LI	for aip in ${aips}; do
376451640b7SAaron LI		args="${args} wgaip ${aip}"
377451640b7SAaron LI	done
378451640b7SAaron LI	wg_run ${cmd} ${args}
379451640b7SAaron LI}
380451640b7SAaron LI
381451640b7SAaron LI# usage: wg_exec_hook <preup|postup|predown|postdown>
382451640b7SAaron LIwg_exec_hook()
383451640b7SAaron LI{
384451640b7SAaron LI	local hook=$1
385451640b7SAaron LI	local count
386451640b7SAaron LI
387451640b7SAaron LI	case ${hook} in
388451640b7SAaron LI	preup|postup|predown|postdown)
389451640b7SAaron LI		eval 'count="${_wg_interface_'${hook}'_count:-0}"'
390451640b7SAaron LI		;;
391451640b7SAaron LI	*)
392451640b7SAaron LI		err 1 "unknown hook: ${hook}"
393451640b7SAaron LI		;;
394451640b7SAaron LI	esac
395451640b7SAaron LI
396451640b7SAaron LI	debug "executing [${hook}] hook (${count} actions) ..."
397451640b7SAaron LI
398451640b7SAaron LI	local i=1 cmd ret
399451640b7SAaron LI	while [ ${i} -le ${count} ]; do
400451640b7SAaron LI		eval 'cmd="${_wg_interface_'${hook}${i}'}"'
401451640b7SAaron LI		wg_run sh -c "${cmd}"
402451640b7SAaron LI		ret=$?
403451640b7SAaron LI		if [ ${ret} -ne 0 ]; then
404451640b7SAaron LI			warn "return code: ${ret}, command was: sh -c '${cmd}'"
405451640b7SAaron LI		fi
406451640b7SAaron LI		i=$((i + 1))
407451640b7SAaron LI	done
408451640b7SAaron LI}
409451640b7SAaron LI
410451640b7SAaron LI# usage: wg_start_interface <ifname>
411451640b7SAaron LIwg_start_interface()
412451640b7SAaron LI{
413451640b7SAaron LI	local ifname=$1
414451640b7SAaron LI	info "starting interface [${ifname}] ..."
415451640b7SAaron LI
416e1be3f08SAaron LI	local conffile="${wg_config_dir}/${ifname}.conf"
417451640b7SAaron LI	if [ ! -r "${conffile}" ]; then
418451640b7SAaron LI		err 1 "cannot read config file: ${conffile}"
419451640b7SAaron LI	fi
420451640b7SAaron LI
421451640b7SAaron LI	wg_load_config "${conffile}"
422451640b7SAaron LI
423451640b7SAaron LI	wg_exec_hook preup
424451640b7SAaron LI
425451640b7SAaron LI	local cmd
426*212e48ceSAntonio Huete Jimenez	if expr "${ifname}" : 'wg[0-9][0-9]*$' > /dev/null; then
427451640b7SAaron LI		cmd="ifconfig ${ifname} create"
428451640b7SAaron LI	else
429451640b7SAaron LI		cmd="ifconfig wg create name ${ifname}"
430451640b7SAaron LI	fi
431451640b7SAaron LI	wg_must_run ${cmd} > /dev/null
432451640b7SAaron LI
433451640b7SAaron LI	wg_set_interface ${ifname}
434451640b7SAaron LI
435451640b7SAaron LI	local i=1
436451640b7SAaron LI	while [ ${i} -le ${_wg_peer_count:-0} ]; do
437451640b7SAaron LI		wg_set_peer ${ifname} "peer${i}"
438451640b7SAaron LI		i=$((i + 1))
439451640b7SAaron LI	done
440451640b7SAaron LI
441451640b7SAaron LI	wg_run ifconfig ${ifname} up
442451640b7SAaron LI
443451640b7SAaron LI	wg_exec_hook postup
444451640b7SAaron LI
445451640b7SAaron LI	info "interface [${ifname}] started."
446451640b7SAaron LI}
447451640b7SAaron LI
448451640b7SAaron LI# usage: wg_stop_interface <ifname>
449451640b7SAaron LIwg_stop_interface()
450451640b7SAaron LI{
451451640b7SAaron LI	local ifname=$1
452451640b7SAaron LI	info "stopping interface [${ifname}] ..."
453451640b7SAaron LI
454e1be3f08SAaron LI	local conffile="${wg_config_dir}/${ifname}.conf"
455451640b7SAaron LI	if [ ! -r "${conffile}" ]; then
456451640b7SAaron LI		err 1 "cannot read config file: ${conffile}"
457451640b7SAaron LI	fi
458451640b7SAaron LI
459451640b7SAaron LI	wg_load_config "${conffile}"
460451640b7SAaron LI
461451640b7SAaron LI	wg_exec_hook predown
462451640b7SAaron LI
463451640b7SAaron LI	wg_run ifconfig ${ifname} down
464451640b7SAaron LI	wg_run ifconfig ${ifname} destroy
465451640b7SAaron LI
466451640b7SAaron LI	wg_exec_hook postdown
467451640b7SAaron LI
468451640b7SAaron LI	info "interface [${ifname}] stopped."
469451640b7SAaron LI}
470451640b7SAaron LI
471451640b7SAaron LIwg_start()
472451640b7SAaron LI{
473451640b7SAaron LI	local ifname
474451640b7SAaron LI	for ifname in ${wg_interfaces}; do
475451640b7SAaron LI		if [ "${ifname}" = "wg" ]; then
476451640b7SAaron LI			warn "skip invalid interface name: ${ifname}"
477451640b7SAaron LI			continue
478451640b7SAaron LI		fi
479451640b7SAaron LI		if ifconfig -n ${ifname} >/dev/null 2>&1; then
480451640b7SAaron LI			warn "interface [${ifname}] already exists."
481451640b7SAaron LI			continue
482451640b7SAaron LI		fi
483451640b7SAaron LI		# Use a sub-shell to avoid mixing the configurations of
484451640b7SAaron LI		# different interfaces.
485451640b7SAaron LI		( wg_start_interface ${ifname} )
486451640b7SAaron LI	done
487451640b7SAaron LI}
488451640b7SAaron LI
489451640b7SAaron LIwg_stop()
490451640b7SAaron LI{
491451640b7SAaron LI	local ifname
492451640b7SAaron LI	for ifname in ${wg_interfaces}; do
493451640b7SAaron LI		if ! ifconfig -n ${ifname} >/dev/null 2>&1; then
494451640b7SAaron LI			warn "interface [${ifname}] does not exist."
495451640b7SAaron LI			continue
496451640b7SAaron LI		fi
497451640b7SAaron LI		( wg_stop_interface ${ifname} )
498451640b7SAaron LI	done
499451640b7SAaron LI}
500451640b7SAaron LI
501451640b7SAaron LIwg_status()
502451640b7SAaron LI{
503451640b7SAaron LI	local ifname
504451640b7SAaron LI	for ifname in ${wg_interfaces}; do
505451640b7SAaron LI		wg_run ifconfig -n ${ifname}
506451640b7SAaron LI	done
507451640b7SAaron LI}
508451640b7SAaron LI
509451640b7SAaron LIload_rc_config ${name}
510451640b7SAaron LI
511451640b7SAaron LIcmd=$1
512451640b7SAaron LIshift
513451640b7SAaron LIif [ $# -gt 0 ]; then
514451640b7SAaron LI	wg_interfaces="$@"
515451640b7SAaron LIfi
516451640b7SAaron LIdebug "interfaces: ${wg_interfaces}"
517451640b7SAaron LI
518451640b7SAaron LIrun_rc_command "${cmd}"
519