mirror of
https://github.com/PRVTPRO/Amnezia-Web-Panel.git
synced 2026-10-23 03:37:36 +09:00
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:
1 parent
a1a6a70426
commit
ff50dbec47
8 files changed
+662
-3
No files matched your search
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user