├── .github └── workflows │ ├── build-module.yml │ ├── build-to-all-latest-snapshots.yml │ └── run-release.yml ├── .gitignore ├── README.md ├── amneziawg-install.sh ├── amneziawg-tools ├── Makefile └── files │ ├── amneziawg.sh │ └── amneziawg_watchdog ├── index.js ├── kmod-amneziawg ├── Makefile ├── files │ ├── amnezia-sources.patch │ └── amnezia-uapi.patch └── src │ └── Makefile ├── luci-app-amneziawg ├── Makefile ├── htdocs │ └── luci-static │ │ └── resources │ │ ├── protocol │ │ └── amneziawg.js │ │ └── view │ │ └── amneziawg │ │ └── status.js └── root │ └── usr │ └── share │ ├── luci │ └── menu.d │ │ └── luci-proto-amneziawg.json │ └── rpcd │ ├── acl.d │ └── luci-amneziawg.json │ └── ucode │ └── luci.amneziawg ├── package-lock.json └── package.json /.github/workflows/build-module.yml: -------------------------------------------------------------------------------- 1 | name: Create Release on Tag 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | workflow_call: 8 | inputs: 9 | tag_name: 10 | required: true 11 | type: string 12 | 13 | jobs: 14 | generate-config: 15 | runs-on: ubuntu-latest 16 | outputs: 17 | job-config: ${{ steps.generate-config.outputs.job-config }} 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: '20.16.0' 26 | 27 | - name: Get OpenWRT version from tag 28 | id: get_version 29 | run: | 30 | if [ "${{ github.event_name }}" == "workflow_call" ]; then 31 | echo "VERSION=${{ inputs.tag_name }}" >> $GITHUB_ENV 32 | echo "VERSION=${VERSION#v}" >> $GITHUB_ENV 33 | else 34 | echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV 35 | fi 36 | 37 | - name: Install dependencies 38 | run: npm install 39 | 40 | - name: Generate Job Config 41 | id: generate-config 42 | run: node index.js ${{ env.VERSION }} 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | build: 47 | name: "v${{ matrix.build_env.tag }} - ${{ matrix.build_env.pkgarch}} :: ${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}} build" 48 | runs-on: ubuntu-latest 49 | needs: generate-config 50 | strategy: 51 | matrix: 52 | build_env: ${{ fromJson(needs.generate-config.outputs.job-config) }} 53 | fail-fast: false 54 | 55 | steps: 56 | - uses: actions/checkout@v4 57 | with: 58 | repository: openwrt/openwrt 59 | ref: v${{ matrix.build_env.tag }} 60 | fetch-depth: 0 61 | 62 | - name: Cache Tools and Kernel 63 | id: cache-tools-kernel 64 | uses: actions/cache@v4 65 | env: 66 | cache-name: "cache-tools-kernel-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch}}-${{ matrix.build_env.target}}-${{ matrix.build_env.subtarget}}" 67 | with: 68 | path: "**" 69 | key: ${{ runner.os }}-build-vm4-${{ env.cache-name }} 70 | 71 | - name: Building kernel and tools 72 | #if: ${{ steps.cache-tools-kernel.outputs.cache-hit != 'true' }} 73 | run: | 74 | echo "pkgarch: ${{ matrix.build_env.pkgarch}}, target:${{ matrix.build_env.target}}, subtarget: ${{ matrix.build_env.subtarget}}" 75 | 76 | # Setup & install feeds 77 | wget https://downloads.openwrt.org/releases/${{ matrix.build_env.tag }}/targets/${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}}/feeds.buildinfo -O feeds.conf 78 | echo "src-git awgopenwrt https://github.com/Slava-Shchipunov/awg-openwrt.git" >> ./feeds.conf 79 | ./scripts/feeds update && ./scripts/feeds install -a 80 | 81 | # Setup config with AWG and dependencies 82 | wget https://downloads.openwrt.org/releases/${{ matrix.build_env.tag }}/targets/${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}}/config.buildinfo -O .config 83 | echo "CONFIG_PACKAGE_kmod-amneziawg=m" >> .config 84 | echo "CONFIG_PACKAGE_amneziawg-tools=y" >> .config 85 | echo "CONFIG_PACKAGE_luci-app-amneziawg=y" >> .config 86 | echo "CONFIG_PACKAGE_kmod-crypto-lib-chacha20=m" >> .config 87 | echo "CONFIG_PACKAGE_kmod-crypto-lib-chacha20poly1305=m" >> .config 88 | echo "CONFIG_PACKAGE_kmod-crypto-chacha20poly1305=m" >> .config 89 | 90 | make defconfig 91 | 92 | echo " > make tools/install" 93 | make tools/install -i -j `nproc` 94 | 95 | cat ./build_dir/target-*/linux-*/linux-*/.vermagic || true 96 | 97 | echo " > make toolchain/install" 98 | make toolchain/install -i -j `nproc` 99 | 100 | cat ./build_dir/target-*/linux-*/linux-*/.vermagic || true 101 | 102 | # compile kernel module 103 | echo " > make target/linux/compile" 104 | make target/linux/compile -i -j `nproc` V=s 105 | 106 | VERMAGIC=`cat ./build_dir/target-*/linux-*/linux-*/.vermagic` 107 | VERMAGIC_EXPECTED=${{ matrix.build_env.vermagic }} 108 | 109 | if [ "$VERMAGIC" != "$VERMAGIC_EXPECTED" ]; then 110 | echo Vermagic mismatch: $VERMAGIC, expected $VERMAGIC_EXPECTED 111 | exit 1 112 | fi 113 | 114 | - name: Build AmneziaWG 115 | run: | 116 | VERMAGIC=`cat ./build_dir/target-*/linux-*/linux-*/.vermagic` 117 | echo "Vermagic: $VERMAGIC" 118 | 119 | VERMAGIC_EXPECTED=${{ matrix.build_env.vermagic }} 120 | 121 | if [ "$VERMAGIC" != "$VERMAGIC_EXPECTED" ]; then 122 | echo Vermagic mismatch: $VERMAGIC, expected $VERMAGIC_EXPECTED 123 | exit 1 124 | fi 125 | 126 | # Ignore kmod build for some targets, replace with the awg-go 127 | make package/kmod-amneziawg/{clean,download,prepare} V=s || true 128 | make package/kmod-amneziawg/compile V=s || true 129 | 130 | make package/luci-app-amneziawg/{clean,download,prepare} 131 | make package/luci-app-amneziawg/compile V=s 132 | 133 | make V=s package/amneziawg-tools/{clean,download,prepare} 134 | make V=s package/amneziawg-tools/compile 135 | 136 | - name: Prepare artifacts 137 | run: | 138 | tag_name=${{ github.ref_name }} 139 | mkdir -p awgrelease 140 | postfix="v${{ matrix.build_env.tag }}_${{ matrix.build_env.pkgarch}}_${{ matrix.build_env.target}}_${{ matrix.build_env.subtarget}}" 141 | cp bin/packages/${{ matrix.build_env.pkgarch }}/awgopenwrt/amneziawg-tools_*.ipk awgrelease/amneziawg-tools_${postfix}.ipk 142 | cp bin/packages/${{ matrix.build_env.pkgarch }}/awgopenwrt/luci-app-amneziawg_*.ipk awgrelease/luci-app-amneziawg_${postfix}.ipk 143 | cp bin/targets/${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}}/packages/kmod-amneziawg_*.ipk awgrelease/kmod-amneziawg_${postfix}.ipk 144 | 145 | - name: Release 146 | uses: softprops/action-gh-release@v1 147 | with: 148 | files: awgrelease/*.ipk 149 | -------------------------------------------------------------------------------- /.github/workflows/build-to-all-latest-snapshots.yml: -------------------------------------------------------------------------------- 1 | name: Build AmneziaWG for snapshot OpenWRT 2 | 3 | env: 4 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 5 | 6 | on: 7 | push: 8 | tags: 9 | - "SNAPSHOT" 10 | # schedule: 11 | # - cron: '0 */4 * * *' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | generate-config: 16 | runs-on: ubuntu-latest 17 | outputs: 18 | job-config: ${{ steps.generate-config.outputs.job-config }} 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v4 22 | 23 | - name: Set up Node.js 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: '20.16.0' 27 | 28 | - name: Install dependencies 29 | run: npm install 30 | 31 | - name: Generate Job Config 32 | id: generate-config 33 | run: node index.js SNAPSHOT 34 | 35 | build: 36 | name: "${{ matrix.build_env.tag }} - ${{ matrix.build_env.pkgarch}} :: ${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}} build" 37 | runs-on: ubuntu-latest 38 | needs: generate-config 39 | strategy: 40 | matrix: 41 | build_env: ${{ fromJson(needs.generate-config.outputs.job-config) }} 42 | fail-fast: false 43 | 44 | steps: 45 | - name: Check if release file exists 46 | id: check_release 47 | run: | 48 | postfix="${{ matrix.build_env.tag }}_${{ matrix.build_env.vermagic }}_${{ matrix.build_env.pkgarch}}_${{ matrix.build_env.target}}_${{ matrix.build_env.subtarget}}" 49 | RELEASE_FILE="kmod-amneziawg_${postfix}.ipk" 50 | TAG=${{ matrix.build_env.tag }} 51 | 52 | echo "Checking for release file: $RELEASE_FILE in release: $TAG" 53 | 54 | # Используем GitHub CLI для проверки наличия файла в релизе 55 | if gh release view "$TAG" --repo Slava-Shchipunov/awg-openwrt --json assets --jq ".assets | .[].name" | grep -q "$RELEASE_FILE"; then 56 | echo "Release file $RELEASE_FILE already exists. Skipping job." 57 | echo "FILE_EXISTS=true" >> $GITHUB_ENV 58 | else 59 | echo "FILE_EXISTS=false" >> $GITHUB_ENV 60 | fi 61 | 62 | # Прерываем job'у, если файл уже существует 63 | - name: Skip job if release file exists 64 | if: env.FILE_EXISTS == 'true' 65 | run: | 66 | echo "Skipping job as release file already exists." 67 | exit 0 68 | 69 | - name: Get snapshot commit SHA and device ARCH 70 | if: env.FILE_EXISTS == 'false' 71 | run: | 72 | PROFILE_URL="https://downloads.openwrt.org/snapshots/targets/${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}}/profiles.json" 73 | SNAPSHOT_COMMIT_SHA=$(wget -q $PROFILE_URL -O- | tr ',' '\n' | grep "version_code"| awk -F: '{print $2}' | tr -d '"' | awk -F- '{print $2}') 74 | echo "Snapshot commit sha: $SNAPSHOT_COMMIT_SHA" 75 | echo "SNAPSHOT_COMMIT_SHA=$SNAPSHOT_COMMIT_SHA" >> $GITHUB_ENV 76 | 77 | DEVICE_ARCH=$(wget -q $PROFILE_URL -O- | tr ',' '\n' | grep "arch_packages"| awk -F: '{print $2}' | tr -d '"') 78 | echo "Device ARCH: $DEVICE_ARCH" 79 | echo "DEVICE_ARCH=$DEVICE_ARCH" >> $GITHUB_ENV 80 | 81 | - uses: actions/checkout@v4 82 | if: env.FILE_EXISTS == 'false' 83 | with: 84 | repository: openwrt/openwrt 85 | fetch-depth: 0 86 | 87 | - name: Checkout snapshot commit in openwrt repo 88 | if: env.FILE_EXISTS == 'false' 89 | run: git checkout ${{ env.SNAPSHOT_COMMIT_SHA}} 90 | 91 | - name: Building kernel and tools 92 | if: env.FILE_EXISTS == 'false' 93 | run: | 94 | echo "pkgarch: ${{ matrix.build_env.pkgarch}}, target:${{ matrix.build_env.target}}, subtarget: ${{ matrix.build_env.subtarget}}" 95 | 96 | # Setup & install feeds 97 | wget https://downloads.openwrt.org/snapshots/targets/${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}}/feeds.buildinfo -O feeds.conf 98 | echo "src-git awgopenwrt https://github.com/Slava-Shchipunov/awg-openwrt.git" >> ./feeds.conf 99 | ./scripts/feeds update && ./scripts/feeds install -a 100 | 101 | # Setup config with AWG and dependencies 102 | wget https://downloads.openwrt.org/snapshots/targets/${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}}/config.buildinfo -O .config 103 | echo "CONFIG_PACKAGE_kmod-amneziawg=m" >> .config 104 | echo "CONFIG_PACKAGE_amneziawg-tools=y" >> .config 105 | echo "CONFIG_PACKAGE_luci-app-amneziawg=y" >> .config 106 | echo "CONFIG_PACKAGE_kmod-crypto-lib-chacha20=m" >> .config 107 | echo "CONFIG_PACKAGE_kmod-crypto-lib-chacha20poly1305=m" >> .config 108 | echo "CONFIG_PACKAGE_kmod-crypto-chacha20poly1305=m" >> .config 109 | 110 | make defconfig 111 | 112 | echo " > make tools/install" 113 | make tools/install -i -j `nproc` 114 | 115 | cat ./build_dir/target-*/linux-*/linux-*/.vermagic || true 116 | 117 | echo " > make toolchain/install" 118 | make toolchain/install -i -j `nproc` 119 | 120 | cat ./build_dir/target-*/linux-*/linux-*/.vermagic || true 121 | 122 | # compile kernel module 123 | echo " > make target/linux/compile" 124 | make target/linux/compile -i -j `nproc` V=s 125 | 126 | VERMAGIC=`cat ./build_dir/target-*/linux-*/linux-*/.vermagic` 127 | VERMAGIC_EXPECTED=${{ matrix.build_env.vermagic }} 128 | 129 | if [ "$VERMAGIC" != "$VERMAGIC_EXPECTED" ]; then 130 | echo Vermagic mismatch: $VERMAGIC, expected $VERMAGIC_EXPECTED 131 | exit 1 132 | fi 133 | 134 | - name: Build AmneziaWG 135 | if: env.FILE_EXISTS == 'false' 136 | run: | 137 | VERMAGIC=`cat ./build_dir/target-*/linux-*/linux-*/.vermagic` 138 | echo "Vermagic: $VERMAGIC" 139 | 140 | VERMAGIC_EXPECTED=${{ matrix.build_env.vermagic }} 141 | 142 | if [ "$VERMAGIC" != "$VERMAGIC_EXPECTED" ]; then 143 | echo Vermagic mismatch: $VERMAGIC, expected $VERMAGIC_EXPECTED 144 | exit 1 145 | fi 146 | 147 | # Ignore kmod build for some targets, replace with the awg-go 148 | make package/kmod-amneziawg/{clean,download,prepare} V=s || true 149 | make package/kmod-amneziawg/compile V=s || true 150 | 151 | make package/luci-app-amneziawg/{clean,download,prepare} 152 | make package/luci-app-amneziawg/compile V=s 153 | 154 | make V=s package/amneziawg-tools/{clean,download,prepare} 155 | make V=s package/amneziawg-tools/compile 156 | 157 | - name: Prepare artifacts 158 | if: env.FILE_EXISTS == 'false' 159 | run: | 160 | tag_name=${{ github.ref_name }} 161 | mkdir -p awgrelease 162 | postfix="${{ matrix.build_env.tag }}_${{ matrix.build_env.vermagic }}_${{ matrix.build_env.pkgarch}}_${{ matrix.build_env.target}}_${{ matrix.build_env.subtarget}}" 163 | cp bin/packages/${{ matrix.build_env.pkgarch }}/awgopenwrt/amneziawg-tools_*.ipk awgrelease/amneziawg-tools_${postfix}.ipk 164 | cp bin/packages/${{ matrix.build_env.pkgarch }}/awgopenwrt/luci-app-amneziawg_*.ipk awgrelease/luci-app-amneziawg_${postfix}.ipk 165 | cp bin/targets/${{ matrix.build_env.target}}/${{ matrix.build_env.subtarget}}/packages/kmod-amneziawg_*.ipk awgrelease/kmod-amneziawg_${postfix}.ipk 166 | 167 | - name: Delete old release assets 168 | if: env.FILE_EXISTS == 'false' 169 | run: | 170 | postfix="${{ matrix.build_env.pkgarch }}_${{ matrix.build_env.target }}_${{ matrix.build_env.subtarget }}" 171 | echo "Looking for old release files ending with: ${postfix}" 172 | 173 | # Получаем список всех артефактов релиза по имени 174 | assets=$(gh release view "${{ matrix.build_env.tag }}" --repo Slava-Shchipunov/awg-openwrt --json assets --jq ".assets | .[] | select(.name | endswith(\"$postfix.ipk\")) | .name") 175 | 176 | # Удаляем найденные артефакты по имени 177 | for asset_name in $assets; do 178 | echo "Deleting asset with name: $asset_name" 179 | gh release delete-asset "${{ matrix.build_env.tag }}" "$asset_name" --repo Slava-Shchipunov/awg-openwrt 180 | done 181 | 182 | - name: Release 183 | if: env.FILE_EXISTS == 'false' 184 | uses: softprops/action-gh-release@v1 185 | with: 186 | files: awgrelease/*.ipk 187 | tag_name: ${{ matrix.build_env.tag }} 188 | -------------------------------------------------------------------------------- /.github/workflows/run-release.yml: -------------------------------------------------------------------------------- 1 | name: Sync OpenWRT Releases 2 | 3 | on: 4 | # schedule: 5 | # - cron: '0 0 */3 * *' # Проверка новых релизов раз в три дня 6 | workflow_dispatch: # Возможность вручную запустить Action 7 | 8 | jobs: 9 | sync-releases: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | release_exists: ${{ steps.check_release.outputs.release_exists }} 13 | release_tag: ${{ steps.get_release.outputs.release_tag }} 14 | steps: 15 | - name: Checkout your repo 16 | uses: actions/checkout@v4 17 | 18 | - name: Get the latest release from OpenWRT 19 | id: get_release 20 | run: | 21 | curl -s https://api.github.com/repos/openwrt/openwrt/releases/latest > latest_release.json 22 | RELEASE_TAG=$(jq -r .tag_name latest_release.json) 23 | RELEASE_NAME=$(jq -r .name latest_release.json) 24 | echo "::set-output name=release_tag::$RELEASE_TAG" 25 | echo "::set-output name=release_name::$RELEASE_NAME" 26 | 27 | - name: Check if release exists in your repo 28 | id: check_release 29 | run: | 30 | RELEASE_EXISTS=$(curl -s https://api.github.com/repos/Slava-Shchipunov/awg-openwrt/releases/tags/${{ steps.get_release.outputs.release_tag }} | jq -r .tag_name) 31 | if [ "$RELEASE_EXISTS" == "null" ]; then 32 | echo "::set-output name=release_exists::false" 33 | else 34 | echo "::set-output name=release_exists::true" 35 | fi 36 | 37 | - name: Create release in your repo 38 | if: steps.check_release.outputs.release_exists == 'false' 39 | run: | 40 | ART=" 41 | \`\`\` 42 | _______ ________ __ 43 | | |.-----.-----.-----.| | | |.----.| |_ 44 | | - || _ | -__| || | | || _|| _| 45 | |_______|| __|_____|__|__||________||__| |____| 46 | |__| A M N E Z I A W I R E G U A R D 47 | ----------------------------------------------------- 48 | OpenWrt ${{ steps.get_release.outputs.release_tag }} 49 | ----------------------------------------------------- 50 | \`\`\`" 51 | 52 | curl -X POST https://api.github.com/repos/Slava-Shchipunov/awg-openwrt/releases \ 53 | -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 54 | -H "Content-Type: application/json" \ 55 | -d "$(jq -n --arg tag "${{ steps.get_release.outputs.release_tag }}" \ 56 | --arg name "Build amnezia wg for all devices with openwrt ${{ steps.get_release.outputs.release_tag }}" \ 57 | --arg body "$ART" \ 58 | '{ tag_name: $tag, name: $name, body: $body }')" 59 | 60 | run-build: 61 | runs-on: ubuntu-latest 62 | needs: sync-releases 63 | if: needs.sync-releases.outputs.release_exists == 'false' 64 | uses: Slava-Shchipunov/awg-openwrt/.github/workflows/build-module.yml@master 65 | with: 66 | tag_name: ${{ needs.sync-releases.outputs.release_tag }} 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Автоматическая настройка AmneziaWG для OpenWRT версии 23.05.0 и более новых 2 | Для автоматической настройки рекомендую использовать [скрипт](https://github.com/itdoginfo/domain-routing-openwrt) от пользователя itdog. Этот скрипт позволяет автоматически скачать нужные пакеты из собранных здесь и настроить [точечный обход блокировок по доменам](https://habr.com/ru/articles/767464/). 3 | 4 | Если же вам нужно только установить пакеты, я добавил скрипт amneziawg-install - он автоматически скачает пакеты из этого репозитория под ваше устройство (только для стабильной версии OpenWRT), а также предложит сразу настроить интерфейс с протоколом AmneziaWG. Если пользователь согласится, нужно будет ввести параметры конфига, которые запросит скрипт. При этом скрипт создаст интерфейс, настроит для него правила фаерволла, а также **включит перенаправление всего траффика через тунель AmneziaWG** (установит в настройках Peer галочку Route Allowed IPs). 5 | Для запуска скрипта подключитесь к роутеру по SSH, введите команду и следуйте инструкциям на экране: 6 | ``` 7 | sh <(wget -O - https://raw.githubusercontent.com/Slava-Shchipunov/awg-openwrt/refs/heads/master/amneziawg-install.sh) 8 | ``` 9 | 10 | # Сборка пакетов для всех устройств, поддерживающих OpenWRT 11 | В репозиторий добавлен скрипт, который парсит данные о поддерживаемых платформах со страницы OpenWRT и автоматически запускает сборку пакетов AmneziaWG для всех устройств. 12 | На данный момент я собрал пакеты для всех устройств для OpenWRT версий: 13 | 1) [23.05.0](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.0) 14 | 2) [23.05.1](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.1) 15 | 3) [23.05.2](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.2) 16 | 4) [23.05.3](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.3) 17 | 5) [23.05.4](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.4) 18 | 6) [23.05.5](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.5) 19 | 20 | И собрал пакеты для популярных устройств для OpenWRT [SNAPSHOT](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/SNAPSHOT) 21 | 22 | Также запускал сборку для версии [22.03.7](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v22.03.7), но там для двух платформ сборка завершилась ошибкой. Так как это достаточно старая версия OpenWRT, я не стал разбираться, в чем проблема. 23 | 24 | В дальнейшем при выходе новых релизов OpenWRT будут автоматически создаваться релизы с пакетами AmneziaWG и запускаться сборка пакетов под все устройства, поддерживаемые новой версией. Github action для проверки появления нового релиза запускается автоматически раз в 3 дня, а также может быть запущен вручную. 25 | 26 | ## Автоматическая сборка пакетов для SNAPSHOT версии 27 | В репозитории настроен github action, который запускается каждые 4 часа и проверяет [страницу снапшотов](https://downloads.openwrt.org/snapshots/targets/) сайта OpenWRT. При этом, если для какой-то платформы обнаруживается снапшот с более новой версией ядра, запускается сборка пакетов под эту платформу, а новые файлы заменяют старые. В целях экономии ресурсов и ускорения процесса сборки, пакеты собираются только для популярных платформ, которые указаны в массиве `SNAPSHOT_SUBTARGETS_TO_BUILD` в файле index.js. 28 | 29 | ## Выбор пакетов для своего устройства 30 | В соответствии с пунктом [Указываем переменные для сборки](https://github.com/itdoginfo/domain-routing-openwrt/wiki/Amnezia-WG-Build#%D1%83%D0%BA%D0%B0%D0%B7%D1%8B%D0%B2%D0%B0%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D0%B4%D0%BB%D1%8F-%D1%81%D0%B1%D0%BE%D1%80%D0%BA%D0%B8) 31 | определить `target` и `subtarget` вашего устройства. Далее перейти на страницу релиза, соответствующего вашей версии OpenWRT, затем поиском по странице (Ctrl+F) найти 3 пакета, название которых оканчивается на `target_subtarget.ipk`, соответствующие вашему устройству. 32 | 33 | ## Как запустить сборку для всех поддерживаемых устройств 34 | 1) Создать форк этого репозитория 35 | 2) Переключиться на вкладку Actions и включить Github actions (по умолчанию для форков они выключены) 36 | 3) Затем перейти на вкладку Code => Releases (в правой части экрана) => Draft a new release 37 | 4) Нажать Choose a tag и создать новый тег формата vX.X.X, где вместо X.X.X нужно подставить требуемую версию OpenWRT, например, v23.05.4 38 | 5) Выбрать в качестве target ветку `master` 39 | 6) Ввести Release title 40 | 7) Нажать внизу зеленую кнопку Publish release 41 | 42 | Для публичных репозиториев Github предоставляет неограниченное по времени использование раннеров, у меня запускалось до 20 параллельных джоб. Каждая джоба выполняется около 2 часов, общее время на сборку около 10 часов. 43 | В случае возникновения ошибок в одной джобе, будут отменены все незавершенные - в этом случае на вкладке Actions можно выбрать неудавшийся запуск и нажать Re-run failed jobs 44 | 45 | ## Сборка пакетов под определенную платформу 46 | Как запустить сборку пакетов для определенной платформы можно посмотреть в [инструкции на вики](https://github.com/itdoginfo/domain-routing-openwrt/wiki/Amnezia-WG-Build). Сборка под одно устройство займет около 2 часов. 47 | 48 | # Automatic configuration of AmneziaWG for OpenWRT version 23.05.0 and newer 49 | For automatic configuration, I recommend using the [script](https://github.com/itdoginfo/domain-routing-openwrt) from the user itdog. This script allows you to automatically download the necessary packages from those collected here and configure [point-by-point bypass of blocking by domains](https://habr.com/ru/articles/767464/) (instructions in Russian). 50 | 51 | If you only need to install packages, I added the amneziawg-install script - it will automatically download packages from this repository for your device (only for the stable version of OpenWRT), and also offer to immediately configure the interface with the AmneziaWG protocol. If the user agrees, you will need to enter the config parameters that the script will request. The script will create an interface, configure firewall rules for it, and also **enable redirection of all traffic through the AmneziaWG tunnel** (check the Route Allowed IPs box in the Peer settings). 52 | To run the script, connect to the router via SSH, enter the command and follow the instructions on the screen: 53 | ``` 54 | sh <(wget -O - https://raw.githubusercontent.com/Slava-Shchipunov/awg-openwrt/refs/heads/master/amneziawg-install.sh) 55 | ``` 56 | 57 | # Building packages for all devices that support OpenWRT 58 | A script has been added to the repository that parses data on supported platforms from the OpenWRT page and automatically starts building AmneziaWG packages for all devices. 59 | At the moment I have collected packages for all devices for OpenWRT versions: 60 | 1) [23.05.0](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.0) 61 | 2) [23.05.1](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.1) 62 | 3) [23.05.2](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.2) 63 | 4) [23.05.3](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.3) 64 | 5) [23.05.4](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v23.05.4) 65 | 66 | And collected packages for popular devices for OpenWRT [SNAPSHOT](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/SNAPSHOT) 67 | 68 | I also ran the build for version [22.03.7](https://github.com/Slava-Shchipunov/awg-openwrt/releases/tag/v22.03.7), but the build ended with an error for two platforms. Since this is a fairly old version of OpenWRT, I did not bother to figure out what the problem was. 69 | 70 | In the future, when new OpenWRT releases are released, releases with AmneziaWG packages will be automatically created and the package build will be launched for all devices supported by the new version. Github action for checking for a new release is launched automatically every 3 days, and can also be launched manually. 71 | 72 | ## Automatic package build for SNAPSHOT version 73 | A github action is configured in the repository, which runs every 4 hours and checks the [snapshots page](https://downloads.openwrt.org/snapshots/targets/) of the OpenWRT website. At the same time, if a snapshot with a newer kernel version is found for some platform, the package build for this platform is launched, and the new files replace the old ones. In order to save resources and speed up the build process, packages are built only for popular platforms, which are specified in the `SNAPSHOT_SUBTARGETS_TO_BUILD` array in the index.js file. 74 | 75 | ## Selecting packages for your device 76 | In accordance with the paragraph [Specify variables for builds](https://github.com/itdoginfo/domain-routing-openwrt/wiki/Amnezia-WG-Build#%D1%83%D0%BA%D0%B0%D0%B7%D1%8B%D0%B2%D0%B0%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D0%B4%D0%BB%D1%8F-%D1%81%D0%B1%D0%BE%D1%80%D0%BA%D0%B8) (instructions in Russian) determine `target` and `subtarget` of your device. Then go to the release page corresponding to your OpenWRT version, then search the page (Ctrl+F) to find 3 packages whose names end in `target_subtarget.ipk` corresponding to your device. 77 | 78 | ## How to run a build for all supported devices 79 | 1) Create a fork of this repository 80 | 2) Switch to the Actions tab and enable Github actions (they are disabled for forks by default) 81 | 3) Then go to the Code tab => Releases (on the right side of the screen) => Draft a new release 82 | 4) Click Choose a tag and create a new tag in the vX.X.X format, where you need to substitute the required OpenWRT version for X.X.X, for example, v23.05.4 83 | 5) Select the `master` branch as the target 84 | 6) Enter Release title 85 | 7) Click the green Publish release button at the bottom 86 | 87 | For public repositories, Github provides unlimited use of runners, I had up to 20 parallel jobs running. Each job takes about 2 hours, the total build time is about 10 hours. 88 | If errors occur in one job, all unfinished ones will be canceled - in this case, you can select the failed launch on the Actions tab and click Re-run failed jobs 89 | 90 | ## Building packages for a specific platform 91 | You can see how to start building packages for a specific platform in the [wiki instructions](https://github.com/itdoginfo/domain-routing-openwrt/wiki/Amnezia-WG-Build) (instructions in Russian). Building for one device will take about 2 hours. 92 | -------------------------------------------------------------------------------- /amneziawg-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #set -x 4 | 5 | #Репозиторий OpenWRT должен быть доступен для установки зависимостей пакета kmod-amneziawg 6 | check_repo() { 7 | printf "\033[32;1mChecking OpenWrt repo availability...\033[0m\n" 8 | opkg update | grep -q "Failed to download" && printf "\033[32;1mopkg failed. Check internet or date. Command for force ntp sync: ntpd -p ptbtime1.ptb.de\033[0m\n" && exit 1 9 | } 10 | 11 | install_awg_packages() { 12 | # Получение pkgarch с наибольшим приоритетом 13 | PKGARCH=$(opkg print-architecture | awk 'BEGIN {max=0} {if ($3 > max) {max = $3; arch = $2}} END {print arch}') 14 | 15 | TARGET=$(ubus call system board | jsonfilter -e '@.release.target' | cut -d '/' -f 1) 16 | SUBTARGET=$(ubus call system board | jsonfilter -e '@.release.target' | cut -d '/' -f 2) 17 | VERSION=$(ubus call system board | jsonfilter -e '@.release.version') 18 | PKGPOSTFIX="_v${VERSION}_${PKGARCH}_${TARGET}_${SUBTARGET}.ipk" 19 | BASE_URL="https://github.com/Slava-Shchipunov/awg-openwrt/releases/download/" 20 | 21 | AWG_DIR="/tmp/amneziawg" 22 | mkdir -p "$AWG_DIR" 23 | 24 | if opkg list-installed | grep -q kmod-amneziawg; then 25 | echo "kmod-amneziawg already installed" 26 | else 27 | KMOD_AMNEZIAWG_FILENAME="kmod-amneziawg${PKGPOSTFIX}" 28 | DOWNLOAD_URL="${BASE_URL}v${VERSION}/${KMOD_AMNEZIAWG_FILENAME}" 29 | wget -O "$AWG_DIR/$KMOD_AMNEZIAWG_FILENAME" "$DOWNLOAD_URL" 30 | 31 | if [ $? -eq 0 ]; then 32 | echo "kmod-amneziawg file downloaded successfully" 33 | else 34 | echo "Error downloading kmod-amneziawg. Please, install kmod-amneziawg manually and run the script again" 35 | exit 1 36 | fi 37 | 38 | opkg install "$AWG_DIR/$KMOD_AMNEZIAWG_FILENAME" 39 | 40 | if [ $? -eq 0 ]; then 41 | echo "kmod-amneziawg file downloaded successfully" 42 | else 43 | echo "Error installing kmod-amneziawg. Please, install kmod-amneziawg manually and run the script again" 44 | exit 1 45 | fi 46 | fi 47 | 48 | if opkg list-installed | grep -q amneziawg-tools; then 49 | echo "amneziawg-tools already installed" 50 | else 51 | AMNEZIAWG_TOOLS_FILENAME="amneziawg-tools${PKGPOSTFIX}" 52 | DOWNLOAD_URL="${BASE_URL}v${VERSION}/${AMNEZIAWG_TOOLS_FILENAME}" 53 | wget -O "$AWG_DIR/$AMNEZIAWG_TOOLS_FILENAME" "$DOWNLOAD_URL" 54 | 55 | if [ $? -eq 0 ]; then 56 | echo "amneziawg-tools file downloaded successfully" 57 | else 58 | echo "Error downloading amneziawg-tools. Please, install amneziawg-tools manually and run the script again" 59 | exit 1 60 | fi 61 | 62 | opkg install "$AWG_DIR/$AMNEZIAWG_TOOLS_FILENAME" 63 | 64 | if [ $? -eq 0 ]; then 65 | echo "amneziawg-tools file downloaded successfully" 66 | else 67 | echo "Error installing amneziawg-tools. Please, install amneziawg-tools manually and run the script again" 68 | exit 1 69 | fi 70 | fi 71 | 72 | if opkg list-installed | grep -q luci-app-amneziawg; then 73 | echo "luci-app-amneziawg already installed" 74 | else 75 | LUCI_APP_AMNEZIAWG_FILENAME="luci-app-amneziawg${PKGPOSTFIX}" 76 | DOWNLOAD_URL="${BASE_URL}v${VERSION}/${LUCI_APP_AMNEZIAWG_FILENAME}" 77 | wget -O "$AWG_DIR/$LUCI_APP_AMNEZIAWG_FILENAME" "$DOWNLOAD_URL" 78 | 79 | if [ $? -eq 0 ]; then 80 | echo "luci-app-amneziawg file downloaded successfully" 81 | else 82 | echo "Error downloading luci-app-amneziawg. Please, install luci-app-amneziawg manually and run the script again" 83 | exit 1 84 | fi 85 | 86 | opkg install "$AWG_DIR/$LUCI_APP_AMNEZIAWG_FILENAME" 87 | 88 | if [ $? -eq 0 ]; then 89 | echo "luci-app-amneziawg file downloaded successfully" 90 | else 91 | echo "Error installing luci-app-amneziawg. Please, install luci-app-amneziawg manually and run the script again" 92 | exit 1 93 | fi 94 | fi 95 | 96 | rm -rf "$AWG_DIR" 97 | } 98 | 99 | configure_amneziawg_interface() { 100 | INTERFACE_NAME="awg1" 101 | CONFIG_NAME="amneziawg_awg1" 102 | PROTO="amneziawg" 103 | ZONE_NAME="awg1" 104 | 105 | read -r -p "Enter the private key (from [Interface]):"$'\n' AWG_PRIVATE_KEY_INT 106 | 107 | while true; do 108 | read -r -p "Enter internal IP address with subnet, example 192.168.100.5/24 (from [Interface]):"$'\n' AWG_IP 109 | if echo "$AWG_IP" | egrep -oq '^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+$'; then 110 | break 111 | else 112 | echo "This IP is not valid. Please repeat" 113 | fi 114 | done 115 | 116 | read -r -p "Enter the public key (from [Peer]):"$'\n' AWG_PUBLIC_KEY_INT 117 | read -r -p "If use PresharedKey, Enter this (from [Peer]). If your don't use leave blank:"$'\n' AWG_PRESHARED_KEY_INT 118 | read -r -p "Enter Endpoint host without port (Domain or IP) (from [Peer]):"$'\n' AWG_ENDPOINT_INT 119 | 120 | read -r -p "Enter Endpoint host port (from [Peer]) [51820]:"$'\n' AWG_ENDPOINT_PORT_INT 121 | AWG_ENDPOINT_PORT_INT=${AWG_ENDPOINT_PORT_INT:-51820} 122 | if [ "$AWG_ENDPOINT_PORT_INT" = '51820' ]; then 123 | echo $AWG_ENDPOINT_PORT_INT 124 | fi 125 | 126 | read -r -p "Enter Jc value (from [Interface]):"$'\n' AWG_JC 127 | read -r -p "Enter Jmin value (from [Interface]):"$'\n' AWG_JMIN 128 | read -r -p "Enter Jmax value (from [Interface]):"$'\n' AWG_JMAX 129 | read -r -p "Enter S1 value (from [Interface]):"$'\n' AWG_S1 130 | read -r -p "Enter S2 value (from [Interface]):"$'\n' AWG_S2 131 | read -r -p "Enter H1 value (from [Interface]):"$'\n' AWG_H1 132 | read -r -p "Enter H2 value (from [Interface]):"$'\n' AWG_H2 133 | read -r -p "Enter H3 value (from [Interface]):"$'\n' AWG_H3 134 | read -r -p "Enter H4 value (from [Interface]):"$'\n' AWG_H4 135 | 136 | uci set network.${INTERFACE_NAME}=interface 137 | uci set network.${INTERFACE_NAME}.proto=$PROTO 138 | uci set network.${INTERFACE_NAME}.private_key=$AWG_PRIVATE_KEY_INT 139 | uci set network.${INTERFACE_NAME}.listen_port='51821' 140 | uci set network.${INTERFACE_NAME}.addresses=$AWG_IP 141 | 142 | uci set network.${INTERFACE_NAME}.awg_jc=$AWG_JC 143 | uci set network.${INTERFACE_NAME}.awg_jmin=$AWG_JMIN 144 | uci set network.${INTERFACE_NAME}.awg_jmax=$AWG_JMAX 145 | uci set network.${INTERFACE_NAME}.awg_s1=$AWG_S1 146 | uci set network.${INTERFACE_NAME}.awg_s2=$AWG_S2 147 | uci set network.${INTERFACE_NAME}.awg_h1=$AWG_H1 148 | uci set network.${INTERFACE_NAME}.awg_h2=$AWG_H2 149 | uci set network.${INTERFACE_NAME}.awg_h3=$AWG_H3 150 | uci set network.${INTERFACE_NAME}.awg_h4=$AWG_H4 151 | 152 | if ! uci show network | grep -q ${CONFIG_NAME}; then 153 | uci add network ${CONFIG_NAME} 154 | fi 155 | 156 | uci set network.@${CONFIG_NAME}[0]=$CONFIG_NAME 157 | uci set network.@${CONFIG_NAME}[0].name="${INTERFACE_NAME}_client" 158 | uci set network.@${CONFIG_NAME}[0].public_key=$AWG_PUBLIC_KEY_INT 159 | uci set network.@${CONFIG_NAME}[0].preshared_key=$AWG_PRESHARED_KEY_INT 160 | uci set network.@${CONFIG_NAME}[0].route_allowed_ips='1' 161 | uci set network.@${CONFIG_NAME}[0].persistent_keepalive='25' 162 | uci set network.@${CONFIG_NAME}[0].endpoint_host=$AWG_ENDPOINT_INT 163 | uci set network.@${CONFIG_NAME}[0].allowed_ips='0.0.0.0/0' 164 | uci add_list network.@${CONFIG_NAME}[0].allowed_ips='::/0' 165 | uci set network.@${CONFIG_NAME}[0].endpoint_port=$AWG_ENDPOINT_PORT_INT 166 | uci commit network 167 | 168 | if ! uci show firewall | grep -q "@zone.*name='${ZONE_NAME}'"; then 169 | printf "\033[32;1mZone Create\033[0m\n" 170 | uci add firewall zone 171 | uci set firewall.@zone[-1].name=$ZONE_NAME 172 | uci set firewall.@zone[-1].network=$INTERFACE_NAME 173 | uci set firewall.@zone[-1].forward='REJECT' 174 | uci set firewall.@zone[-1].output='ACCEPT' 175 | uci set firewall.@zone[-1].input='REJECT' 176 | uci set firewall.@zone[-1].masq='1' 177 | uci set firewall.@zone[-1].mtu_fix='1' 178 | uci set firewall.@zone[-1].family='ipv4' 179 | uci commit firewall 180 | fi 181 | 182 | if ! uci show firewall | grep -q "@forwarding.*name='${ZONE_NAME}'"; then 183 | printf "\033[32;1mConfigured forwarding\033[0m\n" 184 | uci add firewall forwarding 185 | uci set firewall.@forwarding[-1]=forwarding 186 | uci set firewall.@forwarding[-1].name="${ZONE_NAME}-lan" 187 | uci set firewall.@forwarding[-1].dest=${ZONE_NAME} 188 | uci set firewall.@forwarding[-1].src='lan' 189 | uci set firewall.@forwarding[-1].family='ipv4' 190 | uci commit firewall 191 | fi 192 | } 193 | 194 | check_repo 195 | 196 | install_awg_packages 197 | 198 | printf "\033[32;1mDo you want to configure the amneziawg interface? (y/n): \033[0m\n" 199 | read IS_SHOULD_CONFIGURE_AWG_INTERFACE 200 | 201 | if [ "$IS_SHOULD_CONFIGURE_AWG_INTERFACE" = "y" ] || [ "$IS_SHOULD_CONFIGURE_AWG_INTERFACE" = "Y" ]; then 202 | configure_amneziawg_interface 203 | else 204 | printf "\033[32;1mSkipping amneziawg interface configuration.\033[0m\n" 205 | fi 206 | 207 | service network restart 208 | -------------------------------------------------------------------------------- /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 | 13 | PKG_VERSION:=1.0.20240213 14 | PKG_RELEASE:=$(AUTORELEASE) 15 | 16 | PKG_SOURCE:=v$(PKG_VERSION).tar.gz 17 | PKG_SOURCE_URL:=https://github.com/amnezia-vpn/amneziawg-tools/archive/refs/tags/ 18 | PKG_HASH:=4bde122630c9ddb1ec013c3e958f2c613b9eea56834674dda92fcb423c6f4d10 19 | 20 | PKG_LICENSE:=GPL-2.0 21 | PKG_LICENSE_FILES:=COPYING 22 | 23 | 24 | include $(INCLUDE_DIR)/package.mk 25 | 26 | MAKE_PATH:=src 27 | MAKE_VARS += PLATFORM=linux 28 | 29 | define Package/amneziawg-tools 30 | CATEGORY:=Network 31 | URL:=https://www.wireguard.com 32 | MAINTAINER:=Jason A. Donenfeld 33 | TITLE:=WireGuard userspace control program (wg) 34 | DEPENDS:= \ 35 | +@BUSYBOX_CONFIG_IP \ 36 | +@BUSYBOX_CONFIG_FEATURE_IP_LINK 37 | endef 38 | 39 | define Package/amneziawg-tools/description 40 | WireGuard is a novel VPN that runs inside the Linux Kernel and utilizes 41 | state-of-the-art cryptography. It aims to be faster, simpler, leaner, and 42 | more useful than IPSec, while avoiding the massive headache. It intends to 43 | be considerably more performant than OpenVPN. WireGuard is designed as a 44 | general purpose VPN for running on embedded interfaces and super computers 45 | alike, fit for many different circumstances. It uses UDP. 46 | 47 | This package provides the userspace control program for WireGuard, 48 | `wg(8)`, a netifd protocol helper, and a re-resolve watchdog script. 49 | endef 50 | 51 | define Package/amneziawg-tools/install 52 | $(INSTALL_DIR) $(1)/usr/bin/ 53 | $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/wg $(1)/usr/bin/amneziawg 54 | $(INSTALL_BIN) ./files/amneziawg_watchdog $(1)/usr/bin/ 55 | $(INSTALL_DIR) $(1)/lib/netifd/proto/ 56 | $(INSTALL_BIN) ./files/amneziawg.sh $(1)/lib/netifd/proto/ 57 | endef 58 | 59 | $(eval $(call BuildPackage,amneziawg-tools)) -------------------------------------------------------------------------------- /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/amneziawg 6 | if [ ! -x $WG ]; then 7 | logger -t "amnezia-wg" "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 $? 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 mtu 159 | 160 | # Amnezia WG specific parameters 161 | local awg_jc 162 | local awg_jmin 163 | local awg_jmax 164 | local awg_s1 165 | local awg_s2 166 | local awg_h1 167 | local awg_h2 168 | local awg_h3 169 | local awg_h4 170 | 171 | ensure_key_is_generated "${config}" 172 | 173 | config_load network 174 | config_get private_key "${config}" "private_key" 175 | config_get listen_port "${config}" "listen_port" 176 | config_get addresses "${config}" "addresses" 177 | config_get mtu "${config}" "mtu" 178 | config_get fwmark "${config}" "fwmark" 179 | config_get ip6prefix "${config}" "ip6prefix" 180 | config_get nohostroute "${config}" "nohostroute" 181 | config_get tunlink "${config}" "tunlink" 182 | 183 | config_get awg_jc "${config}" "awg_jc" 184 | config_get awg_jmin "${config}" "awg_jmin" 185 | config_get awg_jmax "${config}" "awg_jmax" 186 | config_get awg_s1 "${config}" "awg_s1" 187 | config_get awg_s2 "${config}" "awg_s2" 188 | config_get awg_h1 "${config}" "awg_h1" 189 | config_get awg_h2 "${config}" "awg_h2" 190 | config_get awg_h3 "${config}" "awg_h3" 191 | config_get awg_h4 "${config}" "awg_h4" 192 | 193 | if proto_amneziawg_is_kernel_mode; then 194 | logger -t "amneziawg" "info: using kernel-space kmod-amneziawg for ${WG}" 195 | ip link del dev "${config}" 2>/dev/null 196 | ip link add dev "${config}" type amneziawg 197 | else 198 | logger -t "amneziawg" "info: using user-space amneziawg-go for ${WG}" 199 | rm -f "/var/run/wireguard/${config}.sock" 200 | amneziawg-go "${config}" 201 | fi 202 | 203 | if [ "${mtu}" ]; then 204 | ip link set mtu "${mtu}" dev "${config}" 205 | fi 206 | 207 | proto_init_update "${config}" 1 208 | 209 | umask 077 210 | mkdir -p "${wg_dir}" 211 | echo "[Interface]" > "${wg_cfg}" 212 | echo "PrivateKey=${private_key}" >> "${wg_cfg}" 213 | if [ "${listen_port}" ]; then 214 | echo "ListenPort=${listen_port}" >> "${wg_cfg}" 215 | fi 216 | if [ "${fwmark}" ]; then 217 | echo "FwMark=${fwmark}" >> "${wg_cfg}" 218 | fi 219 | # AWG 220 | if [ "${awg_jc}" ]; then 221 | echo "Jc = ${awg_jc}" >> "${wg_cfg}" 222 | fi 223 | if [ "${awg_jmin}" ]; then 224 | echo "Jmin = ${awg_jmin}" >> "${wg_cfg}" 225 | fi 226 | if [ "${awg_jmax}" ]; then 227 | echo "Jmax = ${awg_jmax}" >> "${wg_cfg}" 228 | fi 229 | if [ "${awg_s1}" ]; then 230 | echo "S1 = ${awg_s1}" >> "${wg_cfg}" 231 | fi 232 | if [ "${awg_s2}" ]; then 233 | echo "S2 = ${awg_s2}" >> "${wg_cfg}" 234 | fi 235 | if [ "${awg_h1}" ]; then 236 | echo "H1 = ${awg_h1}" >> "${wg_cfg}" 237 | fi 238 | if [ "${awg_h2}" ]; then 239 | echo "H2 = ${awg_h2}" >> "${wg_cfg}" 240 | fi 241 | if [ "${awg_h3}" ]; then 242 | echo "H3 = ${awg_h3}" >> "${wg_cfg}" 243 | fi 244 | if [ "${awg_h4}" ]; then 245 | echo "H4 = ${awg_h4}" >> "${wg_cfg}" 246 | fi 247 | 248 | config_foreach proto_amneziawg_setup_peer "amneziawg_${config}" 249 | 250 | # apply configuration file 251 | ${WG} setconf ${config} "${wg_cfg}" 252 | WG_RETURN=$? 253 | 254 | rm -f "${wg_cfg}" 255 | 256 | if [ ${WG_RETURN} -ne 0 ]; then 257 | sleep 5 258 | proto_setup_failed "${config}" 259 | exit 1 260 | fi 261 | 262 | for address in ${addresses}; do 263 | case "${address}" in 264 | *:*/*) 265 | proto_add_ipv6_address "${address%%/*}" "${address##*/}" 266 | ;; 267 | *.*/*) 268 | proto_add_ipv4_address "${address%%/*}" "${address##*/}" 269 | ;; 270 | *:*) 271 | proto_add_ipv6_address "${address%%/*}" "128" 272 | ;; 273 | *.*) 274 | proto_add_ipv4_address "${address%%/*}" "32" 275 | ;; 276 | esac 277 | done 278 | 279 | for prefix in ${ip6prefix}; do 280 | proto_add_ipv6_prefix "$prefix" 281 | done 282 | 283 | # endpoint dependency 284 | if [ "${nohostroute}" != "1" ]; then 285 | ${WG} show "${config}" endpoints | \ 286 | sed -E 's/\[?([0-9.:a-f]+)\]?:([0-9]+)/\1 \2/' | \ 287 | while IFS=$'\t ' read -r key address port; do 288 | [ -n "${port}" ] || continue 289 | proto_add_host_dependency "${config}" "${address}" "${tunlink}" 290 | done 291 | fi 292 | 293 | proto_send_update "${config}" 294 | } 295 | 296 | proto_amneziawg_teardown() { 297 | local config="$1" 298 | proto_amneziawg_check_installed 299 | if proto_amneziawg_is_kernel_mode; then 300 | ip link del dev "${config}" >/dev/null 2>&1 301 | else 302 | rm -f /var/run/wireguard/${config}.sock 303 | fi 304 | } 305 | 306 | [ -n "$INCLUDE_ONLY" ] || { 307 | add_protocol amneziawg 308 | } -------------------------------------------------------------------------------- /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 | 15 | . /lib/functions.sh 16 | 17 | check_peer_activity() { 18 | local cfg=$1 19 | local iface=$2 20 | local disabled 21 | local public_key 22 | local endpoint_host 23 | local endpoint_port 24 | local persistent_keepalive 25 | local last_handshake 26 | local idle_seconds 27 | 28 | config_get_bool disabled "${cfg}" "disabled" 0 29 | config_get public_key "${cfg}" "public_key" 30 | config_get endpoint_host "${cfg}" "endpoint_host" 31 | config_get endpoint_port "${cfg}" "endpoint_port" 32 | 33 | if [ "${disabled}" -eq 1 ]; then 34 | # skip disabled peers 35 | return 0 36 | fi 37 | 38 | persistent_keepalive=$(wg show ${iface} persistent-keepalive | grep ${public_key} | awk '{print $2}') 39 | 40 | # only process peers with endpoints and keepalive set 41 | [ -z ${endpoint_host} ] && return 0; 42 | [ -z ${persistent_keepalive} -o ${persistent_keepalive} = "off" ] && return 0; 43 | 44 | # skip IP addresses 45 | # check taken from packages/net/ddns-scripts/files/dynamic_dns_functions.sh 46 | local IPV4_REGEX="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" 47 | 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,\}\)" 48 | local IPV4=$(echo ${endpoint_host} | grep -m 1 -o "$IPV4_REGEX$") # do not detect ip in 0.0.0.0.example.com 49 | local IPV6=$(echo ${endpoint_host} | grep -m 1 -o "$IPV6_REGEX") 50 | [ -n "${IPV4}" -o -n "${IPV6}" ] && return 0; 51 | 52 | # re-resolve endpoint hostname if not responding for too long 53 | last_handshake=$(wg show ${iface} latest-handshakes | grep ${public_key} | awk '{print $2}') 54 | [ -z ${last_handshake} ] && return 0; 55 | idle_seconds=$(($(date +%s)-${last_handshake})) 56 | [ ${idle_seconds} -lt 150 ] && return 0; 57 | logger -t "wireguard_monitor" "${iface} endpoint ${endpoint_host}:${endpoint_port} is not responding for ${idle_seconds} seconds, trying to re-resolve hostname" 58 | wg set ${iface} peer ${public_key} endpoint "${endpoint_host}:${endpoint_port}" 59 | } 60 | 61 | # query ubus for all active wireguard interfaces 62 | wg_ifaces=$(ubus -S call network.interface dump | jsonfilter -e '@.interface[@.up=true]' | jsonfilter -a -e '@[@.proto="wireguard"].interface' | tr "\n" " ") 63 | 64 | # check every peer in every active wireguard interface 65 | config_load network 66 | for iface in $wg_ifaces; do 67 | config_foreach check_peer_activity "wireguard_${iface}" "${iface}" 68 | done -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const cheerio = require('cheerio'); 3 | const core = require('@actions/core'); 4 | 5 | const version = process.argv[2]; // Получение версии OpenWRT из аргумента командной строки 6 | 7 | const SNAPSHOT_TARGETS_TO_BUILD = ['mediatek', 'ramips', 'x86', 'armsr']; 8 | const SNAPSHOT_SUBTARGETS_TO_BUILD = ['filogic', 'mt7622', 'mt7623', 'mt7629', 'mt7620', 'mt7621', 'mt76x8', '64', 'generic', 'armv8']; 9 | 10 | if (!version) { 11 | core.setFailed('Version argument is required'); 12 | process.exit(1); 13 | } 14 | 15 | const url = version === 'SNAPSHOT' ? 'https://downloads.openwrt.org/snapshots/targets/' : `https://downloads.openwrt.org/releases/${version}/targets/`; 16 | 17 | async function fetchHTML(url) { 18 | try { 19 | const { data } = await axios.get(url); 20 | return cheerio.load(data); 21 | } catch (error) { 22 | console.error(`Error fetching HTML for ${url}: ${error}`); 23 | throw error; 24 | } 25 | } 26 | 27 | async function getTargets() { 28 | const $ = await fetchHTML(url); 29 | const targets = []; 30 | $('table tr td.n a').each((index, element) => { 31 | const name = $(element).attr('href'); 32 | if (name && name.endsWith('/')) { 33 | targets.push(name.slice(0, -1)); 34 | } 35 | }); 36 | return targets; 37 | } 38 | 39 | async function getSubtargets(target) { 40 | const $ = await fetchHTML(`${url}${target}/`); 41 | const subtargets = []; 42 | $('table tr td.n a').each((index, element) => { 43 | const name = $(element).attr('href'); 44 | if (name && name.endsWith('/')) { 45 | subtargets.push(name.slice(0, -1)); 46 | } 47 | }); 48 | return subtargets; 49 | } 50 | 51 | async function getDetails(target, subtarget) { 52 | const packagesUrl = `${url}${target}/${subtarget}/packages/`; 53 | const $ = await fetchHTML(packagesUrl); 54 | let vermagic = ''; 55 | let pkgarch = ''; 56 | 57 | $('a').each((index, element) => { 58 | const name = $(element).attr('href'); 59 | if (name && name.startsWith('kernel_')) { 60 | const vermagicMatch = name.match(/kernel_\d+\.\d+\.\d+(?:-\d+)?[-~]([a-f0-9]+)(?:-r\d+)?_([a-zA-Z0-9_-]+)\.ipk$/); 61 | if (vermagicMatch) { 62 | vermagic = vermagicMatch[1]; 63 | pkgarch = vermagicMatch[2]; 64 | } 65 | } 66 | }); 67 | 68 | return { vermagic, pkgarch }; 69 | } 70 | 71 | async function main() { 72 | try { 73 | const targets = await getTargets(); 74 | const jobConfig = []; 75 | 76 | for (const target of targets) { 77 | const subtargets = await getSubtargets(target); 78 | for (const subtarget of subtargets) { 79 | const { vermagic, pkgarch } = await getDetails(target, subtarget); 80 | 81 | if (version !== 'SNAPSHOT' || (SNAPSHOT_SUBTARGETS_TO_BUILD.includes(subtarget) && SNAPSHOT_TARGETS_TO_BUILD.includes(target))) { 82 | jobConfig.push({ 83 | tag: version, 84 | target, 85 | subtarget, 86 | vermagic, 87 | pkgarch, 88 | }); 89 | } 90 | } 91 | } 92 | 93 | core.setOutput('job-config', JSON.stringify(jobConfig)); 94 | } catch (error) { 95 | core.setFailed(error.message); 96 | } 97 | } 98 | 99 | main(); 100 | -------------------------------------------------------------------------------- /kmod-amneziawg/Makefile: -------------------------------------------------------------------------------- 1 | include $(TOPDIR)/rules.mk 2 | 3 | include $(INCLUDE_DIR)/kernel.mk 4 | 5 | PKG_NAME:=kmod-amneziawg 6 | PKG_RELEASE:=1 7 | 8 | include $(INCLUDE_DIR)/package.mk 9 | 10 | define KernelPackage/amneziawg 11 | SECTION:=kernel 12 | CATEGORY:=Kernel modules 13 | SUBMENU:=Network Support 14 | TITLE:=AmneziaWG VPN Kernel Module 15 | FILES:=$(PKG_BUILD_DIR)/amneziawg.ko 16 | DEPENDS:= \ 17 | +kmod-udptunnel4 \ 18 | +kmod-udptunnel6 \ 19 | +kmod-crypto-lib-chacha20poly1305 \ 20 | +kmod-crypto-lib-curve25519 21 | endef 22 | 23 | define Build/Prepare 24 | cp -fr $(LINUX_DIR)/drivers/net/wireguard/{*.c,*.h,selftest/} $(PKG_BUILD_DIR) 25 | mkdir -p $(PKG_BUILD_DIR)/uapi 26 | cp -f $(LINUX_DIR)/include/uapi/linux/wireguard.h $(PKG_BUILD_DIR)/uapi/ 27 | patch -d $(PKG_BUILD_DIR)/ < files/amnezia-sources.patch 28 | patch -d $(PKG_BUILD_DIR)/uapi/ < files/amnezia-uapi.patch 29 | cp -f src/Makefile $(PKG_BUILD_DIR) 30 | endef 31 | 32 | define Build/Compile 33 | $(MAKE) -C "$(LINUX_DIR)" \ 34 | $(KERNEL_MAKE_FLAGS) \ 35 | M="$(PKG_BUILD_DIR)" \ 36 | EXTRA_CFLAGS="$(BUILDFLAGS)" \ 37 | modules 38 | endef 39 | 40 | $(eval $(call KernelPackage,amneziawg)) 41 | -------------------------------------------------------------------------------- /kmod-amneziawg/files/amnezia-sources.patch: -------------------------------------------------------------------------------- 1 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/cookie.c ./cookie.c 2 | --- ../../linux-source-6.2.0/drivers/net/wireguard/cookie.c 2023-11-10 18:10:29 3 | +++ ./cookie.c 2023-11-23 18:59:07 4 | @@ -179,13 +179,13 @@ 5 | 6 | void wg_cookie_message_create(struct message_handshake_cookie *dst, 7 | struct sk_buff *skb, __le32 index, 8 | - struct cookie_checker *checker) 9 | + struct cookie_checker *checker, u32 message_type) 10 | { 11 | struct message_macs *macs = (struct message_macs *) 12 | ((u8 *)skb->data + skb->len - sizeof(*macs)); 13 | u8 cookie[COOKIE_LEN]; 14 | 15 | - dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE); 16 | + dst->header.type = cpu_to_le32(message_type); 17 | dst->receiver_index = index; 18 | get_random_bytes_wait(dst->nonce, COOKIE_NONCE_LEN); 19 | 20 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/cookie.h ./cookie.h 21 | --- ../../linux-source-6.2.0/drivers/net/wireguard/cookie.h 2023-11-10 18:10:29 22 | +++ ./cookie.h 2023-11-23 13:11:40 23 | @@ -52,7 +52,7 @@ 24 | 25 | void wg_cookie_message_create(struct message_handshake_cookie *src, 26 | struct sk_buff *skb, __le32 index, 27 | - struct cookie_checker *checker); 28 | + struct cookie_checker *checker, u32 message_type); 29 | void wg_cookie_message_consume(struct message_handshake_cookie *src, 30 | struct wg_device *wg); 31 | 32 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/device.c ./device.c 33 | --- ../../linux-source-6.2.0/drivers/net/wireguard/device.c 2023-11-10 18:10:29 34 | +++ ./device.c 2023-11-26 17:42:17 35 | @@ -379,6 +379,11 @@ 36 | */ 37 | dev->priv_destructor = wg_destruct; 38 | 39 | + wg->advanced_security_config.init_packet_magic_header = MESSAGE_HANDSHAKE_INITIATION; 40 | + wg->advanced_security_config.response_packet_magic_header = MESSAGE_HANDSHAKE_RESPONSE; 41 | + wg->advanced_security_config.cookie_packet_magic_header = MESSAGE_HANDSHAKE_COOKIE; 42 | + wg->advanced_security_config.transport_packet_magic_header = MESSAGE_DATA; 43 | + 44 | pr_debug("%s: Interface created\n", dev->name); 45 | return ret; 46 | 47 | @@ -475,4 +480,118 @@ 48 | unregister_random_vmfork_notifier(&vm_notifier); 49 | unregister_pm_notifier(&pm_notifier); 50 | rcu_barrier(); 51 | +} 52 | + 53 | +int wg_device_handle_post_config(struct net_device *dev, struct amnezia_config *asc) 54 | +{ 55 | + struct wg_device *wg = netdev_priv(dev); 56 | + bool a_sec_on = false; 57 | + int ret = 0; 58 | + 59 | + if (!asc->advanced_security_enabled) 60 | + goto out; 61 | + 62 | + if (asc->junk_packet_count < 0) { 63 | + net_dbg_ratelimited("%s: JunkPacketCount should be non negative\n", dev->name); 64 | + ret = -EINVAL; 65 | + } 66 | + 67 | + wg->advanced_security_config.junk_packet_count = asc->junk_packet_count; 68 | + if (asc->junk_packet_count != 0) 69 | + a_sec_on = true; 70 | + 71 | + wg->advanced_security_config.junk_packet_min_size = asc->junk_packet_min_size; 72 | + if (asc->junk_packet_min_size != 0) 73 | + a_sec_on = true; 74 | + 75 | + if (asc->junk_packet_count > 0 && asc->junk_packet_min_size == asc->junk_packet_max_size) 76 | + asc->junk_packet_max_size++; 77 | + 78 | + if (asc->junk_packet_max_size >= MESSAGE_MAX_SIZE) { 79 | + wg->advanced_security_config.junk_packet_min_size = 0; 80 | + wg->advanced_security_config.junk_packet_max_size = 1; 81 | + 82 | + net_dbg_ratelimited("%s: JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d\n", 83 | + dev->name, asc->junk_packet_max_size, 84 | + MESSAGE_MAX_SIZE); 85 | + ret = -EINVAL; 86 | + } else if (asc->junk_packet_max_size < asc->junk_packet_min_size) { 87 | + net_dbg_ratelimited("%s: maxSize: %d; should be greater than minSize: %d\n", 88 | + dev->name, asc->junk_packet_max_size, 89 | + asc->junk_packet_min_size); 90 | + ret = -EINVAL; 91 | + } else 92 | + wg->advanced_security_config.junk_packet_max_size = asc->junk_packet_max_size; 93 | + 94 | + if (asc->junk_packet_max_size != 0) 95 | + a_sec_on = true; 96 | + 97 | + if (asc->init_packet_junk_size + MESSAGE_INITIATION_SIZE >= MESSAGE_MAX_SIZE) { 98 | + net_dbg_ratelimited("%s: init header size (%d) + junkSize (%d) should be smaller than maxSegmentSize: %d\n", 99 | + dev->name, MESSAGE_INITIATION_SIZE, 100 | + asc->init_packet_junk_size, MESSAGE_MAX_SIZE); 101 | + ret = -EINVAL; 102 | + } else 103 | + wg->advanced_security_config.init_packet_junk_size = asc->init_packet_junk_size; 104 | + 105 | + if (asc->init_packet_junk_size != 0) 106 | + a_sec_on = true; 107 | + 108 | + if (asc->response_packet_junk_size + MESSAGE_RESPONSE_SIZE >= MESSAGE_MAX_SIZE) { 109 | + net_dbg_ratelimited("%s: response header size (%d) + junkSize (%d) should be smaller than maxSegmentSize: %d\n", 110 | + dev->name, MESSAGE_RESPONSE_SIZE, 111 | + asc->response_packet_junk_size, MESSAGE_MAX_SIZE); 112 | + ret = -EINVAL; 113 | + } else 114 | + wg->advanced_security_config.response_packet_junk_size = asc->response_packet_junk_size; 115 | + 116 | + if (asc->response_packet_junk_size != 0) 117 | + a_sec_on = true; 118 | + 119 | + if (asc->init_packet_magic_header > MESSAGE_DATA) { 120 | + a_sec_on = true; 121 | + wg->advanced_security_config.init_packet_magic_header = asc->init_packet_magic_header; 122 | + } 123 | + 124 | + if (asc->response_packet_magic_header > MESSAGE_DATA) { 125 | + a_sec_on = true; 126 | + wg->advanced_security_config.response_packet_magic_header = asc->response_packet_magic_header; 127 | + } 128 | + 129 | + if (asc->cookie_packet_magic_header > MESSAGE_DATA) { 130 | + a_sec_on = true; 131 | + wg->advanced_security_config.cookie_packet_magic_header = asc->cookie_packet_magic_header; 132 | + } 133 | + 134 | + if (asc->transport_packet_magic_header > MESSAGE_DATA) { 135 | + a_sec_on = true; 136 | + wg->advanced_security_config.transport_packet_magic_header = asc->transport_packet_magic_header; 137 | + } 138 | + 139 | + if (asc->init_packet_magic_header == asc->response_packet_magic_header || 140 | + asc->init_packet_magic_header == asc->cookie_packet_magic_header || 141 | + asc->init_packet_magic_header == asc->transport_packet_magic_header || 142 | + asc->response_packet_magic_header == asc->cookie_packet_magic_header || 143 | + asc->response_packet_magic_header == asc->transport_packet_magic_header || 144 | + asc->cookie_packet_magic_header == asc->transport_packet_magic_header) { 145 | + net_dbg_ratelimited("%s: magic headers should differ; got: init:%d; recv:%d; unde:%d; tran:%d\n", 146 | + dev->name, 147 | + asc->init_packet_magic_header, 148 | + asc->response_packet_magic_header, 149 | + asc->cookie_packet_magic_header, 150 | + asc->transport_packet_magic_header); 151 | + ret = -EINVAL; 152 | + } 153 | + 154 | + if (MESSAGE_INITIATION_SIZE + asc->init_packet_junk_size == MESSAGE_RESPONSE_SIZE + asc->response_packet_junk_size) { 155 | + net_dbg_ratelimited("%s: new init size:%d; and new response size:%d; should differ\n", 156 | + dev->name, 157 | + MESSAGE_INITIATION_SIZE + asc->init_packet_junk_size, 158 | + MESSAGE_RESPONSE_SIZE + asc->response_packet_junk_size); 159 | + ret = -EINVAL; 160 | + } 161 | + 162 | + wg->advanced_security_config.advanced_security_enabled = a_sec_on; 163 | +out: 164 | + return ret; 165 | } 166 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/device.h ./device.h 167 | --- ../../linux-source-6.2.0/drivers/net/wireguard/device.h 2023-11-10 18:10:29 168 | +++ ./device.h 2023-11-23 18:48:52 169 | @@ -37,6 +37,19 @@ 170 | atomic_t count; 171 | }; 172 | 173 | +struct amnezia_config { 174 | + bool advanced_security_enabled; 175 | + u16 junk_packet_count; 176 | + u16 junk_packet_min_size; 177 | + u16 junk_packet_max_size; 178 | + u16 init_packet_junk_size; 179 | + u16 response_packet_junk_size; 180 | + u32 init_packet_magic_header; 181 | + u32 response_packet_magic_header; 182 | + u32 cookie_packet_magic_header; 183 | + u32 transport_packet_magic_header; 184 | +}; 185 | + 186 | struct wg_device { 187 | struct net_device *dev; 188 | struct crypt_queue encrypt_queue, decrypt_queue, handshake_queue; 189 | @@ -50,6 +63,7 @@ 190 | struct allowedips peer_allowedips; 191 | struct mutex device_update_lock, socket_update_lock; 192 | struct list_head device_list, peer_list; 193 | + struct amnezia_config advanced_security_config; 194 | atomic_t handshake_queue_len; 195 | unsigned int num_peers, device_update_gen; 196 | u32 fwmark; 197 | @@ -58,5 +72,6 @@ 198 | 199 | int wg_device_init(void); 200 | void wg_device_uninit(void); 201 | +int wg_device_handle_post_config(struct net_device *dev, struct amnezia_config *asc); 202 | 203 | #endif /* _WG_DEVICE_H */ 204 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/main.c ./main.c 205 | --- ../../linux-source-6.2.0/drivers/net/wireguard/main.c 2023-11-10 18:10:29 206 | +++ ./main.c 2023-11-22 16:37:56 207 | @@ -3,14 +3,13 @@ 208 | * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. 209 | */ 210 | 211 | -#include "version.h" 212 | #include "device.h" 213 | #include "noise.h" 214 | #include "queueing.h" 215 | #include "ratelimiter.h" 216 | #include "netlink.h" 217 | 218 | -#include 219 | +#include "uapi/wireguard.h" 220 | 221 | #include 222 | #include 223 | @@ -45,7 +44,7 @@ 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 " (Amnezia VPN) loaded. See www.wireguard.com for information.\n"); 229 | pr_info("Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved.\n"); 230 | 231 | return 0; 232 | @@ -71,7 +70,7 @@ 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 (Amnezia VPN) secure network tunnel"); 238 | MODULE_AUTHOR("Jason A. Donenfeld "); 239 | MODULE_VERSION(WIREGUARD_VERSION); 240 | MODULE_ALIAS_RTNL_LINK(KBUILD_MODNAME); 241 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/messages.h ./messages.h 242 | --- ../../linux-source-6.2.0/drivers/net/wireguard/messages.h 2023-11-10 18:10:29 243 | +++ ./messages.h 2023-11-22 19:16:03 244 | @@ -117,6 +117,14 @@ 245 | MESSAGE_MINIMUM_LENGTH = message_data_len(0) 246 | }; 247 | 248 | +enum message_size { 249 | + MESSAGE_INITIATION_SIZE = sizeof(struct message_handshake_initiation), 250 | + MESSAGE_RESPONSE_SIZE = sizeof(struct message_handshake_response), 251 | + MESSAGE_COOKIE_REPLY_SIZE = sizeof(struct message_handshake_cookie), 252 | + MESSAGE_TRANSPORT_SIZE = sizeof(struct message_data), 253 | + MESSAGE_MAX_SIZE = 65535 254 | +}; 255 | + 256 | #define SKB_HEADER_LEN \ 257 | (max(sizeof(struct iphdr), sizeof(struct ipv6hdr)) + \ 258 | sizeof(struct udphdr) + NET_SKB_PAD) 259 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/netlink.c ./netlink.c 260 | --- ../../linux-source-6.2.0/drivers/net/wireguard/netlink.c 2023-11-10 18:10:29 261 | +++ ./netlink.c 2023-11-26 17:34:30 262 | @@ -10,7 +10,7 @@ 263 | #include "queueing.h" 264 | #include "messages.h" 265 | 266 | -#include 267 | +#include "uapi/wireguard.h" 268 | 269 | #include 270 | #include 271 | @@ -27,7 +27,16 @@ 272 | [WGDEVICE_A_FLAGS] = { .type = NLA_U32 }, 273 | [WGDEVICE_A_LISTEN_PORT] = { .type = NLA_U16 }, 274 | [WGDEVICE_A_FWMARK] = { .type = NLA_U32 }, 275 | - [WGDEVICE_A_PEERS] = { .type = NLA_NESTED } 276 | + [WGDEVICE_A_PEERS] = { .type = NLA_NESTED }, 277 | + [WGDEVICE_A_JC] = { .type = NLA_U16 }, 278 | + [WGDEVICE_A_JMIN] = { .type = NLA_U16 }, 279 | + [WGDEVICE_A_JMAX] = { .type = NLA_U16 }, 280 | + [WGDEVICE_A_S1] = { .type = NLA_U16 }, 281 | + [WGDEVICE_A_S2] = { .type = NLA_U16 }, 282 | + [WGDEVICE_A_H1] = { .type = NLA_U32 }, 283 | + [WGDEVICE_A_H2] = { .type = NLA_U32 }, 284 | + [WGDEVICE_A_H3] = { .type = NLA_U32 }, 285 | + [WGDEVICE_A_H4] = { .type = NLA_U32 } 286 | }; 287 | 288 | static const struct nla_policy peer_policy[WGPEER_A_MAX + 1] = { 289 | @@ -233,7 +242,25 @@ 290 | wg->incoming_port) || 291 | nla_put_u32(skb, WGDEVICE_A_FWMARK, wg->fwmark) || 292 | nla_put_u32(skb, WGDEVICE_A_IFINDEX, wg->dev->ifindex) || 293 | - nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name)) 294 | + nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name) || 295 | + nla_put_u16(skb, WGDEVICE_A_JC, 296 | + wg->advanced_security_config.junk_packet_count) || 297 | + nla_put_u16(skb, WGDEVICE_A_JMIN, 298 | + wg->advanced_security_config.junk_packet_min_size) || 299 | + nla_put_u16(skb, WGDEVICE_A_JMAX, 300 | + wg->advanced_security_config.junk_packet_max_size) || 301 | + nla_put_u16(skb, WGDEVICE_A_S1, 302 | + wg->advanced_security_config.init_packet_junk_size) || 303 | + nla_put_u16(skb, WGDEVICE_A_S2, 304 | + wg->advanced_security_config.response_packet_junk_size) || 305 | + nla_put_u32(skb, WGDEVICE_A_H1, 306 | + wg->advanced_security_config.init_packet_magic_header) || 307 | + nla_put_u32(skb, WGDEVICE_A_H2, 308 | + wg->advanced_security_config.response_packet_magic_header) || 309 | + nla_put_u32(skb, WGDEVICE_A_H3, 310 | + wg->advanced_security_config.cookie_packet_magic_header) || 311 | + nla_put_u32(skb, WGDEVICE_A_H4, 312 | + wg->advanced_security_config.transport_packet_magic_header)) 313 | goto out; 314 | 315 | down_read(&wg->static_identity.lock); 316 | @@ -493,6 +520,7 @@ 317 | static int wg_set_device(struct sk_buff *skb, struct genl_info *info) 318 | { 319 | struct wg_device *wg = lookup_interface(info->attrs, skb); 320 | + struct amnezia_config *asc = kzalloc(sizeof(*asc), GFP_KERNEL); 321 | u32 flags = 0; 322 | int ret; 323 | 324 | @@ -537,6 +565,51 @@ 325 | goto out; 326 | } 327 | 328 | + if (info->attrs[WGDEVICE_A_JC]) { 329 | + asc->advanced_security_enabled = true; 330 | + asc->junk_packet_count = nla_get_u16(info->attrs[WGDEVICE_A_JC]); 331 | + } 332 | + 333 | + if (info->attrs[WGDEVICE_A_JMIN]) { 334 | + asc->advanced_security_enabled = true; 335 | + asc->junk_packet_min_size = nla_get_u16(info->attrs[WGDEVICE_A_JMIN]); 336 | + } 337 | + 338 | + if (info->attrs[WGDEVICE_A_JMAX]) { 339 | + asc->advanced_security_enabled = true; 340 | + asc->junk_packet_max_size = nla_get_u16(info->attrs[WGDEVICE_A_JMAX]); 341 | + } 342 | + 343 | + if (info->attrs[WGDEVICE_A_S1]) { 344 | + asc->advanced_security_enabled = true; 345 | + asc->init_packet_junk_size = nla_get_u16(info->attrs[WGDEVICE_A_S1]); 346 | + } 347 | + 348 | + if (info->attrs[WGDEVICE_A_S2]) { 349 | + asc->advanced_security_enabled = true; 350 | + asc->response_packet_junk_size = nla_get_u16(info->attrs[WGDEVICE_A_S2]); 351 | + } 352 | + 353 | + if (info->attrs[WGDEVICE_A_H1]) { 354 | + asc->advanced_security_enabled = true; 355 | + asc->init_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H1]); 356 | + } 357 | + 358 | + if (info->attrs[WGDEVICE_A_H2]) { 359 | + asc->advanced_security_enabled = true; 360 | + asc->response_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H2]); 361 | + } 362 | + 363 | + if (info->attrs[WGDEVICE_A_H3]) { 364 | + asc->advanced_security_enabled = true; 365 | + asc->cookie_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H3]); 366 | + } 367 | + 368 | + if (info->attrs[WGDEVICE_A_H4]) { 369 | + asc->advanced_security_enabled = true; 370 | + asc->transport_packet_magic_header = nla_get_u32(info->attrs[WGDEVICE_A_H4]); 371 | + } 372 | + 373 | if (flags & WGDEVICE_F_REPLACE_PEERS) 374 | wg_peer_remove_all(wg); 375 | 376 | @@ -597,10 +670,14 @@ 377 | ret = 0; 378 | 379 | out: 380 | + if (!ret) 381 | + ret = wg_device_handle_post_config(wg->dev, asc); 382 | + 383 | mutex_unlock(&wg->device_update_lock); 384 | rtnl_unlock(); 385 | dev_put(wg->dev); 386 | out_nodev: 387 | + kfree(asc); 388 | if (info->attrs[WGDEVICE_A_PRIVATE_KEY]) 389 | memzero_explicit(nla_data(info->attrs[WGDEVICE_A_PRIVATE_KEY]), 390 | nla_len(info->attrs[WGDEVICE_A_PRIVATE_KEY])); 391 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/noise.c ./noise.c 392 | --- ../../linux-source-6.2.0/drivers/net/wireguard/noise.c 2023-11-10 18:10:29 393 | +++ ./noise.c 2023-11-23 18:58:26 394 | @@ -515,7 +515,7 @@ 395 | 396 | bool 397 | wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst, 398 | - struct noise_handshake *handshake) 399 | + struct noise_handshake *handshake, u32 message_type) 400 | { 401 | u8 timestamp[NOISE_TIMESTAMP_LEN]; 402 | u8 key[NOISE_SYMMETRIC_KEY_LEN]; 403 | @@ -532,7 +532,7 @@ 404 | if (unlikely(!handshake->static_identity->has_identity)) 405 | goto out; 406 | 407 | - dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION); 408 | + dst->header.type = cpu_to_le32(message_type); 409 | 410 | handshake_init(handshake->chaining_key, handshake->hash, 411 | handshake->remote_static); 412 | @@ -665,7 +665,7 @@ 413 | } 414 | 415 | bool wg_noise_handshake_create_response(struct message_handshake_response *dst, 416 | - struct noise_handshake *handshake) 417 | + struct noise_handshake *handshake, u32 message_type) 418 | { 419 | u8 key[NOISE_SYMMETRIC_KEY_LEN]; 420 | bool ret = false; 421 | @@ -681,7 +681,7 @@ 422 | if (handshake->state != HANDSHAKE_CONSUMED_INITIATION) 423 | goto out; 424 | 425 | - dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE); 426 | + dst->header.type = cpu_to_le32(message_type); 427 | dst->receiver_index = handshake->remote_index; 428 | 429 | /* e */ 430 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/noise.h ./noise.h 431 | --- ../../linux-source-6.2.0/drivers/net/wireguard/noise.h 2023-11-10 18:10:29 432 | +++ ./noise.h 2023-11-23 13:12:55 433 | @@ -118,13 +118,13 @@ 434 | 435 | bool 436 | wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst, 437 | - struct noise_handshake *handshake); 438 | + struct noise_handshake *handshake, u32 message_type); 439 | struct wg_peer * 440 | wg_noise_handshake_consume_initiation(struct message_handshake_initiation *src, 441 | struct wg_device *wg); 442 | 443 | bool wg_noise_handshake_create_response(struct message_handshake_response *dst, 444 | - struct noise_handshake *handshake); 445 | + struct noise_handshake *handshake, u32 message_type); 446 | struct wg_peer * 447 | wg_noise_handshake_consume_response(struct message_handshake_response *src, 448 | struct wg_device *wg); 449 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/receive.c ./receive.c 450 | --- ../../linux-source-6.2.0/drivers/net/wireguard/receive.c 2023-11-10 18:10:29 451 | +++ ./receive.c 2023-11-23 19:15:51 452 | @@ -25,25 +25,51 @@ 453 | 454 | #define SKB_TYPE_LE32(skb) (((struct message_header *)(skb)->data)->type) 455 | 456 | -static size_t validate_header_len(struct sk_buff *skb) 457 | +static size_t validate_header_len(struct sk_buff *skb, struct wg_device *wg) 458 | { 459 | if (unlikely(skb->len < sizeof(struct message_header))) 460 | return 0; 461 | - if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_DATA) && 462 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.transport_packet_magic_header) && 463 | skb->len >= MESSAGE_MINIMUM_LENGTH) 464 | return sizeof(struct message_data); 465 | - if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION) && 466 | - skb->len == sizeof(struct message_handshake_initiation)) 467 | - return sizeof(struct message_handshake_initiation); 468 | - if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE) && 469 | - skb->len == sizeof(struct message_handshake_response)) 470 | - return sizeof(struct message_handshake_response); 471 | - if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE) && 472 | - skb->len == sizeof(struct message_handshake_cookie)) 473 | - return sizeof(struct message_handshake_cookie); 474 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.init_packet_magic_header) && 475 | + skb->len == MESSAGE_INITIATION_SIZE) 476 | + return MESSAGE_INITIATION_SIZE; 477 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.response_packet_magic_header) && 478 | + skb->len == MESSAGE_RESPONSE_SIZE) 479 | + return MESSAGE_RESPONSE_SIZE; 480 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.cookie_packet_magic_header) && 481 | + skb->len == MESSAGE_COOKIE_REPLY_SIZE) 482 | + return MESSAGE_COOKIE_REPLY_SIZE; 483 | return 0; 484 | } 485 | 486 | +void prepare_advanced_secured_message(struct sk_buff *skb, struct wg_device *wg) 487 | +{ 488 | + u32 assumed_type = SKB_TYPE_LE32(skb); 489 | + u32 assumed_offset; 490 | + 491 | + if (wg->advanced_security_config.advanced_security_enabled) { 492 | + if (skb->len == MESSAGE_INITIATION_SIZE + wg->advanced_security_config.init_packet_junk_size) { 493 | + assumed_type = cpu_to_le32(wg->advanced_security_config.init_packet_magic_header); 494 | + assumed_offset = wg->advanced_security_config.init_packet_junk_size; 495 | + } else if (skb->len == MESSAGE_RESPONSE_SIZE + wg->advanced_security_config.response_packet_junk_size) { 496 | + assumed_type = cpu_to_le32(wg->advanced_security_config.response_packet_magic_header); 497 | + assumed_offset = wg->advanced_security_config.response_packet_junk_size; 498 | + } else 499 | + return; 500 | + 501 | + if (unlikely(assumed_offset <= 0) || unlikely(!pskb_may_pull(skb, assumed_offset))) 502 | + return; 503 | + 504 | + skb_pull(skb, assumed_offset); 505 | + 506 | + if (SKB_TYPE_LE32(skb) != assumed_type) { 507 | + skb_push(skb, assumed_offset); 508 | + } 509 | + } 510 | +} 511 | + 512 | static int prepare_skb_header(struct sk_buff *skb, struct wg_device *wg) 513 | { 514 | size_t data_offset, data_len, header_len; 515 | @@ -79,7 +105,8 @@ 516 | if (unlikely(skb->len != data_len)) 517 | /* Final len does not agree with calculated len */ 518 | return -EINVAL; 519 | - header_len = validate_header_len(skb); 520 | + prepare_advanced_secured_message(skb, wg); 521 | + header_len = validate_header_len(skb, wg); 522 | if (unlikely(!header_len)) 523 | return -EINVAL; 524 | __skb_push(skb, data_offset); 525 | @@ -101,7 +128,7 @@ 526 | bool packet_needs_cookie; 527 | bool under_load; 528 | 529 | - if (SKB_TYPE_LE32(skb) == cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE)) { 530 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.cookie_packet_magic_header)) { 531 | net_dbg_skb_ratelimited("%s: Receiving cookie response from %pISpfsc\n", 532 | wg->dev->name, skb); 533 | wg_cookie_message_consume( 534 | @@ -131,8 +158,7 @@ 535 | return; 536 | } 537 | 538 | - switch (SKB_TYPE_LE32(skb)) { 539 | - case cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION): { 540 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.init_packet_magic_header)) { 541 | struct message_handshake_initiation *message = 542 | (struct message_handshake_initiation *)skb->data; 543 | 544 | @@ -152,9 +178,8 @@ 545 | wg->dev->name, peer->internal_id, 546 | &peer->endpoint.addr); 547 | wg_packet_send_handshake_response(peer); 548 | - break; 549 | } 550 | - case cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE): { 551 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.response_packet_magic_header)) { 552 | struct message_handshake_response *message = 553 | (struct message_handshake_response *)skb->data; 554 | 555 | @@ -185,9 +210,7 @@ 556 | */ 557 | wg_packet_send_keepalive(peer); 558 | } 559 | - break; 560 | } 561 | - } 562 | 563 | if (unlikely(!peer)) { 564 | WARN(1, "Somehow a wrong type of packet wound up in the handshake queue!\n"); 565 | @@ -543,10 +566,10 @@ 566 | { 567 | if (unlikely(prepare_skb_header(skb, wg) < 0)) 568 | goto err; 569 | - switch (SKB_TYPE_LE32(skb)) { 570 | - case cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION): 571 | - case cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE): 572 | - case cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE): { 573 | + 574 | + if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.init_packet_magic_header) || 575 | + SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.response_packet_magic_header) || 576 | + SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.cookie_packet_magic_header)) { 577 | int cpu, ret = -EBUSY; 578 | 579 | if (unlikely(!rng_is_initialized())) 580 | @@ -559,23 +582,20 @@ 581 | } else 582 | ret = ptr_ring_produce_bh(&wg->handshake_queue.ring, skb); 583 | if (ret) { 584 | - drop: 585 | +drop: 586 | net_dbg_skb_ratelimited("%s: Dropping handshake packet from %pISpfsc\n", 587 | - wg->dev->name, skb); 588 | + wg->dev->name, skb); 589 | goto err; 590 | } 591 | atomic_inc(&wg->handshake_queue_len); 592 | cpu = wg_cpumask_next_online(&wg->handshake_queue.last_cpu); 593 | /* Queues up a call to packet_process_queued_handshake_packets(skb): */ 594 | queue_work_on(cpu, wg->handshake_receive_wq, 595 | - &per_cpu_ptr(wg->handshake_queue.worker, cpu)->work); 596 | - break; 597 | - } 598 | - case cpu_to_le32(MESSAGE_DATA): 599 | + &per_cpu_ptr(wg->handshake_queue.worker, cpu)->work); 600 | + } else if (SKB_TYPE_LE32(skb) == cpu_to_le32(wg->advanced_security_config.transport_packet_magic_header)) { 601 | PACKET_CB(skb)->ds = ip_tunnel_get_dsfield(ip_hdr(skb), skb); 602 | wg_packet_consume_data(wg, skb); 603 | - break; 604 | - default: 605 | + } else { 606 | WARN(1, "Non-exhaustive parsing of packet header lead to unknown packet type!\n"); 607 | goto err; 608 | } 609 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/send.c ./send.c 610 | --- ../../linux-source-6.2.0/drivers/net/wireguard/send.c 2023-11-10 18:10:29 611 | +++ ./send.c 2023-11-24 18:25:50 612 | @@ -14,13 +14,24 @@ 613 | #include 614 | #include 615 | #include 616 | +#include 617 | #include 618 | #include 619 | #include 620 | 621 | +u32 wg_get_random_u32_inclusive(u32 floor, u32 ceil) 622 | +{ 623 | + u32 diff = ceil - floor + 1; 624 | + return floor + (get_random_u32() % diff); 625 | +} 626 | + 627 | static void wg_packet_send_handshake_initiation(struct wg_peer *peer) 628 | { 629 | struct message_handshake_initiation packet; 630 | + struct wg_device *wg = peer->device; 631 | + void *buffer; 632 | + u8 ds; 633 | + u16 junk_packet_count, junk_packet_size; 634 | 635 | if (!wg_birthdate_has_expired(atomic64_read(&peer->last_sent_handshake), 636 | REKEY_TIMEOUT)) 637 | @@ -31,14 +42,37 @@ 638 | peer->device->dev->name, peer->internal_id, 639 | &peer->endpoint.addr); 640 | 641 | - if (wg_noise_handshake_create_initiation(&packet, &peer->handshake)) { 642 | + if (wg->advanced_security_config.advanced_security_enabled) { 643 | + junk_packet_count = wg->advanced_security_config.junk_packet_count; 644 | + buffer = kzalloc(wg->advanced_security_config.junk_packet_max_size, GFP_KERNEL); 645 | + 646 | + while (junk_packet_count-- > 0) { 647 | + junk_packet_size = (u16) wg_get_random_u32_inclusive( 648 | + wg->advanced_security_config.junk_packet_min_size, 649 | + wg->advanced_security_config.junk_packet_max_size); 650 | + 651 | + get_random_bytes(buffer, junk_packet_size); 652 | + get_random_bytes(&ds, 1); 653 | + wg_socket_send_buffer_to_peer(peer, buffer, junk_packet_size, ds); 654 | + } 655 | + 656 | + kfree(buffer); 657 | + } 658 | + 659 | + if (wg_noise_handshake_create_initiation(&packet, &peer->handshake, wg->advanced_security_config.init_packet_magic_header)) { 660 | wg_cookie_add_mac_to_packet(&packet, sizeof(packet), peer); 661 | wg_timers_any_authenticated_packet_traversal(peer); 662 | wg_timers_any_authenticated_packet_sent(peer); 663 | atomic64_set(&peer->last_sent_handshake, 664 | ktime_get_coarse_boottime_ns()); 665 | - wg_socket_send_buffer_to_peer(peer, &packet, sizeof(packet), 666 | - HANDSHAKE_DSCP); 667 | + 668 | + if (wg->advanced_security_config.advanced_security_enabled) { 669 | + wg_socket_send_junked_buffer_to_peer(peer, &packet, sizeof(packet), 670 | + HANDSHAKE_DSCP, wg->advanced_security_config.init_packet_junk_size); 671 | + } else { 672 | + wg_socket_send_buffer_to_peer(peer, &packet, sizeof(packet), 673 | + HANDSHAKE_DSCP); 674 | + } 675 | wg_timers_handshake_initiated(peer); 676 | } 677 | } 678 | @@ -85,13 +119,14 @@ 679 | void wg_packet_send_handshake_response(struct wg_peer *peer) 680 | { 681 | struct message_handshake_response packet; 682 | + struct wg_device *wg = peer->device; 683 | 684 | atomic64_set(&peer->last_sent_handshake, ktime_get_coarse_boottime_ns()); 685 | net_dbg_ratelimited("%s: Sending handshake response to peer %llu (%pISpfsc)\n", 686 | peer->device->dev->name, peer->internal_id, 687 | &peer->endpoint.addr); 688 | 689 | - if (wg_noise_handshake_create_response(&packet, &peer->handshake)) { 690 | + if (wg_noise_handshake_create_response(&packet, &peer->handshake, wg->advanced_security_config.response_packet_magic_header)) { 691 | wg_cookie_add_mac_to_packet(&packet, sizeof(packet), peer); 692 | if (wg_noise_handshake_begin_session(&peer->handshake, 693 | &peer->keypairs)) { 694 | @@ -100,9 +135,16 @@ 695 | wg_timers_any_authenticated_packet_sent(peer); 696 | atomic64_set(&peer->last_sent_handshake, 697 | ktime_get_coarse_boottime_ns()); 698 | - wg_socket_send_buffer_to_peer(peer, &packet, 699 | - sizeof(packet), 700 | - HANDSHAKE_DSCP); 701 | + if (wg->advanced_security_config.advanced_security_enabled) { 702 | + wg_socket_send_junked_buffer_to_peer(peer, &packet, 703 | + sizeof(packet), 704 | + HANDSHAKE_DSCP, 705 | + wg->advanced_security_config.response_packet_junk_size); 706 | + } else { 707 | + wg_socket_send_buffer_to_peer(peer, &packet, 708 | + sizeof(packet), 709 | + HANDSHAKE_DSCP); 710 | + } 711 | } 712 | } 713 | } 714 | @@ -116,7 +158,7 @@ 715 | net_dbg_skb_ratelimited("%s: Sending cookie response for denied handshake message for %pISpfsc\n", 716 | wg->dev->name, initiating_skb); 717 | wg_cookie_message_create(&packet, initiating_skb, sender_index, 718 | - &wg->cookie_checker); 719 | + &wg->cookie_checker, wg->advanced_security_config.cookie_packet_magic_header); 720 | wg_socket_send_buffer_as_reply_to_skb(wg, initiating_skb, &packet, 721 | sizeof(packet)); 722 | } 723 | @@ -159,7 +201,7 @@ 724 | return padded_size - last_unit; 725 | } 726 | 727 | -static bool encrypt_packet(struct sk_buff *skb, struct noise_keypair *keypair) 728 | +static bool encrypt_packet(struct sk_buff *skb, struct noise_keypair *keypair, u32 message_type) 729 | { 730 | unsigned int padding_len, plaintext_len, trailer_len; 731 | struct scatterlist sg[MAX_SKB_FRAGS + 8]; 732 | @@ -203,7 +245,7 @@ 733 | */ 734 | skb_set_inner_network_header(skb, 0); 735 | header = (struct message_data *)skb_push(skb, sizeof(*header)); 736 | - header->header.type = cpu_to_le32(MESSAGE_DATA); 737 | + header->header.type = cpu_to_le32(message_type); 738 | header->key_idx = keypair->remote_index; 739 | header->counter = cpu_to_le64(PACKET_CB(skb)->nonce); 740 | pskb_put(skb, trailer, trailer_len); 741 | @@ -289,13 +331,17 @@ 742 | struct crypt_queue *queue = container_of(work, struct multicore_worker, 743 | work)->ptr; 744 | struct sk_buff *first, *skb, *next; 745 | + struct wg_device *wg; 746 | 747 | while ((first = ptr_ring_consume_bh(&queue->ring)) != NULL) { 748 | enum packet_state state = PACKET_STATE_CRYPTED; 749 | 750 | skb_list_walk_safe(first, skb, next) { 751 | + wg = PACKET_PEER(first)->device; 752 | + 753 | if (likely(encrypt_packet(skb, 754 | - PACKET_CB(first)->keypair))) { 755 | + PACKET_CB(first)->keypair, 756 | + wg->advanced_security_config.transport_packet_magic_header))) { 757 | wg_reset_packet(skb, true); 758 | } else { 759 | state = PACKET_STATE_DEAD; 760 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/socket.c ./socket.c 761 | --- ../../linux-source-6.2.0/drivers/net/wireguard/socket.c 2023-11-10 18:10:29 762 | +++ ./socket.c 2023-11-23 15:45:07 763 | @@ -200,6 +200,18 @@ 764 | return wg_socket_send_skb_to_peer(peer, skb, ds); 765 | } 766 | 767 | +int wg_socket_send_junked_buffer_to_peer(struct wg_peer *peer, void *buffer, 768 | + size_t len, u8 ds, u16 junk_size) 769 | +{ 770 | + int ret; 771 | + void *new_buffer = kzalloc(len + junk_size, GFP_KERNEL); 772 | + get_random_bytes(new_buffer, junk_size); 773 | + memcpy(new_buffer + junk_size, buffer, len); 774 | + ret = wg_socket_send_buffer_to_peer(peer, new_buffer, len + junk_size, ds); 775 | + kfree(new_buffer); 776 | + return ret; 777 | +} 778 | + 779 | int wg_socket_send_buffer_as_reply_to_skb(struct wg_device *wg, 780 | struct sk_buff *in_skb, void *buffer, 781 | size_t len) 782 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/socket.h ./socket.h 783 | --- ../../linux-source-6.2.0/drivers/net/wireguard/socket.h 2023-11-10 18:10:29 784 | +++ ./socket.h 2023-11-23 13:20:24 785 | @@ -16,6 +16,8 @@ 786 | struct sock *new6); 787 | int wg_socket_send_buffer_to_peer(struct wg_peer *peer, void *data, 788 | size_t len, u8 ds); 789 | +int wg_socket_send_junked_buffer_to_peer(struct wg_peer *peer, void *data, 790 | + size_t len, u8 ds, u16 junk_size); 791 | int wg_socket_send_skb_to_peer(struct wg_peer *peer, struct sk_buff *skb, 792 | u8 ds); 793 | int wg_socket_send_buffer_as_reply_to_skb(struct wg_device *wg, 794 | diff --color -urN -x uapi -x compat -x crypto -x .idea -x tests -x Kbuild -x Kconfig -x Makefile -x dkms.conf ../../linux-source-6.2.0/drivers/net/wireguard/version.h ./version.h 795 | --- ../../linux-source-6.2.0/drivers/net/wireguard/version.h 2023-11-10 18:10:29 796 | +++ ./version.h 1970-01-01 02:00:00 797 | @@ -1 +0,0 @@ 798 | -#define WIREGUARD_VERSION "1.0.0" 799 | -------------------------------------------------------------------------------- /kmod-amneziawg/files/amnezia-uapi.patch: -------------------------------------------------------------------------------- 1 | --- ../../linux-source-6.2.0/include/uapi/linux/wireguard.h 2023-09-23 12:11:13 2 | +++ ./uapi/wireguard.h 2023-11-28 16:12:36 3 | @@ -131,7 +131,7 @@ 4 | #ifndef _WG_UAPI_WIREGUARD_H 5 | #define _WG_UAPI_WIREGUARD_H 6 | 7 | -#define WG_GENL_NAME "wireguard" 8 | +#define WG_GENL_NAME "amneziawg" 9 | #define WG_GENL_VERSION 1 10 | 11 | #define WG_KEY_LEN 32 12 | @@ -157,6 +157,15 @@ 13 | WGDEVICE_A_LISTEN_PORT, 14 | WGDEVICE_A_FWMARK, 15 | WGDEVICE_A_PEERS, 16 | + WGDEVICE_A_JC, 17 | + WGDEVICE_A_JMIN, 18 | + WGDEVICE_A_JMAX, 19 | + WGDEVICE_A_S1, 20 | + WGDEVICE_A_S2, 21 | + WGDEVICE_A_H1, 22 | + WGDEVICE_A_H2, 23 | + WGDEVICE_A_H3, 24 | + WGDEVICE_A_H4, 25 | __WGDEVICE_A_LAST 26 | }; 27 | #define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1) 28 | -------------------------------------------------------------------------------- /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-app-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 | LUCI_TITLE:=Support for AmneziaWG VPN 10 | LUCI_DEPENDS:=+amneziawg-tools +ucode 11 | LUCI_PKGARCH:=all 12 | 13 | PKG_PROVIDES:=luci-app-amneziawg 14 | 15 | include $(TOPDIR)/feeds/luci/luci.mk 16 | 17 | # call BuildPackage - OpenWrt buildroot signature 18 | -------------------------------------------------------------------------------- /luci-app-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 | function validateBase64(section_id, value) { 33 | if (value.length == 0) 34 | return true; 35 | 36 | if (value.length != 44 || !value.match(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/)) 37 | return _('Invalid Base64 key string'); 38 | 39 | if (value[43] != "=" ) 40 | return _('Invalid Base64 key string'); 41 | 42 | return true; 43 | } 44 | 45 | var stubValidator = { 46 | factory: validation, 47 | apply: function(type, value, args) { 48 | if (value != null) 49 | this.value = value; 50 | 51 | return validation.types[type].apply(this, args); 52 | }, 53 | assert: function(condition) { 54 | return !!condition; 55 | } 56 | }; 57 | 58 | function generateDescription(name, texts) { 59 | return E('li', { 'style': 'color: inherit;' }, [ 60 | E('span', name), 61 | E('ul', texts.map(function (text) { 62 | return E('li', { 'style': 'color: inherit;' }, text); 63 | })) 64 | ]); 65 | } 66 | 67 | function invokeQREncode(data, code) { 68 | return fs.exec_direct('/usr/bin/qrencode', [ 69 | '--inline', '--8bit', '--type=SVG', 70 | '--output=-', '--', data 71 | ]).then(function(svg) { 72 | code.style.opacity = ''; 73 | dom.content(code, Object.assign(E(svg), { style: 'width:100%;height:auto' })); 74 | }).catch(function(error) { 75 | code.style.opacity = ''; 76 | 77 | if (L.isObject(error) && error.name == 'NotFoundError') { 78 | dom.content(code, [ 79 | Object.assign(E(qrIcon), { style: 'width:32px;height:32px;opacity:.2' }), 80 | E('p', _('The qrencode package is required for generating an QR code image of the configuration.')) 81 | ]); 82 | } 83 | else { 84 | dom.content(code, [ 85 | _('Unable to generate QR code: %s').format(L.isObject(error) ? error.message : error) 86 | ]); 87 | } 88 | }); 89 | } 90 | 91 | var cbiKeyPairGenerate = form.DummyValue.extend({ 92 | cfgvalue: function(section_id, value) { 93 | return E('button', { 94 | 'class': 'btn', 95 | 'click': ui.createHandlerFn(this, function(section_id, ev) { 96 | var prv = this.section.getUIElement(section_id, 'private_key'), 97 | pub = this.section.getUIElement(section_id, 'public_key'), 98 | map = this.map; 99 | 100 | if ((prv.getValue() || pub.getValue()) && !confirm(_('Do you want to replace the current keys?'))) 101 | return; 102 | 103 | return generateKey().then(function(keypair) { 104 | prv.setValue(keypair.priv); 105 | pub.setValue(keypair.pub); 106 | map.save(null, true); 107 | }); 108 | }, section_id) 109 | }, [ _('Generate new key pair') ]); 110 | } 111 | }); 112 | 113 | function handleWindowDragDropIgnore(ev) { 114 | ev.preventDefault() 115 | } 116 | 117 | return network.registerProtocol('amneziawg', { 118 | getI18n: function() { 119 | return _('AmneziaWG VPN'); 120 | }, 121 | 122 | getIfname: function() { 123 | return this._ubus('l3_device') || this.sid; 124 | }, 125 | 126 | getOpkgPackage: function() { 127 | return 'amneziawg-tools'; 128 | }, 129 | 130 | isFloating: function() { 131 | return true; 132 | }, 133 | 134 | isVirtual: function() { 135 | return true; 136 | }, 137 | 138 | getDevices: function() { 139 | return null; 140 | }, 141 | 142 | containsDevice: function(ifname) { 143 | return (network.getIfnameOf(ifname) == this.getIfname()); 144 | }, 145 | 146 | renderFormOptions: function(s) { 147 | var o, ss, ss2; 148 | 149 | // -- general --------------------------------------------------------------------- 150 | 151 | o = s.taboption('general', form.Value, 'private_key', _('Private Key'), _('Required. Base64-encoded private key for this interface.')); 152 | o.password = true; 153 | o.validate = validateBase64; 154 | o.rmempty = false; 155 | 156 | var serverName = this.getIfname(); 157 | 158 | o = s.taboption('general', form.Value, 'public_key', _('Public Key'), _('Base64-encoded public key of this interface for sharing.')); 159 | o.rmempty = false; 160 | o.write = function() {/* write nothing */}; 161 | 162 | o.load = function(section_id) { 163 | var privKey = s.formvalue(section_id, 'private_key') || uci.get('network', section_id, 'private_key'); 164 | 165 | return getPublicAndPrivateKeyFromPrivate(privKey).then( 166 | function(keypair) { 167 | return keypair.pub || ''; 168 | }, 169 | function(error) { 170 | return _('Error getting PublicKey'); 171 | }, this) 172 | }; 173 | 174 | s.taboption('general', cbiKeyPairGenerate, '_gen_server_keypair', ' '); 175 | 176 | o = s.taboption('general', form.Value, 'listen_port', _('Listen Port'), _('Optional. UDP port used for outgoing and incoming packets.')); 177 | o.datatype = 'port'; 178 | o.placeholder = _('random'); 179 | o.optional = true; 180 | 181 | o = s.taboption('general', form.DynamicList, 'addresses', _('IP Addresses'), _('Recommended. IP addresses of the AmneziaWG interface.')); 182 | o.datatype = 'ipaddr'; 183 | o.optional = true; 184 | 185 | o = s.taboption('general', form.Flag, 'nohostroute', _('No Host Routes'), _('Optional. Do not create host routes to peers.')); 186 | o.optional = true; 187 | 188 | o = s.taboption('general', form.Button, '_import', _('Import configuration'), _('Imports settings from an existing AmneziaWG configuration file')); 189 | o.inputtitle = _('Load configuration…'); 190 | o.onclick = function() { 191 | return ss.handleConfigImport('full'); 192 | }; 193 | 194 | // -- advanced -------------------------------------------------------------------- 195 | 196 | o = s.taboption('advanced', form.Value, 'mtu', _('MTU'), _('Optional. Maximum Transmission Unit of tunnel interface.')); 197 | o.datatype = 'range(0,8940)'; 198 | o.placeholder = '1420'; 199 | o.optional = true; 200 | 201 | o = s.taboption('advanced', form.Value, 'fwmark', _('Firewall Mark'), _('Optional. 32-bit mark for outgoing encrypted packets. Enter value in hex, starting with 0x.')); 202 | o.optional = true; 203 | o.validate = function(section_id, value) { 204 | if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,8}$/)) 205 | return _('Invalid hexadecimal value'); 206 | 207 | return true; 208 | }; 209 | 210 | // AmneziaWG 211 | 212 | try { 213 | s.tab('amneziawg', _('AmneziaWG Settings'), _('Further information about AmneziaWG interfaces and peers at amnezia.org.')); 214 | } 215 | catch(e) {} 216 | 217 | o = s.taboption('amneziawg', form.Value, 'awg_jc', _('Jc'), _('Junk packet count.')); 218 | o.datatype = 'uinteger'; 219 | o.optional = true; 220 | 221 | o = s.taboption('amneziawg', form.Value, 'awg_jmin', _('Jmin'), _('Junk packet minimum size.')); 222 | o.datatype = 'uinteger'; 223 | o.optional = true; 224 | 225 | o = s.taboption('amneziawg', form.Value, 'awg_jmax', _('Jmax'), _('Junk packet maximum size.')); 226 | o.datatype = 'uinteger'; 227 | o.optional = true; 228 | 229 | o = s.taboption('amneziawg', form.Value, 'awg_s1', _('S1'), _('Handshake initiation packet junk header size.')); 230 | o.datatype = 'uinteger'; 231 | o.optional = true; 232 | 233 | o = s.taboption('amneziawg', form.Value, 'awg_s2', _('S2'), _('Handshake response packet junk header size.')); 234 | o.datatype = 'uinteger'; 235 | o.optional = true; 236 | 237 | o = s.taboption('amneziawg', form.Value, 'awg_h1', _('H1'), _('Handshake initiation packet type header.')); 238 | o.datatype = 'uinteger'; 239 | o.optional = true; 240 | 241 | o = s.taboption('amneziawg', form.Value, 'awg_h2', _('H2'), _('Handshake response packet type header.')); 242 | o.datatype = 'uinteger'; 243 | o.optional = true; 244 | 245 | o = s.taboption('amneziawg', form.Value, 'awg_h3', _('H3'), _('Handshake cookie packet type header.')); 246 | o.datatype = 'uinteger'; 247 | o.optional = true; 248 | 249 | o = s.taboption('amneziawg', form.Value, 'awg_h4', _('H4'), _('Transport packet type header.')); 250 | o.datatype = 'uinteger'; 251 | o.optional = true; 252 | 253 | // -- peers ----------------------------------------------------------------------- 254 | 255 | try { 256 | s.tab('peers', _('Peers'), _('Further information about AmneziaWG interfaces and peers at amneziawg.com.')); 257 | } 258 | catch(e) {} 259 | 260 | o = s.taboption('peers', form.SectionValue, '_peers', form.GridSection, 'amneziawg_%s'.format(s.section)); 261 | o.depends('proto', 'amneziawg'); 262 | 263 | ss = o.subsection; 264 | ss.anonymous = true; 265 | ss.addremove = true; 266 | ss.addbtntitle = _('Add peer'); 267 | ss.nodescriptions = true; 268 | ss.modaltitle = _('Edit peer'); 269 | 270 | ss.handleDragConfig = function(ev) { 271 | ev.stopPropagation(); 272 | ev.preventDefault(); 273 | ev.dataTransfer.dropEffect = 'copy'; 274 | }; 275 | 276 | ss.handleDropConfig = function(mode, ev) { 277 | var file = ev.dataTransfer.files[0], 278 | nodes = ev.currentTarget, 279 | input = nodes.querySelector('textarea'), 280 | reader = new FileReader(); 281 | 282 | if (file) { 283 | reader.onload = function(rev) { 284 | input.value = rev.target.result.trim(); 285 | ss.handleApplyConfig(mode, nodes, file.name, ev); 286 | }; 287 | 288 | reader.readAsText(file); 289 | } 290 | 291 | ev.stopPropagation(); 292 | ev.preventDefault(); 293 | }; 294 | 295 | ss.parseConfig = function(data) { 296 | var lines = String(data).split(/(\r?\n)+/), 297 | section = null, 298 | config = { peers: [] }, 299 | s; 300 | 301 | for (var i = 0; i < lines.length; i++) { 302 | var line = lines[i].replace(/#.*$/, '').trim(); 303 | 304 | if (line.match(/^\[(\w+)\]$/)) { 305 | section = RegExp.$1.toLowerCase(); 306 | 307 | if (section == 'peer') 308 | config.peers.push(s = {}); 309 | else 310 | s = config; 311 | } 312 | else if (section && line.match(/^(\w+)\s*=\s*(.+)$/)) { 313 | var key = RegExp.$1, 314 | val = RegExp.$2.trim(); 315 | 316 | if (val.length) 317 | s[section + '_' + key.toLowerCase()] = val; 318 | } 319 | } 320 | 321 | if (config.interface_address) { 322 | config.interface_address = config.interface_address.split(/[, ]+/); 323 | 324 | for (var i = 0; i < config.interface_address.length; i++) 325 | if (!stubValidator.apply('ipaddr', config.interface_address[i])) 326 | return _('Address setting is invalid'); 327 | } 328 | 329 | if (config.interface_dns) { 330 | config.interface_dns = config.interface_dns.split(/[, ]+/); 331 | 332 | for (var i = 0; i < config.interface_dns.length; i++) 333 | if (!stubValidator.apply('ipaddr', config.interface_dns[i], ['nomask'])) 334 | return _('DNS setting is invalid'); 335 | } 336 | 337 | if (!config.interface_privatekey || validateBase64(null, config.interface_privatekey) !== true) 338 | return _('PrivateKey setting is missing or invalid'); 339 | 340 | if (!stubValidator.apply('port', config.interface_listenport || '0')) 341 | return _('ListenPort setting is invalid'); 342 | 343 | for (var i = 0; i < config.peers.length; i++) { 344 | var pconf = config.peers[i]; 345 | 346 | if (pconf.peer_publickey != null && validateBase64(null, pconf.peer_publickey) !== true) 347 | return _('PublicKey setting is invalid'); 348 | 349 | if (pconf.peer_presharedkey != null && validateBase64(null, pconf.peer_presharedkey) !== true) 350 | return _('PresharedKey setting is invalid'); 351 | 352 | if (pconf.peer_allowedips) { 353 | pconf.peer_allowedips = pconf.peer_allowedips.split(/[, ]+/); 354 | 355 | for (var j = 0; j < pconf.peer_allowedips.length; j++) 356 | if (!stubValidator.apply('ipaddr', pconf.peer_allowedips[j])) 357 | return _('AllowedIPs setting is invalid'); 358 | } 359 | else { 360 | pconf.peer_allowedips = [ '0.0.0.0/0', '::/0' ]; 361 | } 362 | 363 | if (pconf.peer_endpoint) { 364 | var host_port = pconf.peer_endpoint.match(/^\[([a-fA-F0-9:]+)\]:(\d+)$/) || pconf.peer_endpoint.match(/^(.+):(\d+)$/); 365 | 366 | if (!host_port || !stubValidator.apply('host', host_port[1]) || !stubValidator.apply('port', host_port[2])) 367 | return _('Endpoint setting is invalid'); 368 | 369 | pconf.peer_endpoint = [ host_port[1], host_port[2] ]; 370 | } 371 | 372 | if (pconf.peer_persistentkeepalive == 'off' || pconf.peer_persistentkeepalive == '0') 373 | delete pconf.peer_persistentkeepalive; 374 | 375 | if (!stubValidator.apply('port', pconf.peer_persistentkeepalive || '0')) 376 | return _('PersistentKeepAlive setting is invalid'); 377 | } 378 | 379 | return config; 380 | }; 381 | 382 | ss.handleApplyConfig = function(mode, nodes, comment, ev) { 383 | var input = nodes.querySelector('textarea').value, 384 | error = nodes.querySelector('.alert-message'), 385 | cancel = nodes.nextElementSibling.querySelector('.btn'), 386 | config = this.parseConfig(input); 387 | 388 | if (typeof(config) == 'string') { 389 | error.firstChild.data = _('Cannot parse configuration: %s').format(config); 390 | error.style.display = 'block'; 391 | return; 392 | } 393 | 394 | if (mode == 'full') { 395 | var prv = s.formvalue(s.section, 'private_key'); 396 | 397 | if (prv && prv != config.interface_privatekey && !confirm(_('Overwrite the current settings with the imported configuration?'))) 398 | return; 399 | 400 | return getPublicAndPrivateKeyFromPrivate(config.interface_privatekey).then(function(keypair) { 401 | s.getOption('private_key').getUIElement(s.section).setValue(keypair.priv); 402 | s.getOption('public_key').getUIElement(s.section).setValue(keypair.pub); 403 | s.getOption('listen_port').getUIElement(s.section).setValue(config.interface_listenport || ''); 404 | s.getOption('addresses').getUIElement(s.section).setValue(config.interface_address); 405 | s.getOption('awg_jc').getUIElement(s.section).setValue(config.interface_jc); 406 | s.getOption('awg_jmin').getUIElement(s.section).setValue(config.interface_jmin); 407 | s.getOption('awg_jmax').getUIElement(s.section).setValue(config.interface_jmax); 408 | s.getOption('awg_s1').getUIElement(s.section).setValue(config.interface_s1); 409 | s.getOption('awg_s2').getUIElement(s.section).setValue(config.interface_s2); 410 | s.getOption('awg_h1').getUIElement(s.section).setValue(config.interface_h1); 411 | s.getOption('awg_h2').getUIElement(s.section).setValue(config.interface_h2); 412 | s.getOption('awg_h3').getUIElement(s.section).setValue(config.interface_h3); 413 | s.getOption('awg_h4').getUIElement(s.section).setValue(config.interface_h4); 414 | 415 | if (config.interface_dns) 416 | s.getOption('dns').getUIElement(s.section).setValue(config.interface_dns); 417 | 418 | for (var i = 0; i < config.peers.length; i++) { 419 | var pconf = config.peers[i]; 420 | var sid = uci.add('network', 'amneziawg_' + s.section); 421 | 422 | uci.sections('network', 'amneziawg_' + s.section, function(peer) { 423 | if (peer.public_key == pconf.peer_publickey) 424 | uci.remove('network', peer['.name']); 425 | }); 426 | 427 | uci.set('network', sid, 'description', comment || _('Imported peer configuration')); 428 | uci.set('network', sid, 'public_key', pconf.peer_publickey); 429 | uci.set('network', sid, 'preshared_key', pconf.peer_presharedkey); 430 | uci.set('network', sid, 'allowed_ips', pconf.peer_allowedips); 431 | uci.set('network', sid, 'persistent_keepalive', pconf.peer_persistentkeepalive); 432 | 433 | if (pconf.peer_endpoint) { 434 | uci.set('network', sid, 'endpoint_host', pconf.peer_endpoint[0]); 435 | uci.set('network', sid, 'endpoint_port', pconf.peer_endpoint[1]); 436 | } 437 | } 438 | 439 | return s.map.save(null, true); 440 | }).then(function() { 441 | cancel.click(); 442 | }); 443 | } 444 | else { 445 | return getPublicAndPrivateKeyFromPrivate(config.interface_privatekey).then(function(keypair) { 446 | var sid = uci.add('network', 'amneziawg_' + s.section); 447 | var pub = s.formvalue(s.section, 'public_key'); 448 | 449 | uci.sections('network', 'amneziawg_' + s.section, function(peer) { 450 | if (peer.public_key == keypair.pub) 451 | uci.remove('network', peer['.name']); 452 | }); 453 | 454 | uci.set('network', sid, 'description', comment || _('Imported peer configuration')); 455 | uci.set('network', sid, 'public_key', keypair.pub); 456 | uci.set('network', sid, 'private_key', keypair.priv); 457 | 458 | for (var i = 0; i < config.peers.length; i++) { 459 | var pconf = config.peers[i]; 460 | 461 | if (pconf.peer_publickey == pub) { 462 | uci.set('network', sid, 'preshared_key', pconf.peer_presharedkey); 463 | uci.set('network', sid, 'allowed_ips', pconf.peer_allowedips); 464 | uci.set('network', sid, 'persistent_keepalive', pconf.peer_persistentkeepalive); 465 | break; 466 | } 467 | } 468 | 469 | return s.map.save(null, true); 470 | }).then(function() { 471 | cancel.click(); 472 | }); 473 | } 474 | }; 475 | 476 | ss.handleConfigImport = function(mode) { 477 | var mapNode = ss.getActiveModalMap(), 478 | headNode = mapNode.parentNode.querySelector('h4'), 479 | parent = this.map; 480 | 481 | var nodes = E('div', { 482 | 'dragover': this.handleDragConfig, 483 | 'drop': this.handleDropConfig.bind(this, mode) 484 | }, [ 485 | E([], (mode == 'full') ? [ 486 | E('p', _('Drag or paste a valid *.conf file below to configure the local AmneziaWG interface.')) 487 | ] : [ 488 | E('p', _('Paste or drag a AmneziaWG configuration (commonly wg0.conf) from another system below to create a matching peer entry allowing that system to connect to the local AmneziaWG interface.')), 489 | E('p', _('To fully configure the local AmneziaWG interface from an existing (e.g. provider supplied) configuration file, use the configuration import instead.')) 490 | ]), 491 | E('p', [ 492 | E('textarea', { 493 | 'placeholder': (mode == 'full') 494 | ? _('Paste or drag supplied AmneziaWG configuration file…') 495 | : _('Paste or drag AmneziaWG peer configuration (wg0.conf) file…'), 496 | 'style': 'height:5em;width:100%; white-space:pre' 497 | }) 498 | ]), 499 | E('div', { 500 | 'class': 'alert-message', 501 | 'style': 'display:none' 502 | }, ['']) 503 | ]); 504 | 505 | var cancelFn = function() { 506 | nodes.parentNode.removeChild(nodes.nextSibling); 507 | nodes.parentNode.removeChild(nodes); 508 | mapNode.classList.remove('hidden'); 509 | mapNode.nextSibling.classList.remove('hidden'); 510 | headNode.removeChild(headNode.lastChild); 511 | window.removeEventListener('dragover', handleWindowDragDropIgnore); 512 | window.removeEventListener('drop', handleWindowDragDropIgnore); 513 | }; 514 | 515 | var a = nodes.querySelector('a.full-import'); 516 | 517 | if (a) { 518 | a.addEventListener('click', ui.createHandlerFn(this, function(mode) { 519 | cancelFn(); 520 | this.handleConfigImport('full'); 521 | })); 522 | } 523 | 524 | mapNode.classList.add('hidden'); 525 | mapNode.nextElementSibling.classList.add('hidden'); 526 | 527 | headNode.appendChild(E('span', [ ' » ', (mode == 'full') ? _('Import configuration') : _('Import as peer') ])); 528 | mapNode.parentNode.appendChild(E([], [ 529 | nodes, 530 | E('div', { 531 | 'class': 'right' 532 | }, [ 533 | E('button', { 534 | 'class': 'btn', 535 | 'click': cancelFn 536 | }, [ _('Cancel') ]), 537 | ' ', 538 | E('button', { 539 | 'class': 'btn primary', 540 | 'click': ui.createHandlerFn(this, 'handleApplyConfig', mode, nodes, null) 541 | }, [ _('Import settings') ]) 542 | ]) 543 | ])); 544 | 545 | window.addEventListener('dragover', handleWindowDragDropIgnore); 546 | window.addEventListener('drop', handleWindowDragDropIgnore); 547 | }; 548 | 549 | ss.renderSectionAdd = function(/* ... */) { 550 | var nodes = this.super('renderSectionAdd', arguments); 551 | 552 | nodes.appendChild(E('button', { 553 | 'class': 'btn', 554 | 'click': ui.createHandlerFn(this, 'handleConfigImport', 'peer') 555 | }, [ _('Import configuration as peer…') ])); 556 | 557 | return nodes; 558 | }; 559 | 560 | ss.renderSectionPlaceholder = function() { 561 | return E('em', _('No peers defined yet.')); 562 | }; 563 | 564 | o = ss.option(form.Flag, 'disabled', _('Peer disabled'), _('Enable / Disable peer. Restart amneziawg interface to apply changes.')); 565 | o.modalonly = true; 566 | o.optional = true; 567 | 568 | o = ss.option(form.Value, 'description', _('Description'), _('Optional. Description of peer.')); 569 | o.placeholder = 'My Peer'; 570 | o.datatype = 'string'; 571 | o.optional = true; 572 | o.width = '30%'; 573 | o.textvalue = function(section_id) { 574 | var dis = ss.getOption('disabled'), 575 | pub = ss.getOption('public_key'), 576 | prv = ss.getOption('private_key'), 577 | psk = ss.getOption('preshared_key'), 578 | name = this.cfgvalue(section_id), 579 | key = pub.cfgvalue(section_id); 580 | 581 | var desc = [ 582 | E('p', [ 583 | name ? E('span', [ name ]) : E('em', [ _('Untitled peer') ]) 584 | ]) 585 | ]; 586 | 587 | if (dis.cfgvalue(section_id) == '1') 588 | desc.push(E('span', { 589 | 'class': 'ifacebadge', 590 | 'data-tooltip': _('AmneziaWG peer is disabled') 591 | }, [ 592 | E('em', [ _('Disabled', 'Label indicating that AmneziaWG peer is disabled') ]) 593 | ]), ' '); 594 | 595 | if (!key || !pub.isValid(section_id)) { 596 | desc.push(E('span', { 597 | 'class': 'ifacebadge', 598 | 'data-tooltip': _('Public key is missing') 599 | }, [ 600 | E('em', [ _('Key missing', 'Label indicating that AmneziaWG peer lacks public key') ]) 601 | ])); 602 | } 603 | else { 604 | desc.push( 605 | E('span', { 606 | 'class': 'ifacebadge', 607 | 'data-tooltip': _('Public key: %h', 'Tooltip displaying full AmneziaWG peer public key').format(key) 608 | }, [ 609 | E('code', [ key.replace(/^(.{5}).+(.{6})$/, '$1…$2') ]) 610 | ]), 611 | ' ', 612 | (prv.cfgvalue(section_id) && prv.isValid(section_id)) 613 | ? E('span', { 614 | 'class': 'ifacebadge', 615 | 'data-tooltip': _('Private key present') 616 | }, [ _('Private', 'Label indicating that AmneziaWG peer private key is stored') ]) : '', 617 | ' ', 618 | (psk.cfgvalue(section_id) && psk.isValid(section_id)) 619 | ? E('span', { 620 | 'class': 'ifacebadge', 621 | 'data-tooltip': _('Preshared key in use') 622 | }, [ _('PSK', 'Label indicating that AmneziaWG peer uses a PSK') ]) : '' 623 | ); 624 | } 625 | 626 | return E([], desc); 627 | }; 628 | 629 | function handleKeyChange(ev, section_id, value) { 630 | var prv = this.section.getUIElement(section_id, 'private_key'), 631 | btn = this.map.findElement('.btn.qr-code'); 632 | 633 | btn.disabled = (!prv.isValid() || !prv.getValue()); 634 | } 635 | 636 | o = ss.option(form.Value, 'public_key', _('Public Key'), _('Required. Public key of the AmneziaWG peer.')); 637 | o.modalonly = true; 638 | o.validate = validateBase64; 639 | o.onchange = handleKeyChange; 640 | 641 | o = ss.option(form.Value, 'private_key', _('Private Key'), _('Optional. Private key of the AmneziaWG peer. The key is not required for establishing a connection but allows generating a peer configuration or QR code if available. It can be removed after the configuration has been exported.')); 642 | o.modalonly = true; 643 | o.validate = validateBase64; 644 | o.onchange = handleKeyChange; 645 | o.password = true; 646 | 647 | o = ss.option(cbiKeyPairGenerate, '_gen_peer_keypair', ' '); 648 | o.modalonly = true; 649 | 650 | o = ss.option(form.Value, 'preshared_key', _('Preshared Key'), _('Optional. Base64-encoded preshared key. Adds in an additional layer of symmetric-key cryptography for post-quantum resistance.')); 651 | o.modalonly = true; 652 | o.validate = validateBase64; 653 | o.password = true; 654 | 655 | o = ss.option(form.DummyValue, '_gen_psk', ' '); 656 | o.modalonly = true; 657 | o.cfgvalue = function(section_id, value) { 658 | return E('button', { 659 | 'class': 'btn', 660 | 'click': ui.createHandlerFn(this, function(section_id, ev) { 661 | var psk = this.section.getUIElement(section_id, 'preshared_key'), 662 | map = this.map; 663 | 664 | if (psk.getValue() && !confirm(_('Do you want to replace the current PSK?'))) 665 | return; 666 | 667 | return generatePsk().then(function(key) { 668 | psk.setValue(key); 669 | map.save(null, true); 670 | }); 671 | }, section_id) 672 | }, [ _('Generate preshared key') ]); 673 | }; 674 | 675 | o = ss.option(form.DynamicList, 'allowed_ips', _('Allowed IPs'), _("Optional. IP addresses and prefixes that this peer is allowed to use inside the tunnel. Usually the peer's tunnel IP addresses and the networks the peer routes through the tunnel.")); 676 | o.datatype = 'ipaddr'; 677 | o.textvalue = function(section_id) { 678 | var ips = L.toArray(this.cfgvalue(section_id)), 679 | list = []; 680 | 681 | for (var i = 0; i < ips.length; i++) { 682 | if (i > 7) { 683 | list.push(E('em', { 684 | 'class': 'ifacebadge cbi-tooltip-container' 685 | }, [ 686 | _('+ %d more', 'Label indicating further amount of allowed ips').format(ips.length - i), 687 | E('span', { 688 | 'class': 'cbi-tooltip' 689 | }, [ 690 | E('ul', ips.map(function(ip) { 691 | return E('li', [ 692 | E('span', { 'class': 'ifacebadge' }, [ ip ]) 693 | ]); 694 | })) 695 | ]) 696 | ])); 697 | 698 | break; 699 | } 700 | 701 | list.push(E('span', { 'class': 'ifacebadge' }, [ ips[i] ])); 702 | } 703 | 704 | if (!list.length) 705 | list.push('*'); 706 | 707 | return E('span', { 'style': 'display:inline-flex;flex-wrap:wrap;gap:.125em' }, list); 708 | }; 709 | 710 | o = ss.option(form.Flag, 'route_allowed_ips', _('Route Allowed IPs'), _('Optional. Create routes for Allowed IPs for this peer.')); 711 | o.modalonly = true; 712 | 713 | o = ss.option(form.Value, 'endpoint_host', _('Endpoint Host'), _('Optional. Host of peer. Names are resolved prior to bringing up the interface.')); 714 | o.placeholder = 'vpn.example.com'; 715 | o.datatype = 'host'; 716 | o.textvalue = function(section_id) { 717 | var host = this.cfgvalue(section_id), 718 | port = this.section.cfgvalue(section_id, 'endpoint_port'); 719 | 720 | return (host && port) 721 | ? '%h:%d'.format(host, port) 722 | : (host 723 | ? '%h:*'.format(host) 724 | : (port 725 | ? '*:%d'.format(port) 726 | : '*')); 727 | }; 728 | 729 | o = ss.option(form.Value, 'endpoint_port', _('Endpoint Port'), _('Optional. Port of peer.')); 730 | o.modalonly = true; 731 | o.placeholder = '51820'; 732 | o.datatype = 'port'; 733 | 734 | o = ss.option(form.Value, 'persistent_keepalive', _('Persistent Keep Alive'), _('Optional. Seconds between keep alive messages. Default is 0 (disabled). Recommended value if this device is behind a NAT is 25.')); 735 | o.modalonly = true; 736 | o.datatype = 'range(0,65535)'; 737 | o.placeholder = '0'; 738 | 739 | 740 | 741 | o = ss.option(form.DummyValue, '_keyops', _('Configuration Export'), 742 | _('Generates a configuration suitable for import on a AmneziaWG peer')); 743 | 744 | o.modalonly = true; 745 | 746 | o.createPeerConfig = function(section_id, endpoint, ips) { 747 | var pub = s.formvalue(s.section, 'public_key'), 748 | port = s.formvalue(s.section, 'listen_port') || '51820', 749 | jc = s.formvalue 750 | prv = this.section.formvalue(section_id, 'private_key'), 751 | psk = this.section.formvalue(section_id, 'preshared_key'), 752 | eport = this.section.formvalue(section_id, 'endpoint_port'), 753 | keep = this.section.formvalue(section_id, 'persistent_keepalive'); 754 | 755 | // If endpoint is IPv6 we must escape it with [] 756 | if (endpoint.indexOf(':') > 0) { 757 | endpoint = '['+endpoint+']'; 758 | } 759 | 760 | return [ 761 | '[Interface]', 762 | 'PrivateKey = ' + prv, 763 | eport ? 'ListenPort = ' + eport : '# ListenPort not defined', 764 | '', 765 | '[Peer]', 766 | 'PublicKey = ' + pub, 767 | psk ? 'PresharedKey = ' + psk : '# PresharedKey not used', 768 | ips && ips.length ? 'AllowedIPs = ' + ips.join(', ') : '# AllowedIPs not defined', 769 | endpoint ? 'Endpoint = ' + endpoint + ':' + port : '# Endpoint not defined', 770 | keep ? 'PersistentKeepAlive = ' + keep : '# PersistentKeepAlive not defined' 771 | ].join('\n'); 772 | }; 773 | 774 | o.handleGenerateQR = function(section_id, ev) { 775 | var mapNode = ss.getActiveModalMap(), 776 | headNode = mapNode.parentNode.querySelector('h4'), 777 | configGenerator = this.createPeerConfig.bind(this, section_id), 778 | parent = this.map; 779 | 780 | return Promise.all([ 781 | network.getWANNetworks(), 782 | network.getWAN6Networks(), 783 | L.resolveDefault(uci.load('ddns')), 784 | L.resolveDefault(uci.load('system')), 785 | parent.save(null, true) 786 | ]).then(function(data) { 787 | var hostnames = []; 788 | 789 | uci.sections('ddns', 'service', function(s) { 790 | if (typeof(s.lookup_host) == 'string' && s.enabled == '1') 791 | hostnames.push(s.lookup_host); 792 | }); 793 | 794 | uci.sections('system', 'system', function(s) { 795 | if (typeof(s.hostname) == 'string' && s.hostname.indexOf('.') > 0) 796 | hostnames.push(s.hostname); 797 | }); 798 | 799 | for (var i = 0; i < data[0].length; i++) 800 | hostnames.push.apply(hostnames, data[0][i].getIPAddrs().map(function(ip) { return ip.split('/')[0] })); 801 | 802 | for (var i = 0; i < data[1].length; i++) 803 | hostnames.push.apply(hostnames, data[1][i].getIP6Addrs().map(function(ip) { return ip.split('/')[0] })); 804 | 805 | var ips = [ '0.0.0.0/0', '::/0' ]; 806 | 807 | var qrm, qrs, qro; 808 | 809 | qrm = new form.JSONMap({ config: { endpoint: hostnames[0], allowed_ips: ips } }, null, _('The generated configuration can be imported into a AmneziaWG client application to set up a connection towards this device.')); 810 | qrm.parent = parent; 811 | 812 | qrs = qrm.section(form.NamedSection, 'config'); 813 | 814 | function handleConfigChange(ev, section_id, value) { 815 | var code = this.map.findElement('.qr-code'), 816 | conf = this.map.findElement('.client-config'), 817 | endpoint = this.section.getUIElement(section_id, 'endpoint'), 818 | ips = this.section.getUIElement(section_id, 'allowed_ips'); 819 | 820 | if (this.isValid(section_id)) { 821 | conf.firstChild.data = configGenerator(endpoint.getValue(), ips.getValue()); 822 | code.style.opacity = '.5'; 823 | 824 | invokeQREncode(conf.firstChild.data, code); 825 | } 826 | }; 827 | 828 | qro = qrs.option(form.Value, 'endpoint', _('Connection endpoint'), _('The public hostname or IP address of this system the peer should connect to. This usually is a static public IP address, a static hostname or a DDNS domain.')); 829 | qro.datatype = 'or(ipaddr,hostname)'; 830 | hostnames.forEach(function(hostname) { qro.value(hostname) }); 831 | qro.onchange = handleConfigChange; 832 | 833 | qro = qrs.option(form.DynamicList, 'allowed_ips', _('Allowed IPs'), _('IP addresses that are allowed inside the tunnel. The peer will accept tunnelled packets with source IP addresses matching this list and route back packets with matching destination IP.')); 834 | qro.datatype = 'ipaddr'; 835 | qro.default = ips; 836 | ips.forEach(function(ip) { qro.value(ip) }); 837 | qro.onchange = handleConfigChange; 838 | 839 | qro = qrs.option(form.DummyValue, 'output'); 840 | qro.renderWidget = function() { 841 | var peer_config = configGenerator(hostnames[0], ips); 842 | 843 | var node = E('div', { 844 | 'style': 'display:flex;flex-wrap:wrap;align-items:center;gap:.5em;width:100%' 845 | }, [ 846 | E('div', { 847 | 'class': 'qr-code', 848 | 'style': 'width:320px;flex:0 1 320px;text-align:center' 849 | }, [ 850 | E('em', { 'class': 'spinning' }, [ _('Generating QR code…') ]) 851 | ]), 852 | E('pre', { 853 | 'class': 'client-config', 854 | 'style': 'flex:1;white-space:pre;overflow:auto', 855 | 'click': function(ev) { 856 | var sel = window.getSelection(), 857 | range = document.createRange(); 858 | 859 | range.selectNodeContents(ev.currentTarget); 860 | 861 | sel.removeAllRanges(); 862 | sel.addRange(range); 863 | } 864 | }, [ peer_config ]) 865 | ]); 866 | 867 | invokeQREncode(peer_config, node.firstChild); 868 | 869 | return node; 870 | }; 871 | 872 | return qrm.render().then(function(nodes) { 873 | mapNode.classList.add('hidden'); 874 | mapNode.nextElementSibling.classList.add('hidden'); 875 | 876 | headNode.appendChild(E('span', [ ' » ', _('Generate configuration') ])); 877 | mapNode.parentNode.appendChild(E([], [ 878 | nodes, 879 | E('div', { 880 | 'class': 'right' 881 | }, [ 882 | E('button', { 883 | 'class': 'btn', 884 | 'click': function() { 885 | nodes.parentNode.removeChild(nodes.nextSibling); 886 | nodes.parentNode.removeChild(nodes); 887 | mapNode.classList.remove('hidden'); 888 | mapNode.nextSibling.classList.remove('hidden'); 889 | headNode.removeChild(headNode.lastChild); 890 | } 891 | }, [ _('Back to peer configuration') ]) 892 | ]) 893 | ])); 894 | 895 | if (!s.formvalue(s.section, 'listen_port')) { 896 | nodes.appendChild(E('div', { 'class': 'alert-message' }, [ 897 | E('p', [ 898 | _('No fixed interface listening port defined, peers might not be able to initiate connections to this AmneziaWG instance!') 899 | ]) 900 | ])); 901 | } 902 | }); 903 | }); 904 | }; 905 | 906 | o.cfgvalue = function(section_id, value) { 907 | var privkey = this.section.cfgvalue(section_id, 'private_key'); 908 | 909 | return E('button', { 910 | 'class': 'btn qr-code', 911 | 'style': 'display:inline-flex;align-items:center;gap:.5em', 912 | 'click': ui.createHandlerFn(this, 'handleGenerateQR', section_id), 913 | 'disabled': privkey ? null : '' 914 | }, [ 915 | Object.assign(E(qrIcon), { style: 'width:22px;height:22px' }), 916 | _('Generate configuration…') 917 | ]); 918 | }; 919 | }, 920 | 921 | deleteConfiguration: function() { 922 | uci.sections('network', 'amneziawg_%s'.format(this.sid), function(s) { 923 | uci.remove('network', s['.name']); 924 | }); 925 | } 926 | }); 927 | -------------------------------------------------------------------------------- /luci-app-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 | -------------------------------------------------------------------------------- /luci-app-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-app-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" ] 14 | }, 15 | "write": { 16 | "ubus": { 17 | "luci.amneziawg": [ 18 | "generateKeyPair", 19 | "getPublicAndPrivateKeyFromPrivate", 20 | "generatePsk" 21 | ] 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /luci-app-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('amneziawg genpsk 2>/dev/null') }; 23 | } 24 | }, 25 | 26 | generateKeyPair: { 27 | call: function() { 28 | const priv = command('amneziawg genkey 2>/dev/null'); 29 | const pub = command(`echo ${shellquote(priv)} | amneziawg 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)} | amneziawg 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("amneziawg 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 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awg-openwrt", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "awg-openwrt", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@actions/core": "^1.10.1", 13 | "axios": "^1.3.1", 14 | "cheerio": "^1.0.0" 15 | } 16 | }, 17 | "node_modules/@actions/core": { 18 | "version": "1.10.1", 19 | "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz", 20 | "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==", 21 | "license": "MIT", 22 | "dependencies": { 23 | "@actions/http-client": "^2.0.1", 24 | "uuid": "^8.3.2" 25 | } 26 | }, 27 | "node_modules/@actions/http-client": { 28 | "version": "2.2.1", 29 | "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.1.tgz", 30 | "integrity": "sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==", 31 | "license": "MIT", 32 | "dependencies": { 33 | "tunnel": "^0.0.6", 34 | "undici": "^5.25.4" 35 | } 36 | }, 37 | "node_modules/@actions/http-client/node_modules/undici": { 38 | "version": "5.28.4", 39 | "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", 40 | "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", 41 | "license": "MIT", 42 | "dependencies": { 43 | "@fastify/busboy": "^2.0.0" 44 | }, 45 | "engines": { 46 | "node": ">=14.0" 47 | } 48 | }, 49 | "node_modules/@fastify/busboy": { 50 | "version": "2.1.1", 51 | "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", 52 | "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", 53 | "license": "MIT", 54 | "engines": { 55 | "node": ">=14" 56 | } 57 | }, 58 | "node_modules/asynckit": { 59 | "version": "0.4.0", 60 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 61 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 62 | "license": "MIT" 63 | }, 64 | "node_modules/axios": { 65 | "version": "1.7.3", 66 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", 67 | "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", 68 | "license": "MIT", 69 | "dependencies": { 70 | "follow-redirects": "^1.15.6", 71 | "form-data": "^4.0.0", 72 | "proxy-from-env": "^1.1.0" 73 | } 74 | }, 75 | "node_modules/boolbase": { 76 | "version": "1.0.0", 77 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 78 | "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", 79 | "license": "ISC" 80 | }, 81 | "node_modules/cheerio": { 82 | "version": "1.0.0", 83 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", 84 | "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", 85 | "license": "MIT", 86 | "dependencies": { 87 | "cheerio-select": "^2.1.0", 88 | "dom-serializer": "^2.0.0", 89 | "domhandler": "^5.0.3", 90 | "domutils": "^3.1.0", 91 | "encoding-sniffer": "^0.2.0", 92 | "htmlparser2": "^9.1.0", 93 | "parse5": "^7.1.2", 94 | "parse5-htmlparser2-tree-adapter": "^7.0.0", 95 | "parse5-parser-stream": "^7.1.2", 96 | "undici": "^6.19.5", 97 | "whatwg-mimetype": "^4.0.0" 98 | }, 99 | "engines": { 100 | "node": ">=18.17" 101 | }, 102 | "funding": { 103 | "url": "https://github.com/cheeriojs/cheerio?sponsor=1" 104 | } 105 | }, 106 | "node_modules/cheerio-select": { 107 | "version": "2.1.0", 108 | "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", 109 | "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", 110 | "license": "BSD-2-Clause", 111 | "dependencies": { 112 | "boolbase": "^1.0.0", 113 | "css-select": "^5.1.0", 114 | "css-what": "^6.1.0", 115 | "domelementtype": "^2.3.0", 116 | "domhandler": "^5.0.3", 117 | "domutils": "^3.0.1" 118 | }, 119 | "funding": { 120 | "url": "https://github.com/sponsors/fb55" 121 | } 122 | }, 123 | "node_modules/combined-stream": { 124 | "version": "1.0.8", 125 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 126 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 127 | "license": "MIT", 128 | "dependencies": { 129 | "delayed-stream": "~1.0.0" 130 | }, 131 | "engines": { 132 | "node": ">= 0.8" 133 | } 134 | }, 135 | "node_modules/css-select": { 136 | "version": "5.1.0", 137 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", 138 | "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", 139 | "license": "BSD-2-Clause", 140 | "dependencies": { 141 | "boolbase": "^1.0.0", 142 | "css-what": "^6.1.0", 143 | "domhandler": "^5.0.2", 144 | "domutils": "^3.0.1", 145 | "nth-check": "^2.0.1" 146 | }, 147 | "funding": { 148 | "url": "https://github.com/sponsors/fb55" 149 | } 150 | }, 151 | "node_modules/css-what": { 152 | "version": "6.1.0", 153 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", 154 | "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", 155 | "license": "BSD-2-Clause", 156 | "engines": { 157 | "node": ">= 6" 158 | }, 159 | "funding": { 160 | "url": "https://github.com/sponsors/fb55" 161 | } 162 | }, 163 | "node_modules/delayed-stream": { 164 | "version": "1.0.0", 165 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 166 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 167 | "license": "MIT", 168 | "engines": { 169 | "node": ">=0.4.0" 170 | } 171 | }, 172 | "node_modules/dom-serializer": { 173 | "version": "2.0.0", 174 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", 175 | "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", 176 | "license": "MIT", 177 | "dependencies": { 178 | "domelementtype": "^2.3.0", 179 | "domhandler": "^5.0.2", 180 | "entities": "^4.2.0" 181 | }, 182 | "funding": { 183 | "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 184 | } 185 | }, 186 | "node_modules/domelementtype": { 187 | "version": "2.3.0", 188 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 189 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 190 | "funding": [ 191 | { 192 | "type": "github", 193 | "url": "https://github.com/sponsors/fb55" 194 | } 195 | ], 196 | "license": "BSD-2-Clause" 197 | }, 198 | "node_modules/domhandler": { 199 | "version": "5.0.3", 200 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", 201 | "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", 202 | "license": "BSD-2-Clause", 203 | "dependencies": { 204 | "domelementtype": "^2.3.0" 205 | }, 206 | "engines": { 207 | "node": ">= 4" 208 | }, 209 | "funding": { 210 | "url": "https://github.com/fb55/domhandler?sponsor=1" 211 | } 212 | }, 213 | "node_modules/domutils": { 214 | "version": "3.1.0", 215 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", 216 | "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", 217 | "license": "BSD-2-Clause", 218 | "dependencies": { 219 | "dom-serializer": "^2.0.0", 220 | "domelementtype": "^2.3.0", 221 | "domhandler": "^5.0.3" 222 | }, 223 | "funding": { 224 | "url": "https://github.com/fb55/domutils?sponsor=1" 225 | } 226 | }, 227 | "node_modules/encoding-sniffer": { 228 | "version": "0.2.0", 229 | "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", 230 | "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", 231 | "license": "MIT", 232 | "dependencies": { 233 | "iconv-lite": "^0.6.3", 234 | "whatwg-encoding": "^3.1.1" 235 | }, 236 | "funding": { 237 | "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" 238 | } 239 | }, 240 | "node_modules/entities": { 241 | "version": "4.5.0", 242 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 243 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 244 | "license": "BSD-2-Clause", 245 | "engines": { 246 | "node": ">=0.12" 247 | }, 248 | "funding": { 249 | "url": "https://github.com/fb55/entities?sponsor=1" 250 | } 251 | }, 252 | "node_modules/follow-redirects": { 253 | "version": "1.15.6", 254 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 255 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 256 | "funding": [ 257 | { 258 | "type": "individual", 259 | "url": "https://github.com/sponsors/RubenVerborgh" 260 | } 261 | ], 262 | "license": "MIT", 263 | "engines": { 264 | "node": ">=4.0" 265 | }, 266 | "peerDependenciesMeta": { 267 | "debug": { 268 | "optional": true 269 | } 270 | } 271 | }, 272 | "node_modules/form-data": { 273 | "version": "4.0.0", 274 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 275 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 276 | "license": "MIT", 277 | "dependencies": { 278 | "asynckit": "^0.4.0", 279 | "combined-stream": "^1.0.8", 280 | "mime-types": "^2.1.12" 281 | }, 282 | "engines": { 283 | "node": ">= 6" 284 | } 285 | }, 286 | "node_modules/htmlparser2": { 287 | "version": "9.1.0", 288 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", 289 | "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", 290 | "funding": [ 291 | "https://github.com/fb55/htmlparser2?sponsor=1", 292 | { 293 | "type": "github", 294 | "url": "https://github.com/sponsors/fb55" 295 | } 296 | ], 297 | "license": "MIT", 298 | "dependencies": { 299 | "domelementtype": "^2.3.0", 300 | "domhandler": "^5.0.3", 301 | "domutils": "^3.1.0", 302 | "entities": "^4.5.0" 303 | } 304 | }, 305 | "node_modules/iconv-lite": { 306 | "version": "0.6.3", 307 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 308 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 309 | "license": "MIT", 310 | "dependencies": { 311 | "safer-buffer": ">= 2.1.2 < 3.0.0" 312 | }, 313 | "engines": { 314 | "node": ">=0.10.0" 315 | } 316 | }, 317 | "node_modules/mime-db": { 318 | "version": "1.52.0", 319 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 320 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 321 | "license": "MIT", 322 | "engines": { 323 | "node": ">= 0.6" 324 | } 325 | }, 326 | "node_modules/mime-types": { 327 | "version": "2.1.35", 328 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 329 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 330 | "license": "MIT", 331 | "dependencies": { 332 | "mime-db": "1.52.0" 333 | }, 334 | "engines": { 335 | "node": ">= 0.6" 336 | } 337 | }, 338 | "node_modules/nth-check": { 339 | "version": "2.1.1", 340 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", 341 | "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", 342 | "license": "BSD-2-Clause", 343 | "dependencies": { 344 | "boolbase": "^1.0.0" 345 | }, 346 | "funding": { 347 | "url": "https://github.com/fb55/nth-check?sponsor=1" 348 | } 349 | }, 350 | "node_modules/parse5": { 351 | "version": "7.1.2", 352 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", 353 | "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", 354 | "license": "MIT", 355 | "dependencies": { 356 | "entities": "^4.4.0" 357 | }, 358 | "funding": { 359 | "url": "https://github.com/inikulin/parse5?sponsor=1" 360 | } 361 | }, 362 | "node_modules/parse5-htmlparser2-tree-adapter": { 363 | "version": "7.0.0", 364 | "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", 365 | "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", 366 | "license": "MIT", 367 | "dependencies": { 368 | "domhandler": "^5.0.2", 369 | "parse5": "^7.0.0" 370 | }, 371 | "funding": { 372 | "url": "https://github.com/inikulin/parse5?sponsor=1" 373 | } 374 | }, 375 | "node_modules/parse5-parser-stream": { 376 | "version": "7.1.2", 377 | "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", 378 | "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", 379 | "license": "MIT", 380 | "dependencies": { 381 | "parse5": "^7.0.0" 382 | }, 383 | "funding": { 384 | "url": "https://github.com/inikulin/parse5?sponsor=1" 385 | } 386 | }, 387 | "node_modules/proxy-from-env": { 388 | "version": "1.1.0", 389 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 390 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 391 | "license": "MIT" 392 | }, 393 | "node_modules/safer-buffer": { 394 | "version": "2.1.2", 395 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 396 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 397 | "license": "MIT" 398 | }, 399 | "node_modules/tunnel": { 400 | "version": "0.0.6", 401 | "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", 402 | "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", 403 | "license": "MIT", 404 | "engines": { 405 | "node": ">=0.6.11 <=0.7.0 || >=0.7.3" 406 | } 407 | }, 408 | "node_modules/undici": { 409 | "version": "6.19.7", 410 | "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz", 411 | "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==", 412 | "license": "MIT", 413 | "engines": { 414 | "node": ">=18.17" 415 | } 416 | }, 417 | "node_modules/uuid": { 418 | "version": "8.3.2", 419 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 420 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 421 | "license": "MIT", 422 | "bin": { 423 | "uuid": "dist/bin/uuid" 424 | } 425 | }, 426 | "node_modules/whatwg-encoding": { 427 | "version": "3.1.1", 428 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", 429 | "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", 430 | "license": "MIT", 431 | "dependencies": { 432 | "iconv-lite": "0.6.3" 433 | }, 434 | "engines": { 435 | "node": ">=18" 436 | } 437 | }, 438 | "node_modules/whatwg-mimetype": { 439 | "version": "4.0.0", 440 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", 441 | "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", 442 | "license": "MIT", 443 | "engines": { 444 | "node": ">=18" 445 | } 446 | } 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awg-openwrt", 3 | "version": "1.0.0", 4 | "description": "Amnezia Wireguard packages for OpenWRT", 5 | "main": "index.js", 6 | "scripts": { 7 | "get-build-envs": "node index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Slava-Shchipunov/awg-openwrt.git" 12 | }, 13 | "author": "Slava Shchipunov", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/Slava-Shchipunov/awg-openwrt/issues" 17 | }, 18 | "homepage": "https://github.com/Slava-Shchipunov/awg-openwrt#readme", 19 | "dependencies": { 20 | "@actions/core": "^1.10.1", 21 | "axios": "^1.3.1", 22 | "cheerio": "^1.0.0" 23 | } 24 | } 25 | --------------------------------------------------------------------------------