Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Changelog

## v3.5 — 2026-06-27

Мелкий тюнинг-апдейт: одна новая ручка + уточнение по SYNPROXY. Поведение по умолчанию НЕ меняется.

### ⚙️ optimize.sh — `CT_EST_TIMEOUT` (новая ENV-ручка)
- `nf_conntrack_tcp_timeout_established` вынесен из хардкода в ENV `CT_EST_TIMEOUT` (секунды,
дефолт `7440` — поведение прежнее). Валидируется (целое 120…432000, иначе дефолт) и
**персистится** в `optimize.conf` (ре-ран без ENV не сбрасывает значение).
- Зачем: idle-туннели/мосты без частого keepalive можно поднять (напр. `14400`=4ч), не патча
скрипт; ноды под флуд-профилем — оставить дефолт (агрессивнее реклеймит брошенные /
connect-and-hold established, не раздувая conntrack-таблицу).

### 📝 SYNPROXY — уточнение (код не меняется)
- Зафиксирован вывод: на VPN-relay SYNPROXY **избыточен** — анти-спуф уже дают
`tcp_syncookies=1` + per-IP ct-лимиты, а издержки (`be_liberal`, поломка TFO на защищённых
портах, per-packet overhead) не оправданы; против connect-and-hold / PPS-флуда он не
помогает. Остаётся **opt-in, default off**; включать только под подтверждённый спуфнутый
SYN-флуд. (Комментарий в `protect.sh` + строка в README.)

## v3.4 — 2026-06-24

Self-audit на residential-proxy / proxyware-SDK (read-only) — доказательная база для ответа на abuse-тикеты хостеров.

### 🕵 na-report — новый режим `--proxyware`
- проверка сигнатур известного proxyware (IPIDEA/PacketSDK/Honeygain/EarnApp/Peer2Profit/…) в процессах, systemd-юнитах, cron, файлах и docker-образах;
- поиск **установленных** соединений к C2-инфраструктуре proxyware (резолв доменов → match по ESTABLISHED peer-адресам);
- список внешне-доступных listener'ов (контекст для оператора);
- вердикт `clean|suspect`; `--proxyware --json` — один объект для панели/флота.
- Сигнатуры — только конкретные имена продуктов (generic «proxy» не матчим, чтобы не ловить сам xray/VLESS).

## v3.3.1 — 2026-06-16

### 🐞 Фикс
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
- **conntrack phantom-eviction** *(opt-in)* — защита от **distributed connect-and-hold**
по живым сокетам (`conntrack ≫ ss`), CGNAT-safe, observe-режим по умолчанию.
- **SYNPROXY** *(opt-in, done-right)* — `notrack` только host-local (`fib daddr type local`,
не ломает Docker/транзит), verify ядра/модуля, fail-loud при недоступности.
не ломает Docker/транзит), verify ядра/модуля, fail-loud при недоступности. На VPN-relay
обычно избыточен (syncookies + per-IP ct-лимиты уже дают анти-спуф) — **default off**.
- **статич-блоклисты** *(opt-in)* — Spamhaus DROP + FireHOL L1 (+ Tor), bogon-фильтр, таймер.
- **Remnawave fleet auto-sync** *(opt-in)* — ноды флота сами держат IP друг друга в whitelist (с панели).
- **ICMP rate-limit** (пинг жив, флуд режется).
Expand Down Expand Up @@ -104,7 +105,7 @@ curl -fsSL "https://raw.githubusercontent.com/jestivald/node-accelerator/$NA_REF
| `ENABLE_PORTSCAN_BAN` | `1` | автобан за скан закрытых портов |
| `PORTSCAN_RATE`/`PORTSCAN_BURST` | `15`/`30` | порог скана (SYN на закрытые порты/мин, per-IP) до бана — ниже порога просто дроп, без бана |
| `ENABLE_CROWDSEC` | `1` | ставить CrowdSec + bouncer |
| `ENABLE_SYNPROXY` | `0` | nft synproxy на сервисные порты (advanced) |
| `ENABLE_SYNPROXY` | `0` | nft synproxy на сервисные порты (advanced). На VPN-relay избыточен — syncookies + per-IP ct-лимиты уже дают анти-спуф; включать под подтверждённый спуф-SYN-флуд |
| `CROWDSEC_ENROLL_KEY` | _пусто_ | enroll в CrowdSec Console |
| `SAFETY_DELAY` | `300` | сек до авто-сброса правил, если не подтвердить SSH |
| `DRY_RUN` | `0` | `1` — только сгенерировать + `nft -c`, не применять |
Expand All @@ -121,7 +122,7 @@ curl -fsSL "https://raw.githubusercontent.com/jestivald/node-accelerator/$NA_REF
| `NA_CTG_ENFORCE` | `0` | `0` — observe (только лог), `1` — эвиктить фантомы |
| `NA_CTG_PHANTOM_MIN` / `NA_CTG_LIVE_FLOOR` | `4000` / `2` | порог conntrack-холдера / порог живых сокетов |

`optimize.sh`: `ENABLE_XANMOD=1`, `XANMOD_FLAVOR=lts|main|edge|rt`, `XANMOD_PKG=...`, `REMNAWAVE_SWAP_SIZE=2G`, `TCP_ECN_MODE=2` (0/1/2), `DISABLE_TFO=0`, `ENABLE_MSS_CLAMP=0` (для routed/WireGuard-нод), `SETUP_NO_ZRAM=0`. Буферы/conntrack/somaxconn — **tier-aware** (масштаб от RAM).
`optimize.sh`: `ENABLE_XANMOD=1`, `XANMOD_FLAVOR=lts|main|edge|rt`, `XANMOD_PKG=...`, `REMNAWAVE_SWAP_SIZE=2G`, `TCP_ECN_MODE=2` (0/1/2), `DISABLE_TFO=0`, `CT_EST_TIMEOUT=7440` (conntrack established-timeout, сек; ↑ напр. до `14400` для idle-туннелей/мостов без частого keepalive), `ENABLE_MSS_CLAMP=0` (для routed/WireGuard-нод), `SETUP_NO_ZRAM=0`. Буферы/conntrack/somaxconn — **tier-aware** (масштаб от RAM).
`XANMOD_PROBE=1` — проверить, что репозиторий+ключ+сборка ядра резолвятся на этой ОС, **без установки** (для CI и быстрой проверки совместимости).

---
Expand Down
132 changes: 130 additions & 2 deletions scripts/na-report.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,35 @@
# na-report.sh --top 20 — сколько top-IP/ASN показывать
# na-report.sh --ip 1.2.3.4 — глубокий вердикт по IP (rDNS, nft-сеты, conntrack, таймлайн)
# na-report.sh --port 443 — топ дроп-источников по порту + кто слушает
# na-report.sh --proxyware — self-audit: следы proxyware/residential-proxy (для abuse-тикетов; + --json)
#
# JSON-схема:
# {window_hours, generated_at, events_total, ban_rate_5m,
# drops_by_reason{portscan,synflood,ssh-flood,badflags,crowdsec},
# timeline[12], # последние 60 мин, бакеты по 5 мин
# top_asn[{asn,name,country,pct}], top_ips[{ip,asn,country,hits,verdict}]}
#
# JSON-схема (--proxyware --json):
# {verdict:"clean|suspect", generated_at,
# hits{processes[],services[],cron[],files[],docker[]},
# c2_connections[], external_listeners[]}

set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/common.sh
. "$SCRIPT_DIR/lib/common.sh"

# ─── Аргументы ────────────────────────────────────────────────────────────────
JSON=0; HOURS=24; TOPN=15; FOCUS_IP=""; FOCUS_PORT=""
JSON=0; HOURS=24; TOPN=15; FOCUS_IP=""; FOCUS_PORT=""; PROXYAUDIT=0
while [[ $# -gt 0 ]]; do
case "$1" in
--json) JSON=1 ;;
--hours) HOURS="${2:-24}"; shift ;;
--top) TOPN="${2:-15}"; shift ;;
--ip) FOCUS_IP="${2:-}"; shift ;;
--port) FOCUS_PORT="${2:-}"; shift ;;
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
--proxyware) PROXYAUDIT=1 ;;
-h|--help) awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "$0"; exit 0 ;;
*) err "Неизвестный аргумент: $1"; exit 1 ;;
esac
shift
Expand Down Expand Up @@ -296,7 +303,128 @@ port_focus() {
else info "ss недоступен"; fi
}

# ─── Self-audit: proxyware / residential-proxy (`--proxyware`) ──────────────────
# Read-only доказательство «нода НЕ резидентный прокси и proxyware-SDK на ней нет».
# Для ответа на abuse-тикеты (IPIDEA/PacketSDK/Honeygain/EarnApp/…). Сигнатуры —
# ТОЛЬКО конкретные имена продуктов: generic «proxy» не матчим, иначе ложно
# сработает на самом xray/VLESS (нода легально — прокси-сервер).
PROXYWARE_SIG='packetsdk|ipidea|honeygain|earnapp|peer2profit|proxyrack|iproyal|pawnsapp|repocket|packetstream|traffmonetizer|bitping|earnfm|proxylite|infatica|nodemaven|gaganode|urnetwork'
PROXYWARE_C2='api-seed.packetsdk.net packetsdk.net api-seed.packetsdk.xyz martianinc.co dnsnb8.net'

# shellcheck disable=SC2009 # нужен match по args, не по имени — pgrep не подходит
pw_proc() { ps -eo pid=,args= 2>/dev/null | grep -iE "$PROXYWARE_SIG" | grep -ivE 'na-report|--proxyware| grep ' | sed 's/^ *//' | head -20; }
pw_services() { { systemctl list-units --type=service --all --no-legend 2>/dev/null | awk '{print $1}'
systemctl list-unit-files --type=service --no-legend 2>/dev/null | awk '{print $1}'
grep -rilE "$PROXYWARE_SIG" /etc/systemd/system /lib/systemd/system /run/systemd/system 2>/dev/null
} | grep -iE "$PROXYWARE_SIG" | sort -u | head -20; }
pw_cron() { { crontab -l 2>/dev/null
while IFS=: read -r _u _; do crontab -l -u "$_u" 2>/dev/null; done < /etc/passwd
grep -rhsiE "$PROXYWARE_SIG" /etc/crontab /etc/cron.d /etc/cron.* /var/spool/cron 2>/dev/null
} | grep -iE "$PROXYWARE_SIG" | sort -u | head -20; }
pw_files() { timeout 20 find /usr/local /opt /usr/bin /usr/sbin /root /home -maxdepth 4 -xdev -type f 2>/dev/null \
| grep -iE "$PROXYWARE_SIG" | head -20; }
pw_docker() { command -v docker >/dev/null 2>&1 || return 0
docker ps -a --format '{{.Names}} {{.Image}}' 2>/dev/null | grep -iE "$PROXYWARE_SIG" | head -20; }
# Коннекты к C2: резолвим домены → IP, ищем среди ESTABLISHED peer-адресов.
pw_c2conn() {
local ips
ips="$(for d in $PROXYWARE_C2; do getent hosts "$d" 2>/dev/null | awk '{print $1}'; done | sort -u)"
[[ -z "$ips" ]] && return 0
ss -tnH state established 2>/dev/null | awk '{print $4}' \
| sed -E 's/:[0-9]+$//; s/^\[//; s/\]$//' \
| grep -Fxf <(printf '%s\n' "$ips") 2>/dev/null | sort -u | head -20
}
# Внешне-доступные listener'ы — контекст: оператор сверяет, что незнакомых нет.
pw_listeners() {
ss -tlnHp 2>/dev/null | awk '
$4 ~ /(^0\.0\.0\.0:)|(^\*:)|(^\[::\]:)|(^\[::ffff:0\.0\.0\.0\]:)/ {
p=$0; sub(/.*users:\(\(\"/,"",p); sub(/\".*/,"",p); if(p==$0)p="?";
print $4" ["p"]" }' | sort -u | head -30
}

# stdin (строка = элемент) → JSON-массив ["a","b"] (json_str экранирует).
_json_arr() {
local out="" line
while IFS= read -r line; do
[[ -z "$line" ]] && continue
out+="${out:+,}\"$(json_str "$line")\""
done
printf '[%s]' "$out"
}

# Секция аудита: печатает и возвращает 1, если есть хиты.
pw_section() {
local label="$1" data="$2"
if [[ -n "$data" ]]; then
status_line FAIL "$label:"; printf '%s\n' "$data" | sed 's/^/ /'; return 1
fi
status_line OK "$label — чисто"; return 0
}

proxyware_audit() {
clear 2>/dev/null || true
printf "%b" "$BOLD"
cat <<'B'
┌────────────────────────────────────────────┐
│ 🕵 node-accelerator — proxyware self-audit │
└────────────────────────────────────────────┘
B
printf "%b" "$NC"
info "Read-only проверка на residential-proxy / bandwidth-SDK (для abuse-тикетов)."

local hit=0
title "Сигнатуры известного proxyware"
pw_section "процессы" "$(pw_proc)" || hit=1
pw_section "systemd-юниты" "$(pw_services)" || hit=1
pw_section "cron" "$(pw_cron)" || hit=1
pw_section "файлы" "$(pw_files)" || hit=1
pw_section "docker-образы" "$(pw_docker)" || hit=1

title "Соединения к C2-инфраструктуре proxyware"
local c2; c2="$(pw_c2conn)"
if [[ -n "$c2" ]]; then
status_line FAIL "установленные коннекты к C2:"; printf '%s\n' "$c2" | sed 's/^/ /'; hit=1
else
status_line OK "активных соединений к C2 нет (проверено: $PROXYWARE_C2)"
fi

title "Внешне-доступные слушающие порты (контекст)"
local lst; lst="$(pw_listeners)"
if [[ -n "$lst" ]]; then printf '%s\n' "$lst" | sed 's/^/ • /'; else info "внешних listener'ов нет"; fi
info "для VPN-ноды :443 и порт входа моста — норма; убедись, что незнакомых служб нет"

hr
if [[ "$hit" -eq 0 ]]; then
ok "ВЕРДИКТ: признаков proxyware / residential-proxy НЕ найдено — нода чиста."
else
err "ВЕРДИКТ: есть индикаторы proxyware (см. ✘ выше) — разобраться вручную."
fi
hr
}

proxyware_json() {
local proc svc cron files dock c2 lst verdict
proc="$(pw_proc)"; svc="$(pw_services)"; cron="$(pw_cron)"
files="$(pw_files)"; dock="$(pw_docker)"; c2="$(pw_c2conn)"; lst="$(pw_listeners)"
if [[ -n "$proc$svc$cron$files$dock$c2" ]]; then verdict="suspect"; else verdict="clean"; fi
printf '{'
printf '"verdict":"%s","generated_at":%s,' "$verdict" "$NOW"
printf '"hits":{"processes":%s,"services":%s,"cron":%s,"files":%s,"docker":%s},' \
"$(printf '%s\n' "$proc" | _json_arr)" \
"$(printf '%s\n' "$svc" | _json_arr)" \
"$(printf '%s\n' "$cron" | _json_arr)" \
"$(printf '%s\n' "$files" | _json_arr)" \
"$(printf '%s\n' "$dock" | _json_arr)"
printf '"c2_connections":%s,"external_listeners":%s}\n' \
"$(printf '%s\n' "$c2" | _json_arr)" \
"$(printf '%s\n' "$lst" | _json_arr)"
}

# ─── Диспетч ────────────────────────────────────────────────────────────────────
if [[ "$PROXYAUDIT" == "1" ]]; then
if [[ "$JSON" == "1" ]]; then proxyware_json; else proxyware_audit; fi
exit 0
fi
if [[ "$JSON" == "1" ]]; then emit_json; exit 0; fi
[[ -n "$FOCUS_PORT" ]] && { port_focus; exit 0; }
[[ -n "$FOCUS_IP" ]] && { focus_ip; exit 0; }
Expand Down
16 changes: 11 additions & 5 deletions scripts/optimize.sh
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ TCP_ECN_MODE="${TCP_ECN_MODE:-2}"; [[ "$TCP_ECN_MODE" =~ ^[012]$ ]] || TCP_ECN_M
# TFO можно выключить (DISABLE_TFO=1): часть сетей режет SYN с TFO-payload.
DISABLE_TFO="${DISABLE_TFO:-0}"; [[ "$DISABLE_TFO" =~ ^[01]$ ]] || DISABLE_TFO=0
TFO_VAL=3; [[ "$DISABLE_TFO" == "1" ]] && TFO_VAL=0
# nf_conntrack_tcp_timeout_established: за сколько «established» без трафика реклеймится.
# Дефолт 7440с (124 мин) — выше keepalive живого VLESS-мультиплекса, но много короче
# стокового 5-суточного потолка ядра: брошенные / connect-and-hold сессии не раздувают
# таблицу. idle-туннели/мосты без частого keepalive могут поднять (напр. 14400=4ч).
CT_EST_TIMEOUT="${CT_EST_TIMEOUT:-7440}"
[[ "$CT_EST_TIMEOUT" =~ ^[0-9]+$ ]] && [[ "$CT_EST_TIMEOUT" -ge 120 && "$CT_EST_TIMEOUT" -le 432000 ]] || CT_EST_TIMEOUT=7440
# overcommit: на tier1 (≤1.2G) heuristic (0) безопаснее агрессивного always-overcommit (1).
OVERCOMMIT=1; [[ "$TIER" -le 1 ]] && OVERCOMMIT=0
info "RAM-tier $TIER (~${_mem_mb} MB): sock_max=$SOCK_MAX def=$SOCK_DEF ecn=$TCP_ECN_MODE tfo=$TFO_VAL overcommit=$OVERCOMMIT"
Expand Down Expand Up @@ -298,10 +304,10 @@ net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.ipv6.conf.all.forwarding = 1

# --- Conntrack: timeout здесь; ёмкость (max/buckets) — отдельным drop-in ниже,
# масштабируется от RAM (99-node-accelerator-conntrack.conf), чтобы мелкая VPS под
# флудом не словила OOM в ядре ---
net.netfilter.nf_conntrack_tcp_timeout_established = 7440
# --- Conntrack: timeout здесь (tunable CT_EST_TIMEOUT); ёмкость (max/buckets) —
# отдельным drop-in ниже, масштабируется от RAM (99-node-accelerator-conntrack.conf),
# чтобы мелкая VPS под флудом не словила OOM в ядре ---
net.netfilter.nf_conntrack_tcp_timeout_established = $CT_EST_TIMEOUT

# --- SYN flood (ядро) ---
net.ipv4.tcp_syncookies = 1
Expand Down Expand Up @@ -643,7 +649,7 @@ EOF
# Персист конфига оптимизатора → ре-ран без ENV не сбрасывает выбор сборки/флейвора.
save_conf "$CONF_DIR/optimize.conf" \
ENABLE_XANMOD XANMOD_FLAVOR REMNAWAVE_SWAP_SIZE \
DISABLE_TFO TCP_ECN_MODE ENABLE_MSS_CLAMP SETUP_NO_ZRAM
DISABLE_TFO TCP_ECN_MODE ENABLE_MSS_CLAMP SETUP_NO_ZRAM CT_EST_TIMEOUT

title "ГОТОВО"
ok "Оптимизатор применён."
Expand Down
8 changes: 8 additions & 0 deletions scripts/protect.sh
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,14 @@ if [[ "$ENABLE_PORTSCAN_BAN" == "1" ]]; then
fi

# ─── SYNPROXY (опционально, done-right) ──────────────────────────────────────
# ⚠️ На VPN-relay (профиль connect-and-hold / PPS-флуд) SYNPROXY обычно ИЗБЫТОЧЕН:
# его единственный реальный плюс — анти-спуф SYN — уже закрыт tcp_syncookies=1 +
# per-IP ct-лимитами (CONN_LIMIT/SYN_RATE), а издержки (обязательный be_liberal=1,
# per-packet overhead, поломка TFO на защищённых портах) не оправданы. Против самого
# распространённого вектора (connect-and-hold / реальный PPS) он не помогает вовсе.
# Поэтому default OFF; включать ТОЛЬКО под подтверждённый спуфнутый SYN-флуд. Оставлен
# opt-in для не-relay сценариев (голый L4-фронт без syncookies-достаточности).
#
# notrack ТОЛЬКО для трафика к самому хосту (fib daddr type local): иначе правило в
# prerouting цепляет conntrack/NAT ТРАНЗИТА (Docker-контейнер панели → удалённая нода)
# и ломает его. Требует ядро ≥5.14 + модуль nf_synproxy. Запрошен, но недоступен →
Expand Down
Loading