Версия 0.5.3
CI / Линт и типы (push) Canceled after 0s
CI / Сборка (Windows) (push) Canceled after 0s
CI / Сборка (Linux) (push) Canceled after 0s

Добавлена сборка под Linux: nfqws + правила nftables (NFQUEUE) вместо
socks-прокси или службы Windows. Повышение прав — pkexec на каждое
включение/выключение, без службы и без автозапуска.

Стратегии переиспользуют существующий набор Flowseal (strategies.win32.generated.ts):
--wf-tcp/--wf-udp уходят в правила nftables, остальное — в nfqws как есть.
Все правила nftables держат bypass, поэтому упавший или выключенный демон
не рвёт пользователю сеть.

Помощник (resources/linux-helper/rknboost-helper.sh) и бинарник nfqws перед
каждым запуском копируются в userData: root не может прочитать файлы внутри
AppImage (FUSE-примонтирован без allow_other), а обычная файловая система
на диске ему доступна независимо от прав.

Заодно:
- починен баг генератора стратегий (^! не разэкранировался в !, ломало
  fake-tls-auto и на Windows тоже);
- карточка обхода теперь показывает текст ошибки независимо от state;
- счётчик auto-hostlist в настройках больше не привязан к !isWindows;
- CI: отдельная джоба сборки под Linux на ubuntu-latest.
This commit is contained in:
2026-09-04 10:26:00 +03:00
parent 0842c4d4a5
commit 2df1c00516
20 changed files with 956 additions and 78 deletions
+174
View File
@@ -0,0 +1,174 @@
#!/bin/sh
# Привилегированный помощник обхода блокировок на Linux — единственное, что RKNboost
# запускает через `pkexec`. Не редактировать под bash-специфику: /bin/sh на Debian/Ubuntu —
# это dash, а не bash.
#
# Зовётся из src/main/zapret/nfqws.linux.ts ровно двумя способами:
# pkexec <этот файл> start <nfqws> <strategy.cfg> <tcp-порты> <udp-порты>
# pkexec <этот файл> stop
#
# Всё, что можно решить без root (выбор стратегии, генерация strategy.cfg, проверка
# `nfqws --dry-run`), уже сделано на стороне приложения — сюда попадают только готовые пути
# и уже провалидированные строки портов (см. nfqwsConfig() в strategies.ts: там же формат
# портов проверен регуляркой, здесь ему просто доверяем). Раздельные пути/аргументы вместо
# одной строки — чтобы этот скрипт не собирал команду nft из чего-то похожего на shell-код.
#
# Коды выхода — намеренно 3..6, чтобы не путаться с 126/127 самого pkexec (отказ в правах /
# команда не найдена) при разборе результата на стороне приложения.
set -eu
RUN_DIR=/run/rknboost
PIDFILE="$RUN_DIR/nfqws.pid"
CONFIG="$RUN_DIR/strategy.cfg"
ERRLOG="$RUN_DIR/nfqws.err"
TABLE_FAMILY=inet
TABLE_NAME=rknboost
QUEUE_NUM=220
FW_MARK=0x40000000
log() {
echo "rknboost-helper: $*" >&2
}
# pkexec запускает с урезанным PATH (санирует окружение) — sbin, где обычно лежит nft,
# в нём может не быть. Ищем явно по стандартным местам, а не полагаемся на PATH.
find_nft() {
for candidate in /usr/sbin/nft /sbin/nft /usr/local/sbin/nft /usr/bin/nft; do
if [ -x "$candidate" ]; then
echo "$candidate"
return 0
fi
done
if command -v nft >/dev/null 2>&1; then
command -v nft
return 0
fi
return 1
}
# Сносит и таблицу, и демона. Идёт первым шагом `start` (самолечение: следующий успешный
# запуск убирает любой мусор от аварийно прерванной прошлой сессии) и отдельной командой
# `stop`. Всегда завершается успешно — «уже остановлено» не ошибка, тот же принцип, что и
# у stopService() на Windows.
cmd_stop() {
nft="${1:-}"
if [ -f "$PIDFILE" ]; then
pid="$(cat "$PIDFILE" 2>/dev/null || true)"
# /proc/<pid>/comm — это ровно "nfqws\n" (обрезано до 15 символов ядром, но nfqws
# короче), сверяем, чтобы не убить чужой процесс, переиспользовавший тот же pid.
if [ -n "$pid" ] && [ -r "/proc/$pid/comm" ] && [ "$(cat "/proc/$pid/comm" 2>/dev/null)" = "nfqws" ]; then
kill -TERM "$pid" 2>/dev/null || true
i=0
while [ -d "/proc/$pid" ] && [ "$i" -lt 30 ]; do
i=$((i + 1))
sleep 0.1
done
if [ -d "/proc/$pid" ]; then
kill -KILL "$pid" 2>/dev/null || true
fi
fi
rm -f "$PIDFILE"
fi
rm -f "$CONFIG" "$ERRLOG"
if [ -z "$nft" ]; then
nft="$(find_nft || true)"
fi
if [ -n "$nft" ] && "$nft" list table "$TABLE_FAMILY" "$TABLE_NAME" >/dev/null 2>&1; then
"$nft" delete table "$TABLE_FAMILY" "$TABLE_NAME" 2>/dev/null || true
fi
}
cmd_start() {
nfqws_bin="$1"
config_src="$2"
tcp_ports="$3"
udp_ports="$4"
nft="$(find_nft || true)"
if [ -z "$nft" ]; then
log "не найдена утилита nft (пакет nftables)"
exit 3
fi
# На всякий случай предупреждаем, если параллельно уже висит другой zapret со своей
# таблицей/очередью — двойной приём одного трафика в две очереди работать не будет,
# но это не наша таблица, сносить её самовольно нельзя.
if "$nft" list table inet zapret >/dev/null 2>&1 || "$nft" list table inet zapretunix >/dev/null 2>&1; then
log "внимание: обнаружена таблица nftables другого zapret-инструмента — возможен конфликт очередей"
fi
cmd_stop "$nft"
mkdir -p "$RUN_DIR"
chmod 0755 "$RUN_DIR"
# strategy.cfg в /run — источник маркера стратегии для LinuxEngine.sync(): читается без
# root (0644), первая строка — тот же --comment=rknboost:<id>:<hash>, что и на Windows.
cp "$config_src" "$CONFIG"
chmod 0644 "$CONFIG"
if ! "$nft" add table "$TABLE_FAMILY" "$TABLE_NAME"; then
log "не удалось создать таблицу nftables"
cmd_stop "$nft"
exit 4
fi
"$nft" add chain "$TABLE_FAMILY" "$TABLE_NAME" post '{ type filter hook postrouting priority mangle; }'
"$nft" add chain "$TABLE_FAMILY" "$TABLE_NAME" pre '{ type filter hook prerouting priority filter; }'
# bypass обязателен: без него при завершении/падении nfqws весь трафик на эти порты
# молча дропался бы. С bypass ядро принимает пакет, если очередь никто не слушает —
# то есть отключение обхода (или его крах) не рвёт пользователю сеть.
ok=1
"$nft" add rule "$TABLE_FAMILY" "$TABLE_NAME" post \
meta mark and $FW_MARK == 0 tcp dport "{$tcp_ports}" \
ct original packets 1-6 queue num $QUEUE_NUM bypass || ok=0
"$nft" add rule "$TABLE_FAMILY" "$TABLE_NAME" post \
meta mark and $FW_MARK == 0 udp dport "{$udp_ports}" \
ct original packets 1-6 queue num $QUEUE_NUM bypass || ok=0
"$nft" add rule "$TABLE_FAMILY" "$TABLE_NAME" pre \
tcp sport "{$tcp_ports}" \
ct reply packets 1-3 queue num $QUEUE_NUM bypass || ok=0
if [ "$ok" -ne 1 ]; then
log "не удалось создать правила nftables"
cmd_stop "$nft"
exit 4
fi
# nfq_init() у nfqws происходит до daemonize() — если очередь занята, модуля нет в ядре
# или не хватает прав, процесс завершится с ненулевым кодом ДО демонизации, и мы это
# увидим здесь же, без опроса готовности.
if ! "$nfqws_bin" --daemon \
--pidfile="$PIDFILE" \
--qnum=$QUEUE_NUM \
--dpi-desync-fwmark=$FW_MARK \
"@$CONFIG" 2>"$ERRLOG"; then
log "nfqws не запустился: $(cat "$ERRLOG" 2>/dev/null || true)"
cmd_stop "$nft"
exit 5
fi
exit 0
}
action="${1:-}"
case "$action" in
start)
shift
if [ "$#" -ne 4 ]; then
log "start ожидает 4 аргумента: <nfqws> <config> <tcp-порты> <udp-порты>"
exit 6
fi
cmd_start "$1" "$2" "$3" "$4"
;;
stop)
cmd_stop ""
;;
*)
log "неизвестная команда: $action (ожидается start|stop)"
exit 6
;;
esac