vendor rknblock scripts into the panel repo (scripts/rknblock/)

- contour scripts + systemd units live in scripts/rknblock/ now
- install-rkn-extra.sh fetches them straight from PRVTPRO raw URLs
  (no external repo, no git-clone dependency on hosts)
- app.py: RKN_RAW_BASE points at this repo's raw main
This commit is contained in:
leonidorlov-hash committed 2026-10-20 00:55:51 +03:00
1 parent a1a6a70426
commit ff50dbec47
8 files changed
+662 -3

No files matched your search

+5 -3
View File
@@ -3476,13 +3476,15 @@ def api_server_stats(request: Request, server_id: int):
return JSONResponse({'error': str(e)}, status_code=500)
RKN_RAW_BASE = ('https://raw.githubusercontent.com/PRVTPRO/Amnezia-Web-Panel/'
'main/scripts/rknblock')
RKN_INSTALL_CMD = (
'curl -fsSL https://raw.githubusercontent.com/leonidorlov-hash/amnezia-blocker/main/install-rkn-extra.sh '
f'curl -fsSL {RKN_RAW_BASE}/install-rkn-extra.sh '
'-o /tmp/rkn-install.sh && bash /tmp/rkn-install.sh'
)
RKN_SCRIPT_URL = ('https://raw.githubusercontent.com/leonidorlov-hash/amnezia-blocker/'
'main/rkn-extra-block.sh')
RKN_SCRIPT_URL = f'{RKN_RAW_BASE}/rkn-extra-block.sh'
def _rkn_run_cmd(subcommand):
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Установка rkn-extra-block (Debian/Ubuntu, root).
# Скачивает скрипты из репозитория панели и ставит их + systemd-юниты.
set -euo pipefail
RAW="https://raw.githubusercontent.com/PRVTPRO/Amnezia-Web-Panel/main/scripts/rknblock"
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
apt-get update -qq
apt-get install -y -qq ipset iptables curl util-linux >/dev/null
command -v flock >/dev/null || { echo "flock not found (util-linux)" >&2; exit 1; }
mkdir -p "$TMP/repo"
for f in rkn-extra-block.sh rkn-extra-block.service rkn-extra-block.timer \
rkn-agent.sh rkn-agent.service rkn-agent.timer; do
curl -fsSL "$RAW/$f" -o "$TMP/repo/$f"
done
install -m 0755 "$TMP/repo/rkn-extra-block.sh" /usr/local/sbin/rkn-extra-block
install -m 0755 "$TMP/repo/rkn-agent.sh" /usr/local/sbin/rkn-agent-collect
install -m 0644 "$TMP/repo/rkn-extra-block.service" /etc/systemd/system/rkn-extra-block.service
install -m 0644 "$TMP/repo/rkn-extra-block.timer" /etc/systemd/system/rkn-extra-block.timer
install -m 0644 "$TMP/repo/rkn-agent.service" /etc/systemd/system/rkn-agent.service
install -m 0644 "$TMP/repo/rkn-agent.timer" /etc/systemd/system/rkn-agent.timer
mkdir -p /etc/rkn-extra-block
[ -f /etc/rkn-extra-block/config ] || cat > /etc/rkn-extra-block/config <<'EOF'
# Источники списков C24Be/AS_Network_List (менять не обязательно)
URL_IN4="https://raw.githubusercontent.com/C24Be/AS_Network_List/main/blacklists_iptables/blacklist-v4.ipset"
URL_IN6="https://raw.githubusercontent.com/C24Be/AS_Network_List/main/blacklists_iptables/blacklist-v6.ipset"
URL_OUT4="https://raw.githubusercontent.com/C24Be/AS_Network_List/main/blacklists_iptables/blacklist-vk-v4.ipset"
URL_OUT6="https://raw.githubusercontent.com/C24Be/AS_Network_List/main/blacklists_iptables/blacklist-vk-v6.ipset"
EOF
systemctl daemon-reload
systemctl enable --now rkn-extra-block.timer
systemctl enable rkn-extra-block.service
systemctl enable --now rkn-agent.timer
# Первый запуск: подгрузить списки (включая nets.map для агента) и включить
/usr/local/sbin/rkn-extra-block on
# Статистике нужны LOG-правила: включаем журналирование (пересобирает цепочки)
/usr/local/sbin/rkn-extra-block log on || true
touch /var/log/rkn-scans.json
echo "Готово. Управление: rkn-extra-block {on|off|status|update} | log on|off | scan"
+6
View File
@@ -0,0 +1,6 @@
[Unit]
Description=RKN scan-stats collector (hourly aggregation of rkn-extra-block LOG hits)
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/rkn-agent-collect
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# rkn-agent — почасовой сборщик статистики срабатываний rkn-extra-block.
# Читает journalctl -k за последний час, матчит SRC-IP против /etc/rkn-extra-block/nets.map
# (сеть → ведомство), пишет компактные JSON-строки в /var/log/rkn-scans.json
# и подрезает файл до последних 5000 строк. Без зависимостей кроме journalctl/awk.
# Работает только если включено журналирование: rkn-extra-block log on.
set -u
MAP_FILE="/etc/rkn-extra-block/nets.map"
OUT_FILE="/var/log/rkn-scans.json"
SINCE_FILE="/etc/rkn-extra-block/scanlog.since"
MAX_LINES=5000
LOCK_FILE="/run/rkn-agent.lock"
exec 9>"$LOCK_FILE" || exit 1
flock -n 9 || exit 0
[ -f "$MAP_FILE" ] || exit 0 # списки ещё не подгружались
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
since="-65 minutes"
[ -f "$SINCE_FILE" ] && since="$(cat "$SINCE_FILE")"
journalctl -k --no-pager --since "$since" 2>/dev/null | grep "RKN_EXTRA" | \
MAP_FILE="$MAP_FILE" TS="$ts" awk '
function pad(o) { return sprintf("%03d.%03d.%03d.%03d", o[1], o[2], o[3], o[4]) }
BEGIN {
FS = "\t"
while ((getline line < ENVIRON["MAP_FILE"]) > 0) {
n = split(line, f, "\t")
if (n >= 3) { cnt++; first[cnt] = f[1]; last[cnt] = f[2]; label[cnt] = f[3] }
}
close(ENVIRON["MAP_FILE"])
}
function lookup(ip, q, i) {
split(ip, o, ".")
q = pad(o)
for (i = 1; i <= cnt; i++)
if (q >= first[i] && q <= last[i]) return label[i]
return ""
}
/RKN_EXTRA/ {
dir = "in"
if ($0 ~ /RKN_EXTRA_OUT/) dir = "out"
if ($0 ~ /RKN_EXTRA_IN6/ || $0 ~ /RKN_EXTRA_OUT6/) dir = dir "6"
src = ""; dpt = 0
if (match($0, /SRC=[0-9a-fA-F:.]+/)) src = substr($0, RSTART + 4, RLENGTH - 4)
if (match($0, /DPT=[0-9]+/)) dpt = substr($0, RSTART + 4, RLENGTH - 4) + 0
if (src == "") next
org = (src ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ ? lookup(src) : "")
if (org == "") org = (dir ~ /6$/ ? "C24Be v6" : "C24Be")
gsub(/["\\]/, "", org)
printf "{\"t\":\"%s\",\"dir\":\"%s\",\"ip\":\"%s\",\"dpt\":%d,\"org\":\"%s\"}\n", ENVIRON["TS"], dir, src, dpt, org
}
' >> "$OUT_FILE"
# Подрезка, чтобы лог не рос бесконечно
lines=$(wc -l < "$OUT_FILE")
if [ "$lines" -gt $MAX_LINES ]; then
tail -n $MAX_LINES "$OUT_FILE" > "$OUT_FILE.tmp"
mv "$OUT_FILE.tmp" "$OUT_FILE"
fi
# Горизонт следующего сбора — от этого момента (старое из journalctl не тянем)
echo "$ts" > "$SINCE_FILE"
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=Hourly RKN scan-stats collection
[Timer]
OnCalendar=hourly
RandomizedDelaySec=15min
Persistent=true
Unit=rkn-agent.service
[Install]
WantedBy=timers.target
+8
View File
@@ -0,0 +1,8 @@
[Unit]
Description=RKN extra block (C24Be lists): boot restore + daily list update
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/rkn-extra-block boot
+506
View File
@@ -0,0 +1,506 @@
#!/usr/bin/env bash
# rkn-extra-block — дополнительная блокировка по спискам C24Be/AS_Network_List.
# Два независимых от amnezia-blocker контуры:
# IN (RKN_EXTRA_IN): подсети РКН/гос-структур — ВХОДЯЩИЕ соединения (INPUT, DROP NEW)
# OUT (RKN_EXTRA_OUT): сети VK/Max/OK — ИСХОДЯЩИЕ (OUTPUT + FORWARD, REJECT tcp-reset)
# Списки обновляются ежедневно (systemd timer), заливка в ipset атомарная (temp -> swap).
#
# Команды: on | off | status | update | boot | set флаг=on|off... | log on|off|status | scan [файлы...]
# on — включить (подгрузить списки если пустые, повесить прыжки в цепочки)
# off — выключить (снять прыжки; наборы ipset сохраняются для мгновенного on)
# status — состояние, размеры наборов, флаги, дата обновления баз, наличие правил
# update — скачать свежие списки и атомарно перезалить наборы (пишет last_update)
# boot — вызывается systemd при старте (update + восстановить прыжки если state=on)
# set — раздельные флаги block_in|block_out|log_in|log_out = on|off (по умолчанию on)
# log on — журналировать срабатывания (journalctl -k -g RKN_EXTRA), по умолчанию ВЫКЛ
# scan — ретроспектива: найти IP из списков в логах (по умолчанию nginx/auth)
set -u
CONF_DIR="/etc/rkn-extra-block"
STATE_FILE="$CONF_DIR/state"
LOG_STATE_FILE="$CONF_DIR/log_state"
LOG_FILE="/var/log/rkn-extra-block.log"
LOCK_FILE="/run/rkn-extra-block.lock"
CONFIG_FILE="$CONF_DIR/config"
IN_CHAIN="RKN_EXTRA_IN"
OUT_CHAIN="RKN_EXTRA_OUT"
IN_SET4="rkn_extra_in4"
IN_SET6="rkn_extra_in6"
OUT_SET4="rkn_extra_out4"
OUT_SET6="rkn_extra_out6"
# Максимальные размеры с запасом (сейчас: in4 ~1151, out4 ~204 сетей)
MAXELEM=65536
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG_FILE"; }
die() { echo "ERROR: $*" >&2; log "ERROR: $*"; exit 1; }
lock() {
exec 9>"$LOCK_FILE" || die "не удалось открыть $LOCK_FILE"
flock -n 9 || die "другой экземпляр rkn-extra-block уже работает"
}
get_state() { [ -f "$STATE_FILE" ] && cat "$STATE_FILE" || echo "off"; }
set_state() { echo "$1" > "$STATE_FILE"; }
# --- флаги направлений (block_in/block_out/log_in/log_out) -------------------
# Файл-флаг: $CONF_DIR/<flag>, содержимое on|off. Отсутствие файла = on.
# log-флаги наследуют старый лог-мастер LOG_STATE_FILE, пока свои файлы не созданы.
block_flag() { [ ! -f "$CONF_DIR/$1" ] || [ "$(cat "$CONF_DIR/$1" 2>/dev/null)" = "on" ]; }
log_flag() {
if [ -f "$CONF_DIR/$1" ]; then
[ "$(cat "$CONF_DIR/$1" 2>/dev/null)" = "on" ]; return
fi
[ ! -f "$LOG_STATE_FILE" ] && return 0
[ "$(cat "$LOG_STATE_FILE" 2>/dev/null)" = "on" ]
}
flag_val() {
if [ -f "$CONF_DIR/$1" ]; then cat "$CONF_DIR/$1" 2>/dev/null; return; fi
case "$1" in
log_in|log_out) [ -f "$LOG_STATE_FILE" ] && { cat "$LOG_STATE_FILE" 2>/dev/null; return; } ;;
esac
echo "on"
}
flags_line() { echo "flags: block_in=$(flag_val block_in) block_out=$(flag_val block_out) log_in=$(flag_val log_in) log_out=$(flag_val log_out)"; }
load_config() {
# Значения по умолчанию; переопределяются /etc/rkn-extra-block/config
URL_IN4="https://raw.githubusercontent.com/C24Be/AS_Network_List/main/blacklists_iptables/blacklist-v4.ipset"
URL_IN6="https://raw.githubusercontent.com/C24Be/AS_Network_List/main/blacklists_iptables/blacklist-v6.ipset"
URL_OUT4="https://raw.githubusercontent.com/C24Be/AS_Network_List/main/blacklists_iptables/blacklist-vk-v4.ipset"
URL_OUT6="https://raw.githubusercontent.com/C24Be/AS_Network_List/main/blacklists_iptables/blacklist-vk-v6.ipset"
URL_MAP="https://raw.githubusercontent.com/C24Be/AS_Network_List/main/blacklists/blacklist_with_comments.txt"
[ -f "$CONFIG_FILE" ] && . "$CONFIG_FILE"
}
have6() { command -v ip6tables &>/dev/null && [ -e /proc/net/if_inet6 ]; }
ensure_deps() {
for d in ipset iptables curl flock; do
command -v "$d" &>/dev/null || die "нет зависимости: $d (apt install ipset iptables curl)"
done
}
# --- ipset helpers -----------------------------------------------------------
create_set() { # $1=name $2=family(inet|inet6)
# ВАЖНО: family и inet — ДВА отдельных аргумента; "family inet" одним словом
# ipset 7.x отвергает с "Unknown argument".
ipset create "$1" hash:net family "$2" maxelem $MAXELEM 2>/dev/null || true
}
# Атомарная перезаливка: скачать, вытащить 'add'-строки, залить в _tmp, swap.
# При любом провале старый набор остаётся нетронутым, причина — в лог.
swap_list() { # $1=url $2=set_name $3=family
local url="$1" set="$2" family="$3"
local tmp; tmp=$(mktemp) || die "mktemp"
if ! curl -fsS --max-time 90 "$url" -o "$tmp"; then
log "SWAP-FAIL $set: не скачался $url — старый набор сохранён"
rm -f "$tmp"; return 1
fi
local adds; adds=$(grep -c '^add ' "$tmp")
if [ "$adds" -lt 1 ]; then
log "SWAP-FAIL $set: в $url 0 записей — старый набор сохранён"
rm -f "$tmp"; return 1
fi
local restore; restore=$(mktemp) || die "mktemp"
{
echo "create ${set}_tmp hash:net family $family maxelem $MAXELEM"
tr -d '\r' < "$tmp" | grep '^add ' | sed "s|^add [^ ]*|add ${set}_tmp|"
} > "$restore"
local cerr
cerr=$(ipset create "$set" hash:net family "$family" maxelem $MAXELEM 2>&1) \
|| log "CREATE-WARN $set: $cerr"
ipset destroy "${set}_tmp" 2>/dev/null
if ! ipset restore -exist < "$restore" 2>>"$LOG_FILE"; then
log "SWAP-FAIL $set: ipset restore вернул ошибку — старый набор сохранён"
ipset destroy "${set}_tmp" 2>/dev/null
rm -f "$tmp" "$restore"; return 1
fi
cerr=$(ipset swap "${set}_tmp" "$set" 2>&1) || {
log "SWAP-FAIL $set: $cerr — набор $set не обновлён"
ipset destroy "${set}_tmp" 2>/dev/null
rm -f "$tmp" "$restore"; return 1
}
ipset destroy "${set}_tmp" 2>/dev/null
rm -f "$tmp" "$restore"
log "SWAP-OK $set: $adds сетей"
return 0
}
# --- firewall ----------------------------------------------------------------
# Строит /etc/rkn-extra-block/nets.map: "firstPadded\tlastPadded\tlabel" на каждую
# IPv4-сеть из blacklist_with_comments.txt. Label — из секций AS-Name (читаемое имя)
# или MNT-токенов NET-Name (напр. VKCOMPANY). Нужен агенту статистики (rkn-agent).
build_map() {
local url="$1" out="$2"
local tmp; tmp=$(mktemp) || die "mktemp"
if ! curl -fsS --max-time 90 "$url" -o "$tmp"; then
log "MAP-FAIL: не скачался $url — старый nets.map сохранён"
rm -f "$tmp"; return 1
fi
tr -d '\r' < "$tmp" | awk -F'\t' -v OFS='\t' '
function pad(o) { return sprintf("%03d.%03d.%03d.%03d", o[1], o[2], o[3], o[4]) }
function emit(net, lbl, a, p, full, bits, step, i, lo, hi) {
gsub(/\//, ".", net); split(net, a, ".")
p = a[5] + 0; full = int(p / 8); bits = p % 8
step = 1; for (i = 0; i < 8 - bits; i++) step *= 2
for (i = 1; i <= 4; i++) { lo[i] = 0; hi[i] = 255 }
for (i = 1; i <= full; i++) { lo[i] = a[i]; hi[i] = a[i] }
if (bits > 0) {
lo[full + 1] = int(a[full + 1] / step) * step
hi[full + 1] = lo[full + 1] + step - 1
}
print pad(lo), pad(hi), lbl
}
/^# AS-Name \(ORG\): / { label = substr($0, index($0, ": ") + 2); next }
/^# AS-Name: / { label = substr($0, index($0, ": ") + 2); next }
/^# NET-Name: / {
rest = substr($0, index($0, ": ") + 2)
sub(/^[^ ]+ /, "", rest)
gsub(/\([^)]*\)/, "", rest); gsub(/[\[\]]/, "", rest)
n = split(rest, t, /[ \t]+/); lbl = ""
for (i = 1; i <= n; i++) if (t[i] ~ /MNT/) { x = t[i]; sub(/-MNT$/, "", x); lbl = lbl (lbl ? " " : "") x }
label = (lbl ? lbl : "C24Be " substr(rest, 1, 40))
next
}
/^#/ { next }
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\/[0-9]+/ { emit($1, label) }
' > "$out.tmp"
mv "$out.tmp" "$out"
rm -f "$tmp"
log "MAP-OK: $(wc -l < "$out") сетей с метками"
return 0
}
apply_jumps() {
iptables -C INPUT -j "$IN_CHAIN" 2>/dev/null || iptables -I INPUT 1 -j "$IN_CHAIN"
iptables -C OUTPUT -j "$OUT_CHAIN" 2>/dev/null || iptables -I OUTPUT 1 -j "$OUT_CHAIN"
iptables -C FORWARD -j "$OUT_CHAIN" 2>/dev/null || iptables -I FORWARD 1 -j "$OUT_CHAIN"
if have6; then
ip6tables -C INPUT -j "$IN_CHAIN" 2>/dev/null || ip6tables -I INPUT 1 -j "$IN_CHAIN" 2>/dev/null
ip6tables -C OUTPUT -j "$OUT_CHAIN" 2>/dev/null || ip6tables -I OUTPUT 1 -j "$OUT_CHAIN" 2>/dev/null
ip6tables -C FORWARD -j "$OUT_CHAIN" 2>/dev/null || ip6tables -I FORWARD 1 -j "$OUT_CHAIN" 2>/dev/null
fi
}
remove_jumps() {
while iptables -C INPUT -j "$IN_CHAIN" 2>/dev/null; do iptables -D INPUT -j "$IN_CHAIN"; done
while iptables -C OUTPUT -j "$OUT_CHAIN" 2>/dev/null; do iptables -D OUTPUT -j "$OUT_CHAIN"; done
while iptables -C FORWARD -j "$OUT_CHAIN" 2>/dev/null; do iptables -D FORWARD -j "$OUT_CHAIN"; done
if have6; then
while ip6tables -C INPUT -j "$IN_CHAIN" 2>/dev/null; do ip6tables -D INPUT -j "$IN_CHAIN"; done
while ip6tables -C OUTPUT -j "$OUT_CHAIN" 2>/dev/null; do ip6tables -D OUTPUT -j "$OUT_CHAIN"; done
while ip6tables -C FORWARD -j "$OUT_CHAIN" 2>/dev/null; do ip6tables -D FORWARD -j "$OUT_CHAIN"; done
fi
}
build_chains() {
local log_in="no" log_out="no" blk_in="no" blk_out="no"
log_flag "log_in" && log_in="yes"
log_flag "log_out" && log_out="yes"
block_flag "block_in" && blk_in="yes"
block_flag "block_out" && blk_out="yes"
# Правила ссылаются на наборы ipset: если набора нет (контур не установлен
# или набор ещё не создан), цепочка остаётся пустой — без мусора в stderr.
iptables -N "$IN_CHAIN" 2>/dev/null || true
iptables -F "$IN_CHAIN"
if ipset list "$IN_SET4" >/dev/null 2>&1; then
if [ "$log_in" = "yes" ]; then
iptables -A "$IN_CHAIN" -m set --match-set "$IN_SET4" src -m conntrack --ctstate NEW \
-m limit --limit 10/min -j LOG --log-prefix "RKN_EXTRA_IN " --log-level 4
fi
# Входящие от РКН-подсетей: тишина (DROP), только новые соединения
if [ "$blk_in" = "yes" ]; then
iptables -A "$IN_CHAIN" -m set --match-set "$IN_SET4" src -m conntrack --ctstate NEW -j DROP
fi
fi
iptables -N "$OUT_CHAIN" 2>/dev/null || true
iptables -F "$OUT_CHAIN"
if ipset list "$OUT_SET4" >/dev/null 2>&1; then
if [ "$log_out" = "yes" ]; then
iptables -A "$OUT_CHAIN" -p tcp -m set --match-set "$OUT_SET4" dst \
-m limit --limit 10/min -j LOG --log-prefix "RKN_EXTRA_OUT " --log-level 4
fi
# Исходящие к VK/Max/OK: штатный обрыв как в amnezia-blocker (REJECT)
if [ "$blk_out" = "yes" ]; then
iptables -A "$OUT_CHAIN" -p tcp -m set --match-set "$OUT_SET4" dst -j REJECT --reject-with tcp-reset
iptables -A "$OUT_CHAIN" -m set --match-set "$OUT_SET4" dst -j REJECT
fi
fi
if have6; then
ip6tables -N "$IN_CHAIN" 2>/dev/null || true
ip6tables -F "$IN_CHAIN" 2>/dev/null
if ipset list "$IN_SET6" >/dev/null 2>&1; then
if [ "$log_in" = "yes" ]; then
ip6tables -A "$IN_CHAIN" -m set --match-set "$IN_SET6" src -m conntrack --ctstate NEW \
-m limit --limit 10/min -j LOG --log-prefix "RKN_EXTRA_IN6 " --log-level 4 2>/dev/null
fi
if [ "$blk_in" = "yes" ]; then
ip6tables -A "$IN_CHAIN" -m set --match-set "$IN_SET6" src -m conntrack --ctstate NEW -j DROP 2>/dev/null
fi
fi
ip6tables -N "$OUT_CHAIN" 2>/dev/null || true
ip6tables -F "$OUT_CHAIN" 2>/dev/null
if ipset list "$OUT_SET6" >/dev/null 2>&1; then
if [ "$log_out" = "yes" ]; then
ip6tables -A "$OUT_CHAIN" -p tcp -m set --match-set "$OUT_SET6" dst \
-m limit --limit 10/min -j LOG --log-prefix "RKN_EXTRA_OUT6 " --log-level 4 2>/dev/null
fi
if [ "$blk_out" = "yes" ]; then
ip6tables -A "$OUT_CHAIN" -p tcp -m set --match-set "$OUT_SET6" dst -j REJECT --reject-with tcp-reset 2>/dev/null
ip6tables -A "$OUT_CHAIN" -m set --match-set "$OUT_SET6" dst -j REJECT 2>/dev/null
fi
fi
fi
}
# --- commands ----------------------------------------------------------------
cmd_update() {
load_config
swap_list "$URL_IN4" "$IN_SET4" inet
if have6; then
swap_list "$URL_IN6" "$IN_SET6" inet6
else
log "SKIP $IN_SET6: на хосте нет IPv6"
fi
swap_list "$URL_OUT4" "$OUT_SET4" inet
if have6; then
swap_list "$URL_OUT6" "$OUT_SET6" inet6
else
log "SKIP $OUT_SET6: на хосте нет IPv6"
fi
mkdir -p "$CONF_DIR"
build_map "$URL_MAP" "$CONF_DIR/nets.map"
date -u +%Y-%m-%dT%H:%M:%SZ > "$CONF_DIR/last_update"
}
cmd_on() {
create_set "$IN_SET4" inet
if have6; then create_set "$IN_SET6" inet6; fi
create_set "$OUT_SET4" inet
if have6; then create_set "$OUT_SET6" inet6; fi
# Если наборы пустые (первый запуск / после ребута) — подгрузить
local n; n=$(ipset list "$IN_SET4" 2>/dev/null | grep -c '^[0-9]')
[ "$n" -lt 1 ] && cmd_update
build_chains
apply_jumps
set_state on
log "ON"
echo "rkn-extra-block: ON"
cmd_status_short
}
cmd_off() {
remove_jumps
set_state off
log "OFF"
echo "rkn-extra-block: OFF (наборы ipset сохранены, повторное включение мгновенное)"
}
set_count() { ipset list "$1" 2>/dev/null | grep -c '^[0-9]'; }
cmd_status() {
local st; st=$(get_state)
local in4 in6 out4 out6
in4=$(set_count "$IN_SET4"); in6=$(set_count "$IN_SET6")
out4=$(set_count "$OUT_SET4"); out6=$(set_count "$OUT_SET6")
echo "rkn_extra_block: $st"
echo " входящие (РКН, DROP NEW): v4=$in4 сетей v6=$in6 сетей"
echo " исходящие (VK/Max, REJECT): v4=$out4 сетей v6=$out6 сетей"
flags_line | sed 's/^/ /'
echo " last_update: $(cat "$CONF_DIR/last_update" 2>/dev/null || echo '—')"
if iptables -C INPUT -j "$IN_CHAIN" 2>/dev/null; then echo " правила: iptables INPUT/OUTPUT/FORWARD — вешаются"; else echo " правила: СНЯТЫ (блокировка неактивна)"; fi
have6 && ip6tables -C INPUT -j "$IN_CHAIN" 2>/dev/null && echo " правила v6: вешаются"
}
cmd_status_short() {
echo " in4=$(set_count "$IN_SET4") in6=$(set_count "$IN_SET6") out4=$(set_count "$OUT_SET4") out6=$(set_count "$OUT_SET6")"
}
cmd_boot() {
load_config
ensure_deps
cmd_update
if [ "$(get_state)" = "on" ]; then
build_chains
apply_jumps
log "BOOT: восстановлено состояние ON"
else
log "BOOT: состояние OFF, только наборы обновлены"
fi
}
cmd_log() {
case "${1:-}" in
on)
mkdir -p "$CONF_DIR"
echo on > "$LOG_STATE_FILE"
echo on > "$CONF_DIR/log_in"
echo on > "$CONF_DIR/log_out"
build_chains # пересобрать цепочки с LOG-правилами (прыжки не трогаем)
log "LOGGING ON"
echo "Журналирование ВКЛ — срабатывания: journalctl -k -g RKN_EXTRA (лимит 10/мин)"
;;
off)
echo off > "$LOG_STATE_FILE"
mkdir -p "$CONF_DIR"
echo off > "$CONF_DIR/log_in"
echo off > "$CONF_DIR/log_out"
build_chains
log "LOGGING OFF"
echo "Журналирование ВЫКЛ"
;;
status|*)
echo "rkn_extra_block logging: log_in=$(flag_val log_in) log_out=$(flag_val log_out)"
;;
esac
}
# Раздельные флаги направлений: rkn-extra-block set block_in=on log_out=off ...
# Прыжки в INPUT/OUTPUT/FORWARD не трогаем — пересобираются только цепочки.
cmd_set() {
[ "$#" -ge 1 ] || die "set: укажите пары вида block_in=on (флаги: block_in block_out log_in log_out)"
local pair name val
for pair in "$@"; do
case "$pair" in
block_in=on|block_in=off|block_out=on|block_out=off|log_in=on|log_in=off|log_out=on|log_out=off)
name="${pair%%=*}"; val="${pair#*=}"
mkdir -p "$CONF_DIR"
echo "$val" > "$CONF_DIR/$name"
;;
*) die "set: неизвестная пара '$pair' (нужно block_in|block_out|log_in|log_out = on|off)" ;;
esac
done
build_chains
log "SET: $*"
flags_line
}
# Ретроспектива: найти в лог-файлах IP, входящие в текущие наборы rkn-extra-block.
# Использование: rkn-extra-block scan [файл...] (умолчание: access.log nginx + auth.log)
cmd_scan() {
command -v python3 &>/dev/null || die "нужен python3"
local files=("$@")
[ "${#files[@]}" -eq 0 ] && files=(/var/log/nginx/access.log /var/log/auth.log)
IN_SET4="$IN_SET4" OUT_SET4="$OUT_SET4" python3 - "${files[@]}" << 'PYEOF'
import collections, gzip, ipaddress, os, re, subprocess, sys
in_set = os.environ["IN_SET4"]; out_set = os.environ["OUT_SET4"]
out = subprocess.run(["ipset", "save"], capture_output=True, text=True).stdout
nets = []
for line in out.splitlines():
parts = line.split()
if len(parts) >= 3 and parts[0] == "add" and parts[1] in (in_set, out_set):
try:
nets.append(ipaddress.ip_network(parts[2]))
except ValueError:
pass
by_first = collections.defaultdict(list)
for n in nets:
by_first[n.network_address.packed[0] if n.version == 4 else 0].append(n)
ip_re = re.compile(r"\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b")
hits = collections.Counter()
samples = {}
total = matched = 0
for path in sys.argv[1:]:
paths = [path]
if path.endswith("*"):
import glob
paths = sorted(glob.glob(path))
for p in paths:
if not os.path.exists(p):
continue
opener = gzip.open if p.endswith(".gz") else open
try:
with opener(p, "rt", errors="replace") as fh:
for line in fh:
total += 1
m = ip_re.search(line)
if not m:
continue
try:
ip = ipaddress.ip_address(m.group(1))
except ValueError:
continue
for n in by_first.get(ip.packed[0], ()):
if ip in n:
matched += 1
hits[str(ip)] += 1
samples.setdefault(str(ip), line.rstrip())
break
except OSError as e:
print(f"пропуск {p}: {e}", file=sys.stderr)
print(f"просканировано строк: {total}, совпадений с сетями РКН/VK: {matched}")
if not hits:
print("обращений из заблокированных сетей в указанных логах не найдено")
else:
print("топ IP:")
for ip, cnt in hits.most_common(20):
print(f" {ip:15s} {cnt:6d} {samples[ip][:160]}")
PYEOF
}
cmd_clearscans() {
: > /var/log/rkn-scans.json
mkdir -p "$CONF_DIR"
date -u +%Y-%m-%dT%H:%M:%SZ > "$CONF_DIR/scanlog.since"
log "CLEAR: журнал сканирований обнулён, горизонт сбора перенесён на сейчас"
echo "журнал сканирований обнулён (старое из kernel-журнала повторно подхвачено не будет)"
}
# Полное удаление контура: прыжки, цепочки, наборы, юниты, скрипты, конфиги, логи.
# Вызывается панелью при удалении приложения; может запускаться из копии в /tmp
# (сам себя установленный скрипт при этом тоже удалит — inode останется у процесса).
cmd_uninstall() {
remove_jumps
# Цепочки (возможно с правилами, ссылающимися на наборы — сначала flush)
for ch in "$IN_CHAIN" "$OUT_CHAIN"; do
iptables -F "$ch" 2>/dev/null && iptables -X "$ch" 2>/dev/null
if have6; then
ip6tables -F "$ch" 2>/dev/null && ip6tables -X "$ch" 2>/dev/null
fi
done
# Юниты: выключить и убрать
systemctl disable --now rkn-extra-block.timer rkn-agent.timer \
rkn-extra-block.service rkn-agent.service 2>/dev/null
rm -f /etc/systemd/system/rkn-extra-block.timer \
/etc/systemd/system/rkn-extra-block.service \
/etc/systemd/system/rkn-agent.timer \
/etc/systemd/system/rkn-agent.service
systemctl daemon-reload 2>/dev/null
# Наборы ipset
for s in "$IN_SET4" "$IN_SET6" "$OUT_SET4" "$OUT_SET6"; do
ipset destroy "$s" 2>/dev/null
done
# Скрипты, конфиги, логи
rm -f /usr/local/sbin/rkn-extra-block /usr/local/sbin/rkn-agent-collect
rm -rf "$CONF_DIR"
rm -f /run/rkn-extra-block.lock
log "UNINSTALL-OK: контур rkn-extra-block полностью удалён"
rm -f "$LOG_FILE" /var/log/rkn-scans.json
echo "rkn-extra-block удалён: правила, наборы, юниты, скрипты, конфиги и логи сняты"
}
case "${1:-}" in
on) lock; ensure_deps; cmd_on ;;
off) lock; ensure_deps; cmd_off ;;
status) cmd_status ;;
update) lock; ensure_deps; cmd_update; cmd_status_short ;;
boot) lock; cmd_boot ;;
log) lock; ensure_deps; shift; cmd_log "${1:-status}" ;;
scan) shift; cmd_scan "$@" ;;
set) lock; ensure_deps; shift; cmd_set "$@" ;;
clearscans) lock; cmd_clearscans ;;
uninstall) lock; ensure_deps; cmd_uninstall ;;
*) echo "Использование: rkn-extra-block {on|off|status|update|boot|set флаг=on|off...|log on|off|status|scan [файлы...]|clearscans|uninstall}" >&2; exit 2 ;;
esac
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=Daily RKN extra block list update (C24Be/AS_Network_List)
[Timer]
OnCalendar=daily
RandomizedDelaySec=2h
Persistent=true
Unit=rkn-extra-block.service
[Install]
WantedBy=timers.target