diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7395c09 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +*.sh text eol=lf +*.py text eol=lf +*.md text eol=lf +*.json text eol=lf +*.ko binary +*.gz binary diff --git a/ZapretManager_LuCI.sh b/ZapretManager_LuCI.sh index 0b3389a..15fdf75 100644 --- a/ZapretManager_LuCI.sh +++ b/ZapretManager_LuCI.sh @@ -1,6 +1,6 @@ #!/bin/sh read -r _ _ ZM_NEW_VER <<'ZM_VERSION_EOF' -# Version: 4.68 +# Version: 4.68.1 ZM_VERSION_EOF set -e @@ -129,7 +129,7 @@ fi CONF="/etc/config/zapret" ZM_VERSION="@ZM_VERSION@" -ZM_SCRIPT_URL="https://raw.githubusercontent.com/StressOzz/Zapret-Manager/refs/heads/main/ZapretManager_LuCI.sh" +ZM_SCRIPT_URL="https://gitea.bvrdo.online/wenil/Zapret-Manager/raw/branch/main/ZapretManager_LuCI.sh" GH_RAW="https://raw.githubusercontent.com" GH_MAIN="https://github.com" MT_URL_ITDOG="${GH_RAW}/StressOzz/Zapret-Manager/refs/heads/main/files/MagiTrickle/configAD.yaml" @@ -4255,7 +4255,7 @@ _pg_trace() { [ -z "$(_fk_sb_users)" ] && echo "pkg:sing-box-tiny pkg:sing-box pkg:sing-box-extended /usr/bin/sing-box /usr/bin/.sing-box.zm-keep /etc/init.d/sing-box /etc/rc.d/*sing-box /etc/sing-box /etc/config/sing-box /etc/config/sing-box-opkg /etc/config/sing-box.apk-new /var/run/sing-box /tmp/sing-box /usr/share/sing-box /usr/lib/libcronet.so /usr/libexec/sing-box-core /etc/sing-box-version.cache" ;; mixomo) echo "$MIHOMO_BIN $MIHOMO_DIR /tmp/mihomo* /tmp/zashboard /tmp/zashboard.zip /tmp/metacubexd /tmp/metacubexd.tgz /etc/init.d/mihomo /etc/rc.d/*mihomo pkg:magitrickle /etc/magitrickle /etc/init.d/magitrickle /etc/rc.d/*magitrickle /etc/config/magitrickle /etc/config/magitrickle-opkg /usr/bin/magitrickle* /usr/sbin/magitrickle* /var/run/magitrickle* nft:magitrickle uci:network.Mihomo uci:firewall.Mihomo uci:firewall.lan_to_Mihomo cronrx:mihomo cronrx:magitrickle" _bt_installed || echo "pkg:hev-socks5-tunnel /usr/bin/hev-socks5-tunnel /etc/config/hev-socks5-tunnel /etc/config/hev-socks5-tunnel-opkg /etc/init.d/hev-socks5-tunnel /etc/rc.d/*hev-socks5-tunnel /etc/hev-socks5-tunnel" ;; - awg) echo "pkg:luci-i18n-amneziawg-ru pkg:luci-app-amneziawg pkg:luci-proto-amneziawg pkg:amneziawg-tools pkg:kmod-amneziawg $AWG_DIR /usr/bin/awg /usr/bin/awg-quick /lib/netifd/proto/amneziawg.sh /lib/modules/*/amneziawg.ko /etc/modules.d/*amneziawg* $AWG_KMOD_MANUAL $(_zm_luci_files amneziawg) $AWF_MODE $AWF_SKIP $AWF_STATE $AWF_LOG cron:zm-awgfix" ;; + awg) echo "pkg:luci-i18n-amneziawg-ru pkg:luci-app-amneziawg pkg:luci-proto-amneziawg pkg:amneziawg-tools pkg:kmod-amneziawg $AWG_DIR /usr/bin/awg /usr/bin/awg-quick /lib/netifd/proto/amneziawg.sh /lib/modules/*/amneziawg.ko /etc/modules.d/*amneziawg* $AWG_KMOD_MANUAL $AWG_VENDOR_INIT /etc/rc.d/*zm-awg-r3s $(_zm_luci_files amneziawg) $AWF_MODE $AWF_SKIP $AWF_STATE $AWF_LOG cron:zm-awgfix" ;; tgproxy) echo "pkg:tg-ws-proxy /usr/bin/tg-ws-proxy /etc/init.d/tg-ws-proxy /etc/tg-ws-proxy /etc/tg-ws-proxy.conf /etc/config/tg-ws-proxy $TG_BIN_GO $TG_BIN_GO.new $TG_INIT_GO $TG_BIN_RS $TG_INIT_RS $TG_SECRET_RS_FILE $TG_VER_GO_FILE $TG_VER_RS_FILE /etc/config/tg-ws-proxy-rs $TG_WATCH_FLAG /tmp/tg-ws-proxy.ipk /tmp/tg-ws-proxy.apk /etc/rc.d/*tg-ws-proxy* cron:zm-tg-auto cronrx:tg-ws-proxy" ;; stgws) echo "pkg:tgws /etc/init.d/tgws /usr/sbin/tgws /usr/sbin/stgws /etc/config/tgws /etc/rc.d/*tgws* /var/lib/tgws /var/lib/stgws nft:tgws nft:stgws cronrx:tgws" ;; tailscale) echo "cron:zm-tailscale $ZM_STATE_DIR/latest.tsc pkg:luci-i18n-tailscale-community-ru pkg:luci-app-tailscale-community /etc/init.d/tailscale-settings /etc/rc.d/*tailscale-settings /www/luci-static/resources/view/tailscale.js /usr/share/luci/menu.d/luci-app-tailscale-community.json /usr/share/rpcd/acl.d/luci-app-tailscale-community.json /usr/share/rpcd/ucode/tailscale.uc pkg:tailscale /usr/sbin/tailscale /usr/sbin/tailscaled /etc/init.d/tailscale /etc/rc.d/*tailscale /etc/config/tailscale /etc/config/tailscale-opkg /etc/config/tailscale.apk-new /etc/tailscale /var/lib/tailscale $TSC_NFT $TSC_HOOK $TSC_SYSCTL $TSC_DIR $TSC_UPLOG uci:network.$TSC_NET uci:firewall.$TSC_FW_RULE" ;; @@ -13954,6 +13954,86 @@ _awg_fetch() { } AWG_KMOD_MANUAL="/etc/zm-awg/kmod-manual" +AWG_VENDOR_INIT="/etc/init.d/zm-awg-r3s" +AWG_VENDOR_SHA="1cb1ee6eeb9fd90223e03d2287962ae759e9d82a1195fe3bc1ce9a39712ceff6" +AWG_VENDOR_URL="https://gitea.bvrdo.online/wenil/Zapret-Manager/raw/tag/awg-r3s-3.1.20260906-fw20261013/vendor/awg/r3s-fw20261013/amneziawg.ko" +_awg_vendor_managed() { grep -q ' r3s-fw20261013$' "$AWG_KMOD_MANUAL" 2>/dev/null; } +_awg_vendor_profile() { + [ "$PKG" = apk ] && [ "$(uname -r)" = 6.1.141 ] || return 1 + case "$(_awg_board '@.board_name')" in friendlyelec,nanopi-r3s|friendlyarm,nanopi-r3s) ;; *) return 1 ;; esac + if [ "$(_awg_rel_ver)" = 25.12.5 ] && [ "$(_awg_rel_arch)" = aarch64_generic ] && [ "$(_awg_rel_target)" = rockchip/armv8 ] && + printf '%s\n' \ + '32d9574af89064da96815ac6ae3285e51f75f2bebe93734868611acf43862f6f /lib/modules/6.1.141/configs.ko' \ + '8f5dab3d6c010f2626745ac31967cae0a22f7dd8cd9fc832a31e2d908b02aeb2 /lib/modules/6.1.141/udp_tunnel.ko' | sha256sum -c - >/dev/null 2>&1; then return 0; fi + echo "ОШИБКА: эта сборка FriendlyWrt R3S отличается от образа 13.10.2026 — нужен модуль под её ядро" + return 2 +} +_awg_vendor_current() { + _awg_vendor_managed && [ -x "$AWG_VENDOR_INIT" ] && _pkg_is_installed kmod-amneziawg && + printf '%s\n' "$AWG_VENDOR_SHA /lib/modules/6.1.141/amneziawg.ko" | sha256sum -c - >/dev/null 2>&1 +} +_awg_vendor_service_rm() { + [ ! -x "$AWG_VENDOR_INIT" ] || "$AWG_VENDOR_INIT" disable >/dev/null 2>&1 + rm -f "$AWG_VENDOR_INIT" /etc/rc.d/*zm-awg-r3s +} +_awg_vendor_install() { + local d f dst enabled=0 rc=0 + _awg_vendor_profile || return 1 + mkdir -p "$ST_RUN" || return 1 + d="$(mktemp -d "$ST_RUN/awg-r3s.XXXXXX")" || return 1 + _rb_say "Скачиваем модуль AWG 3.1 для родного ядра FriendlyWrt 6.1.141" + if ! _rb_fetch_pkg "$AWG_VENDOR_URL" "$d/amneziawg.ko" || + ! printf '%s\n' "$AWG_VENDOR_SHA $d/amneziawg.ko" | sha256sum -c -; then + echo "ОШИБКА: модуль из Gitea не скачан или его SHA256 не совпал — установка остановлена" + rm -rf "$d"; return 1 + fi + for f in ko marker init; do + case "$f" in ko) dst=/lib/modules/6.1.141/amneziawg.ko ;; marker) dst="$AWG_KMOD_MANUAL" ;; init) dst="$AWG_VENDOR_INIT" ;; esac + if [ -e "$dst" ]; then cp -p "$dst" "$d/$f.old" || { rm -rf "$d"; return 1; }; fi + done + [ ! -x "$AWG_VENDOR_INIT" ] || { "$AWG_VENDOR_INIT" enabled >/dev/null 2>&1 && enabled=1; } + cat > "$d/init.new" <<'ZM_AWG_R3S_INIT' +#!/bin/sh /etc/rc.common +START=18 +STOP=89 +start() { + [ "$(uname -r)" = 6.1.141 ] || return 1 + printf '%s\n' \ + '32d9574af89064da96815ac6ae3285e51f75f2bebe93734868611acf43862f6f /lib/modules/6.1.141/configs.ko' \ + '8f5dab3d6c010f2626745ac31967cae0a22f7dd8cd9fc832a31e2d908b02aeb2 /lib/modules/6.1.141/udp_tunnel.ko' \ + '1cb1ee6eeb9fd90223e03d2287962ae759e9d82a1195fe3bc1ce9a39712ceff6 /lib/modules/6.1.141/amneziawg.ko' | sha256sum -c - >/dev/null 2>&1 || return 1 + [ ! -d /sys/module/amneziawg ] || return 0 + local f n + for f in poly1305-neon libcurve25519-generic udp_tunnel ip6_udp_tunnel libchacha20poly1305; do + n="$(printf '%s' "$f" | tr '-' '_')" + [ -d "/sys/module/$n" ] || insmod "/lib/modules/6.1.141/$f.ko" || return 1 + done + insmod /lib/modules/6.1.141/amneziawg.ko +} +stop() { return 0; } +ZM_AWG_R3S_INIT + if ! { mkdir -p /lib/modules/6.1.141 "${AWG_KMOD_MANUAL%/*}" && + cp "$d/amneziawg.ko" /lib/modules/6.1.141/amneziawg.ko.zm-new && + mv -f /lib/modules/6.1.141/amneziawg.ko.zm-new /lib/modules/6.1.141/amneziawg.ko && + printf '%s\n' '3.1.20260906 6.1.141 r3s-fw20261013' > "$AWG_KMOD_MANUAL" && + cp "$d/init.new" "$AWG_VENDOR_INIT" && chmod 0755 "$AWG_VENDOR_INIT" && + "$AWG_VENDOR_INIT" enable && + { _pkg_is_installed kmod-amneziawg || _zm_run 120 apk add --force-non-repository --virtual kmod-amneziawg; }; }; then rc=1; fi + if [ "$rc" != 0 ]; then + _awg_vendor_service_rm + for f in ko marker init; do + case "$f" in ko) dst=/lib/modules/6.1.141/amneziawg.ko ;; marker) dst="$AWG_KMOD_MANUAL" ;; init) dst="$AWG_VENDOR_INIT" ;; esac + if [ -e "$d/$f.old" ]; then cp -p "$d/$f.old" "$dst"; else rm -f "$dst"; fi + done + [ "$enabled" != 1 ] || "$AWG_VENDOR_INIT" enable >/dev/null 2>&1 + rm -f /lib/modules/6.1.141/amneziawg.ko.zm-new + echo "ОШИБКА: установка модуля не завершена — прежние файлы восстановлены" + else + _rb_say "Модуль FriendlyWrt установлен; остальная установка AmneziaWG продолжается штатно" + fi + rm -rf "$d" + return "$rc" +} _awg_kmod_pin_bad() { local out AWG_KPIN=""; AWG_KMV="" @@ -13980,6 +14060,7 @@ _awg_kmod_ver() { } _awg_kmod_manual_rm() { [ -f "$AWG_KMOD_MANUAL" ] || return 0 + _awg_vendor_service_rm rm -f "/lib/modules/$(uname -r)/amneziawg.ko" "$AWG_KMOD_MANUAL" } _awg_kmod_manual() { @@ -14021,12 +14102,19 @@ _awg_kmod_manual() { _awg_kmod_load() { local kv ko dir out dm need p free msg + if _awg_vendor_managed; then + "$AWG_VENDOR_INIT" start + _st_awg_loaded && return 0 + echo "ОШИБКА: родное ядро не загрузило модуль AWG — проверьте dmesg; модули официальной OpenWrt не устанавливались" + return 1 + fi _st_awg_loaded && return 0 _rb_say "Загружаем модуль ядра" modprobe amneziawg >/dev/null 2>&1 _st_awg_loaded && return 0 kv="$(uname -r)" - ko="$(find /lib/modules -name 'amneziawg.ko' 2>/dev/null | head -n1)" + ko="/lib/modules/$kv/amneziawg.ko" + [ -f "$ko" ] || ko="$(find /lib/modules -name 'amneziawg.ko' 2>/dev/null | head -n1)" if [ -z "$ko" ]; then echo "ОШИБКА: файла модуля amneziawg.ko нет — пакет kmod-amneziawg встал не полностью" free="$(df -k /overlay 2>/dev/null | awk 'NR==2 {print $4}')" @@ -14068,10 +14156,11 @@ _awg_kmod_load() { _st_install_awg() { local mode="$1" if [ "$mode" != update ] && _st_awg_loaded && command -v awg >/dev/null 2>&1 && _awg_proto_ok; then return 0; fi - local rel arch tgt sub post luci old_luci base bases m p f ok=0 was_loaded=0 kver0 kver1 need="" force="" why="" mrc + local rel arch tgt sub post luci old_luci base bases m p f ok=0 was_loaded=0 kver0 kver1 need="" force="" why="" mrc vendor=0 + if _awg_vendor_profile; then vendor=1; else mrc=$?; [ "$mrc" != 2 ] || return 1; fi _st_awg_loaded && was_loaded=1 kver0="$(_awg_kmod_ver)" - _awg_kmod_have || need=" kmod-amneziawg" + if [ "$vendor" = 1 ]; then _awg_vendor_current || need=" kmod-amneziawg"; else _awg_kmod_have || need=" kmod-amneziawg"; fi [ "$mode" = update ] && need=" kmod-amneziawg" if [ "$mode" = update ] || ! _pkg_is_installed amneziawg-tools || ! command -v awg >/dev/null 2>&1; then need="$need amneziawg-tools"; fi if [ -n "$need" ]; then @@ -14088,6 +14177,13 @@ _st_install_awg() { _awg_legacy_feeds _rb_say "Обновляем список пакетов" $UPDATE || _rb_warn "Список пакетов обновился не полностью — продолжаем" + if [ "$vendor" = 1 ]; then + case " $need " in *' kmod-amneziawg '*) + _awg_vendor_install || return 1 + [ "${ST_AWG_OWN:-1}" != 1 ] || _st_own 'pkg kmod-amneziawg' + need="$(printf '%s\n' "$need" | sed 's/ kmod-amneziawg//')" + ;; esac + fi bases="" for m in $ST_AWG_MIRRORS; do bases="$bases $m/v$rel"; done bases="$bases $ST_AWG_MIRROR_FLAT/$rel" @@ -14111,7 +14207,7 @@ _st_install_awg() { [ "$why" = kver ] || why=pin fi elif $INSTALL $force "$f"; then - [ "$p" = kmod-amneziawg ] && rm -f "$AWG_KMOD_MANUAL" + if [ "$p" = kmod-amneziawg ]; then _awg_vendor_service_rm; rm -f "$AWG_KMOD_MANUAL"; fi [ "${ST_AWG_OWN:-1}" = 1 ] && _st_own "pkg $p" elif [ "$p" != "$luci" ]; then ok=0 @@ -55336,6 +55432,7 @@ for _zm_c in "zms Zapret-Manager.sh" "zmw ZapretManager_LuCI.sh"; do #!/bin/sh u="https://raw.githubusercontent.com/StressOzz/Zapret-Manager/main/${_zm_c#* }" ZM_GET_EOF + [ "${_zm_c%% *}" != zmw ] || printf '%s\n' 'u="https://gitea.bvrdo.online/wenil/Zapret-Manager/raw/branch/main/ZapretManager_LuCI.sh"' >> "/usr/bin/zmw" cat >> "/usr/bin/${_zm_c%% *}" <<'ZM_GET_EOF' t="/tmp/zm-get.$$.sh" get() { rm -f "$t"; if command -v curl >/dev/null 2>&1; then curl -fsSL --connect-timeout 10 --max-time 120 -o "$t" "$1"; else wget -q -O "$t" "$1"; fi && [ -s "$t" ] && ! head -c 512 "$t" | grep -qiE '<(!doctype|html)'; } diff --git a/docs/friendlywrt-awg.md b/docs/friendlywrt-awg.md new file mode 100644 index 0000000..0dd2cb6 --- /dev/null +++ b/docs/friendlywrt-awg.md @@ -0,0 +1,51 @@ +# AmneziaWG 3.1 на NanoPi R3S с FriendlyWrt + +Эта адаптация основана на [StressOzz/Zapret-Manager](https://github.com/StressOzz/Zapret-Manager), commit `46eb923f414f5ac2b5bba1f79519696ea57f3b9a`, версия панели 4.68. Версия адаптации — 4.68.1. Изменена установка AWG в LuCI/Web; остальные компоненты продолжают использовать прежние источники. + +## Поддерживаемый образ + +- NanoPi R3S / R3S LTS, архитектура `aarch64_generic`, цель `rockchip/armv8`. +- `R3S-Series-FriendlyWrt-25.12.img.gz` из выпуска производителя `FriendlyWrt-2026-10-13`. +- Пользовательская часть OpenWrt 25.12.5, реально работающее ядро **6.1.141**. +- SHA256 образа: `3cf37e2b91f80f51dd54d601efc3af8c9f2c69256488b874f9324aa54c34e74a`. + +Пакет `kernel` в базе APK этой прошивки сообщает версию 6.12.94. Официальный `kmod-amneziawg` под 6.12.94 на работающем ядре 6.1.141 не загружается. Адаптация проверяет `uname -r`, модель и SHA256 штатных `configs.ko` и `udp_tunnel.ko`. + +## Установка панели + +Сначала сохранить настройки панели её штатной кнопкой резервного копирования. Затем выполнить по SSH: + +```sh +curl -fL -o /tmp/ZapretManager_LuCI.sh https://gitea.bvrdo.online/wenil/Zapret-Manager/raw/branch/main/ZapretManager_LuCI.sh && +sh /tmp/ZapretManager_LuCI.sh +``` + +После обновления открыть вкладку **AmneziaWG** и нажать установку или переустановку. + +Панель скачает только ядровой модуль из фиксированного тега на Gitea и проверит встроенный SHA256. Утилиты `amneziawg-tools`, протокол LuCI, импорт конфигурации и настройка сети проходят через существующий установщик. Зависимость APK удовлетворяется тем же механизмом виртуального `kmod-amneziawg`, который уже был предусмотрен автором панели для вручную установленного модуля. + +При ошибке загрузки, несовпадении SHA256 или отпечатков прошивки установка прекращается. Перехода к модулю официального ядра 6.12.94 для этого профиля нет. Действующая сборка модуля закреплена: обновление утилит через панель не заменяет её случайным новым kmod. + +## Автозагрузка, удаление и обновление + +Создаётся `/etc/init.d/zm-awg-r3s`, который до запуска сети проверяет ядро и отпечатки файлов, затем загружает штатные зависимости и AWG. Он не меняет маршруты, ключи и firewall. Признак ручной установки хранится в существующем `/etc/zm-awg/kmod-manual`. + +Удаление AmneziaWG через панель удаляет также этот init-скрипт. При неудачной записи файлов или регистрации виртуального пакета прежние файлы восстанавливаются. Штатная резервная копия панели сохраняет настройки, а не всю прошивку или бинарный модуль; при восстановлении установщик должен заново получить подходящий модуль. + +Обновления самой панели и команда `zmw` получают адаптированную версию с Gitea, чтобы не потерять эту поддержку при обычном обновлении. Автоматическая синхронизация с GitHub автора не настроена: новые версии автора нужно переносить с сохранением изменений AWG. Старый SSH-менеджер `zms` остаётся исходной версией автора; эта адаптация относится к LuCI/Web. + +После смены прошивки нужна новая проверка совместимости. Установщик не выбирает модуль только по номеру ядра. + +## Проверки и ограничения + +Модуль AWG `3.1.20260906` собран из официального тега с поправкой совместимости `timer_delete` для vendor-ядра. Совпали vermagic и CRC всех 198 импортируемых символов; конфигурация извлечена из точного образа. Исходники для пересборки, конфигурация, таблица CRC и подробности находятся в `vendor/awg/r3s-fw20261013/`. + +На настоящем R3S загрузка, handshake, скорость и температура пока не проверены. Использован GCC 13.3 вместо GCC 11.3 производителя; точное совпадение commit исходников ядра с commit сборки производителя не подтверждено. Проверки CRC не заменяют испытание на устройстве. Первый тест проводить с возможностью локального восстановления роутера. + +Тесты установщика выполняются без роутера и не загружают ядровые модули: + +```sh +python3 tests/check_friendlywrt_awg.py +``` + +Они проверяют установку, повторное обновление закреплённого модуля, удаление, порядок загрузки зависимостей, отказ при повреждённой загрузке или другой прошивке, откат при ошибке APK и сохранение обычного пути для официального OpenWrt. Также проверяется синтаксис сгенерированного backend. diff --git a/readme.md b/readme.md index ffa2b02..abec215 100644 --- a/readme.md +++ b/readme.md @@ -1,3 +1,5 @@ +> **Адаптация FriendlyWrt для NanoPi R3S / R3S LTS:** установка AWG 3.1 под родное ядро 6.1.141 через штатную вкладку LuCI/Web. [Поддерживаемый образ, установка и ограничения](docs/friendlywrt-awg.md). Автор исходного Zapret Manager — [StressOzz](https://github.com/StressOzz/Zapret-Manager). Модуль пока не испытан на настоящем устройстве. +
# Zapret Manager diff --git a/tests/check_friendlywrt_awg.py b/tests/check_friendlywrt_awg.py new file mode 100644 index 0000000..1ec35b3 --- /dev/null +++ b/tests/check_friendlywrt_awg.py @@ -0,0 +1,156 @@ +"""Run the real AWG installer functions against a disposable filesystem and fake commands.""" +from pathlib import Path +import os +import shutil +import subprocess +import tempfile + +repo = Path(__file__).resolve().parents[1] +source = (repo / 'ZapretManager_LuCI.sh').read_text(encoding='utf-8') +functions = source[source.index('_st_awg_loaded()'):source.index('_st_warp_field()')] +bash = os.environ.get('TEST_SHELL', 'bash') + +def posix(path): + value = path.as_posix() + return '/' + value[0].lower() + value[2:] if len(value) > 1 and value[1] == ':' else value + +mocks = r''' +uname() { echo "${TEST_KERNEL:-6.1.141}"; } +_awg_board() { echo "${TEST_BOARD:-friendlyelec,nanopi-r3s}"; } +_awg_rel_ver() { echo "${TEST_RELEASE:-25.12.5}"; } +_awg_rel_arch() { echo aarch64_generic; } +_awg_rel_target() { echo rockchip/armv8; } +sha256sum() { + local expected path actual + if [ "$1 $2" != '-c -' ]; then command sha256sum "$@"; return; fi + while read -r expected path; do + case "$path" in */configs.ko|*/udp_tunnel.ko) [ "${TEST_FIRMWARE:-ok}" = ok ] || return 1 ;; + *) actual=$(command sha256sum "$path") || return 1; [ "${actual%% *}" = "$expected" ] || return 1 ;; + esac + done +} +_rb_say() { echo "$*"; } +_rb_warn() { echo "$*"; } +_rb_fetch_pkg() { + echo "vendor $1" >> "$TEST_ROOT/downloads" + [ "${TEST_DOWNLOAD:-ok}" != failed ] || return 1 + if [ "${TEST_DOWNLOAD:-ok}" = corrupt ]; then echo invalid > "$2"; else cp "$TEST_MODULE" "$2"; fi +} +_pkg_is_installed() { [ -f "$TEST_ROOT/pkg/$1" ]; } +_awg_pkg_ver() { [ ! -s "$AWG_KMOD_MANUAL" ] || awk '{print $1; exit}' "$AWG_KMOD_MANUAL"; } +_awg_fetch() { echo "stock $2" >> "$TEST_ROOT/downloads"; echo package > "$ST_RUN/$2.apk"; echo "$ST_RUN/$2.apk"; } +_awg_legacy_feeds() { :; } +_awg_kmod_pin_bad() { return 1; } +_awg_proto_ok() { return 0; } +_rb_rpcd_ensure() { :; } +_st_own() { echo "$*" >> "$TEST_ROOT/owned"; } +test_update() { :; } +test_install() { + local name + name=${1##*/}; name=${name%.apk} + touch "$TEST_ROOT/pkg/$name" +} +test_delete() { rm -f "$TEST_ROOT/pkg/$1"; } +_zm_run() { shift; "$@"; } +apk() { + [ "${TEST_APK:-ok}" = ok ] || return 1 + touch "$TEST_ROOT/pkg/kmod-amneziawg" +} +modprobe() { mkdir -p "$TEST_ROOT/sys/module/amneziawg"; } +insmod() { + [ -f "$1" ] || return 1 + echo "${1##*/}" >> "$TEST_ROOT/loads" + [ "${1##*/}" != amneziawg.ko ] || mkdir -p "$TEST_ROOT/sys/module/amneziawg" +} +rmmod() { return 1; } +awg() { :; } +_test_init() { + case "$1" in + enabled) [ -f "$TEST_ROOT/enabled" ] ;; + enable) touch "$TEST_ROOT/enabled" ;; + disable) rm -f "$TEST_ROOT/enabled" ;; + start) ( . "$AWG_VENDOR_INIT"; start ) ;; + esac +} +PKG=apk; INSTALL=test_install; DELETE=test_delete; UPDATE=test_update; RAZ=apk +ST_RUN="$TEST_ROOT/run"; ST_AWG_MIRRORS=https://stock.invalid; ST_AWG_MIRROR_FLAT=https://stock.invalid +''' + +def run_case(name, body, **overrides): + temporary = repo.parent / 'zm-awg-tests' + temporary.mkdir(exist_ok=True) + with tempfile.TemporaryDirectory(prefix='case-', dir=temporary) as folder: + root = Path(folder) + for directory in ['pkg', 'run', 'etc/init.d', 'etc/rc.d', 'etc/zm-awg', 'sys/module', 'proc', 'lib/modules/6.1.141']: + (root / directory).mkdir(parents=True, exist_ok=True) + (root / 'proc/modules').touch() + for filename in ['configs', 'udp_tunnel', 'poly1305-neon', 'libcurve25519-generic', 'ip6_udp_tunnel', 'libchacha20poly1305']: + (root / f'lib/modules/6.1.141/{filename}.ko').touch() + mapped = functions + for prefix in ['/etc/', '/lib/', '/sys/', '/proc/']: + mapped = mapped.replace(prefix, posix(root) + prefix) + mapped = mapped.replace('"$AWG_VENDOR_INIT" enable', '_test_init enable').replace('"$AWG_VENDOR_INIT" enabled', '_test_init enabled') + mapped = mapped.replace('"$AWG_VENDOR_INIT" disable', '_test_init disable').replace('"$AWG_VENDOR_INIT" start', '_test_init start') + env = dict(os.environ, TEST_ROOT=posix(root), TEST_MODULE=posix(repo / 'vendor/awg/r3s-fw20261013/amneziawg.ko'), **overrides) + script = mapped + '\n' + mocks + '\nset -e\n' + body + result = subprocess.run([bash, '-s'], input=script.encode('utf-8'), env=env, capture_output=True) + assert result.returncode == 0, f'{name}:\n{result.stdout.decode("utf-8", errors="replace")}\n{result.stderr.decode("utf-8", errors="replace")}' + print(f'PASS {name}') + +run_case('native install, reboot loader and pinned reinstall', r''' +_st_install_awg install +_awg_vendor_current +[ "$(cat "$TEST_ROOT/loads" | tr '\n' ' ')" = 'poly1305-neon.ko libcurve25519-generic.ko udp_tunnel.ko ip6_udp_tunnel.ko libchacha20poly1305.ko amneziawg.ko ' ] +[ "$(grep -c '^stock kmod' "$TEST_ROOT/downloads")" = 0 ] +[ -f "$TEST_ROOT/pkg/amneziawg-tools" ] && [ -f "$TEST_ROOT/pkg/luci-proto-amneziawg" ] +_st_install_awg update +_awg_vendor_current +[ "$(grep -c '^vendor ' "$TEST_ROOT/downloads")" = 2 ] +before=$(wc -l < "$TEST_ROOT/loads") +TEST_KERNEL=6.12.94 +if _test_init start; then exit 1; fi +[ "$(wc -l < "$TEST_ROOT/loads")" = "$before" ] +TEST_KERNEL=6.1.141 +_awg_kmod_manual_rm +[ ! -f "$AWG_KMOD_MANUAL" ] && [ ! -e "$AWG_VENDOR_INIT" ] && [ ! -f "$TEST_ROOT/enabled" ] +''') + +for state in ['failed', 'corrupt']: + run_case(f'{state} download fails without stock fallback', r''' +if _st_install_awg install; then exit 1; fi +[ ! -f "$TEST_ROOT/lib/modules/6.1.141/amneziawg.ko" ] +[ ! -f "$AWG_KMOD_MANUAL" ] && [ ! -e "$AWG_VENDOR_INIT" ] +[ "$(grep -c '^stock ' "$TEST_ROOT/downloads")" = 0 ] +''', TEST_DOWNLOAD=state) + +run_case('different firmware rejected before download', r''' +if _st_install_awg install; then exit 1; fi +[ ! -f "$TEST_ROOT/downloads" ] +''', TEST_FIRMWARE='changed') + +run_case('APK failure restores old module and metadata', r''' +echo previous > "$TEST_ROOT/lib/modules/6.1.141/amneziawg.ko" +echo old-marker > "$AWG_KMOD_MANUAL" +printf '#!/bin/sh\n# old-init\n' > "$AWG_VENDOR_INIT"; chmod +x "$AWG_VENDOR_INIT"; touch "$TEST_ROOT/enabled" +if _awg_vendor_install; then exit 1; fi +[ "$(cat "$TEST_ROOT/lib/modules/6.1.141/amneziawg.ko")" = previous ] +[ "$(cat "$AWG_KMOD_MANUAL")" = old-marker ] +[ "$(tail -n 1 "$AWG_VENDOR_INIT")" = '# old-init' ] && [ -f "$TEST_ROOT/enabled" ] +''', TEST_APK='failed') + +run_case('official firmware keeps upstream package path', r''' +_st_install_awg install +[ "$(grep -c '^vendor ' "$TEST_ROOT/downloads")" = 0 ] +[ "$(grep -c '^stock kmod-amneziawg' "$TEST_ROOT/downloads")" = 1 ] +[ ! -e "$AWG_VENDOR_INIT" ] +''', TEST_KERNEL='6.12.94') + +backend = source.split("<< 'ZM_INSTALLER_EOF'\n", 1)[1].split('\nZM_INSTALLER_EOF\n', 1)[0] +with tempfile.NamedTemporaryFile(suffix='.sh', mode='w', encoding='utf-8', delete=False, dir=repo.parent / 'zm-awg-tests') as stream: + stream.write(backend) + script_path = Path(stream.name) +try: + subprocess.run([bash, '-n', str(script_path)], check=True) +finally: + script_path.unlink() +print('PASS generated backend shell syntax') diff --git a/vendor/awg/r3s-fw20261013/amneziawg.ko b/vendor/awg/r3s-fw20261013/amneziawg.ko new file mode 100644 index 0000000..96dc8f1 Binary files /dev/null and b/vendor/awg/r3s-fw20261013/amneziawg.ko differ diff --git a/vendor/awg/r3s-fw20261013/build-info.json b/vendor/awg/r3s-fw20261013/build-info.json new file mode 100644 index 0000000..1a45ba6 --- /dev/null +++ b/vendor/awg/r3s-fw20261013/build-info.json @@ -0,0 +1,27 @@ +{ + "firmware": "FriendlyWrt-2026-10-13 / R3S-Series-FriendlyWrt-25.12.img.gz", + "firmware_sha256": "3cf37e2b91f80f51dd54d601efc3af8c9f2c69256488b874f9324aa54c34e74a", + "kernel": "6.1.141", + "kernel_source_commit": "30d3e1c2470a7282f37b96fdd4354314dda69aa3", + "awg_source_tag": "v3.1.20260906", + "awg_source_commit": "4569c4c67f3a57414969260cafbbd04694fbaae0", + "module_version": "3.1.20260906", + "vermagic": "6.1.141 SMP mod_unload modversions aarch64", + "dependencies": [ + "libcurve25519_generic", + "udp_tunnel", + "ip6_udp_tunnel", + "libchacha20poly1305" + ], + "dependency_files": { + "libcurve25519_generic": "libcurve25519-generic.ko", + "udp_tunnel": "udp_tunnel.ko", + "ip6_udp_tunnel": "ip6_udp_tunnel.ko", + "libchacha20poly1305": "libchacha20poly1305.ko" + }, + "matched_imported_symbol_crcs": 198, + "runtime_tested": false, + "build_compiler": "aarch64-linux-gnu-gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0", + "firmware_compiler": "aarch64-linux-gnu-gcc (ctng-1.25.0-119g-FA) 11.3.0", + "limitation": "Config and symbol CRCs come from the exact firmware. Kernel sources use the vendor branch commit; its identity with the firmware build commit is not proven. Loading and VPN operation must be tested on the device." +} diff --git a/vendor/awg/r3s-fw20261013/source-kit.tar.gz b/vendor/awg/r3s-fw20261013/source-kit.tar.gz new file mode 100644 index 0000000..9cffbfe Binary files /dev/null and b/vendor/awg/r3s-fw20261013/source-kit.tar.gz differ