├── .gitignore ├── README.md ├── luci-proto-amneziawg ├── root │ └── usr │ │ └── share │ │ ├── luci │ │ └── menu.d │ │ │ └── luci-proto-amneziawg.json │ │ └── rpcd │ │ ├── acl.d │ │ └── luci-amneziawg.json │ │ └── ucode │ │ └── luci.amneziawg ├── Makefile └── htdocs │ └── luci-static │ └── resources │ ├── view │ └── amneziawg │ │ └── status.js │ └── protocol │ └── amneziawg.js ├── kmod-amneziawg ├── src │ └── Makefile ├── Makefile └── files │ └── 000-initial-amneziawg.patch ├── amneziawg-go └── Makefile ├── amneziawg-tools ├── Makefile └── files │ ├── amneziawg_watchdog │ └── amneziawg.sh └── .github └── workflows └── openwrt-awg.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Amnezia ipk's files for OpenWRT-23.05 Mediatek Filogic 2 | 3 | - Required packages and lib for runner or build machine https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem 4 | - For arm64 runner or build machine need external go bootstrap https://go.dev/doc/install 5 | - For success compile view workflow 6 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/root/usr/share/luci/menu.d/luci-proto-amneziawg.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin/status/amneziawg": { 3 | "title": "AmneziaWG", 4 | "order": 92, 5 | "action": { 6 | "type": "view", 7 | "path": "amneziawg/status" 8 | }, 9 | "depends": { 10 | "acl": [ "luci-proto-amneziawg" ], 11 | "uci": { "network": true } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016 Dan Luedtke 3 | # 4 | # This is free software, licensed under the Apache License, Version 2.0 . 5 | # 6 | 7 | include $(TOPDIR)/rules.mk 8 | 9 | PKG_MAINTAINER:=Amnezia Admin 10 | PKG_VERSION:=1.0.0-$(AUTORELEASE) 11 | LUCI_TITLE:=Support for AmneziaWG Web UI 12 | LUCI_DESCRIPTION:=Provides Web UI for AmneziaWG 13 | LUCI_DEPENDS:=+amneziawg-tools +ucode 14 | LUCI_PKGARCH:=all 15 | 16 | PKG_PROVIDES:=luci-proto-amneziawg 17 | 18 | include $(TOPDIR)/feeds/luci/luci.mk 19 | 20 | # call BuildPackage - OpenWrt buildroot signature 21 | -------------------------------------------------------------------------------- /kmod-amneziawg/src/Makefile: -------------------------------------------------------------------------------- 1 | # WIREGUARD_VERSION = 1.0.0-awg 2 | 3 | ccflags-y := -D'pr_fmt(fmt)=KBUILD_MODNAME ": " fmt' 4 | # ccflags-y += -D'WIREGUARD_VERSION="$(WIREGUARD_VERSION)"' 5 | # ccflags-y += -DDEBUG 6 | amneziawg-y := main.o 7 | amneziawg-y += noise.o 8 | amneziawg-y += device.o 9 | amneziawg-y += peer.o 10 | amneziawg-y += timers.o 11 | amneziawg-y += queueing.o 12 | amneziawg-y += send.o 13 | amneziawg-y += receive.o 14 | amneziawg-y += socket.o 15 | amneziawg-y += peerlookup.o 16 | amneziawg-y += allowedips.o 17 | amneziawg-y += ratelimiter.o 18 | amneziawg-y += cookie.o 19 | amneziawg-y += netlink.o 20 | obj-m := amneziawg.o 21 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/root/usr/share/rpcd/acl.d/luci-amneziawg.json: -------------------------------------------------------------------------------- 1 | { 2 | "luci-proto-amneziawg": { 3 | "description": "Grant access to LuCI AmneziaWG procedures", 4 | "read": { 5 | "file": { 6 | "/usr/bin/qrencode --inline --8bit --type=SVG --output=- -- *": [ "exec" ] 7 | }, 8 | "ubus": { 9 | "luci.amneziawg": [ 10 | "getWgInstances" 11 | ] 12 | }, 13 | "uci": [ "ddns", "system", "network" ] 14 | }, 15 | "write": { 16 | "ubus": { 17 | "luci.amneziawg": [ 18 | "generateKeyPair", 19 | "getPublicAndPrivateKeyFromPrivate", 20 | "generatePsk" 21 | ] 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /kmod-amneziawg/Makefile: -------------------------------------------------------------------------------- 1 | include $(TOPDIR)/rules.mk 2 | 3 | include $(INCLUDE_DIR)/kernel.mk 4 | 5 | PKG_NAME:=kmod-amneziawg 6 | PKG_VERSION:=1.0.0 7 | PKG_RELEASE:=$(AUTORELEASE) 8 | 9 | include $(INCLUDE_DIR)/package.mk 10 | 11 | define KernelPackage/amneziawg 12 | SECTION:=kernel 13 | CATEGORY:=Kernel modules 14 | SUBMENU:=Network Support 15 | URL:=https://amnezia.org/ 16 | MAINTAINER:=Amnezia Admin 17 | TITLE:=AmneziaWG Kernel Module 18 | FILES:=$(PKG_BUILD_DIR)/amneziawg.ko 19 | DEPENDS:= \ 20 | +kmod-udptunnel4 \ 21 | +kmod-udptunnel6 \ 22 | +kmod-crypto-lib-chacha20poly1305 \ 23 | +kmod-crypto-lib-curve25519 24 | endef 25 | 26 | define Build/Prepare 27 | cp -fr $(LINUX_DIR)/drivers/net/wireguard/{*.c,*.h,selftest/} $(PKG_BUILD_DIR) 28 | mkdir -p $(PKG_BUILD_DIR)/uapi 29 | cp -f $(LINUX_DIR)/include/uapi/linux/wireguard.h $(PKG_BUILD_DIR)/uapi/ 30 | for patch in `ls files/*`; do \ 31 | patch -d $(PKG_BUILD_DIR)/ -F3 -t -p0 -i "$$$$(pwd)/$$$${patch}"; \ 32 | done 33 | cp -f src/Makefile $(PKG_BUILD_DIR) 34 | endef 35 | 36 | define Build/Compile 37 | $(MAKE) -C "$(LINUX_DIR)" \ 38 | $(KERNEL_MAKE_FLAGS) \ 39 | M="$(PKG_BUILD_DIR)" \ 40 | EXTRA_CFLAGS="$(BUILDFLAGS)" \ 41 | modules 42 | endef 43 | 44 | $(eval $(call KernelPackage,amneziawg)) 45 | -------------------------------------------------------------------------------- /amneziawg-go/Makefile: -------------------------------------------------------------------------------- 1 | # This is free software, licensed under the MIT License. 2 | 3 | include $(TOPDIR)/rules.mk 4 | 5 | PKG_NAME:=amneziawg-go 6 | PKG_VERSION:=0.2.8 7 | PKG_RELEASE:=$(AUTORELEASE) 8 | 9 | PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz 10 | PKG_SOURCE_URL:=https://github.com/amnezia-vpn/amneziawg-go/archive/refs/tags/v${PKG_VERSION} 11 | PKG_HASH:=e26fa72eea51a8e8629de7ec8247eef16c8fddb05f15ef7133a04404f29c3d55 12 | 13 | PKG_LICENSE:=MIT 14 | PKG_LICENSE_FILES:=LICENSE 15 | 16 | PKG_BUILD_DEPENDS:=golang/host 17 | PKG_BUILD_PARALLEL:=1 18 | PKG_BUILD_FLAGS:=no-mips16 19 | 20 | GO_PKG:=github.com/amnezia-vpn/amneziawg-go 21 | GO_PKG_LDFLAGS_X:=\ 22 | main.Build=$(PKG_VERSION) 23 | 24 | include $(INCLUDE_DIR)/package.mk 25 | include ../../packages/lang/golang/golang-package.mk 26 | 27 | define Package/amneziawg-go 28 | SECTION:=net 29 | CATEGORY:=Network 30 | TITLE:=AmneziaWG userspace implementation program (amneziawg-go) 31 | DEPENDS:=$(GO_ARCH_DEPENDS) 32 | endef 33 | 34 | define Build/Compile 35 | $(call GoPackage/Build/Compile) 36 | endef 37 | 38 | define Package/amneziawg-go/description 39 | AmneziaWG is a contemporary version of the WireGuard protocol. It's a fork of 40 | WireGuard-Go and offers protection against detection by Deep Packet Inspection 41 | (DPI) systems. At the same time, it retains the simplified architecture and 42 | high performance of the original. 43 | endef 44 | 45 | define Package/amneziawg-go/install 46 | $(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR)) 47 | $(INSTALL_DIR) $(1)/usr/bin 48 | $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/amneziawg-go $(1)/usr/bin/amneziawg-go 49 | endef 50 | 51 | $(eval $(call BuildPackage,amneziawg-go)) 52 | -------------------------------------------------------------------------------- /amneziawg-tools/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016-2019 Jason A. Donenfeld 3 | # Copyright (C) 2016 Baptiste Jonglez 4 | # Copyright (C) 2016-2017 Dan Luedtke 5 | # 6 | # This is free software, licensed under the GNU General Public License v2. 7 | # See /LICENSE for more information. 8 | 9 | include $(TOPDIR)/rules.mk 10 | 11 | PKG_NAME:=amneziawg-tools 12 | PKG_VERSION:=1.0.20240213 13 | PKG_RELEASE:=$(AUTORELEASE) 14 | 15 | PKG_SOURCE:=v$(PKG_VERSION).tar.gz 16 | PKG_SOURCE_URL:=https://github.com/amnezia-vpn/amneziawg-tools/archive/refs/tags/ 17 | PKG_HASH:=4bde122630c9ddb1ec013c3e958f2c613b9eea56834674dda92fcb423c6f4d10 18 | 19 | PKG_MAINTAINER:=Amnezia Admin 20 | PKG_LICENSE:=GPL-2.0 21 | PKG_LICENSE_FILES:=COPYING 22 | 23 | PKG_BUILD_DIR=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) 24 | 25 | include $(INCLUDE_DIR)/package.mk 26 | 27 | MAKE_PATH:=src 28 | MAKE_VARS += PLATFORM=linux 29 | 30 | define Package/amneziawg-tools 31 | CATEGORY:=Network 32 | URL:=https://amnezia.org/ 33 | MAINTAINER:=Amnezia Admin 34 | TITLE:=AmneziaWG userspace control program (amneziawg) 35 | DEPENDS:= \ 36 | +@BUSYBOX_CONFIG_IP \ 37 | +@BUSYBOX_CONFIG_FEATURE_IP_LINK 38 | endef 39 | 40 | define Package/amneziawg-tools/description 41 | Amnezia VPN — simple and free app to run a self-hosted VPN with 42 | high privacy requirements. 43 | 44 | This package provides the userspace control program for AmneziaWG, 45 | `amneziawg`, a netifd protocol helper, and a re-resolve watchdog script. 46 | endef 47 | 48 | define Package/amneziawg-tools/install 49 | $(INSTALL_DIR) $(1)/usr/bin/ 50 | $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/wg $(1)/usr/bin/awg 51 | $(INSTALL_BIN) ./files/amneziawg_watchdog $(1)/usr/bin/ 52 | $(INSTALL_DIR) $(1)/lib/netifd/proto/ 53 | $(INSTALL_BIN) ./files/amneziawg.sh $(1)/lib/netifd/proto/ 54 | endef 55 | 56 | $(eval $(call BuildPackage,amneziawg-tools)) 57 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/root/usr/share/rpcd/ucode/luci.amneziawg: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Jo-Philipp Wich 2 | // Licensed to the public under the Apache License 2.0. 3 | 4 | 'use strict'; 5 | 6 | import { cursor } from 'uci'; 7 | import { popen } from 'fs'; 8 | 9 | 10 | function shellquote(s) { 11 | return `'${replace(s ?? '', "'", "'\\''")}'`; 12 | } 13 | 14 | function command(cmd) { 15 | return trim(popen(cmd)?.read?.('all')); 16 | } 17 | 18 | 19 | const methods = { 20 | generatePsk: { 21 | call: function() { 22 | return { psk: command('awg genpsk 2>/dev/null') }; 23 | } 24 | }, 25 | 26 | generateKeyPair: { 27 | call: function() { 28 | const priv = command('awg genkey 2>/dev/null'); 29 | const pub = command(`echo ${shellquote(priv)} | awg pubkey 2>/dev/null`); 30 | 31 | return { keys: { priv, pub } }; 32 | } 33 | }, 34 | 35 | getPublicAndPrivateKeyFromPrivate: { 36 | args: { privkey: "privkey" }, 37 | call: function(req) { 38 | const priv = req.args?.privkey; 39 | const pub = command(`echo ${shellquote(priv)} | awg pubkey 2>/dev/null`); 40 | 41 | return { keys: { priv, pub } }; 42 | } 43 | }, 44 | 45 | getWgInstances: { 46 | call: function() { 47 | const data = {}; 48 | let last_device; 49 | let qr_pubkey = {}; 50 | 51 | const uci = cursor(); 52 | const wg_dump = popen("awg show all dump 2>/dev/null"); 53 | 54 | if (wg_dump) { 55 | uci.load("network"); 56 | 57 | for (let line = wg_dump.read('line'); length(line); line = wg_dump.read('line')) { 58 | const record = split(rtrim(line, '\n'), '\t'); 59 | 60 | if (last_device != record[0]) { 61 | last_device = record[0]; 62 | data[last_device] = { 63 | name: last_device, 64 | public_key: record[2], 65 | listen_port: record[3], 66 | fwmark: record[4], 67 | peers: [] 68 | }; 69 | 70 | if (!length(record[2]) || record[2] == '(none)') 71 | qr_pubkey[last_device] = ''; 72 | else 73 | qr_pubkey[last_device] = `PublicKey = ${record[2]}`; 74 | } 75 | else { 76 | let peer_name; 77 | 78 | uci.foreach('network', `amneziawg_${last_device}`, (s) => { 79 | if (s.public_key == record[1]) 80 | peer_name = s.description; 81 | }); 82 | 83 | const peer = { 84 | name: peer_name, 85 | public_key: record[1], 86 | endpoint: record[3], 87 | allowed_ips: [], 88 | latest_handshake: record[5], 89 | transfer_rx: record[6], 90 | transfer_tx: record[7], 91 | persistent_keepalive: record[8] 92 | }; 93 | 94 | if (record[3] != '(none)' && length(record[4])) 95 | push(peer.allowed_ips, ...split(record[4], ',')); 96 | 97 | push(data[last_device].peers, peer); 98 | } 99 | } 100 | } 101 | 102 | return data; 103 | } 104 | } 105 | }; 106 | 107 | return { 'luci.amneziawg': methods }; 108 | -------------------------------------------------------------------------------- /amneziawg-tools/files/amneziawg_watchdog: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: GPL-2.0 3 | # 4 | # Copyright (C) 2018 Aleksandr V. Piskunov . 5 | # Copyright (C) 2015-2018 Jason A. Donenfeld . All Rights Reserved. 6 | # 7 | # This watchdog script tries to re-resolve hostnames for inactive WireGuard peers. 8 | # Use it for peers with a frequently changing dynamic IP. 9 | # persistent_keepalive must be set, recommended value is 25 seconds. 10 | # 11 | # Run this script from cron every minute: 12 | # echo '* * * * * /usr/bin/wireguard_watchdog' >> /etc/crontabs/root 13 | 14 | . /lib/functions.sh 15 | 16 | check_peer_activity() { 17 | local cfg="$1" 18 | local iface="$2" 19 | local disabled 20 | local public_key 21 | local endpoint_host 22 | local endpoint_port 23 | local persistent_keepalive 24 | local last_handshake 25 | local idle_seconds 26 | 27 | config_get_bool disabled "${cfg}" "disabled" 0 28 | config_get public_key "${cfg}" "public_key" 29 | config_get endpoint_host "${cfg}" "endpoint_host" 30 | config_get endpoint_port "${cfg}" "endpoint_port" 31 | 32 | if [ "${disabled}" -eq 1 ]; then 33 | # skip disabled peers 34 | return 0 35 | fi 36 | 37 | persistent_keepalive=$(awg show "${iface}" persistent-keepalive | grep "${public_key}" | awk '{print $2}') 38 | 39 | # only process peers with endpoints and keepalive set 40 | [ -z "${endpoint_host}" ] && return 0; 41 | if [ -z "${persistent_keepalive}" ] || [ "${persistent_keepalive}" = "off" ]; then return 0; fi 42 | 43 | # skip IP addresses 44 | # check taken from packages/net/ddns-scripts/files/dynamic_dns_functions.sh 45 | local IPV4_REGEX="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" 46 | 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,\}\)" 47 | local IPV4="$(echo "${endpoint_host}" | grep -m 1 -o "$IPV4_REGEX$")" # do not detect ip in 0.0.0.0.example.com 48 | local IPV6="$(echo "${endpoint_host}" | grep -m 1 -o "$IPV6_REGEX")" 49 | [ -n "${IPV4}" -o -n "${IPV6}" ] && return 0; 50 | 51 | # re-resolve endpoint hostname if not responding for too long 52 | last_handshake=$(awg show "${iface}" latest-handshakes | grep "${public_key}" | awk '{print $2}') 53 | [ -z "${last_handshake}" ] && return 0; 54 | idle_seconds=$(($(date +%s)-"${last_handshake}")) 55 | [ ${idle_seconds} -lt 150 ] && return 0; 56 | logger -t "amneziawg_monitor" "${iface} endpoint ${endpoint_host}:${endpoint_port} is not responding for ${idle_seconds} seconds, trying to re-resolve hostname" 57 | awg set "${iface}" peer "${public_key}" endpoint "${endpoint_host}:${endpoint_port}" 58 | } 59 | 60 | # query ubus for all active wireguard interfaces 61 | eval $(ubus -S call network.interface dump | jsonfilter -e 'wg_ifaces=@.interface[@.up=true && @.proto="amneziawg"].interface') 62 | 63 | # check every peer in every active wireguard interface 64 | config_load network 65 | for iface in $wg_ifaces; do 66 | config_foreach check_peer_activity "amneziawg_${iface}" "${iface}" 67 | done 68 | -------------------------------------------------------------------------------- /.github/workflows/openwrt-awg.yml: -------------------------------------------------------------------------------- 1 | name: Create Release Mediatek Filogic 2 | #on: [push] 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build: 10 | name: "v${{ matrix.tag }} - ${{ matrix.build_env.pkgarch}} :: ${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}} build" 11 | runs-on: Linux 12 | #runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | tag: ['23.05.5'] 16 | build_env: 17 | - pkgarch: aarch64_cortex-a53 18 | target: mediatek 19 | subtarget: filogic 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | repository: openwrt/openwrt 24 | ref: v${{ matrix.tag }} 25 | fetch-depth: 0 26 | 27 | - name: Update and install feeds for OpenWRT_v${{ matrix.tag }} ${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}} 28 | run: | 29 | pkgarch=${{ matrix.build_env.pkgarch}} 30 | target=${{ matrix.build_env.target}} 31 | subtarget=${{ matrix.build_env.subtarget}} 32 | 33 | echo "pkgarch: ${pkgarch}, target: ${target}, subtarget: ${subtarget}" 34 | 35 | rm -rf bin/packages/${pkgarch}/awgopenwrt/ || true 36 | rm -rf bin/targets/${target}/${subtarget}/packages/ || true 37 | 38 | wget https://downloads.openwrt.org/releases/${{ matrix.tag }}/targets/${target}/${subtarget}/feeds.buildinfo -O feeds.conf 39 | 40 | echo "src-git awgopenwrt https://github.com/lolo6oT/awg-openwrt.git" >> ./feeds.conf 41 | 42 | ./scripts/feeds update -a 43 | ./scripts/feeds install -a 44 | 45 | wget https://downloads.openwrt.org/releases/${{ matrix.tag }}/targets/${target}/${subtarget}/config.buildinfo -O .config 46 | 47 | make defconfig 48 | 49 | - name: Make Download and World OpenWRT_v${{ matrix.tag }} ${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}} 50 | run: | 51 | export FORCE_UNSAFE_CONFIGURE=1 52 | echo "CONFIG_PACKAGE_kmod-amneziawg=m" >> .config 53 | echo "CONFIG_PACKAGE_amneziawg-tools=y" >> .config 54 | echo "CONFIG_PACKAGE_luci-proto-amneziawg=y" >> .config 55 | echo "CONFIG_PACKAGE_amneziawg-go=y" >> .config 56 | 57 | if [[ $(uname -m) == "aarch64" ]]; then 58 | echo 'CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT="/usr/local/go/bin/go"' >> .config 59 | echo 'CONFIG_GOLANG_BUILD_CACHE_DIR=""' >> .config 60 | fi 61 | 62 | make defconfig 63 | 64 | #make -j $(nproc) V=sc download world 65 | echo "Make tools/install" 66 | make tools/install -i -j $(nproc) 67 | echo "Make toolchain/install" 68 | make toolchain/install -i -j $(nproc) 69 | echo "Make Kernel Compile" 70 | make target/linux/compile -i -j $(nproc) 71 | 72 | - name: Make ipks amnezia_${{ github.ref_name }} for original OpenWRT_v${{ matrix.tag }} ${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}} 73 | run: | 74 | make -j $(nproc) package/amneziawg-tools/{clean,download,prepare,compile} 75 | make -j $(nproc) package/amneziawg-go/{clean,download,prepare,compile} 76 | make -j $(nproc) package/kmod-amneziawg/{clean,download,prepare,compile} 77 | make -j $(nproc) package/luci-proto-amneziawg/{clean,download,prepare,compile} 78 | 79 | - name: Prepare artifacts OpenWRT_v${{ matrix.tag }} ${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}} 80 | run: | 81 | tag_name=${{ github.ref_name }} 82 | mkdir -p awgrelease 83 | postfix="${tag_name}_v${{ matrix.tag }}_${{ matrix.build_env.pkgarch}}_${{ matrix.build_env.target}}_${{ matrix.build_env.subtarget}}" 84 | cp bin/packages/${{ matrix.build_env.pkgarch }}/awgopenwrt/amneziawg-tools_*.ipk awgrelease/amneziawg-tools_${postfix}.ipk 85 | cp bin/packages/${{ matrix.build_env.pkgarch }}/awgopenwrt/amneziawg-go_*.ipk awgrelease/amneziawg-go_${postfix}.ipk 86 | cp bin/packages/${{ matrix.build_env.pkgarch }}/awgopenwrt/luci-proto-amneziawg_*.ipk awgrelease/luci-proto-amneziawg_${postfix}.ipk 87 | cp bin/targets/${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}}/packages/kmod-amneziawg_*.ipk awgrelease/kmod-amneziawg_${postfix}.ipk 88 | 89 | - name: Release 90 | uses: softprops/action-gh-release@v1 91 | with: 92 | files: awgrelease/*.ipk 93 | 94 | - name: Cleanup 95 | run: | 96 | rm feeds.conf || true 97 | rm -rf awgrelease || true 98 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/htdocs/luci-static/resources/view/amneziawg/status.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 'require view'; 3 | 'require rpc'; 4 | 'require poll'; 5 | 'require dom'; 6 | 'require ui'; 7 | 8 | 9 | var callGetWgInstances = rpc.declare({ 10 | object: 'luci.amneziawg', 11 | method: 'getWgInstances' 12 | }); 13 | 14 | function timestampToStr(timestamp) { 15 | if (timestamp < 1) 16 | return _('Never', 'No AmneziaWG peer handshake yet'); 17 | 18 | var seconds = (Date.now() / 1000) - timestamp; 19 | var ago; 20 | 21 | if (seconds < 60) 22 | ago = _('%ds ago').format(seconds); 23 | else if (seconds < 3600) 24 | ago = _('%dm ago').format(seconds / 60); 25 | else if (seconds < 86401) 26 | ago = _('%dh ago').format(seconds / 3600); 27 | else 28 | ago = _('over a day ago'); 29 | 30 | return (new Date(timestamp * 1000)).toUTCString() + ' (' + ago + ')'; 31 | } 32 | 33 | function handleInterfaceDetails(iface) { 34 | ui.showModal(_('Instance Details'), [ 35 | ui.itemlist(E([]), [ 36 | _('Name'), iface.name, 37 | _('Public Key'), E('code', [ iface.public_key ]), 38 | _('Listen Port'), iface.listen_port, 39 | _('Firewall Mark'), iface.fwmark != 'off' ? iface.fwmark : E('em', _('none')) 40 | ]), 41 | E('div', { 'class': 'right' }, [ 42 | E('button', { 43 | 'class': 'btn cbi-button', 44 | 'click': ui.hideModal 45 | }, [ _('Dismiss') ]) 46 | ]) 47 | ]); 48 | } 49 | 50 | function handlePeerDetails(peer) { 51 | ui.showModal(_('Peer Details'), [ 52 | ui.itemlist(E([]), [ 53 | _('Description'), peer.name, 54 | _('Public Key'), E('code', [ peer.public_key ]), 55 | _('Endpoint'), peer.endpoint, 56 | _('Allowed IPs'), (Array.isArray(peer.allowed_ips) && peer.allowed_ips.length) ? peer.allowed_ips.join(', ') : E('em', _('none')), 57 | _('Received Data'), '%1024mB'.format(peer.transfer_rx), 58 | _('Transmitted Data'), '%1024mB'.format(peer.transfer_tx), 59 | _('Latest Handshake'), timestampToStr(+peer.latest_handshake), 60 | _('Keep-Alive'), (peer.persistent_keepalive != 'off') ? _('every %ds', 'AmneziaWG keep alive interval').format(+peer.persistent_keepalive) : E('em', _('none')), 61 | ]), 62 | E('div', { 'class': 'right' }, [ 63 | E('button', { 64 | 'class': 'btn cbi-button', 65 | 'click': ui.hideModal 66 | }, [ _('Dismiss') ]) 67 | ]) 68 | ]); 69 | } 70 | 71 | function renderPeerTable(instanceName, peers) { 72 | var t = new L.ui.Table( 73 | [ 74 | _('Peer'), 75 | _('Endpoint'), 76 | _('Data Received'), 77 | _('Data Transmitted'), 78 | _('Latest Handshake') 79 | ], 80 | { 81 | id: 'peers-' + instanceName 82 | }, 83 | E('em', [ 84 | _('No peers connected') 85 | ]) 86 | ); 87 | 88 | t.update(peers.map(function(peer) { 89 | return [ 90 | [ 91 | peer.name || '', 92 | E('div', { 93 | 'style': 'cursor:pointer', 94 | 'click': ui.createHandlerFn(this, handlePeerDetails, peer) 95 | }, [ 96 | E('p', [ 97 | peer.name ? E('span', [ peer.name ]) : E('em', [ _('Untitled peer') ]) 98 | ]), 99 | E('span', { 100 | 'class': 'ifacebadge hide-sm', 101 | 'data-tooltip': _('Public key: %h', 'Tooltip displaying full AmneziaWG peer public key').format(peer.public_key) 102 | }, [ 103 | E('code', [ peer.public_key.replace(/^(.{5}).+(.{6})$/, '$1…$2') ]) 104 | ]) 105 | ]) 106 | ], 107 | peer.endpoint, 108 | [ +peer.transfer_rx, '%1024mB'.format(+peer.transfer_rx) ], 109 | [ +peer.transfer_tx, '%1024mB'.format(+peer.transfer_tx) ], 110 | [ +peer.latest_handshake, timestampToStr(+peer.latest_handshake) ] 111 | ]; 112 | })); 113 | 114 | return t.render(); 115 | } 116 | 117 | return view.extend({ 118 | renderIfaces: function(ifaces) { 119 | var res = [ 120 | E('h2', [ _('AmneziaWG Status') ]) 121 | ]; 122 | 123 | for (var instanceName in ifaces) { 124 | res.push( 125 | E('h3', [ _('Instance "%h"', 'AmneziaWG instance heading').format(instanceName) ]), 126 | E('p', { 127 | 'style': 'cursor:pointer', 128 | 'click': ui.createHandlerFn(this, handleInterfaceDetails, ifaces[instanceName]) 129 | }, [ 130 | E('span', { 'class': 'ifacebadge' }, [ 131 | E('img', { 'src': L.resource('icons', 'tunnel.png') }), 132 | '\xa0', 133 | instanceName 134 | ]), 135 | E('span', { 'style': 'opacity:.8' }, [ 136 | ' · ', 137 | _('Port %d', 'AmneziaWG listen port').format(ifaces[instanceName].listen_port), 138 | ' · ', 139 | E('code', { 'click': '' }, [ ifaces[instanceName].public_key ]) 140 | ]) 141 | ]), 142 | renderPeerTable(instanceName, ifaces[instanceName].peers) 143 | ); 144 | } 145 | 146 | if (res.length == 1) 147 | res.push(E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [ 148 | E('em', [ _('No AmneziaWG interfaces configured.') ]) 149 | ])); 150 | 151 | return E([], res); 152 | }, 153 | 154 | render: function() { 155 | poll.add(L.bind(function () { 156 | return callGetWgInstances().then(L.bind(function(ifaces) { 157 | dom.content( 158 | document.querySelector('#view'), 159 | this.renderIfaces(ifaces) 160 | ); 161 | }, this)); 162 | }, this), 5); 163 | 164 | return E([], [ 165 | E('h2', [ _('AmneziaWG Status') ]), 166 | E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [ 167 | E('em', [ _('Loading data…') ]) 168 | ]) 169 | ]); 170 | }, 171 | 172 | handleReset: null, 173 | handleSaveApply: null, 174 | handleSave: null 175 | }); 176 | -------------------------------------------------------------------------------- /amneziawg-tools/files/amneziawg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2016-2017 Dan Luedtke 3 | # Licensed to the public under the Apache License 2.0. 4 | 5 | WG=/usr/bin/awg 6 | if [ ! -x $WG ]; then 7 | logger -t "amneziawg" "error: missing amneziawg-tools (${WG})" 8 | exit 0 9 | fi 10 | 11 | [ -n "$INCLUDE_ONLY" ] || { 12 | . /lib/functions.sh 13 | . ../netifd-proto.sh 14 | init_proto "$@" 15 | } 16 | 17 | proto_amneziawg_init_config() { 18 | proto_config_add_string "private_key" 19 | proto_config_add_int "listen_port" 20 | proto_config_add_int "mtu" 21 | proto_config_add_string "fwmark" 22 | proto_config_add_int "awg_jc" 23 | proto_config_add_int "awg_jmin" 24 | proto_config_add_int "awg_jmax" 25 | proto_config_add_int "awg_s1" 26 | proto_config_add_int "awg_s2" 27 | proto_config_add_int "awg_h1" 28 | proto_config_add_int "awg_h2" 29 | proto_config_add_int "awg_h3" 30 | proto_config_add_int "awg_h4" 31 | available=1 32 | no_proto_task=1 33 | } 34 | 35 | proto_amneziawg_is_kernel_mode() { 36 | if [ ! -e /sys/module/amneziawg ]; then 37 | modprobe amneziawg >/dev/null 2>&1 || true 38 | 39 | if [ -e /sys/module/amneziawg ]; then 40 | return 0 41 | else 42 | if ! command -v "${WG_QUICK_USERSPACE_IMPLEMENTATION:-amneziawg-go}" >/dev/null; then 43 | ret=$? 44 | echo "Please install either kernel module (kmod-amneziawg package) or user-space implementation in /usr/bin/amneziawg-go." 45 | exit $ret 46 | else 47 | return 1 48 | fi 49 | fi 50 | else 51 | return 0 52 | fi 53 | } 54 | 55 | proto_amneziawg_setup_peer() { 56 | local peer_config="$1" 57 | 58 | local disabled 59 | local public_key 60 | local preshared_key 61 | local allowed_ips 62 | local route_allowed_ips 63 | local endpoint_host 64 | local endpoint_port 65 | local persistent_keepalive 66 | 67 | config_get_bool disabled "${peer_config}" "disabled" 0 68 | config_get public_key "${peer_config}" "public_key" 69 | config_get preshared_key "${peer_config}" "preshared_key" 70 | config_get allowed_ips "${peer_config}" "allowed_ips" 71 | config_get_bool route_allowed_ips "${peer_config}" "route_allowed_ips" 0 72 | config_get endpoint_host "${peer_config}" "endpoint_host" 73 | config_get endpoint_port "${peer_config}" "endpoint_port" 74 | config_get persistent_keepalive "${peer_config}" "persistent_keepalive" 75 | 76 | if [ "${disabled}" -eq 1 ]; then 77 | # skip disabled peers 78 | return 0 79 | fi 80 | 81 | if [ -z "$public_key" ]; then 82 | echo "Skipping peer config $peer_config because public key is not defined." 83 | return 0 84 | fi 85 | 86 | echo "[Peer]" >> "${wg_cfg}" 87 | echo "PublicKey=${public_key}" >> "${wg_cfg}" 88 | if [ "${preshared_key}" ]; then 89 | echo "PresharedKey=${preshared_key}" >> "${wg_cfg}" 90 | fi 91 | for allowed_ip in $allowed_ips; do 92 | echo "AllowedIPs=${allowed_ip}" >> "${wg_cfg}" 93 | done 94 | if [ "${endpoint_host}" ]; then 95 | case "${endpoint_host}" in 96 | *:*) 97 | endpoint="[${endpoint_host}]" 98 | ;; 99 | *) 100 | endpoint="${endpoint_host}" 101 | ;; 102 | esac 103 | if [ "${endpoint_port}" ]; then 104 | endpoint="${endpoint}:${endpoint_port}" 105 | else 106 | endpoint="${endpoint}:51820" 107 | fi 108 | echo "Endpoint=${endpoint}" >> "${wg_cfg}" 109 | fi 110 | if [ "${persistent_keepalive}" ]; then 111 | echo "PersistentKeepalive=${persistent_keepalive}" >> "${wg_cfg}" 112 | fi 113 | 114 | if [ "${route_allowed_ips}" -ne 0 ]; then 115 | for allowed_ip in ${allowed_ips}; do 116 | case "${allowed_ip}" in 117 | *:*/*) 118 | proto_add_ipv6_route "${allowed_ip%%/*}" "${allowed_ip##*/}" 119 | ;; 120 | *.*/*) 121 | proto_add_ipv4_route "${allowed_ip%%/*}" "${allowed_ip##*/}" 122 | ;; 123 | *:*) 124 | proto_add_ipv6_route "${allowed_ip%%/*}" "128" 125 | ;; 126 | *.*) 127 | proto_add_ipv4_route "${allowed_ip%%/*}" "32" 128 | ;; 129 | esac 130 | done 131 | fi 132 | } 133 | 134 | ensure_key_is_generated() { 135 | local private_key 136 | private_key="$(uci get network."$1".private_key)" 137 | 138 | if [ "$private_key" == "generate" ]; then 139 | local ucitmp 140 | oldmask="$(umask)" 141 | umask 077 142 | ucitmp="$(mktemp -d)" 143 | private_key="$("${WG}" genkey)" 144 | uci -q -t "$ucitmp" set network."$1".private_key="$private_key" && \ 145 | uci -q -t "$ucitmp" commit network 146 | rm -rf "$ucitmp" 147 | umask "$oldmask" 148 | fi 149 | } 150 | 151 | proto_amneziawg_setup() { 152 | local config="$1" 153 | local wg_dir="/tmp/wireguard" 154 | local wg_cfg="${wg_dir}/${config}" 155 | 156 | local private_key 157 | local listen_port 158 | local addresses 159 | local mtu 160 | local fwmark 161 | local ip6prefix 162 | local nohostroute 163 | local tunlink 164 | 165 | # Amnezia WG specific parameters 166 | local awg_jc 167 | local awg_jmin 168 | local awg_jmax 169 | local awg_s1 170 | local awg_s2 171 | local awg_h1 172 | local awg_h2 173 | local awg_h3 174 | local awg_h4 175 | 176 | ensure_key_is_generated "${config}" 177 | 178 | config_load network 179 | config_get private_key "${config}" "private_key" 180 | config_get listen_port "${config}" "listen_port" 181 | config_get addresses "${config}" "addresses" 182 | config_get mtu "${config}" "mtu" 183 | config_get fwmark "${config}" "fwmark" 184 | config_get ip6prefix "${config}" "ip6prefix" 185 | config_get nohostroute "${config}" "nohostroute" 186 | config_get tunlink "${config}" "tunlink" 187 | 188 | config_get awg_jc "${config}" "awg_jc" 189 | config_get awg_jmin "${config}" "awg_jmin" 190 | config_get awg_jmax "${config}" "awg_jmax" 191 | config_get awg_s1 "${config}" "awg_s1" 192 | config_get awg_s2 "${config}" "awg_s2" 193 | config_get awg_h1 "${config}" "awg_h1" 194 | config_get awg_h2 "${config}" "awg_h2" 195 | config_get awg_h3 "${config}" "awg_h3" 196 | config_get awg_h4 "${config}" "awg_h4" 197 | 198 | if proto_amneziawg_is_kernel_mode; then 199 | logger -t "amneziawg" "info: using kernel-space kmod-amneziawg for ${WG}" 200 | ip link del dev "${config}" 2>/dev/null 201 | ip link add dev "${config}" type amneziawg 202 | else 203 | logger -t "amneziawg" "info: using user-space amneziawg-go for ${WG}" 204 | rm -f "/var/run/wireguard/${config}.sock" 205 | amneziawg-go "${config}" 206 | fi 207 | 208 | if [ "${mtu}" ]; then 209 | ip link set mtu "${mtu}" dev "${config}" 210 | fi 211 | 212 | proto_init_update "${config}" 1 213 | 214 | umask 077 215 | mkdir -p "${wg_dir}" 216 | echo "[Interface]" > "${wg_cfg}" 217 | echo "PrivateKey=${private_key}" >> "${wg_cfg}" 218 | if [ "${listen_port}" ]; then 219 | echo "ListenPort=${listen_port}" >> "${wg_cfg}" 220 | fi 221 | if [ "${fwmark}" ]; then 222 | echo "FwMark=${fwmark}" >> "${wg_cfg}" 223 | fi 224 | # AWG 225 | if [ "${awg_jc}" ]; then 226 | echo "Jc = ${awg_jc}" >> "${wg_cfg}" 227 | fi 228 | if [ "${awg_jmin}" ]; then 229 | echo "Jmin = ${awg_jmin}" >> "${wg_cfg}" 230 | fi 231 | if [ "${awg_jmax}" ]; then 232 | echo "Jmax = ${awg_jmax}" >> "${wg_cfg}" 233 | fi 234 | if [ "${awg_s1}" ]; then 235 | echo "S1 = ${awg_s1}" >> "${wg_cfg}" 236 | fi 237 | if [ "${awg_s2}" ]; then 238 | echo "S2 = ${awg_s2}" >> "${wg_cfg}" 239 | fi 240 | if [ "${awg_h1}" ]; then 241 | echo "H1 = ${awg_h1}" >> "${wg_cfg}" 242 | fi 243 | if [ "${awg_h2}" ]; then 244 | echo "H2 = ${awg_h2}" >> "${wg_cfg}" 245 | fi 246 | if [ "${awg_h3}" ]; then 247 | echo "H3 = ${awg_h3}" >> "${wg_cfg}" 248 | fi 249 | if [ "${awg_h4}" ]; then 250 | echo "H4 = ${awg_h4}" >> "${wg_cfg}" 251 | fi 252 | 253 | config_foreach proto_amneziawg_setup_peer "amneziawg_${config}" 254 | 255 | # apply configuration file 256 | ${WG} setconf "${config}" "${wg_cfg}" 257 | WG_RETURN=$? 258 | 259 | rm -f "${wg_cfg}" 260 | 261 | if [ ${WG_RETURN} -ne 0 ]; then 262 | sleep 5 263 | proto_setup_failed "${config}" 264 | exit 1 265 | fi 266 | 267 | for address in ${addresses}; do 268 | case "${address}" in 269 | *:*/*) 270 | proto_add_ipv6_address "${address%%/*}" "${address##*/}" 271 | ;; 272 | *.*/*) 273 | proto_add_ipv4_address "${address%%/*}" "${address##*/}" 274 | ;; 275 | *:*) 276 | proto_add_ipv6_address "${address%%/*}" "128" 277 | ;; 278 | *.*) 279 | proto_add_ipv4_address "${address%%/*}" "32" 280 | ;; 281 | esac 282 | done 283 | 284 | for prefix in ${ip6prefix}; do 285 | proto_add_ipv6_prefix "$prefix" 286 | done 287 | 288 | # endpoint dependency 289 | if [ "${nohostroute}" != "1" ]; then 290 | awg show "${config}" endpoints | \ 291 | sed -E 's/\[?([0-9.:a-f]+)\]?:([0-9]+)/\1 \2/' | \ 292 | while IFS=$'\t ' read -r key address port; do 293 | [ -n "${port}" ] || continue 294 | proto_add_host_dependency "${config}" "${address}" "${tunlink}" 295 | done 296 | fi 297 | 298 | proto_send_update "${config}" 299 | } 300 | 301 | proto_amneziawg_teardown() { 302 | local config="$1" 303 | if proto_amneziawg_is_kernel_mode; then 304 | ip link del dev "${config}" >/dev/null 2>&1 305 | else 306 | rm -f "/var/run/amneziawg/${config}.sock" 307 | fi 308 | } 309 | 310 | [ -n "$INCLUDE_ONLY" ] || { 311 | add_protocol amneziawg 312 | } 313 | -------------------------------------------------------------------------------- /kmod-amneziawg/files/000-initial-amneziawg.patch: -------------------------------------------------------------------------------- 1 | diff --git cookie.c cookie.c 2 | index 8b7d1fe..3120094 100644 3 | --- cookie.c 4 | +++ cookie.c 5 | @@ -179,13 +179,13 @@ void wg_cookie_add_mac_to_packet(void *message, size_t len, 6 | 7 | void wg_cookie_message_create(struct message_handshake_cookie *dst, 8 | struct sk_buff *skb, __le32 index, 9 | - struct cookie_checker *checker) 10 | + struct cookie_checker *checker, u32 message_type) 11 | { 12 | struct message_macs *macs = (struct message_macs *) 13 | ((u8 *)skb->data + skb->len - sizeof(*macs)); 14 | u8 cookie[COOKIE_LEN]; 15 | 16 | - dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE); 17 | + dst->header.type = cpu_to_le32(message_type); 18 | dst->receiver_index = index; 19 | get_random_bytes_wait(dst->nonce, COOKIE_NONCE_LEN); 20 | 21 | diff --git cookie.h cookie.h 22 | index c4bd61c..2b50660 100644 23 | --- cookie.h 24 | +++ cookie.h 25 | @@ -52,7 +52,7 @@ void wg_cookie_add_mac_to_packet(void *message, size_t len, 26 | 27 | void wg_cookie_message_create(struct message_handshake_cookie *src, 28 | struct sk_buff *skb, __le32 index, 29 | - struct cookie_checker *checker); 30 | + struct cookie_checker *checker, u32 message_type); 31 | void wg_cookie_message_consume(struct message_handshake_cookie *src, 32 | struct wg_device *wg); 33 | 34 | diff --git device.c device.c 35 | index 062490f..40c4f1c 100644 36 | --- device.c 37 | +++ device.c 38 | @@ -377,6 +377,11 @@ static int wg_newlink(struct net *src_net, struct net_device *dev, 39 | */ 40 | dev->priv_destructor = wg_destruct; 41 | 42 | + wg->advanced_security_config.init_packet_magic_header = MESSAGE_HANDSHAKE_INITIATION; 43 | + wg->advanced_security_config.response_packet_magic_header = MESSAGE_HANDSHAKE_RESPONSE; 44 | + wg->advanced_security_config.cookie_packet_magic_header = MESSAGE_HANDSHAKE_COOKIE; 45 | + wg->advanced_security_config.transport_packet_magic_header = MESSAGE_DATA; 46 | + 47 | pr_debug("%s: Interface created\n", dev->name); 48 | return ret; 49 | 50 | @@ -473,3 +478,118 @@ void wg_device_uninit(void) 51 | #endif 52 | rcu_barrier(); 53 | } 54 | + 55 | +int wg_device_handle_post_config(struct net_device *dev, struct amnezia_config *asc) 56 | +{ 57 | + struct wg_device *wg = netdev_priv(dev); 58 | + bool a_sec_on = false; 59 | + int ret = 0; 60 | + 61 | + if (!asc->advanced_security_enabled) 62 | + goto out; 63 | + 64 | + if (asc->junk_packet_count < 0) { 65 | + net_dbg_ratelimited("%s: JunkPacketCount should be non negative\n", dev->name); 66 | + ret = -EINVAL; 67 | + } 68 | + 69 | + wg->advanced_security_config.junk_packet_count = asc->junk_packet_count; 70 | + if (asc->junk_packet_count != 0) 71 | + a_sec_on = true; 72 | + 73 | + wg->advanced_security_config.junk_packet_min_size = asc->junk_packet_min_size; 74 | + if (asc->junk_packet_min_size != 0) 75 | + a_sec_on = true; 76 | + 77 | + if (asc->junk_packet_count > 0 && asc->junk_packet_min_size == asc->junk_packet_max_size) 78 | + asc->junk_packet_max_size++; 79 | + 80 | + if (asc->junk_packet_max_size >= MESSAGE_MAX_SIZE) { 81 | + wg->advanced_security_config.junk_packet_min_size = 0; 82 | + wg->advanced_security_config.junk_packet_max_size = 1; 83 | + 84 | + net_dbg_ratelimited("%s: JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d\n", 85 | + dev->name, asc->junk_packet_max_size, 86 | + MESSAGE_MAX_SIZE); 87 | + ret = -EINVAL; 88 | + } else if (asc->junk_packet_max_size < asc->junk_packet_min_size) { 89 | + net_dbg_ratelimited("%s: maxSize: %d; should be greater than minSize: %d\n", 90 | + dev->name, asc->junk_packet_max_size, 91 | + asc->junk_packet_min_size); 92 | + ret = -EINVAL; 93 | + } else 94 | + wg->advanced_security_config.junk_packet_max_size = asc->junk_packet_max_size; 95 | + 96 | + if (asc->junk_packet_max_size != 0) 97 | + a_sec_on = true; 98 | + 99 | + if (asc->init_packet_junk_size + MESSAGE_INITIATION_SIZE >= MESSAGE_MAX_SIZE) { 100 | + net_dbg_ratelimited("%s: init header size (%d) + junkSize (%d) should be smaller than maxSegmentSize: %d\n", 101 | + dev->name, MESSAGE_INITIATION_SIZE, 102 | + asc->init_packet_junk_size, MESSAGE_MAX_SIZE); 103 | + ret = -EINVAL; 104 | + } else 105 | + wg->advanced_security_config.init_packet_junk_size = asc->init_packet_junk_size; 106 | + 107 | + if (asc->init_packet_junk_size != 0) 108 | + a_sec_on = true; 109 | + 110 | + if (asc->response_packet_junk_size + MESSAGE_RESPONSE_SIZE >= MESSAGE_MAX_SIZE) { 111 | + net_dbg_ratelimited("%s: response header size (%d) + junkSize (%d) should be smaller than maxSegmentSize: %d\n", 112 | + dev->name, MESSAGE_RESPONSE_SIZE, 113 | + asc->response_packet_junk_size, MESSAGE_MAX_SIZE); 114 | + ret = -EINVAL; 115 | + } else 116 | + wg->advanced_security_config.response_packet_junk_size = asc->response_packet_junk_size; 117 | + 118 | + if (asc->response_packet_junk_size != 0) 119 | + a_sec_on = true; 120 | + 121 | + if (asc->init_packet_magic_header > MESSAGE_DATA) { 122 | + a_sec_on = true; 123 | + wg->advanced_security_config.init_packet_magic_header = asc->init_packet_magic_header; 124 | + } 125 | + 126 | + if (asc->response_packet_magic_header > MESSAGE_DATA) { 127 | + a_sec_on = true; 128 | + wg->advanced_security_config.response_packet_magic_header = asc->response_packet_magic_header; 129 | + } 130 | + 131 | + if (asc->cookie_packet_magic_header > MESSAGE_DATA) { 132 | + a_sec_on = true; 133 | + wg->advanced_security_config.cookie_packet_magic_header = asc->cookie_packet_magic_header; 134 | + } 135 | + 136 | + if (asc->transport_packet_magic_header > MESSAGE_DATA) { 137 | + a_sec_on = true; 138 | + wg->advanced_security_config.transport_packet_magic_header = asc->transport_packet_magic_header; 139 | + } 140 | + 141 | + if (wg->advanced_security_config.init_packet_magic_header == wg->advanced_security_config.response_packet_magic_header || 142 | + wg->advanced_security_config.init_packet_magic_header == wg->advanced_security_config.cookie_packet_magic_header || 143 | + wg->advanced_security_config.init_packet_magic_header == wg->advanced_security_config.transport_packet_magic_header || 144 | + wg->advanced_security_config.response_packet_magic_header == wg->advanced_security_config.cookie_packet_magic_header || 145 | + wg->advanced_security_config.response_packet_magic_header == wg->advanced_security_config.transport_packet_magic_header || 146 | + wg->advanced_security_config.cookie_packet_magic_header == wg->advanced_security_config.transport_packet_magic_header) { 147 | + net_dbg_ratelimited("%s: magic headers should differ; got: init:%d; recv:%d; unde:%d; tran:%d\n", 148 | + dev->name, 149 | + wg->advanced_security_config.init_packet_magic_header, 150 | + wg->advanced_security_config.response_packet_magic_header, 151 | + wg->advanced_security_config.cookie_packet_magic_header, 152 | + wg->advanced_security_config.transport_packet_magic_header); 153 | + ret = -EINVAL; 154 | + } 155 | + 156 | + if (MESSAGE_INITIATION_SIZE + wg->advanced_security_config.init_packet_junk_size == 157 | + MESSAGE_RESPONSE_SIZE + wg->advanced_security_config.response_packet_junk_size) { 158 | + net_dbg_ratelimited("%s: new init size:%d; and new response size:%d; should differ\n", 159 | + dev->name, 160 | + MESSAGE_INITIATION_SIZE + asc->init_packet_junk_size, 161 | + MESSAGE_RESPONSE_SIZE + asc->response_packet_junk_size); 162 | + ret = -EINVAL; 163 | + } 164 | + 165 | + wg->advanced_security_config.advanced_security_enabled = a_sec_on; 166 | +out: 167 | + return ret; 168 | +} 169 | diff --git device.h device.h 170 | index 43c7ceb..89e946c 100644 171 | --- device.h 172 | +++ device.h 173 | @@ -37,6 +37,19 @@ struct prev_queue { 174 | atomic_t count; 175 | }; 176 | 177 | +struct amnezia_config { 178 | + bool advanced_security_enabled; 179 | + u16 junk_packet_count; 180 | + u16 junk_packet_min_size; 181 | + u16 junk_packet_max_size; 182 | + u16 init_packet_junk_size; 183 | + u16 response_packet_junk_size; 184 | + u32 init_packet_magic_header; 185 | + u32 response_packet_magic_header; 186 | + u32 cookie_packet_magic_header; 187 | + u32 transport_packet_magic_header; 188 | +}; 189 | + 190 | struct wg_device { 191 | struct net_device *dev; 192 | struct crypt_queue encrypt_queue, decrypt_queue, handshake_queue; 193 | @@ -50,6 +63,7 @@ struct wg_device { 194 | struct allowedips peer_allowedips; 195 | struct mutex device_update_lock, socket_update_lock; 196 | struct list_head device_list, peer_list; 197 | + struct amnezia_config advanced_security_config; 198 | atomic_t handshake_queue_len; 199 | unsigned int num_peers, device_update_gen; 200 | u32 fwmark; 201 | @@ -58,5 +72,6 @@ struct wg_device { 202 | 203 | int wg_device_init(void); 204 | void wg_device_uninit(void); 205 | +int wg_device_handle_post_config(struct net_device *dev, struct amnezia_config *asc); 206 | 207 | #endif /* _WG_DEVICE_H */ 208 | diff --git main.c main.c 209 | index 5506738..b45253d 100644 210 | --- main.c 211 | +++ main.c 212 | @@ -9,9 +9,7 @@ 213 | #include "queueing.h" 214 | #include "ratelimiter.h" 215 | #include "netlink.h" 216 | - 217 | -#include 218 | - 219 | +#include "uapi/wireguard.h" 220 | #include "crypto/zinc.h" 221 | 222 | #include 223 | @@ -52,7 +50,7 @@ static int __init wg_mod_init(void) 224 | if (ret < 0) 225 | goto err_netlink; 226 | 227 | - pr_info("WireGuard " WIREGUARD_VERSION " loaded. See www.wireguard.com for information.\n"); 228 | + pr_info("WireGuard " WIREGUARD_VERSION " (AmneziaWG) loaded. See www.amnezia.org for information.\n"); 229 | pr_info("Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved.\n"); 230 | 231 | return 0; 232 | @@ -78,7 +76,7 @@ static void __exit wg_mod_exit(void) 233 | module_init(wg_mod_init); 234 | module_exit(wg_mod_exit); 235 | MODULE_LICENSE("GPL v2"); 236 | -MODULE_DESCRIPTION("WireGuard secure network tunnel"); 237 | +MODULE_DESCRIPTION("WireGuard (AmneziaWG) secure network tunnel"); 238 | MODULE_AUTHOR("Jason A. Donenfeld "); 239 | MODULE_VERSION(WIREGUARD_VERSION); 240 | MODULE_ALIAS_RTNL_LINK(KBUILD_MODNAME); 241 | diff --git messages.h messages.h 242 | index 1d1ed18..42cd054 100644 243 | --- messages.h 244 | +++ messages.h 245 | @@ -117,6 +117,14 @@ enum message_alignments { 246 | MESSAGE_MINIMUM_LENGTH = message_data_len(0) 247 | }; 248 | 249 | +enum message_size { 250 | + MESSAGE_INITIATION_SIZE = sizeof(struct message_handshake_initiation), 251 | + MESSAGE_RESPONSE_SIZE = sizeof(struct message_handshake_response), 252 | + MESSAGE_COOKIE_REPLY_SIZE = sizeof(struct message_handshake_cookie), 253 | + MESSAGE_TRANSPORT_SIZE = sizeof(struct message_data), 254 | + MESSAGE_MAX_SIZE = 65535 255 | +}; 256 | + 257 | #define SKB_HEADER_LEN \ 258 | (max(sizeof(struct iphdr), sizeof(struct ipv6hdr)) + \ 259 | sizeof(struct udphdr) + NET_SKB_PAD) 260 | diff --git netlink.c netlink.c 261 | index e3420e0..1d03aef 100644 262 | --- netlink.c 263 | +++ netlink.c 264 | @@ -9,9 +9,7 @@ 265 | #include "socket.h" 266 | #include "queueing.h" 267 | #include "messages.h" 268 | - 269 | -#include 270 | - 271 | +#include "uapi/wireguard.h" 272 | #include 273 | #include 274 | #include 275 | @@ -27,7 +25,16 @@ static const struct nla_policy device_policy[WGDEVICE_A_MAX + 1] = { 276 | [WGDEVICE_A_FLAGS] = { .type = NLA_U32 }, 277 | [WGDEVICE_A_LISTEN_PORT] = { .type = NLA_U16 }, 278 | [WGDEVICE_A_FWMARK] = { .type = NLA_U32 }, 279 | - [WGDEVICE_A_PEERS] = { .type = NLA_NESTED } 280 | + [WGDEVICE_A_PEERS] = { .type = NLA_NESTED }, 281 | + [WGDEVICE_A_JC] = { .type = NLA_U16 }, 282 | + [WGDEVICE_A_JMIN] = { .type = NLA_U16 }, 283 | + [WGDEVICE_A_JMAX] = { .type = NLA_U16 }, 284 | + [WGDEVICE_A_S1] = { .type = NLA_U16 }, 285 | + [WGDEVICE_A_S2] = { .type = NLA_U16 }, 286 | + [WGDEVICE_A_H1] = { .type = NLA_U32 }, 287 | + [WGDEVICE_A_H2] = { .type = NLA_U32 }, 288 | + [WGDEVICE_A_H3] = { .type = NLA_U32 }, 289 | + [WGDEVICE_A_H4] = { .type = NLA_U32 } 290 | }; 291 | 292 | static const struct nla_policy peer_policy[WGPEER_A_MAX + 1] = { 293 | @@ -233,7 +240,25 @@ static int wg_get_device_dump(struct sk_buff *skb, struct netlink_callback *cb) 294 | wg->incoming_port) || 295 | nla_put_u32(skb, WGDEVICE_A_FWMARK, wg->fwmark) || 296 | nla_put_u32(skb, WGDEVICE_A_IFINDEX, wg->dev->ifindex) || 297 | - nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name)) 298 | + nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name) || 299 | + nla_put_u16(skb, WGDEVICE_A_JC, 300 | + wg->advanced_security_config.junk_packet_count) || 301 | + nla_put_u16(skb, WGDEVICE_A_JMIN, 302 | + wg->advanced_security_config.junk_packet_min_size) || 303 | + nla_put_u16(skb, WGDEVICE_A_JMAX, 304 | + wg->advanced_security_config.junk_packet_max_size) || 305 | + nla_put_u16(skb, WGDEVICE_A_S1, 306 | + wg->advanced_security_config.init_packet_junk_size) || 307 | + nla_put_u16(skb, WGDEVICE_A_S2, 308 | + wg->advanced_security_config.response_packet_junk_size) || 309 | + nla_put_u32(skb, WGDEVICE_A_H1, 310 | + wg->advanced_security_config.init_packet_magic_header) || 311 | + nla_put_u32(skb, WGDEVICE_A_H2, 312 | + wg->advanced_security_config.response_packet_magic_header) || 313 | + nla_put_u32(skb, WGDEVICE_A_H3, 314 | + wg->advanced_security_config.cookie_packet_magic_header) || 315 | + nla_put_u32(skb, WGDEVICE_A_H4, 316 | + wg->advanced_security_config.transport_packet_magic_header)) 317 | goto out; 318 | 319 | down_read(&wg->static_identity.lock); 320 | @@ -494,6 +519,7 @@ out: 321 | static int wg_set_device(struct sk_buff *skb, struct genl_info *info) 322 | { 323 | struct wg_device *wg = lookup_interface(info->attrs, skb); 324 | + struct amnezia_config *asc = kzalloc(sizeof(*asc), GFP_KERNEL); 325 | u32 flags = 0; 326 | int ret; 327 | 328 | @@ -538,6 +564,51 @@ static int wg_set_device(struct sk_buff *skb, struct genl_info *info) 329 | goto out; 330 | } 331 | 332 | + if (info->attrs[WGDEVICE_A_JC]) { 333 | + asc->advanced_security_enabled = true; 334 | + asc->junk_packet_count = nla_get_u16(info->attrs[WGDEVICE_A_JC]); 335 | + } 336 | + 337 | + if (info->attrs[WGDEVICE_A_JMIN]) { 338 | + asc->advanced_security_enabled = true; 339 | + asc->junk_packet_min_size = nla_get_u16(info->attrs[WGDEVICE_A_JMIN]); 340 | + } 341 | + 342 | + if (info->attrs[WGDEVICE_A_JMAX]) { 343 | + asc->advanced_security_enabled = true; 344 | + asc->junk_packet_max_size = nla_get_u16(info->attrs[WGDEVICE_A_JMAX]); 345 | + } 346 | + 347 | + if (info->attrs[WGDEVICE_A_S1]) { 348 | + asc->advanced_security_enabled = true; 349 | + asc->init_packet_junk_size = nla_get_u16(info->attrs[WGDEVICE_A_S1]); 350 | + } 351 | + 352 | + if (info->attrs[WGDEVICE_A_S2]) { 353 | + asc->advanced_security_enabled = true; 354 | + asc->response_packet_junk_size = nla_get_u16(info->attrs[WGDEVICE_A_S2]); 355 | + } 356 | + 357 | + if (info->attrs[WGDEVICE_A_H1]) { 358 | + asc->advanced_security_enabled = true; 359 | + asc->init_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H1]); 360 | + } 361 | + 362 | + if (info->attrs[WGDEVICE_A_H2]) { 363 | + asc->advanced_security_enabled = true; 364 | + asc->response_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H2]); 365 | + } 366 | + 367 | + if (info->attrs[WGDEVICE_A_H3]) { 368 | + asc->advanced_security_enabled = true; 369 | + asc->cookie_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H3]); 370 | + } 371 | + 372 | + if (info->attrs[WGDEVICE_A_H4]) { 373 | + asc->advanced_security_enabled = true; 374 | + asc->transport_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H4]); 375 | + } 376 | + 377 | if (flags & WGDEVICE_F_REPLACE_PEERS) 378 | wg_peer_remove_all(wg); 379 | 380 | @@ -591,13 +662,14 @@ skip_set_private_key: 381 | goto out; 382 | } 383 | } 384 | - ret = 0; 385 | + ret = wg_device_handle_post_config(wg->dev, asc); 386 | 387 | out: 388 | mutex_unlock(&wg->device_update_lock); 389 | rtnl_unlock(); 390 | dev_put(wg->dev); 391 | out_nodev: 392 | + kfree(asc); 393 | if (info->attrs[WGDEVICE_A_PRIVATE_KEY]) 394 | memzero_explicit(nla_data(info->attrs[WGDEVICE_A_PRIVATE_KEY]), 395 | nla_len(info->attrs[WGDEVICE_A_PRIVATE_KEY])); 396 | diff --git noise.c noise.c 397 | index baf455e..9a4e8e0 100644 398 | --- noise.c 399 | +++ noise.c 400 | @@ -484,7 +484,7 @@ static void tai64n_now(u8 output[NOISE_TIMESTAMP_LEN]) 401 | 402 | bool 403 | wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst, 404 | - struct noise_handshake *handshake) 405 | + struct noise_handshake *handshake, u32 message_type) 406 | { 407 | u8 timestamp[NOISE_TIMESTAMP_LEN]; 408 | u8 key[NOISE_SYMMETRIC_KEY_LEN]; 409 | @@ -501,7 +501,7 @@ wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst, 410 | if (unlikely(!handshake->static_identity->has_identity)) 411 | goto out; 412 | 413 | - dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION); 414 | + dst->header.type = cpu_to_le32(message_type); 415 | 416 | handshake_init(handshake->chaining_key, handshake->hash, 417 | handshake->remote_static); 418 | @@ -634,7 +634,7 @@ out: 419 | } 420 | 421 | bool wg_noise_handshake_create_response(struct message_handshake_response *dst, 422 | - struct noise_handshake *handshake) 423 | + struct noise_handshake *handshake, u32 message_type) 424 | { 425 | u8 key[NOISE_SYMMETRIC_KEY_LEN]; 426 | bool ret = false; 427 | @@ -650,7 +650,7 @@ bool wg_noise_handshake_create_response(struct message_handshake_response *dst, 428 | if (handshake->state != HANDSHAKE_CONSUMED_INITIATION) 429 | goto out; 430 | 431 | - dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE); 432 | + dst->header.type = cpu_to_le32(message_type); 433 | dst->receiver_index = handshake->remote_index; 434 | 435 | /* e */ 436 | diff --git noise.h noise.h 437 | index c527253..300d9d4 100644 438 | --- noise.h 439 | +++ noise.h 440 | @@ -118,13 +118,13 @@ void wg_noise_precompute_static_static(struct wg_peer *peer); 441 | 442 | bool 443 | wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst, 444 | - struct noise_handshake *handshake); 445 | + struct noise_handshake *handshake, u32 message_type); 446 | struct wg_peer * 447 | wg_noise_handshake_consume_initiation(struct message_handshake_initiation *src, 448 | struct wg_device *wg); 449 | 450 | bool wg_noise_handshake_create_response(struct message_handshake_response *dst, 451 | - struct noise_handshake *handshake); 452 | + struct noise_handshake *handshake, u32 message_type); 453 | struct wg_peer * 454 | wg_noise_handshake_consume_response(struct message_handshake_response *src, 455 | struct wg_device *wg); 456 | diff --git receive.c receive.c 457 | index 214889e..d6566e6 100644 458 | --- receive.c 459 | +++ receive.c 460 | @@ -33,25 +33,51 @@ static void update_rx_stats(struct wg_peer *peer, size_t len) 461 | 462 | #define SKB_TYPE_LE32(skb) (((struct message_header *)(skb)->data)->type) 463 | 464 | -static size_t validate_header_len(struct sk_buff *skb) 465 | +static size_t validate_header_len(struct sk_buff *skb, struct wg_device *wg) 466 | { 467 | if (unlikely(skb->len < sizeof(struct message_header))) 468 | return 0; 469 | - if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_DATA) && 470 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.transport_packet_magic_header) && 471 | skb->len >= MESSAGE_MINIMUM_LENGTH) 472 | return sizeof(struct message_data); 473 | - if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION) && 474 | - skb->len == sizeof(struct message_handshake_initiation)) 475 | - return sizeof(struct message_handshake_initiation); 476 | - if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE) && 477 | - skb->len == sizeof(struct message_handshake_response)) 478 | - return sizeof(struct message_handshake_response); 479 | - if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE) && 480 | - skb->len == sizeof(struct message_handshake_cookie)) 481 | - return sizeof(struct message_handshake_cookie); 482 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.init_packet_magic_header) && 483 | + skb->len == MESSAGE_INITIATION_SIZE) 484 | + return MESSAGE_INITIATION_SIZE; 485 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.response_packet_magic_header) && 486 | + skb->len == MESSAGE_RESPONSE_SIZE) 487 | + return MESSAGE_RESPONSE_SIZE; 488 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.cookie_packet_magic_header) && 489 | + skb->len == MESSAGE_COOKIE_REPLY_SIZE) 490 | + return MESSAGE_COOKIE_REPLY_SIZE; 491 | return 0; 492 | } 493 | 494 | +void prepare_advanced_secured_message(struct sk_buff *skb, struct wg_device *wg) 495 | +{ 496 | + u32 assumed_type = SKB_TYPE_LE32(skb); 497 | + u32 assumed_offset; 498 | + 499 | + if (wg->advanced_security_config.advanced_security_enabled) { 500 | + if (skb->len == MESSAGE_INITIATION_SIZE + wg->advanced_security_config.init_packet_junk_size) { 501 | + assumed_type = cpu_to_le32(wg->advanced_security_config.init_packet_magic_header); 502 | + assumed_offset = wg->advanced_security_config.init_packet_junk_size; 503 | + } else if (skb->len == MESSAGE_RESPONSE_SIZE + wg->advanced_security_config.response_packet_junk_size) { 504 | + assumed_type = cpu_to_le32(wg->advanced_security_config.response_packet_magic_header); 505 | + assumed_offset = wg->advanced_security_config.response_packet_junk_size; 506 | + } else 507 | + return; 508 | + 509 | + if (unlikely(assumed_offset <= 0) || unlikely(!pskb_may_pull(skb, assumed_offset))) 510 | + return; 511 | + 512 | + skb_pull(skb, assumed_offset); 513 | + 514 | + if (SKB_TYPE_LE32(skb) != assumed_type) { 515 | + skb_push(skb, assumed_offset); 516 | + } 517 | + } 518 | +} 519 | + 520 | static int prepare_skb_header(struct sk_buff *skb, struct wg_device *wg) 521 | { 522 | size_t data_offset, data_len, header_len; 523 | @@ -87,7 +113,8 @@ static int prepare_skb_header(struct sk_buff *skb, struct wg_device *wg) 524 | if (unlikely(skb->len != data_len)) 525 | /* Final len does not agree with calculated len */ 526 | return -EINVAL; 527 | - header_len = validate_header_len(skb); 528 | + prepare_advanced_secured_message(skb, wg); 529 | + header_len = validate_header_len(skb, wg); 530 | if (unlikely(!header_len)) 531 | return -EINVAL; 532 | __skb_push(skb, data_offset); 533 | @@ -109,7 +136,7 @@ static void wg_receive_handshake_packet(struct wg_device *wg, 534 | bool packet_needs_cookie; 535 | bool under_load; 536 | 537 | - if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE)) { 538 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.cookie_packet_magic_header)) { 539 | net_dbg_skb_ratelimited("%s: Receiving cookie response from %pISpfsc\n", 540 | wg->dev->name, skb); 541 | wg_cookie_message_consume( 542 | @@ -139,8 +166,7 @@ static void wg_receive_handshake_packet(struct wg_device *wg, 543 | return; 544 | } 545 | 546 | - switch (SKB_TYPE_LE32(skb)) { 547 | - case cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION): { 548 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.init_packet_magic_header)) { 549 | struct message_handshake_initiation *message = 550 | (struct message_handshake_initiation *)skb->data; 551 | 552 | @@ -160,9 +186,8 @@ static void wg_receive_handshake_packet(struct wg_device *wg, 553 | wg->dev->name, peer->internal_id, 554 | &peer->endpoint.addr); 555 | wg_packet_send_handshake_response(peer); 556 | - break; 557 | } 558 | - case cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE): { 559 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.response_packet_magic_header)) { 560 | struct message_handshake_response *message = 561 | (struct message_handshake_response *)skb->data; 562 | 563 | @@ -193,8 +218,6 @@ static void wg_receive_handshake_packet(struct wg_device *wg, 564 | */ 565 | wg_packet_send_keepalive(peer); 566 | } 567 | - break; 568 | - } 569 | } 570 | 571 | if (unlikely(!peer)) { 572 | @@ -559,10 +582,10 @@ void wg_packet_receive(struct wg_device *wg, struct sk_buff *skb) 573 | { 574 | if (unlikely(prepare_skb_header(skb, wg) < 0)) 575 | goto err; 576 | - switch (SKB_TYPE_LE32(skb)) { 577 | - case cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION): 578 | - case cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE): 579 | - case cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE): { 580 | + 581 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.init_packet_magic_header) || 582 | + SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.response_packet_magic_header) || 583 | + SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.cookie_packet_magic_header)) { 584 | int cpu, ret = -EBUSY; 585 | 586 | if (unlikely(!rng_is_initialized())) 587 | @@ -575,23 +598,20 @@ void wg_packet_receive(struct wg_device *wg, struct sk_buff *skb) 588 | } else 589 | ret = ptr_ring_produce_bh(&wg->handshake_queue.ring, skb); 590 | if (ret) { 591 | - drop: 592 | +drop: 593 | net_dbg_skb_ratelimited("%s: Dropping handshake packet from %pISpfsc\n", 594 | - wg->dev->name, skb); 595 | + wg->dev->name, skb); 596 | goto err; 597 | } 598 | atomic_inc(&wg->handshake_queue_len); 599 | cpu = wg_cpumask_next_online(&wg->handshake_queue.last_cpu); 600 | /* Queues up a call to packet_process_queued_handshake_packets(skb): */ 601 | queue_work_on(cpu, wg->handshake_receive_wq, 602 | - &per_cpu_ptr(wg->handshake_queue.worker, cpu)->work); 603 | - break; 604 | - } 605 | - case cpu_to_le32(MESSAGE_DATA): 606 | + &per_cpu_ptr(wg->handshake_queue.worker, cpu)->work); 607 | + } else if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.transport_packet_magic_header)) { 608 | PACKET_CB(skb)->ds = ip_tunnel_get_dsfield(ip_hdr(skb), skb); 609 | wg_packet_consume_data(wg, skb); 610 | - break; 611 | - default: 612 | + } else { 613 | WARN(1, "Non-exhaustive parsing of packet header lead to unknown packet type!\n"); 614 | goto err; 615 | } 616 | diff --git send.c send.c 617 | index 2b19344..c96d2a2 100644 618 | --- send.c 619 | +++ send.c 620 | @@ -15,13 +15,24 @@ 621 | #include 622 | #include 623 | #include 624 | +#include 625 | #include 626 | #include 627 | #include 628 | 629 | +u32 wg_get_random_u32_inclusive(u32 floor, u32 ceil) 630 | +{ 631 | + u32 diff = ceil - floor + 1; 632 | + return floor + (get_random_u32() % diff); 633 | +} 634 | + 635 | static void wg_packet_send_handshake_initiation(struct wg_peer *peer) 636 | { 637 | struct message_handshake_initiation packet; 638 | + struct wg_device *wg = peer->device; 639 | + void *buffer; 640 | + u8 ds; 641 | + u16 junk_packet_count, junk_packet_size; 642 | 643 | if (!wg_birthdate_has_expired(atomic64_read(&peer->last_sent_handshake), 644 | REKEY_TIMEOUT)) 645 | @@ -32,14 +43,37 @@ static void wg_packet_send_handshake_initiation(struct wg_peer *peer) 646 | peer->device->dev->name, peer->internal_id, 647 | &peer->endpoint.addr); 648 | 649 | - if (wg_noise_handshake_create_initiation(&packet, &peer->handshake)) { 650 | + if (wg->advanced_security_config.advanced_security_enabled) { 651 | + junk_packet_count = wg->advanced_security_config.junk_packet_count; 652 | + buffer = kzalloc(wg->advanced_security_config.junk_packet_max_size, GFP_KERNEL); 653 | + 654 | + while (junk_packet_count-- > 0) { 655 | + junk_packet_size = (u16) wg_get_random_u32_inclusive( 656 | + wg->advanced_security_config.junk_packet_min_size, 657 | + wg->advanced_security_config.junk_packet_max_size); 658 | + 659 | + get_random_bytes(buffer, junk_packet_size); 660 | + get_random_bytes(&ds, 1); 661 | + wg_socket_send_buffer_to_peer(peer, buffer, junk_packet_size, ds); 662 | + } 663 | + 664 | + kfree(buffer); 665 | + } 666 | + 667 | + if (wg_noise_handshake_create_initiation(&packet, &peer->handshake, wg->advanced_security_config.init_packet_magic_header)) { 668 | wg_cookie_add_mac_to_packet(&packet, sizeof(packet), peer); 669 | wg_timers_any_authenticated_packet_traversal(peer); 670 | wg_timers_any_authenticated_packet_sent(peer); 671 | atomic64_set(&peer->last_sent_handshake, 672 | ktime_get_coarse_boottime_ns()); 673 | - wg_socket_send_buffer_to_peer(peer, &packet, sizeof(packet), 674 | - HANDSHAKE_DSCP); 675 | + 676 | + if (wg->advanced_security_config.advanced_security_enabled) { 677 | + wg_socket_send_junked_buffer_to_peer(peer, &packet, sizeof(packet), 678 | + HANDSHAKE_DSCP, wg->advanced_security_config.init_packet_junk_size); 679 | + } else { 680 | + wg_socket_send_buffer_to_peer(peer, &packet, sizeof(packet), 681 | + HANDSHAKE_DSCP); 682 | + } 683 | wg_timers_handshake_initiated(peer); 684 | } 685 | } 686 | @@ -86,13 +120,14 @@ out: 687 | void wg_packet_send_handshake_response(struct wg_peer *peer) 688 | { 689 | struct message_handshake_response packet; 690 | + struct wg_device *wg = peer->device; 691 | 692 | atomic64_set(&peer->last_sent_handshake, ktime_get_coarse_boottime_ns()); 693 | net_dbg_ratelimited("%s: Sending handshake response to peer %llu (%pISpfsc)\n", 694 | peer->device->dev->name, peer->internal_id, 695 | &peer->endpoint.addr); 696 | 697 | - if (wg_noise_handshake_create_response(&packet, &peer->handshake)) { 698 | + if (wg_noise_handshake_create_response(&packet, &peer->handshake, wg->advanced_security_config.response_packet_magic_header)) { 699 | wg_cookie_add_mac_to_packet(&packet, sizeof(packet), peer); 700 | if (wg_noise_handshake_begin_session(&peer->handshake, 701 | &peer->keypairs)) { 702 | @@ -101,9 +136,16 @@ void wg_packet_send_handshake_response(struct wg_peer *peer) 703 | wg_timers_any_authenticated_packet_sent(peer); 704 | atomic64_set(&peer->last_sent_handshake, 705 | ktime_get_coarse_boottime_ns()); 706 | - wg_socket_send_buffer_to_peer(peer, &packet, 707 | - sizeof(packet), 708 | - HANDSHAKE_DSCP); 709 | + if (wg->advanced_security_config.advanced_security_enabled) { 710 | + wg_socket_send_junked_buffer_to_peer(peer, &packet, 711 | + sizeof(packet), 712 | + HANDSHAKE_DSCP, 713 | + wg->advanced_security_config.response_packet_junk_size); 714 | + } else { 715 | + wg_socket_send_buffer_to_peer(peer, &packet, 716 | + sizeof(packet), 717 | + HANDSHAKE_DSCP); 718 | + } 719 | } 720 | } 721 | } 722 | @@ -117,7 +159,7 @@ void wg_packet_send_handshake_cookie(struct wg_device *wg, 723 | net_dbg_skb_ratelimited("%s: Sending cookie response for denied handshake message for %pISpfsc\n", 724 | wg->dev->name, initiating_skb); 725 | wg_cookie_message_create(&packet, initiating_skb, sender_index, 726 | - &wg->cookie_checker); 727 | + &wg->cookie_checker, wg->advanced_security_config.cookie_packet_magic_header); 728 | wg_socket_send_buffer_as_reply_to_skb(wg, initiating_skb, &packet, 729 | sizeof(packet)); 730 | } 731 | @@ -160,7 +202,7 @@ static unsigned int calculate_skb_padding(struct sk_buff *skb) 732 | return padded_size - last_unit; 733 | } 734 | 735 | -static bool encrypt_packet(struct sk_buff *skb, struct noise_keypair *keypair) 736 | +static bool encrypt_packet(u32 message_type, struct sk_buff *skb, struct noise_keypair *keypair) 737 | { 738 | unsigned int padding_len, plaintext_len, trailer_len; 739 | struct scatterlist sg[MAX_SKB_FRAGS + 8]; 740 | @@ -204,7 +246,7 @@ static bool encrypt_packet(struct sk_buff *skb, struct noise_keypair *keypair) 741 | */ 742 | skb_set_inner_network_header(skb, 0); 743 | header = (struct message_data *)skb_push(skb, sizeof(*header)); 744 | - header->header.type = cpu_to_le32(MESSAGE_DATA); 745 | + header->header.type = cpu_to_le32(message_type); 746 | header->key_idx = keypair->remote_index; 747 | header->counter = cpu_to_le64(PACKET_CB(skb)->nonce); 748 | pskb_put(skb, trailer, trailer_len); 749 | @@ -291,6 +333,7 @@ void wg_packet_encrypt_worker(struct work_struct *work) 750 | struct crypt_queue *queue = container_of(work, struct multicore_worker, 751 | work)->ptr; 752 | struct sk_buff *first, *skb, *next; 753 | + struct wg_device *wg; 754 | simd_context_t simd_context; 755 | 756 | simd_get(&simd_context); 757 | @@ -298,7 +341,10 @@ void wg_packet_encrypt_worker(struct work_struct *work) 758 | enum packet_state state = PACKET_STATE_CRYPTED; 759 | 760 | skb_list_walk_safe(first, skb, next) { 761 | - if (likely(encrypt_packet(skb, 762 | + wg = PACKET_PEER(first)->device; 763 | + 764 | + if (likely(encrypt_packet(wg->advanced_security_config.transport_packet_magic_header, 765 | + skb, 766 | PACKET_CB(first)->keypair, 767 | &simd_context))) { 768 | wg_reset_packet(skb, true); 769 | diff --git socket.c socket.c 770 | index 9e0af93..2dd574f 100644 771 | --- socket.c 772 | +++ socket.c 773 | @@ -200,6 +200,18 @@ int wg_socket_send_buffer_to_peer(struct wg_peer *peer, void *buffer, 774 | return wg_socket_send_skb_to_peer(peer, skb, ds); 775 | } 776 | 777 | +int wg_socket_send_junked_buffer_to_peer(struct wg_peer *peer, void *buffer, 778 | + size_t len, u8 ds, u16 junk_size) 779 | +{ 780 | + int ret; 781 | + void *new_buffer = kzalloc(len + junk_size, GFP_KERNEL); 782 | + get_random_bytes(new_buffer, junk_size); 783 | + memcpy(new_buffer + junk_size, buffer, len); 784 | + ret = wg_socket_send_buffer_to_peer(peer, new_buffer, len + junk_size, ds); 785 | + kfree(new_buffer); 786 | + return ret; 787 | +} 788 | + 789 | int wg_socket_send_buffer_as_reply_to_skb(struct wg_device *wg, 790 | struct sk_buff *in_skb, void *buffer, 791 | size_t len) 792 | diff --git socket.h socket.h 793 | index bab5848..e4e3f96 100644 794 | --- socket.h 795 | +++ socket.h 796 | @@ -16,6 +16,8 @@ void wg_socket_reinit(struct wg_device *wg, struct sock *new4, 797 | struct sock *new6); 798 | int wg_socket_send_buffer_to_peer(struct wg_peer *peer, void *data, 799 | size_t len, u8 ds); 800 | +int wg_socket_send_junked_buffer_to_peer(struct wg_peer *peer, void *data, 801 | + size_t len, u8 ds, u16 junk_size); 802 | int wg_socket_send_skb_to_peer(struct wg_peer *peer, struct sk_buff *skb, 803 | u8 ds); 804 | int wg_socket_send_buffer_as_reply_to_skb(struct wg_device *wg, 805 | diff --git uapi/wireguard.h uapi/wireguard.h 806 | index ae88be1..f6698e8 100644 807 | --- uapi/wireguard.h 808 | +++ uapi/wireguard.h 809 | @@ -131,7 +131,7 @@ 810 | #ifndef _WG_UAPI_WIREGUARD_H 811 | #define _WG_UAPI_WIREGUARD_H 812 | 813 | -#define WG_GENL_NAME "wireguard" 814 | +#define WG_GENL_NAME "amneziawg" 815 | #define WG_GENL_VERSION 1 816 | 817 | #define WG_KEY_LEN 32 818 | @@ -157,6 +157,15 @@ enum wgdevice_attribute { 819 | WGDEVICE_A_LISTEN_PORT, 820 | WGDEVICE_A_FWMARK, 821 | WGDEVICE_A_PEERS, 822 | + WGDEVICE_A_JC, 823 | + WGDEVICE_A_JMIN, 824 | + WGDEVICE_A_JMAX, 825 | + WGDEVICE_A_S1, 826 | + WGDEVICE_A_S2, 827 | + WGDEVICE_A_H1, 828 | + WGDEVICE_A_H2, 829 | + WGDEVICE_A_H3, 830 | + WGDEVICE_A_H4, 831 | __WGDEVICE_A_LAST 832 | }; 833 | #define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1) 834 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/htdocs/luci-static/resources/protocol/amneziawg.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | "require fs"; 3 | "require ui"; 4 | "require dom"; 5 | "require uci"; 6 | "require rpc"; 7 | "require form"; 8 | "require network"; 9 | "require validation"; 10 | 11 | var generateKey = rpc.declare({ 12 | object: "luci.amneziawg", 13 | method: "generateKeyPair", 14 | expect: { keys: {} } 15 | }); 16 | 17 | var getPublicAndPrivateKeyFromPrivate = rpc.declare({ 18 | object: "luci.amneziawg", 19 | method: "getPublicAndPrivateKeyFromPrivate", 20 | params: ["privkey"], 21 | expect: { keys: {} } 22 | }); 23 | 24 | var generatePsk = rpc.declare({ 25 | object: "luci.amneziawg", 26 | method: "generatePsk", 27 | expect: { psk: "" } 28 | }); 29 | 30 | var qrIcon = 31 | ''; 32 | 33 | function validateBase64(section_id, value) { 34 | if (value.length == 0) return true; 35 | 36 | if ( 37 | value.length != 44 || 38 | !value.match( 39 | /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/ 40 | ) 41 | ) 42 | return _("Invalid Base64 key string"); 43 | 44 | if (value[43] != "=") return _("Invalid Base64 key string"); 45 | 46 | return true; 47 | } 48 | 49 | var stubValidator = { 50 | factory: validation, 51 | apply: function (type, value, args) { 52 | if (value != null) this.value = value; 53 | 54 | return validation.types[type].apply(this, args); 55 | }, 56 | assert: function (condition) { 57 | return !!condition; 58 | }, 59 | }; 60 | 61 | function generateDescription(name, texts) { 62 | return E("li", { style: "color: inherit;" }, [ 63 | E("span", name), 64 | E( 65 | "ul", 66 | texts.map(function (text) { 67 | return E("li", { style: "color: inherit;" }, text); 68 | }) 69 | ), 70 | ]); 71 | } 72 | 73 | function invokeQREncode(data, code) { 74 | return fs 75 | .exec_direct("/usr/bin/qrencode", [ 76 | "--inline", 77 | "--8bit", 78 | "--type=SVG", 79 | "--output=-", 80 | "--", 81 | data, 82 | ]) 83 | .then(function (svg) { 84 | code.style.opacity = ""; 85 | dom.content( 86 | code, 87 | Object.assign(E(svg), { style: "width:100%;height:auto" }) 88 | ); 89 | }) 90 | .catch(function (error) { 91 | code.style.opacity = ""; 92 | 93 | if (L.isObject(error) && error.name == "NotFoundError") { 94 | dom.content(code, [ 95 | Object.assign(E(qrIcon), { 96 | style: "width:32px;height:32px;opacity:.2", 97 | }), 98 | E( 99 | "p", 100 | _( 101 | "The %sqrencode%s package is required for generating an QR code image of the configuration." 102 | ).format("", "") 103 | ), 104 | ]); 105 | } else { 106 | dom.content(code, [ 107 | _("Unable to generate QR code: %s").format( 108 | L.isObject(error) ? error.message : error 109 | ), 110 | ]); 111 | } 112 | }); 113 | } 114 | 115 | var cbiKeyPairGenerate = form.DummyValue.extend({ 116 | cfgvalue: function (section_id, value) { 117 | return E( 118 | "button", 119 | { 120 | class: "btn", 121 | click: ui.createHandlerFn( 122 | this, 123 | function (section_id, ev) { 124 | var prv = this.section.getUIElement(section_id, "private_key"), 125 | pub = this.section.getUIElement(section_id, "public_key"), 126 | map = this.map; 127 | 128 | if ( 129 | (prv.getValue() || pub.getValue()) && 130 | !confirm(_("Do you want to replace the current keys?")) 131 | ) 132 | return; 133 | 134 | return generateKey().then(function (keypair) { 135 | prv.setValue(keypair.priv); 136 | pub.setValue(keypair.pub); 137 | map.save(null, true); 138 | }); 139 | }, 140 | section_id 141 | ), 142 | }, 143 | [_("Generate new key pair")] 144 | ); 145 | }, 146 | }); 147 | 148 | function handleWindowDragDropIgnore(ev) { 149 | ev.preventDefault(); 150 | } 151 | 152 | return network.registerProtocol("amneziawg", { 153 | getI18n: function () { 154 | return _("AmneziaWG VPN"); 155 | }, 156 | 157 | getIfname: function () { 158 | return this._ubus("l3_device") || this.sid; 159 | }, 160 | 161 | getOpkgPackage: function () { 162 | return "amneziawg-tools"; 163 | }, 164 | 165 | isFloating: function () { 166 | return true; 167 | }, 168 | 169 | isVirtual: function () { 170 | return true; 171 | }, 172 | 173 | getDevices: function () { 174 | return null; 175 | }, 176 | 177 | containsDevice: function (ifname) { 178 | return (network.getIfnameOf(ifname) == this.getIfname()); 179 | }, 180 | 181 | renderFormOptions: function (s) { 182 | var o, ss, ss2; 183 | 184 | // -- general --------------------------------------------------------------------- 185 | 186 | o = s.taboption( 187 | "general", 188 | form.Value, 189 | "private_key", 190 | _("Private Key"), 191 | _("Required. Base64-encoded private key for this interface.") 192 | ); 193 | o.password = true; 194 | o.validate = validateBase64; 195 | o.rmempty = false; 196 | 197 | var serverName = this.getIfname(); 198 | 199 | o = s.taboption( 200 | "general", 201 | form.Value, 202 | "public_key", 203 | _("Public Key"), 204 | _("Base64-encoded public key of this interface for sharing.") 205 | ); 206 | o.rmempty = false; 207 | o.write = function () { 208 | /* write nothing */ 209 | }; 210 | 211 | o.load = function (section_id) { 212 | var privKey = 213 | s.formvalue(section_id, "private_key") || 214 | uci.get("network", section_id, "private_key"); 215 | 216 | return getPublicAndPrivateKeyFromPrivate(privKey).then( 217 | function (keypair) { 218 | return keypair.pub || ""; 219 | }, 220 | function (error) { 221 | return _("Error getting PublicKey"); 222 | }, 223 | this 224 | ); 225 | }; 226 | 227 | s.taboption("general", cbiKeyPairGenerate, "_gen_server_keypair", " "); 228 | 229 | o = s.taboption( 230 | "general", 231 | form.Value, 232 | "listen_port", 233 | _("Listen Port"), 234 | _("Optional. UDP port used for outgoing and incoming packets.") 235 | ); 236 | o.datatype = "port"; 237 | o.placeholder = _("random"); 238 | o.optional = true; 239 | 240 | o = s.taboption( 241 | "general", 242 | form.DynamicList, 243 | "addresses", 244 | _("IP Addresses"), 245 | _("Recommended. IP addresses of the AmneziaWG interface.") 246 | ); 247 | o.datatype = "ipaddr"; 248 | o.optional = true; 249 | 250 | o = s.taboption( 251 | "general", 252 | form.Flag, 253 | "nohostroute", 254 | _("No Host Routes"), 255 | _("Optional. Do not create host routes to peers.") 256 | ); 257 | o.optional = true; 258 | 259 | o = s.taboption( 260 | "general", 261 | form.Button, 262 | "_import", 263 | _("Import configuration"), 264 | _("Imports settings from an existing AmneziaWG configuration file") 265 | ); 266 | o.inputtitle = _("Load configuration…"); 267 | o.onclick = function () { 268 | return ss.handleConfigImport("full"); 269 | }; 270 | 271 | // -- advanced -------------------------------------------------------------------- 272 | 273 | o = s.taboption( 274 | "advanced", 275 | form.Value, 276 | "mtu", 277 | _("MTU"), 278 | _("Optional. Maximum Transmission Unit of tunnel interface.") 279 | ); 280 | o.datatype = "range(0,8940)"; 281 | o.placeholder = "1420"; 282 | o.optional = true; 283 | 284 | o = s.taboption( 285 | "advanced", 286 | form.Value, 287 | "fwmark", 288 | _("Firewall Mark"), 289 | _( 290 | "Optional. 32-bit mark for outgoing encrypted packets. Enter value in hex, starting with %s." 291 | ).format("0x") 292 | ); 293 | o.optional = true; 294 | o.validate = function (section_id, value) { 295 | if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,8}$/)) 296 | return _("Invalid hexadecimal value"); 297 | 298 | return true; 299 | }; 300 | 301 | // AmneziaWG Settings 302 | 303 | try { 304 | s.tab( 305 | "amneziawg", 306 | _("AmneziaWG Settings"), 307 | _( 308 | "Further information about AmneziaWG interfaces and peers at %s." 309 | ).format("amnezia.org") 310 | ); 311 | } catch (e) {} 312 | 313 | o = s.taboption( 314 | "amneziawg", 315 | form.Value, 316 | "awg_jc", 317 | _("Jc"), 318 | _("Junk packet count.") 319 | ); 320 | o.datatype = "uinteger"; 321 | o.optional = true; 322 | 323 | o = s.taboption( 324 | "amneziawg", 325 | form.Value, 326 | "awg_jmin", 327 | _("Jmin"), 328 | _("Junk packet minimum size.") 329 | ); 330 | o.datatype = "uinteger"; 331 | o.optional = true; 332 | 333 | o = s.taboption( 334 | "amneziawg", 335 | form.Value, 336 | "awg_jmax", 337 | _("Jmax"), 338 | _("Junk packet maximum size.") 339 | ); 340 | o.datatype = "uinteger"; 341 | o.optional = true; 342 | 343 | o = s.taboption( 344 | "amneziawg", 345 | form.Value, 346 | "awg_s1", 347 | _("S1"), 348 | _("Handshake initiation packet junk header size.") 349 | ); 350 | o.datatype = "uinteger"; 351 | o.optional = true; 352 | 353 | o = s.taboption( 354 | "amneziawg", 355 | form.Value, 356 | "awg_s2", 357 | _("S2"), 358 | _("Handshake response packet junk header size.") 359 | ); 360 | o.datatype = "uinteger"; 361 | o.optional = true; 362 | 363 | o = s.taboption( 364 | "amneziawg", 365 | form.Value, 366 | "awg_h1", 367 | _("H1"), 368 | _("Handshake initiation packet type header.") 369 | ); 370 | o.datatype = "uinteger"; 371 | o.optional = true; 372 | 373 | o = s.taboption( 374 | "amneziawg", 375 | form.Value, 376 | "awg_h2", 377 | _("H2"), 378 | _("Handshake response packet type header.") 379 | ); 380 | o.datatype = "uinteger"; 381 | o.optional = true; 382 | 383 | o = s.taboption( 384 | "amneziawg", 385 | form.Value, 386 | "awg_h3", 387 | _("H3"), 388 | _("Handshake cookie packet type header.") 389 | ); 390 | o.datatype = "uinteger"; 391 | o.optional = true; 392 | 393 | o = s.taboption( 394 | "amneziawg", 395 | form.Value, 396 | "awg_h4", 397 | _("H4"), 398 | _("Transport packet type header.") 399 | ); 400 | o.datatype = "uinteger"; 401 | o.optional = true; 402 | 403 | // -- peers ----------------------------------------------------------------------- 404 | 405 | try { 406 | s.tab( 407 | "peers", 408 | _("Peers"), 409 | _( 410 | "Further information about AmneziaWG interfaces and peers at %s." 411 | ).format("amnezia.org") 412 | ); 413 | } catch (e) {} 414 | 415 | o = s.taboption( 416 | "peers", 417 | form.SectionValue, 418 | "_peers", 419 | form.GridSection, 420 | "amneziawg_%s".format(s.section) 421 | ); 422 | o.depends("proto", "amneziawg"); 423 | 424 | ss = o.subsection; 425 | ss.anonymous = true; 426 | ss.addremove = true; 427 | ss.addbtntitle = _("Add peer"); 428 | ss.nodescriptions = true; 429 | ss.modaltitle = _("Edit peer"); 430 | 431 | ss.handleDragConfig = function (ev) { 432 | ev.stopPropagation(); 433 | ev.preventDefault(); 434 | ev.dataTransfer.dropEffect = "copy"; 435 | }; 436 | 437 | ss.handleDropConfig = function (mode, ev) { 438 | var file = ev.dataTransfer.files[0], 439 | nodes = ev.currentTarget, 440 | input = nodes.querySelector("textarea"), 441 | reader = new FileReader(); 442 | 443 | if (file) { 444 | reader.onload = function (rev) { 445 | input.value = rev.target.result.trim(); 446 | ss.handleApplyConfig(mode, nodes, file.name, ev); 447 | }; 448 | 449 | reader.readAsText(file); 450 | } 451 | 452 | ev.stopPropagation(); 453 | ev.preventDefault(); 454 | }; 455 | 456 | ss.parseConfig = function (data) { 457 | var lines = String(data).split(/(\r?\n)+/), 458 | section = null, 459 | config = { peers: [] }, 460 | s; 461 | 462 | for (var i = 0; i < lines.length; i++) { 463 | var line = lines[i].replace(/#.*$/, "").trim(); 464 | 465 | if (line.match(/^\[(\w+)\]$/)) { 466 | section = RegExp.$1.toLowerCase(); 467 | 468 | if (section == "peer") config.peers.push((s = {})); 469 | else s = config; 470 | } else if (section && line.match(/^(\w+)\s*=\s*(.+)$/)) { 471 | var key = RegExp.$1, 472 | val = RegExp.$2.trim(); 473 | 474 | if (val.length) s[section + "_" + key.toLowerCase()] = val; 475 | } 476 | } 477 | 478 | if (config.interface_address) { 479 | config.interface_address = config.interface_address.split(/[, ]+/); 480 | 481 | for (var i = 0; i < config.interface_address.length; i++) 482 | if (!stubValidator.apply("ipaddr", config.interface_address[i])) 483 | return _("Address setting is invalid"); 484 | } 485 | 486 | if (config.interface_dns) { 487 | config.interface_dns = config.interface_dns.split(/[, ]+/); 488 | 489 | for (var i = 0; i < config.interface_dns.length; i++) 490 | if ( 491 | !stubValidator.apply("ipaddr", config.interface_dns[i], ["nomask"]) 492 | ) 493 | return _("DNS setting is invalid"); 494 | } 495 | 496 | if ( 497 | !config.interface_privatekey || 498 | validateBase64(null, config.interface_privatekey) !== true 499 | ) 500 | return _("PrivateKey setting is missing or invalid"); 501 | 502 | if (!stubValidator.apply("port", config.interface_listenport || "0")) 503 | return _("ListenPort setting is invalid"); 504 | 505 | for (var i = 0; i < config.peers.length; i++) { 506 | var pconf = config.peers[i]; 507 | 508 | if ( 509 | pconf.peer_publickey != null && 510 | validateBase64(null, pconf.peer_publickey) !== true 511 | ) 512 | return _("PublicKey setting is invalid"); 513 | 514 | if ( 515 | pconf.peer_presharedkey != null && 516 | validateBase64(null, pconf.peer_presharedkey) !== true 517 | ) 518 | return _("PresharedKey setting is invalid"); 519 | 520 | if (pconf.peer_allowedips) { 521 | pconf.peer_allowedips = pconf.peer_allowedips.split(/[, ]+/); 522 | 523 | for (var j = 0; j < pconf.peer_allowedips.length; j++) 524 | if (!stubValidator.apply("ipaddr", pconf.peer_allowedips[j])) 525 | return _("AllowedIPs setting is invalid"); 526 | } else { 527 | pconf.peer_allowedips = ["0.0.0.0/0", "::/0"]; 528 | } 529 | 530 | if (pconf.peer_endpoint) { 531 | var host_port = 532 | pconf.peer_endpoint.match(/^\[([a-fA-F0-9:]+)\]:(\d+)$/) || 533 | pconf.peer_endpoint.match(/^(.+):(\d+)$/); 534 | 535 | if ( 536 | !host_port || 537 | !stubValidator.apply("host", host_port[1]) || 538 | !stubValidator.apply("port", host_port[2]) 539 | ) 540 | return _("Endpoint setting is invalid"); 541 | 542 | pconf.peer_endpoint = [host_port[1], host_port[2]]; 543 | } 544 | 545 | if ( 546 | pconf.peer_persistentkeepalive == "off" || 547 | pconf.peer_persistentkeepalive == "0" 548 | ) 549 | delete pconf.peer_persistentkeepalive; 550 | 551 | if (!stubValidator.apply("port", pconf.peer_persistentkeepalive || "0")) 552 | return _("PersistentKeepAlive setting is invalid"); 553 | } 554 | 555 | return config; 556 | }; 557 | 558 | ss.handleApplyConfig = function (mode, nodes, comment, ev) { 559 | var input = nodes.querySelector("textarea").value, 560 | error = nodes.querySelector(".alert-message"), 561 | cancel = nodes.nextElementSibling.querySelector(".btn"), 562 | config = this.parseConfig(input); 563 | 564 | if (typeof config == "string") { 565 | error.firstChild.data = _("Cannot parse configuration: %s").format( 566 | config 567 | ); 568 | error.style.display = "block"; 569 | return; 570 | } 571 | 572 | if (mode == "full") { 573 | var prv = s.formvalue(s.section, "private_key"); 574 | 575 | if ( 576 | prv && 577 | prv != config.interface_privatekey && 578 | !confirm( 579 | _("Overwrite the current settings with the imported configuration?") 580 | ) 581 | ) 582 | return; 583 | 584 | return getPublicAndPrivateKeyFromPrivate(config.interface_privatekey) 585 | .then(function (keypair) { 586 | s.getOption("private_key") 587 | .getUIElement(s.section) 588 | .setValue(keypair.priv); 589 | s.getOption("public_key") 590 | .getUIElement(s.section) 591 | .setValue(keypair.pub); 592 | s.getOption("listen_port") 593 | .getUIElement(s.section) 594 | .setValue(config.interface_listenport || ""); 595 | s.getOption("addresses") 596 | .getUIElement(s.section) 597 | .setValue(config.interface_address); 598 | s.getOption("awg_jc") 599 | .getUIElement(s.section) 600 | .setValue(config.awg_jc); 601 | s.getOption("awg_jmin") 602 | .getUIElement(s.section) 603 | .setValue(config.awg_jmin); 604 | s.getOption("awg_jmax") 605 | .getUIElement(s.section) 606 | .setValue(config.awg_jmax); 607 | s.getOption("awg_s1") 608 | .getUIElement(s.section) 609 | .setValue(config.awg_s1); 610 | s.getOption("awg_s2") 611 | .getUIElement(s.section) 612 | .setValue(config.awg_s2); 613 | s.getOption("awg_h1") 614 | .getUIElement(s.section) 615 | .setValue(config.awg_h1); 616 | s.getOption("awg_h2") 617 | .getUIElement(s.section) 618 | .setValue(config.awg_h2); 619 | s.getOption("awg_h3") 620 | .getUIElement(s.section) 621 | .setValue(config.awg_h3); 622 | s.getOption("awg_h4") 623 | .getUIElement(s.section) 624 | .setValue(config.awg_h4); 625 | 626 | if (config.interface_dns) 627 | s.getOption("dns") 628 | .getUIElement(s.section) 629 | .setValue(config.interface_dns); 630 | 631 | for (var i = 0; i < config.peers.length; i++) { 632 | var pconf = config.peers[i]; 633 | var sid = uci.add("network", "amneziawg_" + s.section); 634 | 635 | uci.sections( 636 | "network", 637 | "amneziawg_" + s.section, 638 | function (peer) { 639 | if (peer.public_key == pconf.peer_publickey) 640 | uci.remove("network", peer[".name"]); 641 | } 642 | ); 643 | 644 | uci.set( 645 | "network", 646 | sid, 647 | "description", 648 | comment || _("Imported peer configuration") 649 | ); 650 | uci.set("network", sid, "public_key", pconf.peer_publickey); 651 | uci.set("network", sid, "preshared_key", pconf.peer_presharedkey); 652 | uci.set("network", sid, "allowed_ips", pconf.peer_allowedips); 653 | uci.set( 654 | "network", 655 | sid, 656 | "persistent_keepalive", 657 | pconf.peer_persistentkeepalive 658 | ); 659 | 660 | if (pconf.peer_endpoint) { 661 | uci.set( 662 | "network", 663 | sid, 664 | "endpoint_host", 665 | pconf.peer_endpoint[0] 666 | ); 667 | uci.set( 668 | "network", 669 | sid, 670 | "endpoint_port", 671 | pconf.peer_endpoint[1] 672 | ); 673 | } 674 | } 675 | 676 | return s.map.save(null, true); 677 | }) 678 | .then(function () { 679 | cancel.click(); 680 | }); 681 | } else { 682 | return getPublicAndPrivateKeyFromPrivate(config.interface_privatekey) 683 | .then(function (keypair) { 684 | var sid = uci.add("network", "amneziawg_" + s.section); 685 | var pub = s.formvalue(s.section, "public_key"); 686 | 687 | uci.sections("network", "amneziawg_" + s.section, function (peer) { 688 | if (peer.public_key == keypair.pub) 689 | uci.remove("network", peer[".name"]); 690 | }); 691 | 692 | uci.set( 693 | "network", 694 | sid, 695 | "description", 696 | comment || _("Imported peer configuration") 697 | ); 698 | uci.set("network", sid, "public_key", keypair.pub); 699 | uci.set("network", sid, "private_key", keypair.priv); 700 | 701 | for (var i = 0; i < config.peers.length; i++) { 702 | var pconf = config.peers[i]; 703 | 704 | if (pconf.peer_publickey == pub) { 705 | uci.set( 706 | "network", 707 | sid, 708 | "preshared_key", 709 | pconf.peer_presharedkey 710 | ); 711 | uci.set("network", sid, "allowed_ips", pconf.peer_allowedips); 712 | uci.set( 713 | "network", 714 | sid, 715 | "persistent_keepalive", 716 | pconf.peer_persistentkeepalive 717 | ); 718 | break; 719 | } 720 | } 721 | 722 | return s.map.save(null, true); 723 | }) 724 | .then(function () { 725 | cancel.click(); 726 | }); 727 | } 728 | }; 729 | 730 | ss.handleConfigImport = function (mode) { 731 | var mapNode = ss.getActiveModalMap(), 732 | headNode = mapNode.parentNode.querySelector("h4"), 733 | parent = this.map; 734 | 735 | var nodes = E( 736 | "div", 737 | { 738 | dragover: this.handleDragConfig, 739 | drop: this.handleDropConfig.bind(this, mode), 740 | }, 741 | [ 742 | E( 743 | [], 744 | mode == "full" 745 | ? [ 746 | E( 747 | "p", 748 | _( 749 | "Drag or paste a valid %s file below to configure the local AmneziaWG interface." 750 | ).format("*.conf") 751 | ), 752 | ] 753 | : [ 754 | E( 755 | "p", 756 | _( 757 | "Paste or drag a AmneziaWG configuration (commonly %s) from another system below to create a matching peer entry allowing that system to connect to the local AmneziaWG interface." 758 | ).format("wg0.conf") 759 | ), 760 | E( 761 | "p", 762 | _( 763 | "To fully configure the local AmneziaWG interface from an existing (e.g. provider supplied) configuration file, use the %sconfiguration import%s instead." 764 | ).format( 765 | '', 766 | "" 767 | ) 768 | ), 769 | ] 770 | ), 771 | E("p", [ 772 | E("textarea", { 773 | placeholder: 774 | mode == "full" 775 | ? _("Paste or drag supplied AmneziaWG configuration file…") 776 | : _( 777 | "Paste or drag AmneziaWG peer configuration (%s) file…" 778 | ).format("wg0.conf"), 779 | style: "height:5em;width:100%; white-space:pre", 780 | }), 781 | ]), 782 | E( 783 | "div", 784 | { 785 | class: "alert-message", 786 | style: "display:none", 787 | }, 788 | [""] 789 | ), 790 | ] 791 | ); 792 | 793 | var cancelFn = function () { 794 | nodes.parentNode.removeChild(nodes.nextSibling); 795 | nodes.parentNode.removeChild(nodes); 796 | mapNode.classList.remove("hidden"); 797 | mapNode.nextSibling.classList.remove("hidden"); 798 | headNode.removeChild(headNode.lastChild); 799 | window.removeEventListener("dragover", handleWindowDragDropIgnore); 800 | window.removeEventListener("drop", handleWindowDragDropIgnore); 801 | }; 802 | 803 | var a = nodes.querySelector("a.full-import"); 804 | 805 | if (a) { 806 | a.addEventListener( 807 | "click", 808 | ui.createHandlerFn(this, function (mode) { 809 | cancelFn(); 810 | this.handleConfigImport("full"); 811 | }) 812 | ); 813 | } 814 | 815 | mapNode.classList.add("hidden"); 816 | mapNode.nextElementSibling.classList.add("hidden"); 817 | 818 | headNode.appendChild( 819 | E("span", [ 820 | " » ", 821 | mode == "full" ? _("Import configuration") : _("Import as peer"), 822 | ]) 823 | ); 824 | mapNode.parentNode.appendChild( 825 | E( 826 | [], 827 | [ 828 | nodes, 829 | E( 830 | "div", 831 | { 832 | class: "right", 833 | }, 834 | [ 835 | E( 836 | "button", 837 | { 838 | class: "btn", 839 | click: cancelFn, 840 | }, 841 | [_("Cancel")] 842 | ), 843 | " ", 844 | E( 845 | "button", 846 | { 847 | class: "btn primary", 848 | click: ui.createHandlerFn( 849 | this, 850 | "handleApplyConfig", 851 | mode, 852 | nodes, 853 | null 854 | ), 855 | }, 856 | [_("Import settings")] 857 | ), 858 | ] 859 | ), 860 | ] 861 | ) 862 | ); 863 | 864 | window.addEventListener("dragover", handleWindowDragDropIgnore); 865 | window.addEventListener("drop", handleWindowDragDropIgnore); 866 | }; 867 | 868 | ss.renderSectionAdd = function (/* ... */) { 869 | var nodes = this.super("renderSectionAdd", arguments); 870 | 871 | nodes.appendChild( 872 | E( 873 | "button", 874 | { 875 | class: "btn", 876 | click: ui.createHandlerFn(this, "handleConfigImport", "peer"), 877 | }, 878 | [_("Import configuration as peer…")] 879 | ) 880 | ); 881 | 882 | return nodes; 883 | }; 884 | 885 | ss.renderSectionPlaceholder = function () { 886 | return E("em", _("No peers defined yet.")); 887 | }; 888 | 889 | o = ss.option( 890 | form.Flag, 891 | "disabled", 892 | _("Peer disabled"), 893 | _("Enable / Disable peer. Restart amneziawg interface to apply changes.") 894 | ); 895 | o.modalonly = true; 896 | o.optional = true; 897 | 898 | o = ss.option( 899 | form.Value, 900 | "description", 901 | _("Description"), 902 | _("Optional. Description of peer.") 903 | ); 904 | o.placeholder = "My Peer"; 905 | o.datatype = "string"; 906 | o.optional = true; 907 | o.width = "30%"; 908 | o.textvalue = function (section_id) { 909 | var dis = ss.getOption("disabled"), 910 | pub = ss.getOption("public_key"), 911 | prv = ss.getOption("private_key"), 912 | psk = ss.getOption("preshared_key"), 913 | name = this.cfgvalue(section_id), 914 | key = pub.cfgvalue(section_id); 915 | 916 | var desc = [ 917 | E("p", [name ? E("span", [name]) : E("em", [_("Untitled peer")])]), 918 | ]; 919 | 920 | if (dis.cfgvalue(section_id) == "1") 921 | desc.push( 922 | E( 923 | "span", 924 | { 925 | class: "ifacebadge", 926 | "data-tooltip": _("AmneziaWG peer is disabled"), 927 | }, 928 | [ 929 | E("em", [ 930 | _( 931 | "Disabled", 932 | "Label indicating that AmneziaWG peer is disabled" 933 | ), 934 | ]), 935 | ] 936 | ), 937 | " " 938 | ); 939 | 940 | if (!key || !pub.isValid(section_id)) { 941 | desc.push( 942 | E( 943 | "span", 944 | { 945 | class: "ifacebadge", 946 | "data-tooltip": _("Public key is missing"), 947 | }, 948 | [ 949 | E("em", [ 950 | _( 951 | "Key missing", 952 | "Label indicating that AmneziaWG peer lacks public key" 953 | ), 954 | ]), 955 | ] 956 | ) 957 | ); 958 | } else { 959 | desc.push( 960 | E( 961 | "span", 962 | { 963 | class: "ifacebadge", 964 | "data-tooltip": _( 965 | "Public key: %h", 966 | "Tooltip displaying full AmneziaWG peer public key" 967 | ).format(key), 968 | }, 969 | [E("code", [key.replace(/^(.{5}).+(.{6})$/, "$1…$2")])] 970 | ), 971 | " ", 972 | prv.cfgvalue(section_id) && prv.isValid(section_id) 973 | ? E( 974 | "span", 975 | { 976 | class: "ifacebadge", 977 | "data-tooltip": _("Private key present"), 978 | }, 979 | [ 980 | _( 981 | "Private", 982 | "Label indicating that AmneziaWG peer private key is stored" 983 | ), 984 | ] 985 | ) 986 | : "", 987 | " ", 988 | psk.cfgvalue(section_id) && psk.isValid(section_id) 989 | ? E( 990 | "span", 991 | { 992 | class: "ifacebadge", 993 | "data-tooltip": _("Preshared key in use"), 994 | }, 995 | [_("PSK", "Label indicating that AmneziaWG peer uses a PSK")] 996 | ) 997 | : "" 998 | ); 999 | } 1000 | 1001 | return E([], desc); 1002 | }; 1003 | 1004 | function handleKeyChange(ev, section_id, value) { 1005 | var prv = this.section.getUIElement(section_id, "private_key"), 1006 | btn = this.map.findElement(".btn.qr-code"); 1007 | 1008 | btn.disabled = !prv.isValid() || !prv.getValue(); 1009 | } 1010 | 1011 | o = ss.option( 1012 | form.Value, 1013 | "public_key", 1014 | _("Public Key"), 1015 | _("Required. Public key of the AmneziaWG peer.") 1016 | ); 1017 | o.modalonly = true; 1018 | o.validate = validateBase64; 1019 | o.onchange = handleKeyChange; 1020 | 1021 | o = ss.option( 1022 | form.Value, 1023 | "private_key", 1024 | _("Private Key"), 1025 | _( 1026 | "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." 1027 | ) 1028 | ); 1029 | o.modalonly = true; 1030 | o.validate = validateBase64; 1031 | o.onchange = handleKeyChange; 1032 | o.password = true; 1033 | 1034 | o = ss.option(cbiKeyPairGenerate, "_gen_peer_keypair", " "); 1035 | o.modalonly = true; 1036 | 1037 | o = ss.option( 1038 | form.Value, 1039 | "preshared_key", 1040 | _("Preshared Key"), 1041 | _( 1042 | "Optional. Base64-encoded preshared key. Adds in an additional layer of symmetric-key cryptography for post-quantum resistance." 1043 | ) 1044 | ); 1045 | o.modalonly = true; 1046 | o.validate = validateBase64; 1047 | o.password = true; 1048 | 1049 | o = ss.option(form.DummyValue, "_gen_psk", " "); 1050 | o.modalonly = true; 1051 | o.cfgvalue = function (section_id, value) { 1052 | return E( 1053 | "button", 1054 | { 1055 | class: "btn", 1056 | click: ui.createHandlerFn( 1057 | this, 1058 | function (section_id, ev) { 1059 | var psk = this.section.getUIElement(section_id, "preshared_key"), 1060 | map = this.map; 1061 | 1062 | if ( 1063 | psk.getValue() && 1064 | !confirm(_("Do you want to replace the current PSK?")) 1065 | ) 1066 | return; 1067 | 1068 | return generatePsk().then(function (key) { 1069 | psk.setValue(key); 1070 | map.save(null, true); 1071 | }); 1072 | }, 1073 | section_id 1074 | ), 1075 | }, 1076 | [_("Generate preshared key")] 1077 | ); 1078 | }; 1079 | 1080 | o = ss.option( 1081 | form.DynamicList, 1082 | "allowed_ips", 1083 | _("Allowed IPs"), 1084 | _( 1085 | "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." 1086 | ) 1087 | ); 1088 | o.datatype = "ipaddr"; 1089 | o.textvalue = function (section_id) { 1090 | var ips = L.toArray(this.cfgvalue(section_id)), 1091 | list = []; 1092 | 1093 | for (var i = 0; i < ips.length; i++) { 1094 | if (i > 7) { 1095 | list.push( 1096 | E( 1097 | "em", 1098 | { 1099 | class: "ifacebadge cbi-tooltip-container", 1100 | }, 1101 | [ 1102 | _( 1103 | "+ %d more", 1104 | "Label indicating further amount of allowed ips" 1105 | ).format(ips.length - i), 1106 | E( 1107 | "span", 1108 | { 1109 | class: "cbi-tooltip", 1110 | }, 1111 | [ 1112 | E( 1113 | "ul", 1114 | ips.map(function (ip) { 1115 | return E("li", [ 1116 | E("span", { class: "ifacebadge" }, [ip]), 1117 | ]); 1118 | }) 1119 | ), 1120 | ] 1121 | ), 1122 | ] 1123 | ) 1124 | ); 1125 | 1126 | break; 1127 | } 1128 | 1129 | list.push(E("span", { class: "ifacebadge" }, [ips[i]])); 1130 | } 1131 | 1132 | if (!list.length) list.push("*"); 1133 | 1134 | return E( 1135 | "span", 1136 | { style: "display:inline-flex;flex-wrap:wrap;gap:.125em" }, 1137 | list 1138 | ); 1139 | }; 1140 | 1141 | o = ss.option( 1142 | form.Flag, 1143 | "route_allowed_ips", 1144 | _("Route Allowed IPs"), 1145 | _("Optional. Create routes for Allowed IPs for this peer.") 1146 | ); 1147 | o.modalonly = true; 1148 | 1149 | o = ss.option( 1150 | form.Value, 1151 | "endpoint_host", 1152 | _("Endpoint Host"), 1153 | _( 1154 | "Optional. Host of peer. Names are resolved prior to bringing up the interface." 1155 | ) 1156 | ); 1157 | o.placeholder = "vpn.example.com"; 1158 | o.datatype = "host"; 1159 | o.textvalue = function (section_id) { 1160 | var host = this.cfgvalue(section_id), 1161 | port = this.section.cfgvalue(section_id, "endpoint_port"); 1162 | 1163 | return host && port 1164 | ? "%h:%d".format(host, port) 1165 | : host 1166 | ? "%h:*".format(host) 1167 | : port 1168 | ? "*:%d".format(port) 1169 | : "*"; 1170 | }; 1171 | 1172 | o = ss.option( 1173 | form.Value, 1174 | "endpoint_port", 1175 | _("Endpoint Port"), 1176 | _("Optional. Port of peer.") 1177 | ); 1178 | o.modalonly = true; 1179 | o.placeholder = "51820"; 1180 | o.datatype = "port"; 1181 | 1182 | o = ss.option( 1183 | form.Value, 1184 | "persistent_keepalive", 1185 | _("Persistent Keep Alive"), 1186 | _( 1187 | "Optional. Seconds between keep alive messages. Default is 0 (disabled). Recommended value if this device is behind a NAT is 25." 1188 | ) 1189 | ); 1190 | o.modalonly = true; 1191 | o.datatype = "range(0,65535)"; 1192 | o.placeholder = "0"; 1193 | 1194 | o = ss.option(form.DummyValue,"_keyops",_("Configuration Export"), 1195 | _("Generates a configuration suitable for import on a AmneziaWG peer")); 1196 | 1197 | o.modalonly = true; 1198 | 1199 | o.createPeerConfig = function (section_id, endpoint, ips, eips, dns) { 1200 | var pub = s.formvalue(s.section, "public_key"), 1201 | port = s.formvalue(s.section, "listen_port") || "51820", 1202 | jc = s.formvalue(s.section, "awg_jc") || "2", 1203 | jmin = s.formvalue(s.section, "awg_jmin") || "1", 1204 | jmax = s.formvalue(s.section, "awg_jmax") || "1000", 1205 | s1 = s.formvalue(s.section, "awg_s1") || "0", 1206 | s2 = s.formvalue(s.section, "awg_s2") || "0", 1207 | h1 = s.formvalue(s.section, "awg_h1") || "1", 1208 | h2 = s.formvalue(s.section, "awg_h2") || "2", 1209 | h3 = s.formvalue(s.section, "awg_h3") || "3", 1210 | h4 = s.formvalue(s.section, "awg_h4") || "4", 1211 | prv = this.section.formvalue(section_id, "private_key"), 1212 | psk = this.section.formvalue(section_id, "preshared_key"), 1213 | eport = this.section.formvalue(section_id, "endpoint_port"), 1214 | keep = this.section.formvalue(section_id, "persistent_keepalive"); 1215 | 1216 | // If endpoint is IPv6 we must escape it with [] 1217 | if (endpoint.indexOf(":") > 0) { 1218 | endpoint = "[" + endpoint + "]"; 1219 | } 1220 | 1221 | return [ 1222 | "[Interface]", 1223 | "PrivateKey = " + prv, 1224 | eips && eips.length ? "Address = " + eips.join(", ") : "# Address not defined", 1225 | eport ? "ListenPort = " + eport : "# ListenPort not defined", 1226 | dns && dns.length ? "DNS = " + dns.join(", ") : "# DNS not defined", 1227 | "Jc = " + jc, 1228 | "Jmin = " + jmin, 1229 | "Jmax = " + jmax, 1230 | "S1 = " + s1, 1231 | "S2 = " + s2, 1232 | "H1 = " + h1, 1233 | "H2 = " + h2, 1234 | "H3 = " + h3, 1235 | "H4 = " + h4, 1236 | "", 1237 | "[Peer]", 1238 | "PublicKey = " + pub, 1239 | psk ? "PresharedKey = " + psk : "# PresharedKey not used", 1240 | ips && ips.length 1241 | ? "AllowedIPs = " + ips.join(", ") 1242 | : "# AllowedIPs not defined", 1243 | endpoint 1244 | ? "Endpoint = " + endpoint + ":" + port 1245 | : "# Endpoint not defined", 1246 | keep 1247 | ? "PersistentKeepAlive = " + keep 1248 | : "# PersistentKeepAlive not defined", 1249 | ].join("\n"); 1250 | }; 1251 | 1252 | o.handleGenerateQR = function (section_id, ev) { 1253 | var mapNode = ss.getActiveModalMap(), 1254 | headNode = mapNode.parentNode.querySelector("h4"), 1255 | configGenerator = this.createPeerConfig.bind(this, section_id), 1256 | parent = this.map, 1257 | eips = this.section.formvalue(section_id, 'allowed_ips'); 1258 | 1259 | return Promise.all([ 1260 | network.getWANNetworks(), 1261 | network.getWAN6Networks(), 1262 | network.getNetwork('lan'), 1263 | L.resolveDefault(uci.load("ddns")), 1264 | L.resolveDefault(uci.load("system")), 1265 | parent.save(null, true), 1266 | ]).then(function (data) { 1267 | var hostnames = []; 1268 | 1269 | uci.sections("ddns", "service", function (s) { 1270 | if (typeof s.lookup_host == "string" && s.enabled == "1") 1271 | hostnames.push(s.lookup_host); 1272 | }); 1273 | 1274 | uci.sections("system", "system", function (s) { 1275 | if (typeof s.hostname == "string" && s.hostname.indexOf(".") > 0) 1276 | hostnames.push(s.hostname); 1277 | }); 1278 | 1279 | for (var i = 0; i < data[0].length; i++) 1280 | hostnames.push.apply( 1281 | hostnames, 1282 | data[0][i].getIPAddrs().map(function (ip) { 1283 | return ip.split("/")[0]; 1284 | }) 1285 | ); 1286 | 1287 | for (var i = 0; i < data[1].length; i++) 1288 | hostnames.push.apply( 1289 | hostnames, 1290 | data[1][i].getIP6Addrs().map(function (ip) { 1291 | return ip.split("/")[0]; 1292 | }) 1293 | ); 1294 | 1295 | var ips = ["0.0.0.0/0", "::/0"]; 1296 | 1297 | var dns = []; 1298 | 1299 | var lan = data[2]; 1300 | if (lan) { 1301 | var lanIp = lan.getIPAddr(); 1302 | if (lanIp) { 1303 | dns.unshift(lanIp) 1304 | } 1305 | } 1306 | 1307 | var qrm, qrs, qro; 1308 | 1309 | qrm = new form.JSONMap( 1310 | { config: { endpoint: hostnames[0], allowed_ips: ips, addresses: eips, dns_servers: dns } }, 1311 | null, 1312 | _( 1313 | "The generated configuration can be imported into a AmneziaWG client application to set up a connection towards this device." 1314 | ) 1315 | ); 1316 | qrm.parent = parent; 1317 | 1318 | qrs = qrm.section(form.NamedSection, "config"); 1319 | 1320 | function handleConfigChange(ev, section_id, value) { 1321 | var code = this.map.findElement(".qr-code"), 1322 | conf = this.map.findElement(".client-config"), 1323 | endpoint = this.section.getUIElement(section_id, "endpoint"), 1324 | ips = this.section.getUIElement(section_id, "allowed_ips"), 1325 | eips = this.section.getUIElement(section_id, 'addresses'), 1326 | dns = this.section.getUIElement(section_id, 'dns_servers'); 1327 | 1328 | if (this.isValid(section_id)) { 1329 | conf.firstChild.data = configGenerator( 1330 | endpoint.getValue(), 1331 | ips.getValue(), 1332 | eips.getValue(), 1333 | dns.getValue() 1334 | ); 1335 | code.style.opacity = ".5"; 1336 | 1337 | invokeQREncode(conf.firstChild.data, code); 1338 | } 1339 | } 1340 | 1341 | qro = qrs.option( 1342 | form.Value, 1343 | "endpoint", 1344 | _("Connection endpoint"), 1345 | _( 1346 | "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." 1347 | ) 1348 | ); 1349 | qro.datatype = "or(ipaddr,hostname)"; 1350 | hostnames.forEach(function (hostname) { 1351 | qro.value(hostname); 1352 | }); 1353 | qro.onchange = handleConfigChange; 1354 | 1355 | qro = qrs.option( 1356 | form.DynamicList, 1357 | "allowed_ips", 1358 | _("Allowed IPs"), 1359 | _( 1360 | "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." 1361 | ) 1362 | ); 1363 | qro.datatype = "ipaddr"; 1364 | qro.default = ips; 1365 | ips.forEach(function (ip) { 1366 | qro.value(ip); 1367 | }); 1368 | qro.onchange = handleConfigChange; 1369 | 1370 | 1371 | qro = qrs.option(form.DynamicList, "dns_servers", _("DNS Servers"), _("DNS servers for the remote clients using this tunnel to your openwrt device. Some AmneziaWG clients require this to be set.")); 1372 | qro.datatype = "ipaddr"; 1373 | qro.default = dns; 1374 | qro.onchange = handleConfigChange; 1375 | 1376 | qro = qrs.option(form.DynamicList, "addresses", _("Addresses"), _("IP addresses for the peer to use inside the tunnel. Some clients require this setting.")); 1377 | qro.datatype = "ipaddr"; 1378 | qro.default = eips; 1379 | eips.forEach(function(eip) { qro.value(eip) }); 1380 | qro.onchange = handleConfigChange; 1381 | 1382 | qro = qrs.option(form.DummyValue, "output"); 1383 | qro.renderWidget = function () { 1384 | var peer_config = configGenerator(hostnames[0], ips); 1385 | 1386 | var node = E( 1387 | "div", 1388 | { 1389 | style: 1390 | "display:flex;flex-wrap:wrap;align-items:center;gap:.5em;width:100%", 1391 | }, 1392 | [ 1393 | E( 1394 | "div", 1395 | { 1396 | class: "qr-code", 1397 | style: "width:320px;flex:0 1 320px;text-align:center", 1398 | }, 1399 | [E("em", { class: "spinning" }, [_("Generating QR code…")])] 1400 | ), 1401 | E( 1402 | "pre", 1403 | { 1404 | class: "client-config", 1405 | style: "flex:1;white-space:pre;overflow:auto", 1406 | click: function (ev) { 1407 | var sel = window.getSelection(), 1408 | range = document.createRange(); 1409 | 1410 | range.selectNodeContents(ev.currentTarget); 1411 | 1412 | sel.removeAllRanges(); 1413 | sel.addRange(range); 1414 | }, 1415 | }, 1416 | [peer_config] 1417 | ), 1418 | ] 1419 | ); 1420 | 1421 | invokeQREncode(peer_config, node.firstChild); 1422 | 1423 | return node; 1424 | }; 1425 | 1426 | return qrm.render().then(function (nodes) { 1427 | mapNode.classList.add("hidden"); 1428 | mapNode.nextElementSibling.classList.add("hidden"); 1429 | 1430 | headNode.appendChild(E("span", [" » ", _("Generate configuration")])); 1431 | mapNode.parentNode.appendChild( 1432 | E( 1433 | [], 1434 | [ 1435 | nodes, 1436 | E( 1437 | "div", 1438 | { 1439 | class: "right", 1440 | }, 1441 | [ 1442 | E( 1443 | "button", 1444 | { 1445 | class: "btn", 1446 | click: function () { 1447 | nodes.parentNode.removeChild(nodes.nextSibling); 1448 | nodes.parentNode.removeChild(nodes); 1449 | mapNode.classList.remove("hidden"); 1450 | mapNode.nextSibling.classList.remove("hidden"); 1451 | headNode.removeChild(headNode.lastChild); 1452 | }, 1453 | }, 1454 | [_("Back to peer configuration")] 1455 | ), 1456 | ] 1457 | ), 1458 | ] 1459 | ) 1460 | ); 1461 | 1462 | if (!s.formvalue(s.section, "listen_port")) { 1463 | nodes.appendChild( 1464 | E("div", { class: "alert-message" }, [ 1465 | E("p", [ 1466 | _( 1467 | "No fixed interface listening port defined, peers might not be able to initiate connections to this AmneziaWG instance!" 1468 | ), 1469 | ]), 1470 | ]) 1471 | ); 1472 | } 1473 | }); 1474 | }); 1475 | }; 1476 | 1477 | o.cfgvalue = function (section_id, value) { 1478 | var privkey = this.section.cfgvalue(section_id, "private_key"); 1479 | 1480 | return E( 1481 | "button", 1482 | { 1483 | class: "btn qr-code", 1484 | style: "display:inline-flex;align-items:center;gap:.5em", 1485 | click: ui.createHandlerFn(this, "handleGenerateQR", section_id), 1486 | disabled: privkey ? null : "", 1487 | }, 1488 | [ 1489 | Object.assign(E(qrIcon), { style: "width:22px;height:22px" }), 1490 | _("Generate configuration…"), 1491 | ] 1492 | ); 1493 | }; 1494 | }, 1495 | 1496 | deleteConfiguration: function () { 1497 | uci.sections("network", "amneziawg_%s".format(this.sid), function (s) { 1498 | uci.remove("network", s[".name"]); 1499 | }); 1500 | }, 1501 | }); 1502 | --------------------------------------------------------------------------------