diff --git a/package/features b/package/features index 72887e3a..c6e94a1a 100644 --- a/package/features +++ b/package/features @@ -16,7 +16,7 @@ when(_'web-wizard' and _'autoupdater', { 'gluon-config-mode-autoupdater', }) -when(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), { +when(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger' or _'mesh-vpn-wireguard'), { 'gluon-config-mode-mesh-vpn', }) diff --git a/package/gluon-mesh-vpn-core/files/lib/netifd/proto/gluon_wireguard.sh b/package/gluon-mesh-vpn-core/files/lib/netifd/proto/gluon_wireguard.sh new file mode 100755 index 00000000..05eaad53 --- /dev/null +++ b/package/gluon-mesh-vpn-core/files/lib/netifd/proto/gluon_wireguard.sh @@ -0,0 +1,86 @@ +#!/bin/sh + +PROTO_DEBUG=1 + +. /lib/functions.sh +. ../netifd-proto.sh +init_proto "$@" + +WG=/usr/bin/wg + +proto_gluon_wireguard_init_config() { + proto_config_add_int index + proto_config_add_int mtu +} + +interface_linklocal_from_wg_public_key() { + # We generate a predictable v6 address + local macaddr="$(printf "%s" "$1"|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')" + local oldIFS="$IFS"; IFS=':'; + # shellcheck disable=SC2086 + set -- $macaddr; IFS="$oldIFS" + echo "fe80::$1$2:$3ff:fe$4:$5$6" +} + +proto_gluon_wireguard_setup() { + local config="$1" + local ifname="$2" + + local index mtu + json_get_vars index mtu + + local public_key="$(/lib/gluon/mesh-vpn/wireguard_pubkey.sh)" + + # The wireguard proto itself can not be moved here, as the proto does not + # allow add_dynamic. + + wireguard_ip=$(interface_linklocal_from_wg_public_key "$public_key") + + ## Add IP + + proto_add_host_dependency "$config" '' "$ifname" + proto_init_update "$ifname" 1 + proto_add_data + json_add_string zone 'wired_mesh' + proto_close_data + proto_add_ipv6_address "$wireguard_ip" "128" + proto_send_update "$ifname" + + ## wgpeerselector + + json_init + json_add_string name "${ifname}_peerselector" + json_add_string ifname "$ifname" + json_add_string proto 'wgpeerselector' + json_add_string unix_group 'gluon-mesh-vpn' + json_add_boolean transitive 1 + json_close_object + ubus call network add_dynamic "$(json_dump)" + + ## vxlan + + json_init + json_add_string name "mesh-vpn" + json_add_string proto 'vxlan6' + json_add_string tunlink "$ifname" + # ip6addr (the lower interface ip6) is used by the vxlan.sh proto + json_add_string ip6addr "$wireguard_ip" + json_add_string peer6addr "fe80::1" + json_add_int vid "$(lua -e 'print(tonumber(require("gluon.util").domain_seed_bytes("gluon-mesh-vxlan", 3), 16))')" + json_add_boolean rxcsum '0' + json_add_boolean txcsum '0' + json_close_object + ubus call network add_dynamic "$(json_dump)" + + proto_init_update "$ifname" 1 + proto_send_update "$config" +} + +proto_gluon_wireguard_teardown() { + local config="$1" + + proto_init_update "*" 0 + proto_send_update "$config" +} + +add_protocol gluon_wireguard diff --git a/package/gluon-mesh-vpn-core/luasrc/lib/gluon/upgrade/500-mesh-vpn b/package/gluon-mesh-vpn-core/luasrc/lib/gluon/upgrade/500-mesh-vpn index 58bfa30e..a476afdb 100755 --- a/package/gluon-mesh-vpn-core/luasrc/lib/gluon/upgrade/500-mesh-vpn +++ b/package/gluon-mesh-vpn-core/luasrc/lib/gluon/upgrade/500-mesh-vpn @@ -54,6 +54,8 @@ if not uci:get('gluon', 'mesh_vpn') then else enabled = site.mesh_vpn.enabled(false) end + -- wireguard is not listed here, as it didn't exist before the uci section + -- gluon.mesh_vpn was introduced. Therefore no migration is necessary. local limit_enabled = tonumber((uci:get('simple-tc', 'mesh_vpn', 'enabled'))) diff --git a/package/gluon-mesh-vpn-wireguard/Makefile b/package/gluon-mesh-vpn-wireguard/Makefile new file mode 100644 index 00000000..c7c21e53 --- /dev/null +++ b/package/gluon-mesh-vpn-wireguard/Makefile @@ -0,0 +1,13 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=gluon-mesh-vpn-wireguard +PKG_VERSION:=1 + +include ../gluon.mk + +define Package/gluon-mesh-vpn-wireguard + TITLE:=Support for connecting meshes via wireguard + DEPENDS:=+gluon-core +libgluonutil +gluon-mesh-vpn-core +wireguard-tools +@GLUON_SPECIALIZE_KERNEL:KERNEL_TUN +wgpeerselector +libubus +endef + +$(eval $(call BuildPackageGluon,gluon-mesh-vpn-wireguard)) diff --git a/package/gluon-mesh-vpn-wireguard/check_site.lua b/package/gluon-mesh-vpn-wireguard/check_site.lua new file mode 100644 index 00000000..f5a0d94e --- /dev/null +++ b/package/gluon-mesh-vpn-wireguard/check_site.lua @@ -0,0 +1,9 @@ +local function check_peer(k) + need_alphanumeric_key(k) + + need_string_match(in_domain(extend(k, + {'public_key'})), "^" .. ("[%a%d+/]"):rep(42) .. "[AEIMQUYcgkosw480]=$") + need_string(in_domain(extend(k, {'endpoint'}))) +end + +need_table({'mesh_vpn', 'wireguard', 'peers'}, check_peer) diff --git a/package/gluon-mesh-vpn-wireguard/files/lib/gluon/mesh-vpn/provider/wireguard b/package/gluon-mesh-vpn-wireguard/files/lib/gluon/mesh-vpn/provider/wireguard new file mode 100644 index 00000000..e69de29b diff --git a/package/gluon-mesh-vpn-wireguard/files/lib/gluon/mesh-vpn/wireguard_pubkey.sh b/package/gluon-mesh-vpn-wireguard/files/lib/gluon/mesh-vpn/wireguard_pubkey.sh new file mode 100755 index 00000000..bbcba571 --- /dev/null +++ b/package/gluon-mesh-vpn-wireguard/files/lib/gluon/mesh-vpn/wireguard_pubkey.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +INCLUDE_ONLY=1 +. /lib/netifd/proto/wireguard.sh + +ensure_key_is_generated wg_mesh +uci get "network.wg_mesh.private_key" | /usr/bin/wg pubkey diff --git a/package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/400-mesh-vpn-wireguard b/package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/400-mesh-vpn-wireguard new file mode 100755 index 00000000..dfadc95b --- /dev/null +++ b/package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/400-mesh-vpn-wireguard @@ -0,0 +1,39 @@ +#!/usr/bin/lua + +local uci = require('simple-uci').cursor() +local site = require 'gluon.site' + +local private_key = uci:get("network", 'wg_mesh', "private_key") + +if not private_key or not private_key:match("^" .. ("[%a%d+/]"):rep(42) .. "[AEIMQUYcgkosw480]=$") then + private_key = "generate" +end + +uci:section('network', 'interface', 'wg_mesh', { + proto = 'wireguard', + fwmark = 1, + private_key = private_key, +}) + +uci:section('network', 'interface', 'mesh_wg_mesh', { + ifname = 'wg_mesh', + proto = 'gluon_wireguard' +}) + +-- Clean up previous configuration +uci:delete_all('wgpeerselector', 'peer', function(peer) + return peer.preserve ~= '1' +end) + +for name, peer in pairs(site.mesh_vpn.wireguard.peers()) do + uci:section("wgpeerselector", "peer", name, { + enabled = true, + endpoint = peer.endpoint, + public_key = peer.public_key, + allowed_ips = { "fe80::1/128" }, + ifname = 'wg_mesh', + }) +end + +uci:save("wgpeerselector") +uci:save("network") diff --git a/package/gluon-mesh-vpn-wireguard/luasrc/usr/lib/lua/gluon/mesh-vpn/provider/wireguard.lua b/package/gluon-mesh-vpn-wireguard/luasrc/usr/lib/lua/gluon/mesh-vpn/provider/wireguard.lua new file mode 100644 index 00000000..5065e217 --- /dev/null +++ b/package/gluon-mesh-vpn-wireguard/luasrc/usr/lib/lua/gluon/mesh-vpn/provider/wireguard.lua @@ -0,0 +1,37 @@ +local uci = require('simple-uci').cursor() + +local site = require 'gluon.site' +local util = require 'gluon.util' +local vpn_core = require 'gluon.mesh-vpn' + +local M = {} + +function M.public_key() + return util.trim(util.exec("/lib/gluon/mesh-vpn/wireguard_pubkey.sh")) +end + +function M.enable(val) + uci:set('network', 'wg_mesh', 'disabled', not val) + uci:save('network') +end + +function M.active() + return site.mesh_vpn.wireguard() ~= nil +end + +function M.set_limit(ingress_limit, egress_limit) + -- TODO: Test that limiting this via simple-tc here is correct! + uci:delete('simple-tc', 'mesh_vpn') + if ingress_limit ~= nil and egress_limit ~= nil then + uci:section('simple-tc', 'interface', 'mesh_vpn', { + ifname = vpn_core.get_interface(), + enabled = true, + limit_egress = egress_limit, + limit_ingress = ingress_limit, + }) + end + + uci:save('simple-tc') +end + +return M diff --git a/package/gluon-mesh-vpn-wireguard/src/Makefile b/package/gluon-mesh-vpn-wireguard/src/Makefile new file mode 100644 index 00000000..0b027848 --- /dev/null +++ b/package/gluon-mesh-vpn-wireguard/src/Makefile @@ -0,0 +1,6 @@ +all: respondd.so + +CFLAGS += -Wall -Werror-implicit-function-declaration + +respondd.so: respondd.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -fPIC -D_GNU_SOURCE -o $@ $^ $(LDLIBS) -lgluonutil -lubus diff --git a/package/gluon-mesh-vpn-wireguard/src/respondd.c b/package/gluon-mesh-vpn-wireguard/src/respondd.c new file mode 100644 index 00000000..59f6511c --- /dev/null +++ b/package/gluon-mesh-vpn-wireguard/src/respondd.c @@ -0,0 +1,231 @@ +/* + Copyright (c) 2020, Leonardo Mörlein + Copyright (c) 2016, Matthias Schiffer + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "libubus.h" + +static struct json_object * stdout_read(const char *cmd, const char *skip, bool oneword) { + FILE *f = popen(cmd, "r"); + if (!f) + return NULL; + + char *line = NULL; + size_t len = 0; + size_t skiplen = strlen(skip); + + ssize_t read_chars = getline(&line, &len, f); + + pclose(f); + + if (read_chars < 1) { + free(line); + return NULL; + } + + if (line[read_chars-1] == '\n') + line[read_chars-1] = '\0'; + + const char *content = line; + if (strncmp(content, skip, skiplen) == 0) + content += skiplen; + + if (oneword) { + for (int i = 0; i < len; i++) { + if (isspace(line[i])) { + line[i] = 0; + } + } + } + + struct json_object *ret = gluonutil_wrap_string(content); + free(line); + return ret; +} + +static struct json_object * get_wireguard_public_key(void) { + return stdout_read("exec /lib/gluon/mesh-vpn/wireguard_pubkey.sh", "", false); +} + +static struct json_object * get_wireguard_version(void) { + return stdout_read("exec wg -v", "wireguard-tools ", true); +} + +static bool wireguard_enabled(void) { + bool enabled = true; + + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) + goto disabled_nofree; + ctx->flags &= ~UCI_FLAG_STRICT; + + struct uci_package *p; + if (uci_load(ctx, "network", &p)) + goto disabled; + + struct uci_section *s = uci_lookup_section(ctx, p, "wg_mesh"); + if (!s) + goto disabled; + + const char *disabled_str = uci_lookup_option_string(ctx, s, "disabled"); + if (!disabled_str || !strcmp(disabled_str, "1")) + enabled = false; + +disabled: + uci_free_context(ctx); + +disabled_nofree: + return enabled; +} + +static bool get_pubkey_privacy(void) { + bool ret = true; + struct json_object *site = NULL; + + site = gluonutil_load_site_config(); + if (!site) + goto end; + + struct json_object *mesh_vpn; + if (!json_object_object_get_ex(site, "mesh_vpn", &mesh_vpn)) + goto end; + + struct json_object *pubkey_privacy; + if (!json_object_object_get_ex(mesh_vpn, "pubkey_privacy", &pubkey_privacy)) + goto end; + + ret = json_object_get_boolean(pubkey_privacy); + +end: + json_object_put(site); + + return ret; +} + +static struct json_object * get_wireguard(void) { + bool wg_enabled = wireguard_enabled(); + + struct json_object *ret = json_object_new_object(); + json_object_object_add(ret, "version", get_wireguard_version()); + json_object_object_add(ret, "enabled", json_object_new_boolean(wg_enabled)); + if (wg_enabled && !get_pubkey_privacy()) + json_object_object_add(ret, "public_key", get_wireguard_public_key()); + return ret; +} + +static struct json_object * respondd_provider_nodeinfo(void) { + struct json_object *ret = json_object_new_object(); + + struct json_object *software = json_object_new_object(); + json_object_object_add(software, "wireguard", get_wireguard()); + json_object_object_add(ret, "software", software); + + return ret; +} + +static json_object *blobmsg_attr2json(struct blob_attr *attr, int type) +{ + int len = blobmsg_data_len(attr); + struct blobmsg_data *data = blobmsg_data(attr); + struct blob_attr *inner_attr; + json_object *res = NULL; + switch(type) { + case BLOBMSG_TYPE_STRING: + return gluonutil_wrap_string(blobmsg_get_string(attr)); + case BLOBMSG_TYPE_BOOL: + return json_object_new_boolean(blobmsg_get_bool(attr)); + case BLOBMSG_TYPE_INT16: + return json_object_new_double(blobmsg_get_u16(attr)); + case BLOBMSG_TYPE_INT32: + return json_object_new_double(blobmsg_get_u32(attr)); + case BLOBMSG_TYPE_INT64: + return json_object_new_double(blobmsg_get_u64(attr)); + case BLOBMSG_TYPE_DOUBLE: + return json_object_new_double(blobmsg_get_double(attr)); + case BLOBMSG_TYPE_TABLE: + res = json_object_new_object(); + __blob_for_each_attr(inner_attr, data, len) { + json_object_object_add(res, blobmsg_name(inner_attr), blobmsg_attr2json(inner_attr, blobmsg_type(inner_attr))); + }; + break; + case BLOBMSG_TYPE_ARRAY: + res = json_object_new_array(); + __blob_for_each_attr(inner_attr, data, len) { + json_object_array_add(res, blobmsg_attr2json(inner_attr, blobmsg_type(inner_attr))); + } + break; + } + + return res; +} + +static void cb_wgpeerselector_vpn(struct ubus_request *req, int type, struct blob_attr *msg) +{ + json_object_object_add(req->priv, "mesh_vpn", blobmsg_attr2json(msg, type)); +} + +static struct json_object * respondd_provider_statistics(void) { + struct json_object *ret = json_object_new_object(); + struct ubus_context *ctx = ubus_connect(NULL); + uint32_t ubus_path_id; + + if (!ctx) { + fprintf(stderr, "Error in gluon-mesh-vpn-wireguard.so: Failed to connect to ubus.\n"); + goto err; + } + + if (ubus_lookup_id(ctx, "wgpeerselector.wg_mesh", &ubus_path_id)) { + goto err; + } + + ubus_invoke(ctx, ubus_path_id, "status", NULL, cb_wgpeerselector_vpn, ret, 1000); + +err: + if (ctx) + ubus_free(ctx); + return ret; +} + + +const struct respondd_provider_info respondd_providers[] = { + {"nodeinfo", respondd_provider_nodeinfo}, + {"statistics", respondd_provider_statistics}, + {} +}; diff --git a/patches/openwrt/0005-package-uci-backport-cli-add-option-for-changing-save-path.patch b/patches/openwrt/0005-package-uci-backport-cli-add-option-for-changing-save-path.patch new file mode 100644 index 00000000..a3f4f92a --- /dev/null +++ b/patches/openwrt/0005-package-uci-backport-cli-add-option-for-changing-save-path.patch @@ -0,0 +1,83 @@ +From: Leonardo Mörlein +Date: Sat, 16 Jan 2021 23:11:01 +0100 +Subject: package/uci: backport: "cli: add option for changing save path" + +This is a backport of + +https://git.openwrt.org/?p=project/uci.git;a=commit;h=4b3db1179747b6a6779029407984bacef851325c + +diff --git a/package/system/uci/Makefile b/package/system/uci/Makefile +index 75fc1bdfad0694aac99830b9b0cc87b42ea16e7d..924d5bb4824f567888e2ffd2954429af8f4fd504 100644 +--- a/package/system/uci/Makefile ++++ b/package/system/uci/Makefile +@@ -9,7 +9,7 @@ + include $(TOPDIR)/rules.mk + + PKG_NAME:=uci +-PKG_RELEASE:=5 ++PKG_RELEASE:=6 + + PKG_SOURCE_URL=$(PROJECT_GIT)/project/uci.git + PKG_SOURCE_PROTO:=git +diff --git a/package/system/uci/patches/0001-cli-add-option-for-changin-save-path.patch b/package/system/uci/patches/0001-cli-add-option-for-changin-save-path.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..377aec41fe6928aa26bccdde9fd77576d57ec4ed +--- /dev/null ++++ b/package/system/uci/patches/0001-cli-add-option-for-changin-save-path.patch +@@ -0,0 +1,56 @@ ++From: Rafał Miłecki ++Date: Mon, 12 Apr 2021 14:05:52 +0000 (+0200) ++Subject: cli: add option for changing save path ++X-Git-Url: http://git.openwrt.org/?p=project%2Fuci.git;a=commitdiff_plain;h=4b3db1179747b6a6779029407984bacef851325c;hp=52bbc99f69ea6f67b6fe264f424dac91bde5016c ++ ++cli: add option for changing save path ++ ++Save path is a directory where config change (delta) files are stored. ++Having a custom individual save dir can be used to prevent two (or more) ++"uci" cli callers (e.g. bash scripts) from commiting each other changes. ++ ++In the following example: ++ ++App0 App1 ++---- ---- ++uci set system.@system[0].timezone=UTC ++ uci set system.@system[0].hostname=OpenWrt ++ uci commit system ++ ++App1 would unintentionally commit changes made by App0. This can be ++avoided by at least 1 "uci" cli user specifying a custom -t option. ++ ++Signed-off-by: Rafał Miłecki ++--- ++ ++diff --git a/cli.c b/cli.c ++index 267437d..2fce39d 100644 ++--- a/cli.c +++++ b/cli.c ++@@ -167,6 +167,7 @@ static void uci_usage(void) ++ "\t-N don't name unnamed sections\n" ++ "\t-p add a search path for config change files\n" ++ "\t-P add a search path for config change files and use as default\n" +++ "\t-t set save path for config change files\n" ++ "\t-q quiet mode (don't print error messages)\n" ++ "\t-s force strict mode (stop on parser errors, default)\n" ++ "\t-S disable strict mode\n" ++@@ -706,7 +707,7 @@ int main(int argc, char **argv) ++ return 1; ++ } ++ ++- while((c = getopt(argc, argv, "c:d:f:LmnNp:P:sSqX")) != -1) { +++ while((c = getopt(argc, argv, "c:d:f:LmnNp:P:qsSt:X")) != -1) { ++ switch(c) { ++ case 'c': ++ uci_set_confdir(ctx, optarg); ++@@ -754,6 +755,9 @@ int main(int argc, char **argv) ++ case 'q': ++ flags |= CLI_FLAG_QUIET; ++ break; +++ case 't': +++ uci_set_savedir(ctx, optarg); +++ break; ++ case 'X': ++ flags &= ~CLI_FLAG_SHOW_EXT; ++ break; diff --git a/patches/openwrt/0006-wireguard-tools-allow-generating-private_key-v3.patch b/patches/openwrt/0006-wireguard-tools-allow-generating-private_key-v3.patch new file mode 100644 index 00000000..9d801d02 --- /dev/null +++ b/patches/openwrt/0006-wireguard-tools-allow-generating-private_key-v3.patch @@ -0,0 +1,53 @@ +From: lemoer +Date: Sat, 3 Jul 2021 22:50:29 +0200 +Subject: wireguard-tools: allow generating private_key (v3) + +When the uci configuration is created automatically during a very early +stage, where no entropy daemon is set up, generating the key directly is +not an option. Therefore we allow to set the private_key to "generate" +and generate the private key directly before the interface is taken up. + +v3: Somebody has implemented another uci cli flag '-t' upstream to handle + this, before my patch to implement the new uci flag '-x' syntax was + accepted. So I dropped my suggestion of '-x'. +v2: We now use a new uci cli flag to commit only the private_key and do + not commit uncommited user changes. This is not yet upstream as of + now. + +diff --git a/package/network/utils/wireguard-tools/files/wireguard.sh b/package/network/utils/wireguard-tools/files/wireguard.sh +index 63261aea71daa058bf37014ba7d670a5e74a2e04..845f9eb902bf3655b631d52aa3ee69231366f657 100644 +--- a/package/network/utils/wireguard-tools/files/wireguard.sh ++++ b/package/network/utils/wireguard-tools/files/wireguard.sh +@@ -95,6 +95,23 @@ proto_wireguard_setup_peer() { + fi + } + ++ensure_key_is_generated() { ++ local private_key ++ private_key="$(uci get network."$1".private_key)" ++ ++ if [ "$private_key" == "generate" ]; then ++ local ucitmp ++ oldmask="$(umask)" ++ umask 077 ++ ucitmp="$(mktemp -d)" ++ private_key="$("${WG}" genkey)" ++ uci -q -t "$ucitmp" set network."$1".private_key="$private_key" && \ ++ uci -q -t "$ucitmp" commit network ++ rm -rf "$ucitmp" ++ umask "$oldmask" ++ fi ++} ++ + proto_wireguard_setup() { + local config="$1" + local wg_dir="/tmp/wireguard" +@@ -104,6 +121,8 @@ proto_wireguard_setup() { + local listen_port + local mtu + ++ ensure_key_is_generated "${config}" ++ + config_load network + config_get private_key "${config}" "private_key" + config_get listen_port "${config}" "listen_port"