From 9de9841236152f0361401680054902290f0b6aee Mon Sep 17 00:00:00 2001 From: Iurii Egorov Date: Thu, 30 Nov 2023 11:04:56 +0300 Subject: [PATCH 1/5] Initial commit --- .gitignore | 1 + amnezia-wg-tools/Makefile | 59 ++ amnezia-wg-tools/files/amneziawg.sh | 308 ++++++ amnezia-wg-tools/files/amneziawg_watchdog | 68 ++ kmod-amneziawg/Makefile | 40 + kmod-amneziawg/files/amnezia-sources.patch | 757 ++++++++++++++ kmod-amneziawg/files/amnezia-uapi.patch | 27 + kmod-amneziawg/src/Makefile | 20 + luci-app-amneziawg/Makefile | 17 + .../resources/protocol/amneziawg.js | 926 ++++++++++++++++++ .../resources/view/amneziawg/status.js | 175 ++++ .../luci/menu.d/luci-proto-amneziawg.json | 14 + .../usr/share/rpcd/acl.d/luci-amneziawg.json | 25 + .../root/usr/share/rpcd/ucode/luci.amneziawg | 107 ++ 14 files changed, 2544 insertions(+) create mode 100644 .gitignore create mode 100644 amnezia-wg-tools/Makefile create mode 100755 amnezia-wg-tools/files/amneziawg.sh create mode 100755 amnezia-wg-tools/files/amneziawg_watchdog create mode 100644 kmod-amneziawg/Makefile create mode 100644 kmod-amneziawg/files/amnezia-sources.patch create mode 100644 kmod-amneziawg/files/amnezia-uapi.patch create mode 100644 kmod-amneziawg/src/Makefile create mode 100644 luci-app-amneziawg/Makefile create mode 100644 luci-app-amneziawg/htdocs/luci-static/resources/protocol/amneziawg.js create mode 100644 luci-app-amneziawg/htdocs/luci-static/resources/view/amneziawg/status.js create mode 100644 luci-app-amneziawg/root/usr/share/luci/menu.d/luci-proto-amneziawg.json create mode 100644 luci-app-amneziawg/root/usr/share/rpcd/acl.d/luci-amneziawg.json create mode 100644 luci-app-amneziawg/root/usr/share/rpcd/ucode/luci.amneziawg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/amnezia-wg-tools/Makefile b/amnezia-wg-tools/Makefile new file mode 100644 index 0000000..46eab77 --- /dev/null +++ b/amnezia-wg-tools/Makefile @@ -0,0 +1,59 @@ +# +# Copyright (C) 2016-2019 Jason A. Donenfeld +# Copyright (C) 2016 Baptiste Jonglez +# Copyright (C) 2016-2017 Dan Luedtke +# +# 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 + 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)) \ No newline at end of file diff --git a/amnezia-wg-tools/files/amneziawg.sh b/amnezia-wg-tools/files/amneziawg.sh new file mode 100755 index 0000000..664655e --- /dev/null +++ b/amnezia-wg-tools/files/amneziawg.sh @@ -0,0 +1,308 @@ +#!/bin/sh +# Copyright 2016-2017 Dan Luedtke +# 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" + + if proto_amneziawg_is_kernel_mode; then + logger -t "amneziawg" "info: using kernel-space kmod-amneziawg for ${WG}" + ip link del dev "${config}" 2>/dev/null + ip link add dev "${config}" type amneziawg + else + logger -t "amneziawg" "info: using user-space amneziawg-go for ${WG}" + rm -f "/var/run/wireguard/${config}.sock" + 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 "amneziawg_${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 +} \ No newline at end of file diff --git a/amnezia-wg-tools/files/amneziawg_watchdog b/amnezia-wg-tools/files/amneziawg_watchdog new file mode 100755 index 0000000..a9b631a --- /dev/null +++ b/amnezia-wg-tools/files/amneziawg_watchdog @@ -0,0 +1,68 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2018 Aleksandr V. Piskunov . +# Copyright (C) 2015-2018 Jason A. Donenfeld . 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 \ No newline at end of file diff --git a/kmod-amneziawg/Makefile b/kmod-amneziawg/Makefile new file mode 100644 index 0000000..6660664 --- /dev/null +++ b/kmod-amneziawg/Makefile @@ -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)) diff --git a/kmod-amneziawg/files/amnezia-sources.patch b/kmod-amneziawg/files/amnezia-sources.patch new file mode 100644 index 0000000..bfbb8b1 --- /dev/null +++ b/kmod-amneziawg/files/amnezia-sources.patch @@ -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 . All Rights Reserved. + */ + +-#include "version.h" + #include "device.h" + #include "noise.h" + #include "queueing.h" + #include "ratelimiter.h" + #include "netlink.h" + +-#include ++#include "uapi/wireguard.h" + + #include + #include +@@ -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 . 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 "); + 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 ++#include "uapi/wireguard.h" + + #include + #include +@@ -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 + #include + #include ++#include + #include + #include + #include + ++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" diff --git a/kmod-amneziawg/files/amnezia-uapi.patch b/kmod-amneziawg/files/amnezia-uapi.patch new file mode 100644 index 0000000..cbe2193 --- /dev/null +++ b/kmod-amneziawg/files/amnezia-uapi.patch @@ -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) diff --git a/kmod-amneziawg/src/Makefile b/kmod-amneziawg/src/Makefile new file mode 100644 index 0000000..f0473ba --- /dev/null +++ b/kmod-amneziawg/src/Makefile @@ -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 diff --git a/luci-app-amneziawg/Makefile b/luci-app-amneziawg/Makefile new file mode 100644 index 0000000..0e2f6f0 --- /dev/null +++ b/luci-app-amneziawg/Makefile @@ -0,0 +1,17 @@ +# +# Copyright (C) 2016 Dan Luedtke +# +# 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 diff --git a/luci-app-amneziawg/htdocs/luci-static/resources/protocol/amneziawg.js b/luci-app-amneziawg/htdocs/luci-static/resources/protocol/amneziawg.js new file mode 100644 index 0000000..3d0b44a --- /dev/null +++ b/luci-app-amneziawg/htdocs/luci-static/resources/protocol/amneziawg.js @@ -0,0 +1,926 @@ +'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 = ''; + +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 qrencode 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 0x.')); + 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 amnezia.org.')); + } + 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 amneziawg.com.')); + } + 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); + + 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 *.conf file below to configure the local AmneziaWG interface.')) + ] : [ + E('p', _('Paste or drag a AmneziaWG configuration (commonly wg0.conf) 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 configuration import 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', + jc = s.formvalue + 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']); + }); + } +}); diff --git a/luci-app-amneziawg/htdocs/luci-static/resources/view/amneziawg/status.js b/luci-app-amneziawg/htdocs/luci-static/resources/view/amneziawg/status.js new file mode 100644 index 0000000..7cbde1f --- /dev/null +++ b/luci-app-amneziawg/htdocs/luci-static/resources/view/amneziawg/status.js @@ -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 +}); diff --git a/luci-app-amneziawg/root/usr/share/luci/menu.d/luci-proto-amneziawg.json b/luci-app-amneziawg/root/usr/share/luci/menu.d/luci-proto-amneziawg.json new file mode 100644 index 0000000..87787e7 --- /dev/null +++ b/luci-app-amneziawg/root/usr/share/luci/menu.d/luci-proto-amneziawg.json @@ -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 } + } + } +} diff --git a/luci-app-amneziawg/root/usr/share/rpcd/acl.d/luci-amneziawg.json b/luci-app-amneziawg/root/usr/share/rpcd/acl.d/luci-amneziawg.json new file mode 100644 index 0000000..96a22f8 --- /dev/null +++ b/luci-app-amneziawg/root/usr/share/rpcd/acl.d/luci-amneziawg.json @@ -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" + ] + } + } + } +} diff --git a/luci-app-amneziawg/root/usr/share/rpcd/ucode/luci.amneziawg b/luci-app-amneziawg/root/usr/share/rpcd/ucode/luci.amneziawg new file mode 100644 index 0000000..72cc350 --- /dev/null +++ b/luci-app-amneziawg/root/usr/share/rpcd/ucode/luci.amneziawg @@ -0,0 +1,107 @@ +// Copyright 2022 Jo-Philipp Wich +// 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 }; From cc4945292c5a6c72c9b2a6a4fc05dcd5e418405b Mon Sep 17 00:00:00 2001 From: Iurii Egorov Date: Thu, 1 Feb 2024 14:17:37 +0300 Subject: [PATCH 2/5] UAPI errors handling --- kmod-amneziawg/files/amnezia-sources.patch | 41 +++++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/kmod-amneziawg/files/amnezia-sources.patch b/kmod-amneziawg/files/amnezia-sources.patch index bfbb8b1..04dd188 100644 --- a/kmod-amneziawg/files/amnezia-sources.patch +++ b/kmod-amneziawg/files/amnezia-sources.patch @@ -44,23 +44,23 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco pr_debug("%s: Interface created\n", dev->name); return ret; -@@ -475,4 +480,79 @@ +@@ -475,4 +480,92 @@ 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) ++int 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; ++ int ret = 0; + + if (!asc->advanced_security_enabled) -+ return; ++ goto out; + -+ if (asc->junk_packet_count < 0) { -+ // TODO error -+ } ++ if (asc->junk_packet_count < 0) ++ ret = -1; + + wg->advanced_security_config.junk_packet_count = asc->junk_packet_count; + if (asc->junk_packet_count != 0) @@ -77,9 +77,9 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + wg->advanced_security_config.junk_packet_min_size = 0; + wg->advanced_security_config.junk_packet_max_size = 1; + -+ // TODO error ++ ret = -1; + } else if (asc->junk_packet_max_size < asc->junk_packet_min_size) { -+ // TODO error ++ ret = -1; + } else + wg->advanced_security_config.junk_packet_max_size = asc->junk_packet_max_size; + @@ -87,7 +87,7 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + a_sec_on = true; + + if (asc->init_packet_junk_size + MESSAGE_INITIATION_SIZE >= MESSAGE_MAX_SIZE) { -+ // TODO error ++ ret = -1; + } else + wg->advanced_security_config.init_packet_junk_size = asc->init_packet_junk_size; + @@ -95,7 +95,7 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + a_sec_on = true; + + if (asc->response_packet_junk_size + MESSAGE_RESPONSE_SIZE >= MESSAGE_MAX_SIZE) { -+ // TODO error ++ ret = -1; + } else + wg->advanced_security_config.response_packet_junk_size = asc->response_packet_junk_size; + @@ -122,7 +122,20 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + wg->advanced_security_config.transport_packet_magic_header = asc->transport_packet_magic_header; + } + ++ if (asc->init_packet_magic_header == asc->response_packet_magic_header || ++ asc->init_packet_magic_header == asc->cookie_packet_magic_header || ++ asc->init_packet_magic_header == asc->transport_packet_magic_header || ++ asc->response_packet_magic_header == asc->cookie_packet_magic_header || ++ asc->response_packet_magic_header == asc->transport_packet_magic_header || ++ asc->cookie_packet_magic_header == asc->transport_packet_magic_header) ++ ret = -1; ++ ++ if (MESSAGE_INITIATION_SIZE + asc->init_packet_junk_size == MESSAGE_RESPONSE_SIZE + asc->response_packet_junk_size) ++ ret = -1; ++ ++out: + wg->advanced_security_config.advanced_security_enabled = a_sec_on; ++ return ret; } 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 @@ -159,7 +172,7 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco int wg_device_init(void); void wg_device_uninit(void); -+void wg_device_handle_post_config(struct net_device *dev, struct amnezia_config *asc); ++int 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 @@ -334,11 +347,13 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco if (flags & WGDEVICE_F_REPLACE_PEERS) wg_peer_remove_all(wg); -@@ -597,10 +670,12 @@ +@@ -597,10 +670,14 @@ ret = 0; out: -+ wg_device_handle_post_config(wg->dev, asc); ++ if (!ret) ++ ret = wg_device_handle_post_config(wg->dev, asc); ++ mutex_unlock(&wg->device_update_lock); rtnl_unlock(); dev_put(wg->dev); From 7a3e0aeb6fbed082a865dcfe53a04217f4ee3025 Mon Sep 17 00:00:00 2001 From: Iurii Egorov Date: Thu, 1 Feb 2024 14:52:10 +0300 Subject: [PATCH 3/5] UAPI errors handling --- kmod-amneziawg/files/amnezia-sources.patch | 34 +++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/kmod-amneziawg/files/amnezia-sources.patch b/kmod-amneziawg/files/amnezia-sources.patch index 04dd188..d1f58d1 100644 --- a/kmod-amneziawg/files/amnezia-sources.patch +++ b/kmod-amneziawg/files/amnezia-sources.patch @@ -44,7 +44,7 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco pr_debug("%s: Interface created\n", dev->name); return ret; -@@ -475,4 +480,92 @@ +@@ -475,4 +480,118 @@ unregister_random_vmfork_notifier(&vm_notifier); unregister_pm_notifier(&pm_notifier); rcu_barrier(); @@ -59,8 +59,10 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + if (!asc->advanced_security_enabled) + goto out; + -+ if (asc->junk_packet_count < 0) ++ if (asc->junk_packet_count < 0) { ++ net_dbg_ratelimited("%s: JunkPacketCount should be non negative\n", dev->name); + ret = -1; ++ } + + wg->advanced_security_config.junk_packet_count = asc->junk_packet_count; + if (asc->junk_packet_count != 0) @@ -77,8 +79,14 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + wg->advanced_security_config.junk_packet_min_size = 0; + wg->advanced_security_config.junk_packet_max_size = 1; + ++ net_dbg_ratelimited("%s: JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d\n", ++ dev->name, asc->junk_packet_max_size, ++ MESSAGE_MAX_SIZE); + ret = -1; + } else if (asc->junk_packet_max_size < asc->junk_packet_min_size) { ++ net_dbg_ratelimited("%s: maxSize: %d; should be greater than minSize: %d\n", ++ dev->name, asc->junk_packet_max_size, ++ asc->junk_packet_min_size); + ret = -1; + } else + wg->advanced_security_config.junk_packet_max_size = asc->junk_packet_max_size; @@ -87,6 +95,9 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + a_sec_on = true; + + if (asc->init_packet_junk_size + MESSAGE_INITIATION_SIZE >= MESSAGE_MAX_SIZE) { ++ net_dbg_ratelimited("%s: init header size (%d) + junkSize (%d) should be smaller than maxSegmentSize: %d\n", ++ dev->name, MESSAGE_INITIATION_SIZE, ++ asc->init_packet_junk_size, MESSAGE_MAX_SIZE); + ret = -1; + } else + wg->advanced_security_config.init_packet_junk_size = asc->init_packet_junk_size; @@ -95,6 +106,9 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + a_sec_on = true; + + if (asc->response_packet_junk_size + MESSAGE_RESPONSE_SIZE >= MESSAGE_MAX_SIZE) { ++ net_dbg_ratelimited("%s: response header size (%d) + junkSize (%d) should be smaller than maxSegmentSize: %d\n", ++ dev->name, MESSAGE_RESPONSE_SIZE, ++ asc->response_packet_junk_size, MESSAGE_MAX_SIZE); + ret = -1; + } else + wg->advanced_security_config.response_packet_junk_size = asc->response_packet_junk_size; @@ -127,11 +141,23 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + asc->init_packet_magic_header == asc->transport_packet_magic_header || + asc->response_packet_magic_header == asc->cookie_packet_magic_header || + asc->response_packet_magic_header == asc->transport_packet_magic_header || -+ asc->cookie_packet_magic_header == asc->transport_packet_magic_header) ++ asc->cookie_packet_magic_header == asc->transport_packet_magic_header) { ++ net_dbg_ratelimited("%s: magic headers should differ; got: init:%d; recv:%d; unde:%d; tran:%d\n", ++ dev->name, ++ asc->init_packet_magic_header, ++ asc->response_packet_magic_header, ++ asc->cookie_packet_magic_header, ++ asc->transport_packet_magic_header); + ret = -1; ++ } + -+ if (MESSAGE_INITIATION_SIZE + asc->init_packet_junk_size == MESSAGE_RESPONSE_SIZE + asc->response_packet_junk_size) ++ if (MESSAGE_INITIATION_SIZE + asc->init_packet_junk_size == MESSAGE_RESPONSE_SIZE + asc->response_packet_junk_size) { ++ net_dbg_ratelimited("%s: new init size:%d; and new response size:%d; should differ\n", ++ dev->name, ++ MESSAGE_INITIATION_SIZE + asc->init_packet_junk_size, ++ MESSAGE_RESPONSE_SIZE + asc->response_packet_junk_size); + ret = -1; ++ } + +out: + wg->advanced_security_config.advanced_security_enabled = a_sec_on; From d2dbe007367bf211a50df9f4a0868b85ab724789 Mon Sep 17 00:00:00 2001 From: Iurii Egorov Date: Wed, 7 Feb 2024 02:24:25 +0300 Subject: [PATCH 4/5] UAPI errors handling --- kmod-amneziawg/files/amnezia-sources.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kmod-amneziawg/files/amnezia-sources.patch b/kmod-amneziawg/files/amnezia-sources.patch index d1f58d1..4add6d3 100644 --- a/kmod-amneziawg/files/amnezia-sources.patch +++ b/kmod-amneziawg/files/amnezia-sources.patch @@ -159,8 +159,8 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + ret = -1; + } + -+out: + wg->advanced_security_config.advanced_security_enabled = a_sec_on; ++out: + return ret; } 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 From b8eb3377c6fca742a3c915cb1dd2d52484574f82 Mon Sep 17 00:00:00 2001 From: Iurii Egorov Date: Wed, 7 Feb 2024 02:27:05 +0300 Subject: [PATCH 5/5] UAPI errors handling --- kmod-amneziawg/files/amnezia-sources.patch | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/kmod-amneziawg/files/amnezia-sources.patch b/kmod-amneziawg/files/amnezia-sources.patch index 4add6d3..e8f5f62 100644 --- a/kmod-amneziawg/files/amnezia-sources.patch +++ b/kmod-amneziawg/files/amnezia-sources.patch @@ -61,7 +61,7 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + + if (asc->junk_packet_count < 0) { + net_dbg_ratelimited("%s: JunkPacketCount should be non negative\n", dev->name); -+ ret = -1; ++ ret = -EINVAL; + } + + wg->advanced_security_config.junk_packet_count = asc->junk_packet_count; @@ -82,12 +82,12 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + net_dbg_ratelimited("%s: JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d\n", + dev->name, asc->junk_packet_max_size, + MESSAGE_MAX_SIZE); -+ ret = -1; ++ ret = -EINVAL; + } else if (asc->junk_packet_max_size < asc->junk_packet_min_size) { + net_dbg_ratelimited("%s: maxSize: %d; should be greater than minSize: %d\n", + dev->name, asc->junk_packet_max_size, + asc->junk_packet_min_size); -+ ret = -1; ++ ret = -EINVAL; + } else + wg->advanced_security_config.junk_packet_max_size = asc->junk_packet_max_size; + @@ -98,7 +98,7 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + net_dbg_ratelimited("%s: init header size (%d) + junkSize (%d) should be smaller than maxSegmentSize: %d\n", + dev->name, MESSAGE_INITIATION_SIZE, + asc->init_packet_junk_size, MESSAGE_MAX_SIZE); -+ ret = -1; ++ ret = -EINVAL; + } else + wg->advanced_security_config.init_packet_junk_size = asc->init_packet_junk_size; + @@ -109,7 +109,7 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + net_dbg_ratelimited("%s: response header size (%d) + junkSize (%d) should be smaller than maxSegmentSize: %d\n", + dev->name, MESSAGE_RESPONSE_SIZE, + asc->response_packet_junk_size, MESSAGE_MAX_SIZE); -+ ret = -1; ++ ret = -EINVAL; + } else + wg->advanced_security_config.response_packet_junk_size = asc->response_packet_junk_size; + @@ -148,7 +148,7 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + asc->response_packet_magic_header, + asc->cookie_packet_magic_header, + asc->transport_packet_magic_header); -+ ret = -1; ++ ret = -EINVAL; + } + + if (MESSAGE_INITIATION_SIZE + asc->init_packet_junk_size == MESSAGE_RESPONSE_SIZE + asc->response_packet_junk_size) { @@ -156,7 +156,7 @@ diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kco + dev->name, + MESSAGE_INITIATION_SIZE + asc->init_packet_junk_size, + MESSAGE_RESPONSE_SIZE + asc->response_packet_junk_size); -+ ret = -1; ++ ret = -EINVAL; + } + + wg->advanced_security_config.advanced_security_enabled = a_sec_on;