Initial commit

This commit is contained in:
Iurii Egorov
2023-11-30 11:04:56 +03:00
commit 932f0d2134
14 changed files with 2545 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea/

59
amnezia-wg-tools/Makefile Normal file
View File

@@ -0,0 +1,59 @@
#
# Copyright (C) 2016-2019 Jason A. Donenfeld <Jason@zx2c4.com>
# Copyright (C) 2016 Baptiste Jonglez <openwrt@bitsofnetworks.org>
# Copyright (C) 2016-2017 Dan Luedtke <mail@danrl.com>
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
include $(TOPDIR)/rules.mk
PKG_NAME:=amnezia-wg-tools
PKG_VERSION:=0.0.1-kmod
PKG_RELEASE:=$(AUTORELEASE)
PKG_SOURCE:=v$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/amnezia-vpn/amnezia-wg-tools/archive/refs/tags/
PKG_HASH:=0d8cdf6d767700cb6d2cc895d45b1f94668663b00138627329766bb9e36d894e
PKG_LICENSE:=GPL-2.0
PKG_LICENSE_FILES:=COPYING
include $(INCLUDE_DIR)/package.mk
MAKE_PATH:=src
MAKE_VARS += PLATFORM=linux
define Package/amnezia-wg-tools
CATEGORY:=Network
URL:=https://www.wireguard.com
MAINTAINER:=Jason A. Donenfeld <Jason@zx2c4.com>
TITLE:=WireGuard userspace control program (wg)
DEPENDS:= \
+@BUSYBOX_CONFIG_IP \
+@BUSYBOX_CONFIG_FEATURE_IP_LINK
endef
define Package/amnezia-wg-tools/description
WireGuard is a novel VPN that runs inside the Linux Kernel and utilizes
state-of-the-art cryptography. It aims to be faster, simpler, leaner, and
more useful than IPSec, while avoiding the massive headache. It intends to
be considerably more performant than OpenVPN. WireGuard is designed as a
general purpose VPN for running on embedded interfaces and super computers
alike, fit for many different circumstances. It uses UDP.
This package provides the userspace control program for WireGuard,
`wg(8)`, a netifd protocol helper, and a re-resolve watchdog script.
endef
define Package/amnezia-wg-tools/install
$(INSTALL_DIR) $(1)/usr/bin/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/src/wg $(1)/usr/bin/amneziawg
$(INSTALL_BIN) ./files/amneziawg_watchdog $(1)/usr/bin/
$(INSTALL_DIR) $(1)/lib/netifd/proto/
$(INSTALL_BIN) ./files/amneziawg.sh $(1)/lib/netifd/proto/
endef
$(eval $(call BuildPackage,amnezia-wg-tools))

View File

@@ -0,0 +1,308 @@
#!/bin/sh
# Copyright 2016-2017 Dan Luedtke <mail@danrl.com>
# Licensed to the public under the Apache License 2.0.
WG=/usr/bin/amneziawg
if [ ! -x $WG ]; then
logger -t "amnezia-wg" "error: missing amnezia-wg-tools (${WG})"
exit 0
fi
[ -n "$INCLUDE_ONLY" ] || {
. /lib/functions.sh
. ../netifd-proto.sh
init_proto "$@"
}
proto_amneziawg_init_config() {
proto_config_add_string "private_key"
proto_config_add_int "listen_port"
proto_config_add_int "mtu"
proto_config_add_string "fwmark"
proto_config_add_int "awg_jc"
proto_config_add_int "awg_jmin"
proto_config_add_int "awg_jmax"
proto_config_add_int "awg_s1"
proto_config_add_int "awg_s2"
proto_config_add_int "awg_h1"
proto_config_add_int "awg_h2"
proto_config_add_int "awg_h3"
proto_config_add_int "awg_h4"
available=1
no_proto_task=1
}
proto_amneziawg_is_kernel_mode() {
if [ ! -e /sys/module/amneziawg ]; then
modprobe amneziawg > /dev/null 2&>1 || true
if [ -e /sys/module/amneziawg ]; then
return 0
else
if [ ! command -v "${WG_QUICK_USERSPACE_IMPLEMENTATION:-amneziawg-go}" >/dev/null ]; then
ret=$?
echo "Please install either kernel module (kmod-amneziawg package) or user-space implementation in /usr/bin/amneziawg-go."
exit $?
else
return 1
fi
fi
else
return 0
fi
}
proto_amneziawg_setup_peer() {
local peer_config="$1"
local disabled
local public_key
local preshared_key
local allowed_ips
local route_allowed_ips
local endpoint_host
local endpoint_port
local persistent_keepalive
config_get_bool disabled "${peer_config}" "disabled" 0
config_get public_key "${peer_config}" "public_key"
config_get preshared_key "${peer_config}" "preshared_key"
config_get allowed_ips "${peer_config}" "allowed_ips"
config_get_bool route_allowed_ips "${peer_config}" "route_allowed_ips" 0
config_get endpoint_host "${peer_config}" "endpoint_host"
config_get endpoint_port "${peer_config}" "endpoint_port"
config_get persistent_keepalive "${peer_config}" "persistent_keepalive"
if [ "${disabled}" -eq 1 ]; then
# skip disabled peers
return 0
fi
if [ -z "$public_key" ]; then
echo "Skipping peer config $peer_config because public key is not defined."
return 0
fi
echo "[Peer]" >> "${wg_cfg}"
echo "PublicKey=${public_key}" >> "${wg_cfg}"
if [ "${preshared_key}" ]; then
echo "PresharedKey=${preshared_key}" >> "${wg_cfg}"
fi
for allowed_ip in $allowed_ips; do
echo "AllowedIPs=${allowed_ip}" >> "${wg_cfg}"
done
if [ "${endpoint_host}" ]; then
case "${endpoint_host}" in
*:*)
endpoint="[${endpoint_host}]"
;;
*)
endpoint="${endpoint_host}"
;;
esac
if [ "${endpoint_port}" ]; then
endpoint="${endpoint}:${endpoint_port}"
else
endpoint="${endpoint}:51820"
fi
echo "Endpoint=${endpoint}" >> "${wg_cfg}"
fi
if [ "${persistent_keepalive}" ]; then
echo "PersistentKeepalive=${persistent_keepalive}" >> "${wg_cfg}"
fi
if [ ${route_allowed_ips} -ne 0 ]; then
for allowed_ip in ${allowed_ips}; do
case "${allowed_ip}" in
*:*/*)
proto_add_ipv6_route "${allowed_ip%%/*}" "${allowed_ip##*/}"
;;
*.*/*)
proto_add_ipv4_route "${allowed_ip%%/*}" "${allowed_ip##*/}"
;;
*:*)
proto_add_ipv6_route "${allowed_ip%%/*}" "128"
;;
*.*)
proto_add_ipv4_route "${allowed_ip%%/*}" "32"
;;
esac
done
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_amneziawg_setup() {
local config="$1"
local wg_dir="/tmp/wireguard"
local wg_cfg="${wg_dir}/${config}"
local private_key
local listen_port
local mtu
# Amnezia WG specific parameters
local awg_jc
local awg_jmin
local awg_jmax
local awg_s1
local awg_s2
local awg_h1
local awg_h2
local awg_h3
local awg_h4
ensure_key_is_generated "${config}"
config_load network
config_get private_key "${config}" "private_key"
config_get listen_port "${config}" "listen_port"
config_get addresses "${config}" "addresses"
config_get mtu "${config}" "mtu"
config_get fwmark "${config}" "fwmark"
config_get ip6prefix "${config}" "ip6prefix"
config_get nohostroute "${config}" "nohostroute"
config_get tunlink "${config}" "tunlink"
config_get awg_jc "${config}" "awg_jc"
config_get awg_jmin "${config}" "awg_jmin"
config_get awg_jmax "${config}" "awg_jmax"
config_get awg_s1 "${config}" "awg_s1"
config_get awg_s2 "${config}" "awg_s2"
config_get awg_h1 "${config}" "awg_h1"
config_get awg_h2 "${config}" "awg_h2"
config_get awg_h3 "${config}" "awg_h3"
config_get awg_h4 "${config}" "awg_h4"
ip link del dev "${config}" 2>/dev/null
if proto_amneziawg_is_kernel_mode; then
logger -t "amneziawg" "info: using kernel-space kmod-amneziawg for ${WG}"
ip link add dev "${config}" type amneziawg
else
logger -t "amneziawg" "info: using user-space amneziawg-go for ${WG}"
amneziawg-go "${config}"
fi
if [ "${mtu}" ]; then
ip link set mtu "${mtu}" dev "${config}"
fi
proto_init_update "${config}" 1
umask 077
mkdir -p "${wg_dir}"
echo "[Interface]" > "${wg_cfg}"
echo "PrivateKey=${private_key}" >> "${wg_cfg}"
if [ "${listen_port}" ]; then
echo "ListenPort=${listen_port}" >> "${wg_cfg}"
fi
if [ "${fwmark}" ]; then
echo "FwMark=${fwmark}" >> "${wg_cfg}"
fi
# AWG
if [ "${awg_jc}" ]; then
echo "Jc = ${awg_jc}" >> "${wg_cfg}"
fi
if [ "${awg_jmin}" ]; then
echo "Jmin = ${awg_jmin}" >> "${wg_cfg}"
fi
if [ "${awg_jmax}" ]; then
echo "Jmax = ${awg_jmax}" >> "${wg_cfg}"
fi
if [ "${awg_s1}" ]; then
echo "S1 = ${awg_s1}" >> "${wg_cfg}"
fi
if [ "${awg_s2}" ]; then
echo "S2 = ${awg_s2}" >> "${wg_cfg}"
fi
if [ "${awg_h1}" ]; then
echo "H1 = ${awg_h1}" >> "${wg_cfg}"
fi
if [ "${awg_h2}" ]; then
echo "H2 = ${awg_h2}" >> "${wg_cfg}"
fi
if [ "${awg_h3}" ]; then
echo "H3 = ${awg_h3}" >> "${wg_cfg}"
fi
if [ "${awg_h4}" ]; then
echo "H4 = ${awg_h4}" >> "${wg_cfg}"
fi
config_foreach proto_amneziawg_setup_peer "wireguard_${config}"
# apply configuration file
${WG} setconf ${config} "${wg_cfg}"
WG_RETURN=$?
rm -f "${wg_cfg}"
if [ ${WG_RETURN} -ne 0 ]; then
sleep 5
proto_setup_failed "${config}"
exit 1
fi
for address in ${addresses}; do
case "${address}" in
*:*/*)
proto_add_ipv6_address "${address%%/*}" "${address##*/}"
;;
*.*/*)
proto_add_ipv4_address "${address%%/*}" "${address##*/}"
;;
*:*)
proto_add_ipv6_address "${address%%/*}" "128"
;;
*.*)
proto_add_ipv4_address "${address%%/*}" "32"
;;
esac
done
for prefix in ${ip6prefix}; do
proto_add_ipv6_prefix "$prefix"
done
# endpoint dependency
if [ "${nohostroute}" != "1" ]; then
${WG} show "${config}" endpoints | \
sed -E 's/\[?([0-9.:a-f]+)\]?:([0-9]+)/\1 \2/' | \
while IFS=$'\t ' read -r key address port; do
[ -n "${port}" ] || continue
proto_add_host_dependency "${config}" "${address}" "${tunlink}"
done
fi
proto_send_update "${config}"
}
proto_amneziawg_teardown() {
local config="$1"
proto_amneziawg_check_installed
if proto_amneziawg_is_kernel_mode; then
ip link del dev "${config}" >/dev/null 2>&1
else
rm -f /var/run/wireguard/${config}.sock
fi
}
[ -n "$INCLUDE_ONLY" ] || {
add_protocol amneziawg
}

View File

@@ -0,0 +1,68 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (C) 2018 Aleksandr V. Piskunov <aleksandr.v.piskunov@gmail.com>.
# Copyright (C) 2015-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
#
# This watchdog script tries to re-resolve hostnames for inactive WireGuard peers.
# Use it for peers with a frequently changing dynamic IP.
# persistent_keepalive must be set, recommended value is 25 seconds.
#
# Run this script from cron every minute:
# echo '* * * * * /usr/bin/wireguard_watchdog' >> /etc/crontabs/root
. /lib/functions.sh
check_peer_activity() {
local cfg=$1
local iface=$2
local disabled
local public_key
local endpoint_host
local endpoint_port
local persistent_keepalive
local last_handshake
local idle_seconds
config_get_bool disabled "${cfg}" "disabled" 0
config_get public_key "${cfg}" "public_key"
config_get endpoint_host "${cfg}" "endpoint_host"
config_get endpoint_port "${cfg}" "endpoint_port"
if [ "${disabled}" -eq 1 ]; then
# skip disabled peers
return 0
fi
persistent_keepalive=$(wg show ${iface} persistent-keepalive | grep ${public_key} | awk '{print $2}')
# only process peers with endpoints and keepalive set
[ -z ${endpoint_host} ] && return 0;
[ -z ${persistent_keepalive} -o ${persistent_keepalive} = "off" ] && return 0;
# skip IP addresses
# check taken from packages/net/ddns-scripts/files/dynamic_dns_functions.sh
local IPV4_REGEX="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}"
local IPV6_REGEX="\(\([0-9A-Fa-f]\{1,4\}:\)\{1,\}\)\(\([0-9A-Fa-f]\{1,4\}\)\{0,1\}\)\(\(:[0-9A-Fa-f]\{1,4\}\)\{1,\}\)"
local IPV4=$(echo ${endpoint_host} | grep -m 1 -o "$IPV4_REGEX$") # do not detect ip in 0.0.0.0.example.com
local IPV6=$(echo ${endpoint_host} | grep -m 1 -o "$IPV6_REGEX")
[ -n "${IPV4}" -o -n "${IPV6}" ] && return 0;
# re-resolve endpoint hostname if not responding for too long
last_handshake=$(wg show ${iface} latest-handshakes | grep ${public_key} | awk '{print $2}')
[ -z ${last_handshake} ] && return 0;
idle_seconds=$(($(date +%s)-${last_handshake}))
[ ${idle_seconds} -lt 150 ] && return 0;
logger -t "wireguard_monitor" "${iface} endpoint ${endpoint_host}:${endpoint_port} is not responding for ${idle_seconds} seconds, trying to re-resolve hostname"
wg set ${iface} peer ${public_key} endpoint "${endpoint_host}:${endpoint_port}"
}
# query ubus for all active wireguard interfaces
wg_ifaces=$(ubus -S call network.interface dump | jsonfilter -e '@.interface[@.up=true]' | jsonfilter -a -e '@[@.proto="wireguard"].interface' | tr "\n" " ")
# check every peer in every active wireguard interface
config_load network
for iface in $wg_ifaces; do
config_foreach check_peer_activity "wireguard_${iface}" "${iface}"
done

40
kmod-amneziawg/Makefile Normal file
View File

@@ -0,0 +1,40 @@
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=kmod-amneziawg
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define KernelPackage/amneziawg
SECTION:=kernel
CATEGORY:=Kernel modules
SUBMENU:=Network Support
TITLE:=AmneziaWG VPN Kernel Module
FILES:=$(PKG_BUILD_DIR)/amneziawg.ko
DEPENDS:= \
+kmod-udptunnel4 \
+kmod-udptunnel6 \
+kmod-crypto-lib-chacha20poly1305 \
+kmod-crypto-lib-curve25519
endef
define Build/Prepare
cp -fr $(LINUX_DIR)/drivers/net/wireguard/{*.c,*.h,selftest/} $(PKG_BUILD_DIR)
mkdir -p $(PKG_BUILD_DIR)/uapi
cp -f $(LINUX_DIR)/include/uapi/linux/wireguard.h $(PKG_BUILD_DIR)/uapi/
patch -d $(PKG_BUILD_DIR)/ < files/amnezia-sources.patch
patch -d $(PKG_BUILD_DIR)/uapi/ < files/amnezia-uapi.patch
cp -f src/Makefile $(PKG_BUILD_DIR)
endef
define Build/Compile
$(MAKE) -C "$(LINUX_DIR)" \
$(KERNEL_MAKE_FLAGS) \
M="$(PKG_BUILD_DIR)" \
EXTRA_CFLAGS="$(BUILDFLAGS)" \
modules
endef
$(eval $(call KernelPackage,amneziawg))

View File

@@ -0,0 +1,757 @@
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/cookie.c ./cookie.c
--- ../../linux-source-6.2.0/drivers/net/wireguard/cookie.c 2023-11-10 18:10:29
+++ ./cookie.c 2023-11-23 18:59:07
@@ -179,13 +179,13 @@
void wg_cookie_message_create(struct message_handshake_cookie *dst,
struct sk_buff *skb, __le32 index,
- struct cookie_checker *checker)
+ struct cookie_checker *checker, u32 message_type)
{
struct message_macs *macs = (struct message_macs *)
((u8 *)skb->data + skb->len - sizeof(*macs));
u8 cookie[COOKIE_LEN];
- dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE);
+ dst->header.type = cpu_to_le32(message_type);
dst->receiver_index = index;
get_random_bytes_wait(dst->nonce, COOKIE_NONCE_LEN);
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/cookie.h ./cookie.h
--- ../../linux-source-6.2.0/drivers/net/wireguard/cookie.h 2023-11-10 18:10:29
+++ ./cookie.h 2023-11-23 13:11:40
@@ -52,7 +52,7 @@
void wg_cookie_message_create(struct message_handshake_cookie *src,
struct sk_buff *skb, __le32 index,
- struct cookie_checker *checker);
+ struct cookie_checker *checker, u32 message_type);
void wg_cookie_message_consume(struct message_handshake_cookie *src,
struct wg_device *wg);
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/device.c ./device.c
--- ../../linux-source-6.2.0/drivers/net/wireguard/device.c 2023-11-10 18:10:29
+++ ./device.c 2023-11-26 17:42:17
@@ -379,6 +379,11 @@
*/
dev->priv_destructor = wg_destruct;
+ wg->advanced_security_config.init_packet_magic_header = MESSAGE_HANDSHAKE_INITIATION;
+ wg->advanced_security_config.response_packet_magic_header = MESSAGE_HANDSHAKE_RESPONSE;
+ wg->advanced_security_config.cookie_packet_magic_header = MESSAGE_HANDSHAKE_COOKIE;
+ wg->advanced_security_config.transport_packet_magic_header = MESSAGE_DATA;
+
pr_debug("%s: Interface created\n", dev->name);
return ret;
@@ -475,4 +480,79 @@
unregister_random_vmfork_notifier(&vm_notifier);
unregister_pm_notifier(&pm_notifier);
rcu_barrier();
+}
+
+void wg_device_handle_post_config(struct net_device *dev, struct amnezia_config *asc)
+{
+ struct wg_device *wg = netdev_priv(dev);
+ bool a_sec_on = false;
+
+ if (!asc->advanced_security_enabled)
+ return;
+
+ if (asc->junk_packet_count < 0) {
+ // TODO error
+ }
+
+ wg->advanced_security_config.junk_packet_count = asc->junk_packet_count;
+ if (asc->junk_packet_count != 0)
+ a_sec_on = true;
+
+ wg->advanced_security_config.junk_packet_min_size = asc->junk_packet_min_size;
+ if (asc->junk_packet_min_size != 0)
+ a_sec_on = true;
+
+ if (asc->junk_packet_count > 0 && asc->junk_packet_min_size == asc->junk_packet_max_size)
+ asc->junk_packet_max_size++;
+
+ if (asc->junk_packet_max_size >= MESSAGE_MAX_SIZE) {
+ wg->advanced_security_config.junk_packet_min_size = 0;
+ wg->advanced_security_config.junk_packet_max_size = 1;
+
+ // TODO error
+ } else if (asc->junk_packet_max_size < asc->junk_packet_min_size) {
+ // TODO error
+ } else
+ wg->advanced_security_config.junk_packet_max_size = asc->junk_packet_max_size;
+
+ if (asc->junk_packet_max_size != 0)
+ a_sec_on = true;
+
+ if (asc->init_packet_junk_size + MESSAGE_INITIATION_SIZE >= MESSAGE_MAX_SIZE) {
+ // TODO error
+ } else
+ wg->advanced_security_config.init_packet_junk_size = asc->init_packet_junk_size;
+
+ if (asc->init_packet_junk_size != 0)
+ a_sec_on = true;
+
+ if (asc->response_packet_junk_size + MESSAGE_RESPONSE_SIZE >= MESSAGE_MAX_SIZE) {
+ // TODO error
+ } else
+ wg->advanced_security_config.response_packet_junk_size = asc->response_packet_junk_size;
+
+ if (asc->response_packet_junk_size != 0)
+ a_sec_on = true;
+
+ if (asc->init_packet_magic_header > MESSAGE_DATA) {
+ a_sec_on = true;
+ wg->advanced_security_config.init_packet_magic_header = asc->init_packet_magic_header;
+ }
+
+ if (asc->response_packet_magic_header > MESSAGE_DATA) {
+ a_sec_on = true;
+ wg->advanced_security_config.response_packet_magic_header = asc->response_packet_magic_header;
+ }
+
+ if (asc->cookie_packet_magic_header > MESSAGE_DATA) {
+ a_sec_on = true;
+ wg->advanced_security_config.cookie_packet_magic_header = asc->cookie_packet_magic_header;
+ }
+
+ if (asc->transport_packet_magic_header > MESSAGE_DATA) {
+ a_sec_on = true;
+ wg->advanced_security_config.transport_packet_magic_header = asc->transport_packet_magic_header;
+ }
+
+ wg->advanced_security_config.advanced_security_enabled = a_sec_on;
}
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/device.h ./device.h
--- ../../linux-source-6.2.0/drivers/net/wireguard/device.h 2023-11-10 18:10:29
+++ ./device.h 2023-11-23 18:48:52
@@ -37,6 +37,19 @@
atomic_t count;
};
+struct amnezia_config {
+ bool advanced_security_enabled;
+ u16 junk_packet_count;
+ u16 junk_packet_min_size;
+ u16 junk_packet_max_size;
+ u16 init_packet_junk_size;
+ u16 response_packet_junk_size;
+ u32 init_packet_magic_header;
+ u32 response_packet_magic_header;
+ u32 cookie_packet_magic_header;
+ u32 transport_packet_magic_header;
+};
+
struct wg_device {
struct net_device *dev;
struct crypt_queue encrypt_queue, decrypt_queue, handshake_queue;
@@ -50,6 +63,7 @@
struct allowedips peer_allowedips;
struct mutex device_update_lock, socket_update_lock;
struct list_head device_list, peer_list;
+ struct amnezia_config advanced_security_config;
atomic_t handshake_queue_len;
unsigned int num_peers, device_update_gen;
u32 fwmark;
@@ -58,5 +72,6 @@
int wg_device_init(void);
void wg_device_uninit(void);
+void wg_device_handle_post_config(struct net_device *dev, struct amnezia_config *asc);
#endif /* _WG_DEVICE_H */
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/main.c ./main.c
--- ../../linux-source-6.2.0/drivers/net/wireguard/main.c 2023-11-10 18:10:29
+++ ./main.c 2023-11-22 16:37:56
@@ -3,14 +3,13 @@
* Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
-#include "version.h"
#include "device.h"
#include "noise.h"
#include "queueing.h"
#include "ratelimiter.h"
#include "netlink.h"
-#include <uapi/linux/wireguard.h>
+#include "uapi/wireguard.h"
#include <linux/init.h>
#include <linux/module.h>
@@ -45,7 +44,7 @@
if (ret < 0)
goto err_netlink;
- pr_info("WireGuard " WIREGUARD_VERSION " loaded. See www.wireguard.com for information.\n");
+ pr_info("WireGuard " WIREGUARD_VERSION " (Amnezia VPN) loaded. See www.wireguard.com for information.\n");
pr_info("Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.\n");
return 0;
@@ -71,7 +70,7 @@
module_init(wg_mod_init);
module_exit(wg_mod_exit);
MODULE_LICENSE("GPL v2");
-MODULE_DESCRIPTION("WireGuard secure network tunnel");
+MODULE_DESCRIPTION("WireGuard (Amnezia VPN) secure network tunnel");
MODULE_AUTHOR("Jason A. Donenfeld <Jason@zx2c4.com>");
MODULE_VERSION(WIREGUARD_VERSION);
MODULE_ALIAS_RTNL_LINK(KBUILD_MODNAME);
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/messages.h ./messages.h
--- ../../linux-source-6.2.0/drivers/net/wireguard/messages.h 2023-11-10 18:10:29
+++ ./messages.h 2023-11-22 19:16:03
@@ -117,6 +117,14 @@
MESSAGE_MINIMUM_LENGTH = message_data_len(0)
};
+enum message_size {
+ MESSAGE_INITIATION_SIZE = sizeof(struct message_handshake_initiation),
+ MESSAGE_RESPONSE_SIZE = sizeof(struct message_handshake_response),
+ MESSAGE_COOKIE_REPLY_SIZE = sizeof(struct message_handshake_cookie),
+ MESSAGE_TRANSPORT_SIZE = sizeof(struct message_data),
+ MESSAGE_MAX_SIZE = 65535
+};
+
#define SKB_HEADER_LEN \
(max(sizeof(struct iphdr), sizeof(struct ipv6hdr)) + \
sizeof(struct udphdr) + NET_SKB_PAD)
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/netlink.c ./netlink.c
--- ../../linux-source-6.2.0/drivers/net/wireguard/netlink.c 2023-11-10 18:10:29
+++ ./netlink.c 2023-11-26 17:34:30
@@ -10,7 +10,7 @@
#include "queueing.h"
#include "messages.h"
-#include <uapi/linux/wireguard.h>
+#include "uapi/wireguard.h"
#include <linux/if.h>
#include <net/genetlink.h>
@@ -27,7 +27,16 @@
[WGDEVICE_A_FLAGS] = { .type = NLA_U32 },
[WGDEVICE_A_LISTEN_PORT] = { .type = NLA_U16 },
[WGDEVICE_A_FWMARK] = { .type = NLA_U32 },
- [WGDEVICE_A_PEERS] = { .type = NLA_NESTED }
+ [WGDEVICE_A_PEERS] = { .type = NLA_NESTED },
+ [WGDEVICE_A_JC] = { .type = NLA_U16 },
+ [WGDEVICE_A_JMIN] = { .type = NLA_U16 },
+ [WGDEVICE_A_JMAX] = { .type = NLA_U16 },
+ [WGDEVICE_A_S1] = { .type = NLA_U16 },
+ [WGDEVICE_A_S2] = { .type = NLA_U16 },
+ [WGDEVICE_A_H1] = { .type = NLA_U32 },
+ [WGDEVICE_A_H2] = { .type = NLA_U32 },
+ [WGDEVICE_A_H3] = { .type = NLA_U32 },
+ [WGDEVICE_A_H4] = { .type = NLA_U32 }
};
static const struct nla_policy peer_policy[WGPEER_A_MAX + 1] = {
@@ -233,7 +242,25 @@
wg->incoming_port) ||
nla_put_u32(skb, WGDEVICE_A_FWMARK, wg->fwmark) ||
nla_put_u32(skb, WGDEVICE_A_IFINDEX, wg->dev->ifindex) ||
- nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name))
+ nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name) ||
+ nla_put_u16(skb, WGDEVICE_A_JC,
+ wg->advanced_security_config.junk_packet_count) ||
+ nla_put_u16(skb, WGDEVICE_A_JMIN,
+ wg->advanced_security_config.junk_packet_min_size) ||
+ nla_put_u16(skb, WGDEVICE_A_JMAX,
+ wg->advanced_security_config.junk_packet_max_size) ||
+ nla_put_u16(skb, WGDEVICE_A_S1,
+ wg->advanced_security_config.init_packet_junk_size) ||
+ nla_put_u16(skb, WGDEVICE_A_S2,
+ wg->advanced_security_config.response_packet_junk_size) ||
+ nla_put_u32(skb, WGDEVICE_A_H1,
+ wg->advanced_security_config.init_packet_magic_header) ||
+ nla_put_u32(skb, WGDEVICE_A_H2,
+ wg->advanced_security_config.response_packet_magic_header) ||
+ nla_put_u32(skb, WGDEVICE_A_H3,
+ wg->advanced_security_config.cookie_packet_magic_header) ||
+ nla_put_u32(skb, WGDEVICE_A_H4,
+ wg->advanced_security_config.transport_packet_magic_header))
goto out;
down_read(&wg->static_identity.lock);
@@ -493,6 +520,7 @@
static int wg_set_device(struct sk_buff *skb, struct genl_info *info)
{
struct wg_device *wg = lookup_interface(info->attrs, skb);
+ struct amnezia_config *asc = kzalloc(sizeof(*asc), GFP_KERNEL);
u32 flags = 0;
int ret;
@@ -537,6 +565,51 @@
goto out;
}
+ if (info->attrs[WGDEVICE_A_JC]) {
+ asc->advanced_security_enabled = true;
+ asc->junk_packet_count = nla_get_u16(info->attrs[WGDEVICE_A_JC]);
+ }
+
+ if (info->attrs[WGDEVICE_A_JMIN]) {
+ asc->advanced_security_enabled = true;
+ asc->junk_packet_min_size = nla_get_u16(info->attrs[WGDEVICE_A_JMIN]);
+ }
+
+ if (info->attrs[WGDEVICE_A_JMAX]) {
+ asc->advanced_security_enabled = true;
+ asc->junk_packet_max_size = nla_get_u16(info->attrs[WGDEVICE_A_JMAX]);
+ }
+
+ if (info->attrs[WGDEVICE_A_S1]) {
+ asc->advanced_security_enabled = true;
+ asc->init_packet_junk_size = nla_get_u16(info->attrs[WGDEVICE_A_S1]);
+ }
+
+ if (info->attrs[WGDEVICE_A_S2]) {
+ asc->advanced_security_enabled = true;
+ asc->response_packet_junk_size = nla_get_u16(info->attrs[WGDEVICE_A_S2]);
+ }
+
+ if (info->attrs[WGDEVICE_A_H1]) {
+ asc->advanced_security_enabled = true;
+ asc->init_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H1]);
+ }
+
+ if (info->attrs[WGDEVICE_A_H2]) {
+ asc->advanced_security_enabled = true;
+ asc->response_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H2]);
+ }
+
+ if (info->attrs[WGDEVICE_A_H3]) {
+ asc->advanced_security_enabled = true;
+ asc->cookie_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H3]);
+ }
+
+ if (info->attrs[WGDEVICE_A_H4]) {
+ asc->advanced_security_enabled = true;
+ asc->transport_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H4]);
+ }
+
if (flags & WGDEVICE_F_REPLACE_PEERS)
wg_peer_remove_all(wg);
@@ -597,10 +670,12 @@
ret = 0;
out:
+ wg_device_handle_post_config(wg->dev, asc);
mutex_unlock(&wg->device_update_lock);
rtnl_unlock();
dev_put(wg->dev);
out_nodev:
+ kfree(asc);
if (info->attrs[WGDEVICE_A_PRIVATE_KEY])
memzero_explicit(nla_data(info->attrs[WGDEVICE_A_PRIVATE_KEY]),
nla_len(info->attrs[WGDEVICE_A_PRIVATE_KEY]));
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/noise.c ./noise.c
--- ../../linux-source-6.2.0/drivers/net/wireguard/noise.c 2023-11-10 18:10:29
+++ ./noise.c 2023-11-23 18:58:26
@@ -515,7 +515,7 @@
bool
wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst,
- struct noise_handshake *handshake)
+ struct noise_handshake *handshake, u32 message_type)
{
u8 timestamp[NOISE_TIMESTAMP_LEN];
u8 key[NOISE_SYMMETRIC_KEY_LEN];
@@ -532,7 +532,7 @@
if (unlikely(!handshake->static_identity->has_identity))
goto out;
- dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION);
+ dst->header.type = cpu_to_le32(message_type);
handshake_init(handshake->chaining_key, handshake->hash,
handshake->remote_static);
@@ -665,7 +665,7 @@
}
bool wg_noise_handshake_create_response(struct message_handshake_response *dst,
- struct noise_handshake *handshake)
+ struct noise_handshake *handshake, u32 message_type)
{
u8 key[NOISE_SYMMETRIC_KEY_LEN];
bool ret = false;
@@ -681,7 +681,7 @@
if (handshake->state != HANDSHAKE_CONSUMED_INITIATION)
goto out;
- dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE);
+ dst->header.type = cpu_to_le32(message_type);
dst->receiver_index = handshake->remote_index;
/* e */
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/noise.h ./noise.h
--- ../../linux-source-6.2.0/drivers/net/wireguard/noise.h 2023-11-10 18:10:29
+++ ./noise.h 2023-11-23 13:12:55
@@ -118,13 +118,13 @@
bool
wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst,
- struct noise_handshake *handshake);
+ struct noise_handshake *handshake, u32 message_type);
struct wg_peer *
wg_noise_handshake_consume_initiation(struct message_handshake_initiation *src,
struct wg_device *wg);
bool wg_noise_handshake_create_response(struct message_handshake_response *dst,
- struct noise_handshake *handshake);
+ struct noise_handshake *handshake, u32 message_type);
struct wg_peer *
wg_noise_handshake_consume_response(struct message_handshake_response *src,
struct wg_device *wg);
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/receive.c ./receive.c
--- ../../linux-source-6.2.0/drivers/net/wireguard/receive.c 2023-11-10 18:10:29
+++ ./receive.c 2023-11-23 19:15:51
@@ -25,25 +25,51 @@
#define SKB_TYPE_LE32(skb) (((struct message_header *)(skb)->data)->type)
-static size_t validate_header_len(struct sk_buff *skb)
+static size_t validate_header_len(struct sk_buff *skb, struct wg_device *wg)
{
if (unlikely(skb->len < sizeof(struct message_header)))
return 0;
- if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_DATA) &&
+ if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.transport_packet_magic_header) &&
skb->len >= MESSAGE_MINIMUM_LENGTH)
return sizeof(struct message_data);
- if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION) &&
- skb->len == sizeof(struct message_handshake_initiation))
- return sizeof(struct message_handshake_initiation);
- if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE) &&
- skb->len == sizeof(struct message_handshake_response))
- return sizeof(struct message_handshake_response);
- if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE) &&
- skb->len == sizeof(struct message_handshake_cookie))
- return sizeof(struct message_handshake_cookie);
+ if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.init_packet_magic_header) &&
+ skb->len == MESSAGE_INITIATION_SIZE)
+ return MESSAGE_INITIATION_SIZE;
+ if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.response_packet_magic_header) &&
+ skb->len == MESSAGE_RESPONSE_SIZE)
+ return MESSAGE_RESPONSE_SIZE;
+ if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.cookie_packet_magic_header) &&
+ skb->len == MESSAGE_COOKIE_REPLY_SIZE)
+ return MESSAGE_COOKIE_REPLY_SIZE;
return 0;
}
+void prepare_advanced_secured_message(struct sk_buff *skb, struct wg_device *wg)
+{
+ u32 assumed_type = SKB_TYPE_LE32(skb);
+ u32 assumed_offset;
+
+ if (wg->advanced_security_config.advanced_security_enabled) {
+ if (skb->len == MESSAGE_INITIATION_SIZE + wg->advanced_security_config.init_packet_junk_size) {
+ assumed_type = cpu_to_le32(wg->advanced_security_config.init_packet_magic_header);
+ assumed_offset = wg->advanced_security_config.init_packet_junk_size;
+ } else if (skb->len == MESSAGE_RESPONSE_SIZE + wg->advanced_security_config.response_packet_junk_size) {
+ assumed_type = cpu_to_le32(wg->advanced_security_config.response_packet_magic_header);
+ assumed_offset = wg->advanced_security_config.response_packet_junk_size;
+ } else
+ return;
+
+ if (unlikely(assumed_offset <= 0) || unlikely(!pskb_may_pull(skb, assumed_offset)))
+ return;
+
+ skb_pull(skb, assumed_offset);
+
+ if (SKB_TYPE_LE32(skb) != assumed_type) {
+ skb_push(skb, assumed_offset);
+ }
+ }
+}
+
static int prepare_skb_header(struct sk_buff *skb, struct wg_device *wg)
{
size_t data_offset, data_len, header_len;
@@ -79,7 +105,8 @@
if (unlikely(skb->len != data_len))
/* Final len does not agree with calculated len */
return -EINVAL;
- header_len = validate_header_len(skb);
+ prepare_advanced_secured_message(skb, wg);
+ header_len = validate_header_len(skb, wg);
if (unlikely(!header_len))
return -EINVAL;
__skb_push(skb, data_offset);
@@ -101,7 +128,7 @@
bool packet_needs_cookie;
bool under_load;
- if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE)) {
+ if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.cookie_packet_magic_header)) {
net_dbg_skb_ratelimited("%s: Receiving cookie response from %pISpfsc\n",
wg->dev->name, skb);
wg_cookie_message_consume(
@@ -131,8 +158,7 @@
return;
}
- switch (SKB_TYPE_LE32(skb)) {
- case cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION): {
+ if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.init_packet_magic_header)) {
struct message_handshake_initiation *message =
(struct message_handshake_initiation *)skb->data;
@@ -152,9 +178,8 @@
wg->dev->name, peer->internal_id,
&peer->endpoint.addr);
wg_packet_send_handshake_response(peer);
- break;
}
- case cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE): {
+ if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.response_packet_magic_header)) {
struct message_handshake_response *message =
(struct message_handshake_response *)skb->data;
@@ -185,9 +210,7 @@
*/
wg_packet_send_keepalive(peer);
}
- break;
}
- }
if (unlikely(!peer)) {
WARN(1, "Somehow a wrong type of packet wound up in the handshake queue!\n");
@@ -543,10 +566,10 @@
{
if (unlikely(prepare_skb_header(skb, wg) < 0))
goto err;
- switch (SKB_TYPE_LE32(skb)) {
- case cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION):
- case cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE):
- case cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE): {
+
+ if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.init_packet_magic_header) ||
+ SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.response_packet_magic_header) ||
+ SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.cookie_packet_magic_header)) {
int cpu, ret = -EBUSY;
if (unlikely(!rng_is_initialized()))
@@ -559,23 +582,20 @@
} else
ret = ptr_ring_produce_bh(&wg->handshake_queue.ring, skb);
if (ret) {
- drop:
+drop:
net_dbg_skb_ratelimited("%s: Dropping handshake packet from %pISpfsc\n",
- wg->dev->name, skb);
+ wg->dev->name, skb);
goto err;
}
atomic_inc(&wg->handshake_queue_len);
cpu = wg_cpumask_next_online(&wg->handshake_queue.last_cpu);
/* Queues up a call to packet_process_queued_handshake_packets(skb): */
queue_work_on(cpu, wg->handshake_receive_wq,
- &per_cpu_ptr(wg->handshake_queue.worker, cpu)->work);
- break;
- }
- case cpu_to_le32(MESSAGE_DATA):
+ &per_cpu_ptr(wg->handshake_queue.worker, cpu)->work);
+ } else if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.transport_packet_magic_header)) {
PACKET_CB(skb)->ds = ip_tunnel_get_dsfield(ip_hdr(skb), skb);
wg_packet_consume_data(wg, skb);
- break;
- default:
+ } else {
WARN(1, "Non-exhaustive parsing of packet header lead to unknown packet type!\n");
goto err;
}
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/send.c ./send.c
--- ../../linux-source-6.2.0/drivers/net/wireguard/send.c 2023-11-10 18:10:29
+++ ./send.c 2023-11-24 18:25:50
@@ -14,13 +14,24 @@
#include <linux/uio.h>
#include <linux/inetdevice.h>
#include <linux/socket.h>
+#include <linux/random.h>
#include <net/ip_tunnels.h>
#include <net/udp.h>
#include <net/sock.h>
+u32 wg_get_random_u32_inclusive(u32 floor, u32 ceil)
+{
+ u32 diff = ceil - floor + 1;
+ return floor + (get_random_u32() % diff);
+}
+
static void wg_packet_send_handshake_initiation(struct wg_peer *peer)
{
struct message_handshake_initiation packet;
+ struct wg_device *wg = peer->device;
+ void *buffer;
+ u8 ds;
+ u16 junk_packet_count, junk_packet_size;
if (!wg_birthdate_has_expired(atomic64_read(&peer->last_sent_handshake),
REKEY_TIMEOUT))
@@ -31,14 +42,37 @@
peer->device->dev->name, peer->internal_id,
&peer->endpoint.addr);
- if (wg_noise_handshake_create_initiation(&packet, &peer->handshake)) {
+ if (wg->advanced_security_config.advanced_security_enabled) {
+ junk_packet_count = wg->advanced_security_config.junk_packet_count;
+ buffer = kzalloc(wg->advanced_security_config.junk_packet_max_size, GFP_KERNEL);
+
+ while (junk_packet_count-- > 0) {
+ junk_packet_size = (u16) wg_get_random_u32_inclusive(
+ wg->advanced_security_config.junk_packet_min_size,
+ wg->advanced_security_config.junk_packet_max_size);
+
+ get_random_bytes(buffer, junk_packet_size);
+ get_random_bytes(&ds, 1);
+ wg_socket_send_buffer_to_peer(peer, buffer, junk_packet_size, ds);
+ }
+
+ kfree(buffer);
+ }
+
+ if (wg_noise_handshake_create_initiation(&packet, &peer->handshake, wg->advanced_security_config.init_packet_magic_header)) {
wg_cookie_add_mac_to_packet(&packet, sizeof(packet), peer);
wg_timers_any_authenticated_packet_traversal(peer);
wg_timers_any_authenticated_packet_sent(peer);
atomic64_set(&peer->last_sent_handshake,
ktime_get_coarse_boottime_ns());
- wg_socket_send_buffer_to_peer(peer, &packet, sizeof(packet),
- HANDSHAKE_DSCP);
+
+ if (wg->advanced_security_config.advanced_security_enabled) {
+ wg_socket_send_junked_buffer_to_peer(peer, &packet, sizeof(packet),
+ HANDSHAKE_DSCP, wg->advanced_security_config.init_packet_junk_size);
+ } else {
+ wg_socket_send_buffer_to_peer(peer, &packet, sizeof(packet),
+ HANDSHAKE_DSCP);
+ }
wg_timers_handshake_initiated(peer);
}
}
@@ -85,13 +119,14 @@
void wg_packet_send_handshake_response(struct wg_peer *peer)
{
struct message_handshake_response packet;
+ struct wg_device *wg = peer->device;
atomic64_set(&peer->last_sent_handshake, ktime_get_coarse_boottime_ns());
net_dbg_ratelimited("%s: Sending handshake response to peer %llu (%pISpfsc)\n",
peer->device->dev->name, peer->internal_id,
&peer->endpoint.addr);
- if (wg_noise_handshake_create_response(&packet, &peer->handshake)) {
+ if (wg_noise_handshake_create_response(&packet, &peer->handshake, wg->advanced_security_config.response_packet_magic_header)) {
wg_cookie_add_mac_to_packet(&packet, sizeof(packet), peer);
if (wg_noise_handshake_begin_session(&peer->handshake,
&peer->keypairs)) {
@@ -100,9 +135,16 @@
wg_timers_any_authenticated_packet_sent(peer);
atomic64_set(&peer->last_sent_handshake,
ktime_get_coarse_boottime_ns());
- wg_socket_send_buffer_to_peer(peer, &packet,
- sizeof(packet),
- HANDSHAKE_DSCP);
+ if (wg->advanced_security_config.advanced_security_enabled) {
+ wg_socket_send_junked_buffer_to_peer(peer, &packet,
+ sizeof(packet),
+ HANDSHAKE_DSCP,
+ wg->advanced_security_config.response_packet_junk_size);
+ } else {
+ wg_socket_send_buffer_to_peer(peer, &packet,
+ sizeof(packet),
+ HANDSHAKE_DSCP);
+ }
}
}
}
@@ -116,7 +158,7 @@
net_dbg_skb_ratelimited("%s: Sending cookie response for denied handshake message for %pISpfsc\n",
wg->dev->name, initiating_skb);
wg_cookie_message_create(&packet, initiating_skb, sender_index,
- &wg->cookie_checker);
+ &wg->cookie_checker, wg->advanced_security_config.cookie_packet_magic_header);
wg_socket_send_buffer_as_reply_to_skb(wg, initiating_skb, &packet,
sizeof(packet));
}
@@ -159,7 +201,7 @@
return padded_size - last_unit;
}
-static bool encrypt_packet(struct sk_buff *skb, struct noise_keypair *keypair)
+static bool encrypt_packet(struct sk_buff *skb, struct noise_keypair *keypair, u32 message_type)
{
unsigned int padding_len, plaintext_len, trailer_len;
struct scatterlist sg[MAX_SKB_FRAGS + 8];
@@ -203,7 +245,7 @@
*/
skb_set_inner_network_header(skb, 0);
header = (struct message_data *)skb_push(skb, sizeof(*header));
- header->header.type = cpu_to_le32(MESSAGE_DATA);
+ header->header.type = cpu_to_le32(message_type);
header->key_idx = keypair->remote_index;
header->counter = cpu_to_le64(PACKET_CB(skb)->nonce);
pskb_put(skb, trailer, trailer_len);
@@ -289,13 +331,17 @@
struct crypt_queue *queue = container_of(work, struct multicore_worker,
work)->ptr;
struct sk_buff *first, *skb, *next;
+ struct wg_device *wg;
while ((first = ptr_ring_consume_bh(&queue->ring)) != NULL) {
enum packet_state state = PACKET_STATE_CRYPTED;
skb_list_walk_safe(first, skb, next) {
+ wg = PACKET_PEER(first)->device;
+
if (likely(encrypt_packet(skb,
- PACKET_CB(first)->keypair))) {
+ PACKET_CB(first)->keypair,
+ wg->advanced_security_config.transport_packet_magic_header))) {
wg_reset_packet(skb, true);
} else {
state = PACKET_STATE_DEAD;
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/socket.c ./socket.c
--- ../../linux-source-6.2.0/drivers/net/wireguard/socket.c 2023-11-10 18:10:29
+++ ./socket.c 2023-11-23 15:45:07
@@ -200,6 +200,18 @@
return wg_socket_send_skb_to_peer(peer, skb, ds);
}
+int wg_socket_send_junked_buffer_to_peer(struct wg_peer *peer, void *buffer,
+ size_t len, u8 ds, u16 junk_size)
+{
+ int ret;
+ void *new_buffer = kzalloc(len + junk_size, GFP_KERNEL);
+ get_random_bytes(new_buffer, junk_size);
+ memcpy(new_buffer + junk_size, buffer, len);
+ ret = wg_socket_send_buffer_to_peer(peer, new_buffer, len + junk_size, ds);
+ kfree(new_buffer);
+ return ret;
+}
+
int wg_socket_send_buffer_as_reply_to_skb(struct wg_device *wg,
struct sk_buff *in_skb, void *buffer,
size_t len)
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/socket.h ./socket.h
--- ../../linux-source-6.2.0/drivers/net/wireguard/socket.h 2023-11-10 18:10:29
+++ ./socket.h 2023-11-23 13:20:24
@@ -16,6 +16,8 @@
struct sock *new6);
int wg_socket_send_buffer_to_peer(struct wg_peer *peer, void *data,
size_t len, u8 ds);
+int wg_socket_send_junked_buffer_to_peer(struct wg_peer *peer, void *data,
+ size_t len, u8 ds, u16 junk_size);
int wg_socket_send_skb_to_peer(struct wg_peer *peer, struct sk_buff *skb,
u8 ds);
int wg_socket_send_buffer_as_reply_to_skb(struct wg_device *wg,
diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/version.h ./version.h
--- ../../linux-source-6.2.0/drivers/net/wireguard/version.h 2023-11-10 18:10:29
+++ ./version.h 1970-01-01 02:00:00
@@ -1 +0,0 @@
-#define WIREGUARD_VERSION "1.0.0"

View File

@@ -0,0 +1,27 @@
--- ../../linux-source-6.2.0/include/uapi/linux/wireguard.h 2023-09-23 12:11:13
+++ ./uapi/wireguard.h 2023-11-28 16:12:36
@@ -131,7 +131,7 @@
#ifndef _WG_UAPI_WIREGUARD_H
#define _WG_UAPI_WIREGUARD_H
-#define WG_GENL_NAME "wireguard"
+#define WG_GENL_NAME "amneziawg"
#define WG_GENL_VERSION 1
#define WG_KEY_LEN 32
@@ -157,6 +157,15 @@
WGDEVICE_A_LISTEN_PORT,
WGDEVICE_A_FWMARK,
WGDEVICE_A_PEERS,
+ WGDEVICE_A_JC,
+ WGDEVICE_A_JMIN,
+ WGDEVICE_A_JMAX,
+ WGDEVICE_A_S1,
+ WGDEVICE_A_S2,
+ WGDEVICE_A_H1,
+ WGDEVICE_A_H2,
+ WGDEVICE_A_H3,
+ WGDEVICE_A_H4,
__WGDEVICE_A_LAST
};
#define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1)

View File

@@ -0,0 +1,20 @@
WIREGUARD_VERSION = 1.0.0-awg
ccflags-y := -D'pr_fmt(fmt)=KBUILD_MODNAME ": " fmt'
ccflags-y += -D'WIREGUARD_VERSION="$(WIREGUARD_VERSION)"'
# ccflags-y += -DDEBUG
amneziawg-y := main.o
amneziawg-y += noise.o
amneziawg-y += device.o
amneziawg-y += peer.o
amneziawg-y += timers.o
amneziawg-y += queueing.o
amneziawg-y += send.o
amneziawg-y += receive.o
amneziawg-y += socket.o
amneziawg-y += peerlookup.o
amneziawg-y += allowedips.o
amneziawg-y += ratelimiter.o
amneziawg-y += cookie.o
amneziawg-y += netlink.o
obj-m := amneziawg.o

View File

@@ -0,0 +1,17 @@
#
# Copyright (C) 2016 Dan Luedtke <mail@danrl.com>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Support for AmneziaWG VPN
LUCI_DEPENDS:=+amnezia-wg-tools +ucode
LUCI_PKGARCH:=all
PKG_PROVIDES:=luci-app-amneziawg
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@@ -0,0 +1,927 @@
'use strict';
'require fs';
'require ui';
'require dom';
'require uci';
'require rpc';
'require form';
'require network';
'require validation';
var generateKey = rpc.declare({
object: 'luci.amneziawg',
method: 'generateKeyPair',
expect: { keys: {} }
});
var getPublicAndPrivateKeyFromPrivate = rpc.declare({
object: 'luci.amneziawg',
method: 'getPublicAndPrivateKeyFromPrivate',
params: ['privkey'],
expect: { keys: {} }
});
var generatePsk = rpc.declare({
object: 'luci.amneziawg',
method: 'generatePsk',
expect: { psk: '' }
});
var qrIcon = '<svg viewBox="0 0 29 29" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M0 0h29v29H0z"/><path d="M4 4h1v1H4zM5 4h1v1H5zM6 4h1v1H6zM7 4h1v1H7zM8 4h1v1H8zM9 4h1v1H9zM10 4h1v1h-1zM12 4h1v1h-1zM13 4h1v1h-1zM14 4h1v1h-1zM15 4h1v1h-1zM16 4h1v1h-1zM18 4h1v1h-1zM19 4h1v1h-1zM20 4h1v1h-1zM21 4h1v1h-1zM22 4h1v1h-1zM23 4h1v1h-1zM24 4h1v1h-1zM4 5h1v1H4zM10 5h1v1h-1zM12 5h1v1h-1zM14 5h1v1h-1zM16 5h1v1h-1zM18 5h1v1h-1zM24 5h1v1h-1zM4 6h1v1H4zM6 6h1v1H6zM7 6h1v1H7zM8 6h1v1H8zM10 6h1v1h-1zM12 6h1v1h-1zM18 6h1v1h-1zM20 6h1v1h-1zM21 6h1v1h-1zM22 6h1v1h-1zM24 6h1v1h-1zM4 7h1v1H4zM6 7h1v1H6zM7 7h1v1H7zM8 7h1v1H8zM10 7h1v1h-1zM12 7h1v1h-1zM13 7h1v1h-1zM14 7h1v1h-1zM15 7h1v1h-1zM18 7h1v1h-1zM20 7h1v1h-1zM21 7h1v1h-1zM22 7h1v1h-1zM24 7h1v1h-1zM4 8h1v1H4zM6 8h1v1H6zM7 8h1v1H7zM8 8h1v1H8zM10 8h1v1h-1zM16 8h1v1h-1zM18 8h1v1h-1zM20 8h1v1h-1zM21 8h1v1h-1zM22 8h1v1h-1zM24 8h1v1h-1zM4 9h1v1H4zM10 9h1v1h-1zM12 9h1v1h-1zM13 9h1v1h-1zM15 9h1v1h-1zM18 9h1v1h-1zM24 9h1v1h-1zM4 10h1v1H4zM5 10h1v1H5zM6 10h1v1H6zM7 10h1v1H7zM8 10h1v1H8zM9 10h1v1H9zM10 10h1v1h-1zM12 10h1v1h-1zM14 10h1v1h-1zM16 10h1v1h-1zM18 10h1v1h-1zM19 10h1v1h-1zM20 10h1v1h-1zM21 10h1v1h-1zM22 10h1v1h-1zM23 10h1v1h-1zM24 10h1v1h-1zM13 11h1v1h-1zM14 11h1v1h-1zM15 11h1v1h-1zM16 11h1v1h-1zM4 12h1v1H4zM5 12h1v1H5zM8 12h1v1H8zM9 12h1v1H9zM10 12h1v1h-1zM13 12h1v1h-1zM15 12h1v1h-1zM19 12h1v1h-1zM21 12h1v1h-1zM22 12h1v1h-1zM23 12h1v1h-1zM24 12h1v1h-1zM5 13h1v1H5zM6 13h1v1H6zM8 13h1v1H8zM11 13h1v1h-1zM13 13h1v1h-1zM14 13h1v1h-1zM15 13h1v1h-1zM16 13h1v1h-1zM19 13h1v1h-1zM22 13h1v1h-1zM4 14h1v1H4zM5 14h1v1H5zM9 14h1v1H9zM10 14h1v1h-1zM11 14h1v1h-1zM15 14h1v1h-1zM18 14h1v1h-1zM19 14h1v1h-1zM20 14h1v1h-1zM21 14h1v1h-1zM22 14h1v1h-1zM23 14h1v1h-1zM7 15h1v1H7zM8 15h1v1H8zM9 15h1v1H9zM11 15h1v1h-1zM12 15h1v1h-1zM13 15h1v1h-1zM17 15h1v1h-1zM18 15h1v1h-1zM20 15h1v1h-1zM21 15h1v1h-1zM23 15h1v1h-1zM4 16h1v1H4zM6 16h1v1H6zM10 16h1v1h-1zM11 16h1v1h-1zM13 16h1v1h-1zM14 16h1v1h-1zM16 16h1v1h-1zM17 16h1v1h-1zM18 16h1v1h-1zM22 16h1v1h-1zM23 16h1v1h-1zM24 16h1v1h-1zM12 17h1v1h-1zM16 17h1v1h-1zM17 17h1v1h-1zM18 17h1v1h-1zM4 18h1v1H4zM5 18h1v1H5zM6 18h1v1H6zM7 18h1v1H7zM8 18h1v1H8zM9 18h1v1H9zM10 18h1v1h-1zM14 18h1v1h-1zM16 18h1v1h-1zM17 18h1v1h-1zM21 18h1v1h-1zM22 18h1v1h-1zM23 18h1v1h-1zM4 19h1v1H4zM10 19h1v1h-1zM12 19h1v1h-1zM13 19h1v1h-1zM15 19h1v1h-1zM16 19h1v1h-1zM19 19h1v1h-1zM21 19h1v1h-1zM23 19h1v1h-1zM24 19h1v1h-1zM4 20h1v1H4zM6 20h1v1H6zM7 20h1v1H7zM8 20h1v1H8zM10 20h1v1h-1zM12 20h1v1h-1zM13 20h1v1h-1zM15 20h1v1h-1zM18 20h1v1h-1zM19 20h1v1h-1zM20 20h1v1h-1zM22 20h1v1h-1zM23 20h1v1h-1zM24 20h1v1h-1zM4 21h1v1H4zM6 21h1v1H6zM7 21h1v1H7zM8 21h1v1H8zM10 21h1v1h-1zM13 21h1v1h-1zM15 21h1v1h-1zM16 21h1v1h-1zM19 21h1v1h-1zM21 21h1v1h-1zM23 21h1v1h-1zM24 21h1v1h-1zM4 22h1v1H4zM6 22h1v1H6zM7 22h1v1H7zM8 22h1v1H8zM10 22h1v1h-1zM13 22h1v1h-1zM15 22h1v1h-1zM18 22h1v1h-1zM19 22h1v1h-1zM20 22h1v1h-1zM21 22h1v1h-1zM22 22h1v1h-1zM4 23h1v1H4zM10 23h1v1h-1zM12 23h1v1h-1zM13 23h1v1h-1zM14 23h1v1h-1zM17 23h1v1h-1zM18 23h1v1h-1zM20 23h1v1h-1zM22 23h1v1h-1zM4 24h1v1H4zM5 24h1v1H5zM6 24h1v1H6zM7 24h1v1H7zM8 24h1v1H8zM9 24h1v1H9zM10 24h1v1h-1zM12 24h1v1h-1zM13 24h1v1h-1zM14 24h1v1h-1zM16 24h1v1h-1zM17 24h1v1h-1zM18 24h1v1h-1zM22 24h1v1h-1zM24 24h1v1h-1z"/></svg>';
function validateBase64(section_id, value) {
if (value.length == 0)
return true;
if (value.length != 44 || !value.match(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/))
return _('Invalid Base64 key string');
if (value[43] != "=" )
return _('Invalid Base64 key string');
return true;
}
var stubValidator = {
factory: validation,
apply: function(type, value, args) {
if (value != null)
this.value = value;
return validation.types[type].apply(this, args);
},
assert: function(condition) {
return !!condition;
}
};
function generateDescription(name, texts) {
return E('li', { 'style': 'color: inherit;' }, [
E('span', name),
E('ul', texts.map(function (text) {
return E('li', { 'style': 'color: inherit;' }, text);
}))
]);
}
function invokeQREncode(data, code) {
return fs.exec_direct('/usr/bin/qrencode', [
'--inline', '--8bit', '--type=SVG',
'--output=-', '--', data
]).then(function(svg) {
code.style.opacity = '';
dom.content(code, Object.assign(E(svg), { style: 'width:100%;height:auto' }));
}).catch(function(error) {
code.style.opacity = '';
if (L.isObject(error) && error.name == 'NotFoundError') {
dom.content(code, [
Object.assign(E(qrIcon), { style: 'width:32px;height:32px;opacity:.2' }),
E('p', _('The <em>qrencode</em> package is required for generating an QR code image of the configuration.'))
]);
}
else {
dom.content(code, [
_('Unable to generate QR code: %s').format(L.isObject(error) ? error.message : error)
]);
}
});
}
var cbiKeyPairGenerate = form.DummyValue.extend({
cfgvalue: function(section_id, value) {
return E('button', {
'class': 'btn',
'click': ui.createHandlerFn(this, function(section_id, ev) {
var prv = this.section.getUIElement(section_id, 'private_key'),
pub = this.section.getUIElement(section_id, 'public_key'),
map = this.map;
if ((prv.getValue() || pub.getValue()) && !confirm(_('Do you want to replace the current keys?')))
return;
return generateKey().then(function(keypair) {
prv.setValue(keypair.priv);
pub.setValue(keypair.pub);
map.save(null, true);
});
}, section_id)
}, [ _('Generate new key pair') ]);
}
});
function handleWindowDragDropIgnore(ev) {
ev.preventDefault()
}
return network.registerProtocol('amneziawg', {
getI18n: function() {
return _('AmneziaWG VPN');
},
getIfname: function() {
return this._ubus('l3_device') || this.sid;
},
getOpkgPackage: function() {
return 'amnezia-wg-tools';
},
isFloating: function() {
return true;
},
isVirtual: function() {
return true;
},
getDevices: function() {
return null;
},
containsDevice: function(ifname) {
return (network.getIfnameOf(ifname) == this.getIfname());
},
renderFormOptions: function(s) {
var o, ss, ss2;
// -- general ---------------------------------------------------------------------
o = s.taboption('general', form.Value, 'private_key', _('Private Key'), _('Required. Base64-encoded private key for this interface.'));
o.password = true;
o.validate = validateBase64;
o.rmempty = false;
var serverName = this.getIfname();
o = s.taboption('general', form.Value, 'public_key', _('Public Key'), _('Base64-encoded public key of this interface for sharing.'));
o.rmempty = false;
o.write = function() {/* write nothing */};
o.load = function(section_id) {
var privKey = s.formvalue(section_id, 'private_key') || uci.get('network', section_id, 'private_key');
return getPublicAndPrivateKeyFromPrivate(privKey).then(
function(keypair) {
return keypair.pub || '';
},
function(error) {
return _('Error getting PublicKey');
}, this)
};
s.taboption('general', cbiKeyPairGenerate, '_gen_server_keypair', ' ');
o = s.taboption('general', form.Value, 'listen_port', _('Listen Port'), _('Optional. UDP port used for outgoing and incoming packets.'));
o.datatype = 'port';
o.placeholder = _('random');
o.optional = true;
o = s.taboption('general', form.DynamicList, 'addresses', _('IP Addresses'), _('Recommended. IP addresses of the AmneziaWG interface.'));
o.datatype = 'ipaddr';
o.optional = true;
o = s.taboption('general', form.Flag, 'nohostroute', _('No Host Routes'), _('Optional. Do not create host routes to peers.'));
o.optional = true;
o = s.taboption('general', form.Button, '_import', _('Import configuration'), _('Imports settings from an existing AmneziaWG configuration file'));
o.inputtitle = _('Load configuration…');
o.onclick = function() {
return ss.handleConfigImport('full');
};
// -- advanced --------------------------------------------------------------------
o = s.taboption('advanced', form.Value, 'mtu', _('MTU'), _('Optional. Maximum Transmission Unit of tunnel interface.'));
o.datatype = 'range(0,8940)';
o.placeholder = '1420';
o.optional = true;
o = s.taboption('advanced', form.Value, 'fwmark', _('Firewall Mark'), _('Optional. 32-bit mark for outgoing encrypted packets. Enter value in hex, starting with <code>0x</code>.'));
o.optional = true;
o.validate = function(section_id, value) {
if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,8}$/))
return _('Invalid hexadecimal value');
return true;
};
// AmneziaWG
try {
s.tab('amneziawg', _('AmneziaWG Settings'), _('Further information about AmneziaWG interfaces and peers at <a href=\'http://amnezia.org\'>amnezia.org</a>.'));
}
catch(e) {}
o = s.taboption('amneziawg', form.Value, 'awg_jc', _('Jc'), _('Junk packet count.'));
o.datatype = 'uinteger';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_jmin', _('Jmin'), _('Junk packet minimum size.'));
o.datatype = 'uinteger';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_jmax', _('Jmax'), _('Junk packet maximum size.'));
o.datatype = 'uinteger';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_s1', _('S1'), _('Handshake initiation packet junk header size.'));
o.datatype = 'uinteger';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_s2', _('S2'), _('Handshake response packet junk header size.'));
o.datatype = 'uinteger';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_h1', _('H1'), _('Handshake initiation packet type header.'));
o.datatype = 'uinteger';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_h2', _('H2'), _('Handshake response packet type header.'));
o.datatype = 'uinteger';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_h3', _('H3'), _('Handshake cookie packet type header.'));
o.datatype = 'uinteger';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_h4', _('H4'), _('Transport packet type header.'));
o.datatype = 'uinteger';
o.optional = true;
// -- peers -----------------------------------------------------------------------
try {
s.tab('peers', _('Peers'), _('Further information about AmneziaWG interfaces and peers at <a href=\'http://amneziawg.com\'>amneziawg.com</a>.'));
}
catch(e) {}
o = s.taboption('peers', form.SectionValue, '_peers', form.GridSection, 'amneziawg_%s'.format(s.section));
o.depends('proto', 'amneziawg');
ss = o.subsection;
ss.anonymous = true;
ss.addremove = true;
ss.addbtntitle = _('Add peer');
ss.nodescriptions = true;
ss.modaltitle = _('Edit peer');
ss.handleDragConfig = function(ev) {
ev.stopPropagation();
ev.preventDefault();
ev.dataTransfer.dropEffect = 'copy';
};
ss.handleDropConfig = function(mode, ev) {
var file = ev.dataTransfer.files[0],
nodes = ev.currentTarget,
input = nodes.querySelector('textarea'),
reader = new FileReader();
if (file) {
reader.onload = function(rev) {
input.value = rev.target.result.trim();
ss.handleApplyConfig(mode, nodes, file.name, ev);
};
reader.readAsText(file);
}
ev.stopPropagation();
ev.preventDefault();
};
ss.parseConfig = function(data) {
var lines = String(data).split(/(\r?\n)+/),
section = null,
config = { peers: [] },
s;
for (var i = 0; i < lines.length; i++) {
var line = lines[i].replace(/#.*$/, '').trim();
if (line.match(/^\[(\w+)\]$/)) {
section = RegExp.$1.toLowerCase();
if (section == 'peer')
config.peers.push(s = {});
else
s = config;
}
else if (section && line.match(/^(\w+)\s*=\s*(.+)$/)) {
var key = RegExp.$1,
val = RegExp.$2.trim();
if (val.length)
s[section + '_' + key.toLowerCase()] = val;
}
}
if (config.interface_address) {
config.interface_address = config.interface_address.split(/[, ]+/);
for (var i = 0; i < config.interface_address.length; i++)
if (!stubValidator.apply('ipaddr', config.interface_address[i]))
return _('Address setting is invalid');
}
if (config.interface_dns) {
config.interface_dns = config.interface_dns.split(/[, ]+/);
for (var i = 0; i < config.interface_dns.length; i++)
if (!stubValidator.apply('ipaddr', config.interface_dns[i], ['nomask']))
return _('DNS setting is invalid');
}
if (!config.interface_privatekey || validateBase64(null, config.interface_privatekey) !== true)
return _('PrivateKey setting is missing or invalid');
if (!stubValidator.apply('port', config.interface_listenport || '0'))
return _('ListenPort setting is invalid');
for (var i = 0; i < config.peers.length; i++) {
var pconf = config.peers[i];
if (pconf.peer_publickey != null && validateBase64(null, pconf.peer_publickey) !== true)
return _('PublicKey setting is invalid');
if (pconf.peer_presharedkey != null && validateBase64(null, pconf.peer_presharedkey) !== true)
return _('PresharedKey setting is invalid');
if (pconf.peer_allowedips) {
pconf.peer_allowedips = pconf.peer_allowedips.split(/[, ]+/);
for (var j = 0; j < pconf.peer_allowedips.length; j++)
if (!stubValidator.apply('ipaddr', pconf.peer_allowedips[j]))
return _('AllowedIPs setting is invalid');
}
else {
pconf.peer_allowedips = [ '0.0.0.0/0', '::/0' ];
}
if (pconf.peer_endpoint) {
var host_port = pconf.peer_endpoint.match(/^\[([a-fA-F0-9:]+)\]:(\d+)$/) || pconf.peer_endpoint.match(/^(.+):(\d+)$/);
if (!host_port || !stubValidator.apply('host', host_port[1]) || !stubValidator.apply('port', host_port[2]))
return _('Endpoint setting is invalid');
pconf.peer_endpoint = [ host_port[1], host_port[2] ];
}
if (pconf.peer_persistentkeepalive == 'off' || pconf.peer_persistentkeepalive == '0')
delete pconf.peer_persistentkeepalive;
if (!stubValidator.apply('port', pconf.peer_persistentkeepalive || '0'))
return _('PersistentKeepAlive setting is invalid');
}
return config;
};
ss.handleApplyConfig = function(mode, nodes, comment, ev) {
var input = nodes.querySelector('textarea').value,
error = nodes.querySelector('.alert-message'),
cancel = nodes.nextElementSibling.querySelector('.btn'),
config = this.parseConfig(input);
console.log(input);
if (typeof(config) == 'string') {
error.firstChild.data = _('Cannot parse configuration: %s').format(config);
error.style.display = 'block';
return;
}
if (mode == 'full') {
var prv = s.formvalue(s.section, 'private_key');
if (prv && prv != config.interface_privatekey && !confirm(_('Overwrite the current settings with the imported configuration?')))
return;
return getPublicAndPrivateKeyFromPrivate(config.interface_privatekey).then(function(keypair) {
s.getOption('private_key').getUIElement(s.section).setValue(keypair.priv);
s.getOption('public_key').getUIElement(s.section).setValue(keypair.pub);
s.getOption('listen_port').getUIElement(s.section).setValue(config.interface_listenport || '');
s.getOption('addresses').getUIElement(s.section).setValue(config.interface_address);
s.getOption('awg_jc').getUIElement(s.section).setValue(config.awg_jc);
s.getOption('awg_jmin').getUIElement(s.section).setValue(config.awg_jmin);
s.getOption('awg_jmax').getUIElement(s.section).setValue(config.awg_jmax);
s.getOption('awg_s1').getUIElement(s.section).setValue(config.awg_s1);
s.getOption('awg_s2').getUIElement(s.section).setValue(config.awg_s2);
s.getOption('awg_h1').getUIElement(s.section).setValue(config.awg_h1);
s.getOption('awg_h2').getUIElement(s.section).setValue(config.awg_h2);
s.getOption('awg_h3').getUIElement(s.section).setValue(config.awg_h3);
s.getOption('awg_h4').getUIElement(s.section).setValue(config.awg_h4);
if (config.interface_dns)
s.getOption('dns').getUIElement(s.section).setValue(config.interface_dns);
for (var i = 0; i < config.peers.length; i++) {
var pconf = config.peers[i];
var sid = uci.add('network', 'amneziawg_' + s.section);
uci.sections('network', 'amneziawg_' + s.section, function(peer) {
if (peer.public_key == pconf.peer_publickey)
uci.remove('network', peer['.name']);
});
uci.set('network', sid, 'description', comment || _('Imported peer configuration'));
uci.set('network', sid, 'public_key', pconf.peer_publickey);
uci.set('network', sid, 'preshared_key', pconf.peer_presharedkey);
uci.set('network', sid, 'allowed_ips', pconf.peer_allowedips);
uci.set('network', sid, 'persistent_keepalive', pconf.peer_persistentkeepalive);
if (pconf.peer_endpoint) {
uci.set('network', sid, 'endpoint_host', pconf.peer_endpoint[0]);
uci.set('network', sid, 'endpoint_port', pconf.peer_endpoint[1]);
}
}
return s.map.save(null, true);
}).then(function() {
cancel.click();
});
}
else {
return getPublicAndPrivateKeyFromPrivate(config.interface_privatekey).then(function(keypair) {
var sid = uci.add('network', 'amneziawg_' + s.section);
var pub = s.formvalue(s.section, 'public_key');
uci.sections('network', 'amneziawg_' + s.section, function(peer) {
if (peer.public_key == keypair.pub)
uci.remove('network', peer['.name']);
});
uci.set('network', sid, 'description', comment || _('Imported peer configuration'));
uci.set('network', sid, 'public_key', keypair.pub);
uci.set('network', sid, 'private_key', keypair.priv);
for (var i = 0; i < config.peers.length; i++) {
var pconf = config.peers[i];
if (pconf.peer_publickey == pub) {
uci.set('network', sid, 'preshared_key', pconf.peer_presharedkey);
uci.set('network', sid, 'allowed_ips', pconf.peer_allowedips);
uci.set('network', sid, 'persistent_keepalive', pconf.peer_persistentkeepalive);
break;
}
}
return s.map.save(null, true);
}).then(function() {
cancel.click();
});
}
};
ss.handleConfigImport = function(mode) {
var mapNode = ss.getActiveModalMap(),
headNode = mapNode.parentNode.querySelector('h4'),
parent = this.map;
var nodes = E('div', {
'dragover': this.handleDragConfig,
'drop': this.handleDropConfig.bind(this, mode)
}, [
E([], (mode == 'full') ? [
E('p', _('Drag or paste a valid <em>*.conf</em> file below to configure the local AmneziaWG interface.'))
] : [
E('p', _('Paste or drag a AmneziaWG configuration (commonly <em>wg0.conf</em>) from another system below to create a matching peer entry allowing that system to connect to the local AmneziaWG interface.')),
E('p', _('To fully configure the local AmneziaWG interface from an existing (e.g. provider supplied) configuration file, use the <strong><a class="full-import" href="#">configuration import</a></strong> instead.'))
]),
E('p', [
E('textarea', {
'placeholder': (mode == 'full')
? _('Paste or drag supplied AmneziaWG configuration file…')
: _('Paste or drag AmneziaWG peer configuration (wg0.conf) file…'),
'style': 'height:5em;width:100%; white-space:pre'
})
]),
E('div', {
'class': 'alert-message',
'style': 'display:none'
}, [''])
]);
var cancelFn = function() {
nodes.parentNode.removeChild(nodes.nextSibling);
nodes.parentNode.removeChild(nodes);
mapNode.classList.remove('hidden');
mapNode.nextSibling.classList.remove('hidden');
headNode.removeChild(headNode.lastChild);
window.removeEventListener('dragover', handleWindowDragDropIgnore);
window.removeEventListener('drop', handleWindowDragDropIgnore);
};
var a = nodes.querySelector('a.full-import');
if (a) {
a.addEventListener('click', ui.createHandlerFn(this, function(mode) {
cancelFn();
this.handleConfigImport('full');
}));
}
mapNode.classList.add('hidden');
mapNode.nextElementSibling.classList.add('hidden');
headNode.appendChild(E('span', [ ' » ', (mode == 'full') ? _('Import configuration') : _('Import as peer') ]));
mapNode.parentNode.appendChild(E([], [
nodes,
E('div', {
'class': 'right'
}, [
E('button', {
'class': 'btn',
'click': cancelFn
}, [ _('Cancel') ]),
' ',
E('button', {
'class': 'btn primary',
'click': ui.createHandlerFn(this, 'handleApplyConfig', mode, nodes, null)
}, [ _('Import settings') ])
])
]));
window.addEventListener('dragover', handleWindowDragDropIgnore);
window.addEventListener('drop', handleWindowDragDropIgnore);
};
ss.renderSectionAdd = function(/* ... */) {
var nodes = this.super('renderSectionAdd', arguments);
nodes.appendChild(E('button', {
'class': 'btn',
'click': ui.createHandlerFn(this, 'handleConfigImport', 'peer')
}, [ _('Import configuration as peer…') ]));
return nodes;
};
ss.renderSectionPlaceholder = function() {
return E('em', _('No peers defined yet.'));
};
o = ss.option(form.Flag, 'disabled', _('Peer disabled'), _('Enable / Disable peer. Restart amneziawg interface to apply changes.'));
o.modalonly = true;
o.optional = true;
o = ss.option(form.Value, 'description', _('Description'), _('Optional. Description of peer.'));
o.placeholder = 'My Peer';
o.datatype = 'string';
o.optional = true;
o.width = '30%';
o.textvalue = function(section_id) {
var dis = ss.getOption('disabled'),
pub = ss.getOption('public_key'),
prv = ss.getOption('private_key'),
psk = ss.getOption('preshared_key'),
name = this.cfgvalue(section_id),
key = pub.cfgvalue(section_id);
var desc = [
E('p', [
name ? E('span', [ name ]) : E('em', [ _('Untitled peer') ])
])
];
if (dis.cfgvalue(section_id) == '1')
desc.push(E('span', {
'class': 'ifacebadge',
'data-tooltip': _('AmneziaWG peer is disabled')
}, [
E('em', [ _('Disabled', 'Label indicating that AmneziaWG peer is disabled') ])
]), ' ');
if (!key || !pub.isValid(section_id)) {
desc.push(E('span', {
'class': 'ifacebadge',
'data-tooltip': _('Public key is missing')
}, [
E('em', [ _('Key missing', 'Label indicating that AmneziaWG peer lacks public key') ])
]));
}
else {
desc.push(
E('span', {
'class': 'ifacebadge',
'data-tooltip': _('Public key: %h', 'Tooltip displaying full AmneziaWG peer public key').format(key)
}, [
E('code', [ key.replace(/^(.{5}).+(.{6})$/, '$1…$2') ])
]),
' ',
(prv.cfgvalue(section_id) && prv.isValid(section_id))
? E('span', {
'class': 'ifacebadge',
'data-tooltip': _('Private key present')
}, [ _('Private', 'Label indicating that AmneziaWG peer private key is stored') ]) : '',
' ',
(psk.cfgvalue(section_id) && psk.isValid(section_id))
? E('span', {
'class': 'ifacebadge',
'data-tooltip': _('Preshared key in use')
}, [ _('PSK', 'Label indicating that AmneziaWG peer uses a PSK') ]) : ''
);
}
return E([], desc);
};
function handleKeyChange(ev, section_id, value) {
var prv = this.section.getUIElement(section_id, 'private_key'),
btn = this.map.findElement('.btn.qr-code');
btn.disabled = (!prv.isValid() || !prv.getValue());
}
o = ss.option(form.Value, 'public_key', _('Public Key'), _('Required. Public key of the AmneziaWG peer.'));
o.modalonly = true;
o.validate = validateBase64;
o.onchange = handleKeyChange;
o = ss.option(form.Value, 'private_key', _('Private Key'), _('Optional. Private key of the AmneziaWG peer. The key is not required for establishing a connection but allows generating a peer configuration or QR code if available. It can be removed after the configuration has been exported.'));
o.modalonly = true;
o.validate = validateBase64;
o.onchange = handleKeyChange;
o.password = true;
o = ss.option(cbiKeyPairGenerate, '_gen_peer_keypair', ' ');
o.modalonly = true;
o = ss.option(form.Value, 'preshared_key', _('Preshared Key'), _('Optional. Base64-encoded preshared key. Adds in an additional layer of symmetric-key cryptography for post-quantum resistance.'));
o.modalonly = true;
o.validate = validateBase64;
o.password = true;
o = ss.option(form.DummyValue, '_gen_psk', ' ');
o.modalonly = true;
o.cfgvalue = function(section_id, value) {
return E('button', {
'class': 'btn',
'click': ui.createHandlerFn(this, function(section_id, ev) {
var psk = this.section.getUIElement(section_id, 'preshared_key'),
map = this.map;
if (psk.getValue() && !confirm(_('Do you want to replace the current PSK?')))
return;
return generatePsk().then(function(key) {
psk.setValue(key);
map.save(null, true);
});
}, section_id)
}, [ _('Generate preshared key') ]);
};
o = ss.option(form.DynamicList, 'allowed_ips', _('Allowed IPs'), _("Optional. IP addresses and prefixes that this peer is allowed to use inside the tunnel. Usually the peer's tunnel IP addresses and the networks the peer routes through the tunnel."));
o.datatype = 'ipaddr';
o.textvalue = function(section_id) {
var ips = L.toArray(this.cfgvalue(section_id)),
list = [];
for (var i = 0; i < ips.length; i++) {
if (i > 7) {
list.push(E('em', {
'class': 'ifacebadge cbi-tooltip-container'
}, [
_('+ %d more', 'Label indicating further amount of allowed ips').format(ips.length - i),
E('span', {
'class': 'cbi-tooltip'
}, [
E('ul', ips.map(function(ip) {
return E('li', [
E('span', { 'class': 'ifacebadge' }, [ ip ])
]);
}))
])
]));
break;
}
list.push(E('span', { 'class': 'ifacebadge' }, [ ips[i] ]));
}
if (!list.length)
list.push('*');
return E('span', { 'style': 'display:inline-flex;flex-wrap:wrap;gap:.125em' }, list);
};
o = ss.option(form.Flag, 'route_allowed_ips', _('Route Allowed IPs'), _('Optional. Create routes for Allowed IPs for this peer.'));
o.modalonly = true;
o = ss.option(form.Value, 'endpoint_host', _('Endpoint Host'), _('Optional. Host of peer. Names are resolved prior to bringing up the interface.'));
o.placeholder = 'vpn.example.com';
o.datatype = 'host';
o.textvalue = function(section_id) {
var host = this.cfgvalue(section_id),
port = this.section.cfgvalue(section_id, 'endpoint_port');
return (host && port)
? '%h:%d'.format(host, port)
: (host
? '%h:*'.format(host)
: (port
? '*:%d'.format(port)
: '*'));
};
o = ss.option(form.Value, 'endpoint_port', _('Endpoint Port'), _('Optional. Port of peer.'));
o.modalonly = true;
o.placeholder = '51820';
o.datatype = 'port';
o = ss.option(form.Value, 'persistent_keepalive', _('Persistent Keep Alive'), _('Optional. Seconds between keep alive messages. Default is 0 (disabled). Recommended value if this device is behind a NAT is 25.'));
o.modalonly = true;
o.datatype = 'range(0,65535)';
o.placeholder = '0';
o = ss.option(form.DummyValue, '_keyops', _('Configuration Export'),
_('Generates a configuration suitable for import on a AmneziaWG peer'));
o.modalonly = true;
o.createPeerConfig = function(section_id, endpoint, ips) {
var pub = s.formvalue(s.section, 'public_key'),
port = s.formvalue(s.section, 'listen_port') || '51820',
prv = this.section.formvalue(section_id, 'private_key'),
psk = this.section.formvalue(section_id, 'preshared_key'),
eport = this.section.formvalue(section_id, 'endpoint_port'),
keep = this.section.formvalue(section_id, 'persistent_keepalive');
// If endpoint is IPv6 we must escape it with []
if (endpoint.indexOf(':') > 0) {
endpoint = '['+endpoint+']';
}
return [
'[Interface]',
'PrivateKey = ' + prv,
eport ? 'ListenPort = ' + eport : '# ListenPort not defined',
'',
'[Peer]',
'PublicKey = ' + pub,
psk ? 'PresharedKey = ' + psk : '# PresharedKey not used',
ips && ips.length ? 'AllowedIPs = ' + ips.join(', ') : '# AllowedIPs not defined',
endpoint ? 'Endpoint = ' + endpoint + ':' + port : '# Endpoint not defined',
keep ? 'PersistentKeepAlive = ' + keep : '# PersistentKeepAlive not defined'
].join('\n');
};
o.handleGenerateQR = function(section_id, ev) {
var mapNode = ss.getActiveModalMap(),
headNode = mapNode.parentNode.querySelector('h4'),
configGenerator = this.createPeerConfig.bind(this, section_id),
parent = this.map;
return Promise.all([
network.getWANNetworks(),
network.getWAN6Networks(),
L.resolveDefault(uci.load('ddns')),
L.resolveDefault(uci.load('system')),
parent.save(null, true)
]).then(function(data) {
var hostnames = [];
uci.sections('ddns', 'service', function(s) {
if (typeof(s.lookup_host) == 'string' && s.enabled == '1')
hostnames.push(s.lookup_host);
});
uci.sections('system', 'system', function(s) {
if (typeof(s.hostname) == 'string' && s.hostname.indexOf('.') > 0)
hostnames.push(s.hostname);
});
for (var i = 0; i < data[0].length; i++)
hostnames.push.apply(hostnames, data[0][i].getIPAddrs().map(function(ip) { return ip.split('/')[0] }));
for (var i = 0; i < data[1].length; i++)
hostnames.push.apply(hostnames, data[1][i].getIP6Addrs().map(function(ip) { return ip.split('/')[0] }));
var ips = [ '0.0.0.0/0', '::/0' ];
var qrm, qrs, qro;
qrm = new form.JSONMap({ config: { endpoint: hostnames[0], allowed_ips: ips } }, null, _('The generated configuration can be imported into a AmneziaWG client application to set up a connection towards this device.'));
qrm.parent = parent;
qrs = qrm.section(form.NamedSection, 'config');
function handleConfigChange(ev, section_id, value) {
var code = this.map.findElement('.qr-code'),
conf = this.map.findElement('.client-config'),
endpoint = this.section.getUIElement(section_id, 'endpoint'),
ips = this.section.getUIElement(section_id, 'allowed_ips');
if (this.isValid(section_id)) {
conf.firstChild.data = configGenerator(endpoint.getValue(), ips.getValue());
code.style.opacity = '.5';
invokeQREncode(conf.firstChild.data, code);
}
};
qro = qrs.option(form.Value, 'endpoint', _('Connection endpoint'), _('The public hostname or IP address of this system the peer should connect to. This usually is a static public IP address, a static hostname or a DDNS domain.'));
qro.datatype = 'or(ipaddr,hostname)';
hostnames.forEach(function(hostname) { qro.value(hostname) });
qro.onchange = handleConfigChange;
qro = qrs.option(form.DynamicList, 'allowed_ips', _('Allowed IPs'), _('IP addresses that are allowed inside the tunnel. The peer will accept tunnelled packets with source IP addresses matching this list and route back packets with matching destination IP.'));
qro.datatype = 'ipaddr';
qro.default = ips;
ips.forEach(function(ip) { qro.value(ip) });
qro.onchange = handleConfigChange;
qro = qrs.option(form.DummyValue, 'output');
qro.renderWidget = function() {
var peer_config = configGenerator(hostnames[0], ips);
var node = E('div', {
'style': 'display:flex;flex-wrap:wrap;align-items:center;gap:.5em;width:100%'
}, [
E('div', {
'class': 'qr-code',
'style': 'width:320px;flex:0 1 320px;text-align:center'
}, [
E('em', { 'class': 'spinning' }, [ _('Generating QR code…') ])
]),
E('pre', {
'class': 'client-config',
'style': 'flex:1;white-space:pre;overflow:auto',
'click': function(ev) {
var sel = window.getSelection(),
range = document.createRange();
range.selectNodeContents(ev.currentTarget);
sel.removeAllRanges();
sel.addRange(range);
}
}, [ peer_config ])
]);
invokeQREncode(peer_config, node.firstChild);
return node;
};
return qrm.render().then(function(nodes) {
mapNode.classList.add('hidden');
mapNode.nextElementSibling.classList.add('hidden');
headNode.appendChild(E('span', [ ' » ', _('Generate configuration') ]));
mapNode.parentNode.appendChild(E([], [
nodes,
E('div', {
'class': 'right'
}, [
E('button', {
'class': 'btn',
'click': function() {
nodes.parentNode.removeChild(nodes.nextSibling);
nodes.parentNode.removeChild(nodes);
mapNode.classList.remove('hidden');
mapNode.nextSibling.classList.remove('hidden');
headNode.removeChild(headNode.lastChild);
}
}, [ _('Back to peer configuration') ])
])
]));
if (!s.formvalue(s.section, 'listen_port')) {
nodes.appendChild(E('div', { 'class': 'alert-message' }, [
E('p', [
_('No fixed interface listening port defined, peers might not be able to initiate connections to this AmneziaWG instance!')
])
]));
}
});
});
};
o.cfgvalue = function(section_id, value) {
var privkey = this.section.cfgvalue(section_id, 'private_key');
return E('button', {
'class': 'btn qr-code',
'style': 'display:inline-flex;align-items:center;gap:.5em',
'click': ui.createHandlerFn(this, 'handleGenerateQR', section_id),
'disabled': privkey ? null : ''
}, [
Object.assign(E(qrIcon), { style: 'width:22px;height:22px' }),
_('Generate configuration…')
]);
};
},
deleteConfiguration: function() {
uci.sections('network', 'amneziawg_%s'.format(this.sid), function(s) {
uci.remove('network', s['.name']);
});
}
});

View File

@@ -0,0 +1,175 @@
'use strict';
'require view';
'require rpc';
'require poll';
'require dom';
'require ui';
var callGetWgInstances = rpc.declare({
object: 'luci.amneziawg',
method: 'getWgInstances'
});
function timestampToStr(timestamp) {
if (timestamp < 1)
return _('Never', 'No AmneziaWG peer handshake yet');
var seconds = (Date.now() / 1000) - timestamp;
var ago;
if (seconds < 60)
ago = _('%ds ago').format(seconds);
else if (seconds < 3600)
ago = _('%dm ago').format(seconds / 60);
else if (seconds < 86401)
ago = _('%dh ago').format(seconds / 3600);
else
ago = _('over a day ago');
return (new Date(timestamp * 1000)).toUTCString() + ' (' + ago + ')';
}
function handleInterfaceDetails(iface) {
ui.showModal(_('Instance Details'), [
ui.itemlist(E([]), [
_('Name'), iface.name,
_('Public Key'), E('code', [ iface.public_key ]),
_('Listen Port'), iface.listen_port,
_('Firewall Mark'), iface.fwmark != 'off' ? iface.fwmark : E('em', _('none'))
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn cbi-button',
'click': ui.hideModal
}, [ _('Dismiss') ])
])
]);
}
function handlePeerDetails(peer) {
ui.showModal(_('Peer Details'), [
ui.itemlist(E([]), [
_('Description'), peer.name,
_('Public Key'), E('code', [ peer.public_key ]),
_('Endpoint'), peer.endpoint,
_('Allowed IPs'), (Array.isArray(peer.allowed_ips) && peer.allowed_ips.length) ? peer.allowed_ips.join(', ') : E('em', _('none')),
_('Received Data'), '%1024mB'.format(peer.transfer_rx),
_('Transmitted Data'), '%1024mB'.format(peer.transfer_tx),
_('Latest Handshake'), timestampToStr(+peer.latest_handshake),
_('Keep-Alive'), (peer.persistent_keepalive != 'off') ? _('every %ds', 'AmneziaWG keep alive interval').format(+peer.persistent_keepalive) : E('em', _('none')),
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn cbi-button',
'click': ui.hideModal
}, [ _('Dismiss') ])
])
]);
}
function renderPeerTable(instanceName, peers) {
var t = new L.ui.Table(
[
_('Peer'),
_('Endpoint'),
_('Data Received'),
_('Data Transmitted'),
_('Latest Handshake')
],
{
id: 'peers-' + instanceName
},
E('em', [
_('No peers connected')
])
);
t.update(peers.map(function(peer) {
return [
[
peer.name || '',
E('div', {
'style': 'cursor:pointer',
'click': ui.createHandlerFn(this, handlePeerDetails, peer)
}, [
E('p', [
peer.name ? E('span', [ peer.name ]) : E('em', [ _('Untitled peer') ])
]),
E('span', {
'class': 'ifacebadge hide-sm',
'data-tooltip': _('Public key: %h', 'Tooltip displaying full AmneziaWG peer public key').format(peer.public_key)
}, [
E('code', [ peer.public_key.replace(/^(.{5}).+(.{6})$/, '$1…$2') ])
])
])
],
peer.endpoint,
[ +peer.transfer_rx, '%1024mB'.format(+peer.transfer_rx) ],
[ +peer.transfer_tx, '%1024mB'.format(+peer.transfer_tx) ],
[ +peer.latest_handshake, timestampToStr(+peer.latest_handshake) ]
];
}));
return t.render();
}
return view.extend({
renderIfaces: function(ifaces) {
var res = [
E('h2', [ _('AmneziaWG Status') ])
];
for (var instanceName in ifaces) {
res.push(
E('h3', [ _('Instance "%h"', 'AmneziaWG instance heading').format(instanceName) ]),
E('p', {
'style': 'cursor:pointer',
'click': ui.createHandlerFn(this, handleInterfaceDetails, ifaces[instanceName])
}, [
E('span', { 'class': 'ifacebadge' }, [
E('img', { 'src': L.resource('icons', 'tunnel.png') }),
'\xa0',
instanceName
]),
E('span', { 'style': 'opacity:.8' }, [
' · ',
_('Port %d', 'AmneziaWG listen port').format(ifaces[instanceName].listen_port),
' · ',
E('code', { 'click': '' }, [ ifaces[instanceName].public_key ])
])
]),
renderPeerTable(instanceName, ifaces[instanceName].peers)
);
}
if (res.length == 1)
res.push(E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [
E('em', [ _('No AmneziaWG interfaces configured.') ])
]));
return E([], res);
},
render: function() {
poll.add(L.bind(function () {
return callGetWgInstances().then(L.bind(function(ifaces) {
dom.content(
document.querySelector('#view'),
this.renderIfaces(ifaces)
);
}, this));
}, this), 5);
return E([], [
E('h2', [ _('AmneziaWG Status') ]),
E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [
E('em', [ _('Loading data…') ])
])
]);
},
handleReset: null,
handleSaveApply: null,
handleSave: null
});

View File

@@ -0,0 +1,14 @@
{
"admin/status/amneziawg": {
"title": "AmneziaWG",
"order": 92,
"action": {
"type": "view",
"path": "amneziawg/status"
},
"depends": {
"acl": [ "luci-proto-amneziawg" ],
"uci": { "network": true }
}
}
}

View File

@@ -0,0 +1,25 @@
{
"luci-proto-amneziawg": {
"description": "Grant access to LuCI AmneziaWG procedures",
"read": {
"file": {
"/usr/bin/qrencode --inline --8bit --type=SVG --output=- -- *": [ "exec" ]
},
"ubus": {
"luci.amneziawg": [
"getWgInstances"
]
},
"uci": [ "ddns", "system" ]
},
"write": {
"ubus": {
"luci.amneziawg": [
"generateKeyPair",
"getPublicAndPrivateKeyFromPrivate",
"generatePsk"
]
}
}
}
}

View File

@@ -0,0 +1,107 @@
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
// Licensed to the public under the Apache License 2.0.
'use strict';
import { cursor } from 'uci';
import { popen } from 'fs';
function shellquote(s) {
return `'${replace(s ?? '', "'", "'\\''")}'`;
}
function command(cmd) {
return trim(popen(cmd)?.read?.('all'));
}
const methods = {
generatePsk: {
call: function() {
return { psk: command('amneziawg genpsk 2>/dev/null') };
}
},
generateKeyPair: {
call: function() {
const priv = command('amneziawg genkey 2>/dev/null');
const pub = command(`echo ${shellquote(priv)} | wg pubkey 2>/dev/null`);
return { keys: { priv, pub } };
}
},
getPublicAndPrivateKeyFromPrivate: {
args: { privkey: "privkey" },
call: function(req) {
const priv = req.args?.privkey;
const pub = command(`echo ${shellquote(priv)} | wg pubkey 2>/dev/null`);
return { keys: { priv, pub } };
}
},
getWgInstances: {
call: function() {
const data = {};
let last_device;
let qr_pubkey = {};
const uci = cursor();
const wg_dump = popen("amneziawg show all dump 2>/dev/null");
if (wg_dump) {
uci.load("network");
for (let line = wg_dump.read('line'); length(line); line = wg_dump.read('line')) {
const record = split(rtrim(line, '\n'), '\t');
if (last_device != record[0]) {
last_device = record[0];
data[last_device] = {
name: last_device,
public_key: record[2],
listen_port: record[3],
fwmark: record[4],
peers: []
};
if (!length(record[2]) || record[2] == '(none)')
qr_pubkey[last_device] = '';
else
qr_pubkey[last_device] = `PublicKey = ${record[2]}`;
}
else {
let peer_name;
uci.foreach('network', `amneziawg_${last_device}`, (s) => {
if (s.public_key == record[1])
peer_name = s.description;
});
const peer = {
name: peer_name,
public_key: record[1],
endpoint: record[3],
allowed_ips: [],
latest_handshake: record[5],
transfer_rx: record[6],
transfer_tx: record[7],
persistent_keepalive: record[8]
};
if (record[3] != '(none)' && length(record[4]))
push(peer.allowed_ips, ...split(record[4], ','));
push(data[last_device].peers, peer);
}
}
}
return data;
}
}
};
return { 'luci.amneziawg': methods };