fix: comprehensive code review — 40 fixes across 14 files

Critical: CRLF→LF in hostlists (broke nfqws2 matching), die in pipe
subshell (errors silently ignored), update_z2k /bin/sh overwrite guard,
missing quic_rutracker blob registration, telemetry race condition
(multi-process merge).

Security: sed injection via user input → grep -vxF, grep -qx → -qxF
for domain matching, chmod 777/666 → 755/644 + chown nobody,
predictable /tmp/z2k cleanup before mkdir.

High: PID path mismatch /opt/var/run → /var/run, zapret2_nat chain in
cleanup, orphaned tcpdump on monitor stop, cross-filesystem mv → cp+rm,
base.sh re-source clobber fix.

Medium: version mismatch, root check for default install path, early
exit for help/version, backup/restore dir fix, awk special chars,
HTTP RKN failure_detector, youtube_tcp_tcp typo, hostlist warning,
kernel module regex anchoring, state eviction + locking in Lua.

Dead code removed (~200 lines): check_installation_status,
menu_view_strategy, apply_category_strategies, update_init_section,
get_init_tcp/udp_params, TEST_DOMAINS, HTTP_STRATEGIES_CONF,
check_keenetic_specifics, discord_tcp variable.

Added .gitattributes to enforce LF line endings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
necronicle 2026-03-19 00:38:58 +03:00
parent 98c87d3c6f
commit bbd4b5bf1f
14 changed files with 333 additions and 416 deletions

7
.gitattributes vendored Normal file
View file

@ -0,0 +1,7 @@
# Force LF line endings for all shell scripts and text files deployed to Linux/router
*.sh text eol=lf
*.lua text eol=lf
*.txt text eol=lf
*.ini text eol=lf
*.conf text eol=lf
*.md text eol=lf

View file

@ -33,7 +33,7 @@ is_nfqws2_running() {
fi
# Fallback: check common pidfile locations (our init uses nfqws2_*.pid).
for pidfile in /opt/var/run/nfqws2_*.pid /opt/var/run/nfqws2.pid; do
for pidfile in /var/run/nfqws2_*.pid /var/run/nfqws2.pid; do
[ -f "$pidfile" ] || continue
pid="$(cat "$pidfile" 2>/dev/null)"
[ -n "$pid" ] || continue

View file

@ -193,16 +193,20 @@ ensure_autocircular_files()
{
local base="${EXTRA_STRATS_DIR:-$ZAPRET_BASE/extra_strats}/cache/autocircular"
mkdir -p "$base" || return 1
chmod 777 "$base" 2>/dev/null || true
chmod 755 "$base" 2>/dev/null || true
chown nobody "$base" 2>/dev/null || true
[ -f "$base/state.tsv" ] || : > "$base/state.tsv"
chmod 666 "$base/state.tsv" 2>/dev/null || true
chmod 644 "$base/state.tsv" 2>/dev/null || true
chown nobody "$base/state.tsv" 2>/dev/null || true
[ -f "$base/debug.log" ] || : > "$base/debug.log"
chmod 666 "$base/debug.log" 2>/dev/null || true
chmod 644 "$base/debug.log" 2>/dev/null || true
chown nobody "$base/debug.log" 2>/dev/null || true
# Optional debug toggle file; keep if present and readable.
[ -f "$base/debug.flag" ] && chmod 666 "$base/debug.flag" 2>/dev/null || true
[ -f "$base/debug.flag" ] && chmod 644 "$base/debug.flag" 2>/dev/null || true
[ -f "$base/debug.flag" ] && chown nobody "$base/debug.flag" 2>/dev/null || true
}
# ------------------------------------------------------------------------------
@ -216,16 +220,20 @@ ensure_traffic_debug_files()
{
local base="${EXTRA_STRATS_DIR:-$ZAPRET_BASE/extra_strats}/cache/traffic"
mkdir -p "$base" || return 1
chmod 777 "$base" 2>/dev/null || true
chmod 755 "$base" 2>/dev/null || true
chown nobody "$base" 2>/dev/null || true
[ -f "$base/nfqws2.debug.log" ] || : > "$base/nfqws2.debug.log"
chmod 666 "$base/nfqws2.debug.log" 2>/dev/null || true
chmod 644 "$base/nfqws2.debug.log" 2>/dev/null || true
chown nobody "$base/nfqws2.debug.log" 2>/dev/null || true
[ -f "$base/tcpdump.err.log" ] || : > "$base/tcpdump.err.log"
chmod 666 "$base/tcpdump.err.log" 2>/dev/null || true
chmod 644 "$base/tcpdump.err.log" 2>/dev/null || true
chown nobody "$base/tcpdump.err.log" 2>/dev/null || true
# Optional toggle file; keep if present and readable.
[ -f "$base/debug.flag" ] && chmod 666 "$base/debug.flag" 2>/dev/null || true
[ -f "$base/debug.flag" ] && chmod 644 "$base/debug.flag" 2>/dev/null || true
[ -f "$base/debug.flag" ] && chown nobody "$base/debug.flag" 2>/dev/null || true
}
traffic_debug_prepare()
@ -373,7 +381,7 @@ traffic_debug_tcpdump_start()
tcpdump -i any -nn -s 0 -U -w "$pcap" -C 5 -W 10 "$filter" >/dev/null 2>>"$err" &
local pid=$!
echo "$pid" > "$Z2K_TRAFFIC_DEBUG_PIDFILE"
chmod 666 "$Z2K_TRAFFIC_DEBUG_PIDFILE" 2>/dev/null || true
chmod 644 "$Z2K_TRAFFIC_DEBUG_PIDFILE" 2>/dev/null || true
echo "traffic debug: tcpdump filter: $filter"
echo "traffic debug: tcpdump started (PID $pid)"
}
@ -463,6 +471,9 @@ QUIC1_BLOB="$ZAPRET_BASE/files/fake/quic_1.bin"
QUIC_TEST_BLOB="$ZAPRET_BASE/files/fake/quic_test_00.bin"
[ -s "$QUIC_TEST_BLOB" ] && NFQWS2_OPT_BASE="$NFQWS2_OPT_BASE --blob=quic_test:@$QUIC_TEST_BLOB"
QUIC_RUTRACKER_BLOB="$ZAPRET_BASE/files/fake/quic_initial_rutracker_org.bin"
[ -s "$QUIC_RUTRACKER_BLOB" ] && NFQWS2_OPT_BASE="$NFQWS2_OPT_BASE --blob=quic_rutracker:@$QUIC_RUTRACKER_BLOB"
TLS_ONETRUST_BLOB="$ZAPRET_BASE/files/fake/tls_clienthello_www_onetrust_com.bin"
[ -s "$TLS_ONETRUST_BLOB" ] && NFQWS2_OPT_BASE="$NFQWS2_OPT_BASE --blob=tls_clienthello_www_onetrust_com:@$TLS_ONETRUST_BLOB"

View file

@ -212,6 +212,90 @@ local function load_state()
f:close()
end
local MAX_ENTRIES_PER_KEY = 500
local function evict_state_entries(merged)
for askey, hosts in pairs(merged) do
local count = 0
for _ in pairs(hosts) do count = count + 1 end
if count > MAX_ENTRIES_PER_KEY then
-- Collect entries with timestamps, sort by ts ascending, remove oldest
local entries = {}
for hostn, rec in pairs(hosts) do
table.insert(entries, { hostn = hostn, ts = (rec and rec.ts) or 0 })
end
table.sort(entries, function(a, b) return a.ts < b.ts end)
local to_remove = count - MAX_ENTRIES_PER_KEY
for i = 1, to_remove do
hosts[entries[i].hostn] = nil
end
end
end
end
local function evict_telemetry_entries(merged)
for askey, hosts in pairs(merged) do
local count = 0
for _ in pairs(hosts) do count = count + 1 end
if count > MAX_ENTRIES_PER_KEY then
-- Collect entries with total attempts, sort by att ascending, remove lowest
local entries = {}
for hostn, strats in pairs(hosts) do
local att = 0
if type(strats) == "table" then
for _, rec in pairs(strats) do
if rec then
att = att + (tonumber(rec.ok) or 0) + (tonumber(rec.fail) or 0)
end
end
end
table.insert(entries, { hostn = hostn, att = att })
end
table.sort(entries, function(a, b) return a.att < b.att end)
local to_remove = count - MAX_ENTRIES_PER_KEY
for i = 1, to_remove do
hosts[entries[i].hostn] = nil
end
end
end
end
local function acquire_lock(path)
local lockfile = path .. ".lock"
-- Check for stale lock (older than 10 seconds)
local lf_ts = io.open(lockfile, "r")
if lf_ts then
local content = lf_ts:read("*a")
lf_ts:close()
local lock_time = tonumber(content)
if lock_time and ((os.time() or 0) - lock_time) > 10 then
os.remove(lockfile)
else
return nil, lockfile -- lock is fresh, another process holds it
end
end
-- Try exclusive create: "wx" works in Lua 5.3+/glibc; fallback to "w" with
-- prior existence check (not perfectly atomic but good enough for our use case).
local lf = io.open(lockfile, "wx")
if not lf then
-- "wx" not supported or file appeared between check and open
local recheck = io.open(lockfile, "r")
if recheck then
recheck:close()
return nil, lockfile -- another process created it
end
lf = io.open(lockfile, "w")
end
if not lf then return nil, lockfile end
lf:write(tostring(os.time() or 0))
lf:close()
return true, lockfile
end
local function release_lock(lockfile)
if lockfile then os.remove(lockfile) end
end
local function write_state()
local now = os.time() or 0
if now ~= 0 and (now - last_write) < write_interval then
@ -223,6 +307,11 @@ local function write_state()
local path = choose_state_file_for_write()
if not path then return end
-- Acquire lock to prevent concurrent writes
local locked, lockfile = acquire_lock(path)
if not locked then return end -- another process is writing, skip this cycle
local tmp = path .. ".tmp"
-- Read existing file to merge state (prevents split-brain across processes)
@ -253,8 +342,14 @@ local function write_state()
end
end
-- Evict oldest entries if any key exceeds MAX_ENTRIES_PER_KEY
evict_state_entries(merged_state)
local f = io.open(tmp, "w")
if not f then return end
if not f then
release_lock(lockfile)
return
end
f:write("# z2k autocircular state (persisted circular nstrategy)\n")
f:write("# key\thost\tstrategy\tts\n")
@ -268,7 +363,12 @@ local function write_state()
end
f:close()
os.rename(tmp, path)
local ok, err = os.rename(tmp, path)
if not ok then
DLOG("ERROR: rename %s -> %s failed: %s\n", tmp, path, tostring(err))
os.remove(tmp)
end
release_lock(lockfile)
end
local function telemetry_host(askey, hostn, create)
@ -340,13 +440,63 @@ local function write_telemetry()
local path = choose_telemetry_file_for_write()
if not path then return end
-- Acquire lock to prevent concurrent writes
local locked, lockfile = acquire_lock(path)
if not locked then return end
local tmp = path .. ".tmp"
-- Read existing file to merge telemetry (prevents split-brain across processes)
local merged = {}
local f_in = io.open(path, "r")
if f_in then
for line in f_in:lines() do
if line ~= "" and not line:match("^%s*#") then
local askey, hostn, strat, okv, failv, latv, tsv, cdv =
line:match("^([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]*)\t?([^\t]*)")
local s = tonumber(strat)
if askey and hostn and s and s >= 1 then
if not merged[askey] then merged[askey] = {} end
if not merged[askey][hostn] then merged[askey][hostn] = {} end
merged[askey][hostn][s] = {
ok = tonumber(okv) or 0,
fail = tonumber(failv) or 0,
lat = tonumber(latv) or 0,
ts = tonumber(tsv) or 0,
cooldown_until = tonumber(cdv) or 0,
}
end
end
end
f_in:close()
end
-- Apply our in-memory telemetry over the merged data
for askey, hosts in pairs(telemetry) do
if not merged[askey] then merged[askey] = {} end
for hostn, strats in pairs(hosts) do
if not merged[askey][hostn] then merged[askey][hostn] = {} end
for s, rec in pairs(strats) do
if rec then
merged[askey][hostn][s] = rec
end
end
end
end
-- Evict entries with lowest total attempts if any key exceeds MAX_ENTRIES_PER_KEY
evict_telemetry_entries(merged)
local f = io.open(tmp, "w")
if not f then return end
if not f then
release_lock(lockfile)
return
end
f:write("# z2k autocircular telemetry\n")
f:write("# key\thost\tstrategy\tok\tfail\tlat\tts\tcooldown_until\n")
for askey, hosts in pairs(telemetry) do
for askey, hosts in pairs(merged) do
for hostn, strats in pairs(hosts) do
for s, rec in pairs(strats) do
if rec then
@ -365,7 +515,12 @@ local function write_telemetry()
end
end
f:close()
os.rename(tmp, path)
local ok, err = os.rename(tmp, path)
if not ok then
DLOG("ERROR: rename %s -> %s failed: %s\n", tmp, path, tostring(err))
os.remove(tmp)
end
release_lock(lockfile)
end
local function telemetry_total_attempts(h)
@ -623,7 +778,10 @@ local function policy_seed_strategy(desync, askey, hostn, hrec)
end
end
else
-- No telemetry for current strategy yet: let it accumulate data
-- No telemetry for current strategy yet: let it accumulate data.
-- This is critical for TCP profiles where success telemetry is never
-- recorded (incoming packets don't reach circular). Without this,
-- UCB would override persisted working strategies on every connection.
if st then st.policy_seeded = true end
return nil, nil
end

View file

@ -73,17 +73,17 @@ choose_capture_iface() {
ensure_dirs() {
mkdir -p "$CACHE_DIR" || return 1
chmod 777 "$CACHE_DIR" 2>/dev/null || true
chmod 755 "$CACHE_DIR" 2>/dev/null || true
}
init_output_files() {
[ -f "$ALL_TSV" ] || echo "# ts\thost\tip\tproto\tport\treason\tdetails" > "$ALL_TSV"
[ -f "$TCP_TSV" ] || echo "# ts\thost\tip\tproto\tport\treason\tdetails" > "$TCP_TSV"
[ -f "$UDP_TSV" ] || echo "# ts\thost\tip\tproto\tport\treason\tdetails" > "$UDP_TSV"
[ -f "$IPMAP_TSV" ] || echo "# ts\tip\thost" > "$IPMAP_TSV"
[ -f "$ALL_TSV" ] || printf '# ts\thost\tip\tproto\tport\treason\tdetails\n' > "$ALL_TSV"
[ -f "$TCP_TSV" ] || printf '# ts\thost\tip\tproto\tport\treason\tdetails\n' > "$TCP_TSV"
[ -f "$UDP_TSV" ] || printf '# ts\thost\tip\tproto\tport\treason\tdetails\n' > "$UDP_TSV"
[ -f "$IPMAP_TSV" ] || printf '# ts\tip\thost\n' > "$IPMAP_TSV"
[ -f "$ERR_LOG" ] || : > "$ERR_LOG"
[ -f "$PARSER_ERR_LOG" ] || : > "$PARSER_ERR_LOG"
chmod 666 "$ALL_TSV" "$TCP_TSV" "$UDP_TSV" "$IPMAP_TSV" "$ERR_LOG" "$PARSER_ERR_LOG" 2>/dev/null || true
chmod 644 "$ALL_TSV" "$TCP_TSV" "$UDP_TSV" "$IPMAP_TSV" "$ERR_LOG" "$PARSER_ERR_LOG" 2>/dev/null || true
}
collect_ports() {
@ -484,7 +484,7 @@ start_monitor() {
echo "# iface: $iface" >> "$ALL_TSV"
echo "# filter: $filter" >> "$ALL_TSV"
"$tcpdump_bin" -i "$iface" -nn -l -tt "$filter" 2>>"$ERR_LOG" | \
( "$tcpdump_bin" -i "$iface" -nn -l -tt "$filter" 2>>"$ERR_LOG" | \
awk \
-v all_out="$ALL_TSV" \
-v tcp_out="$TCP_TSV" \
@ -498,10 +498,10 @@ start_monitor() {
-v udp_min_out="4" \
-v udp_max_in="1" \
-v dedupe_sec="60" \
-f "$AWK_FILE" 2>>"$PARSER_ERR_LOG" &
-f "$AWK_FILE" 2>>"$PARSER_ERR_LOG" ) &
echo "$!" > "$PID_FILE"
chmod 666 "$PID_FILE" 2>/dev/null || true
chmod 644 "$PID_FILE" 2>/dev/null || true
sleep 1
if ! running_pid >/dev/null; then
@ -524,9 +524,9 @@ stop_monitor() {
return 0
fi
kill "$pid" 2>/dev/null || true
kill -- -"$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
sleep 1
kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null || true
kill -0 "$pid" 2>/dev/null && { kill -9 -- -"$pid" 2>/dev/null || kill -9 "$pid" 2>/dev/null || true; }
rm -f "$PID_FILE" 2>/dev/null || true
echo "blocked monitor stopped"
}

View file

@ -315,7 +315,7 @@ add_custom_domain() {
fi
# Проверить, не существует ли уже
if grep -qx "$domain" "$custom_list" 2>/dev/null; then
if grep -qxF "$domain" "$custom_list" 2>/dev/null; then
print_warning "Домен уже в списке: $domain"
return 0
fi
@ -343,8 +343,8 @@ remove_custom_domain() {
fi
# Удалить домен
if grep -qx "$domain" "$custom_list"; then
grep -vx "$domain" "$custom_list" > "${custom_list}.tmp"
if grep -qxF "$domain" "$custom_list"; then
grep -vxF "$domain" "$custom_list" > "${custom_list}.tmp"
mv "${custom_list}.tmp" "$custom_list"
print_success "Удален домен: $domain"
else
@ -720,24 +720,23 @@ show_current_config() {
print_separator
# Списки доменов
if [ -d "$LISTS_DIR" ]; then
print_info "Списки доменов:"
for list in discord.txt youtube.txt rkn.txt custom.txt; do
if [ -f "${LISTS_DIR}/${list}" ]; then
local count
count=$(wc -l < "${LISTS_DIR}/${list}" 2>/dev/null || echo "0")
printf " %-20s: %s доменов\n" "$list" "$count"
fi
done
local yt_quic_list="${ZAPRET2_DIR}/extra_strats/UDP/YT/List.txt"
if [ -f "$yt_quic_list" ]; then
local yt_quic_count
yt_quic_count=$(wc -l < "$yt_quic_list" 2>/dev/null || echo "0")
printf " %-20s: %s доменов\n" "extra_strats/UDP/YT/List.txt" "$yt_quic_count"
print_info "Списки доменов:"
local _list_path _list_label _list_count
for _list_label in "RKN TCP:${ZAPRET2_DIR}/extra_strats/TCP/RKN/List.txt" \
"YouTube TCP:${ZAPRET2_DIR}/extra_strats/TCP/YT/List.txt" \
"YouTube GV:--hostlist-domains" \
"QUIC YouTube:${ZAPRET2_DIR}/extra_strats/UDP/YT/List.txt" \
"Discord TCP:${ZAPRET2_DIR}/extra_strats/TCP_Discord.txt" \
"Custom:${LISTS_DIR}/custom.txt"; do
_list_path="${_list_label#*:}"
_list_label="${_list_label%%:*}"
if [ "$_list_path" = "--hostlist-domains" ]; then
printf " %-25s: googlevideo.com\n" "$_list_label"
elif [ -f "$_list_path" ]; then
_list_count=$(wc -l < "$_list_path" 2>/dev/null || echo "0")
printf " %-25s: %s доменов\n" "$_list_label" "$_list_count"
fi
else
print_info "Списки доменов: не установлены"
fi
done
print_separator
}
@ -858,10 +857,25 @@ restore_config() {
[Yy]|[Yy][Ee][Ss])
print_info "Восстановление..."
# Извлечь backup
tar -xzf "$latest_backup" -C "$CONFIG_DIR" 2>/dev/null
# Extract to a temp dir first, then move files to their correct locations.
# The tar archive contains files from both $CONFIG_DIR and $LISTS_DIR,
# but with different -C bases, so we cannot extract directly to one dir.
local tmpdir="${CONFIG_DIR}/backups/.restore_tmp"
rm -rf "$tmpdir"
mkdir -p "$tmpdir"
tar -xzf "$latest_backup" -C "$tmpdir" 2>/dev/null
if [ $? -eq 0 ]; then
# Move config files to CONFIG_DIR
for f in strategies.conf current_strategy; do
[ -f "${tmpdir}/${f}" ] && mv -f "${tmpdir}/${f}" "${CONFIG_DIR}/${f}"
done
# Move list files to LISTS_DIR
mkdir -p "$LISTS_DIR"
for f in custom.txt; do
[ -f "${tmpdir}/${f}" ] && mv -f "${tmpdir}/${f}" "${LISTS_DIR}/${f}"
done
rm -rf "$tmpdir"
print_success "Конфигурация восстановлена"
# Предложить перезапуск

View file

@ -9,6 +9,9 @@
generate_nfqws2_opt_from_strategies() {
# Генерирует NFQWS2_OPT для config файла на основе текущих стратегий
# Intentionally hardcoded: this function may be called before utils.sh sets
# the global CONFIG_DIR / ZAPRET2_DIR / LISTS_DIR variables, so we use
# local copies with known absolute paths.
local config_dir="/opt/etc/zapret2"
local extra_strats_dir="/opt/zapret2/extra_strats"
local lists_dir="/opt/zapret2/lists"
@ -32,15 +35,14 @@ AUSTERUS_OPT
fi
# Загрузить текущие стратегии из категорий
local youtube_tcp_tcp=""
local youtube_tcp=""
local youtube_gv_tcp=""
local rkn_tcp=""
local quic_udp=""
local discord_tcp=""
local discord_udp=""
# Прочитать стратегии из файлов категорий
if [ -f "${extra_strats_dir}/TCP/YT/Strategy.txt" ]; then
youtube_tcp_tcp=$(cat "${extra_strats_dir}/TCP/YT/Strategy.txt")
youtube_tcp=$(cat "${extra_strats_dir}/TCP/YT/Strategy.txt")
fi
if [ -f "${extra_strats_dir}/TCP/YT_GV/Strategy.txt" ]; then
@ -69,7 +71,7 @@ AUSTERUS_OPT
local default_strategy="--filter-tcp=443,2053,2083,2087,2096,8443 --filter-l7=tls --payload=tls_client_hello,http_req,http_reply,unknown,tls_server_hello --out-range=-s34228 --lua-desync=fake:blob=fake_default_tls:repeats=6"
# Использовать дефолт если стратегия пустая
[ -z "$youtube_tcp_tcp" ] && youtube_tcp_tcp="$default_strategy"
[ -z "$youtube_tcp" ] && youtube_tcp="$default_strategy"
[ -z "$youtube_gv_tcp" ] && youtube_gv_tcp="$default_strategy"
[ -z "$rkn_tcp" ] && rkn_tcp="$default_strategy"
@ -111,7 +113,7 @@ AUSTERUS_OPT
printf '%s' "$out"
}
youtube_tcp_tcp=$(ensure_circular_nld2 "$youtube_tcp_tcp")
youtube_tcp=$(ensure_circular_nld2 "$youtube_tcp")
youtube_gv_tcp=$(ensure_circular_nld2 "$youtube_gv_tcp")
rkn_tcp=$(ensure_circular_nld2 "$rkn_tcp")
quic_udp=$(ensure_circular_nld2 "$quic_udp")
@ -138,7 +140,7 @@ AUSTERUS_OPT
printf '%s' "$out"
}
youtube_tcp_tcp=$(ensure_circular_failure_detector "$youtube_tcp_tcp")
youtube_tcp=$(ensure_circular_failure_detector "$youtube_tcp")
youtube_gv_tcp=$(ensure_circular_failure_detector "$youtube_gv_tcp")
rkn_tcp=$(ensure_circular_failure_detector "$rkn_tcp")
quic_udp=$(ensure_circular_failure_detector "$quic_udp")
@ -197,7 +199,7 @@ AUSTERUS_OPT
printf '%s' "$out"
}
youtube_tcp_tcp=$(ensure_circular_payload_empty "$youtube_tcp_tcp")
youtube_tcp=$(ensure_circular_payload_empty "$youtube_tcp")
youtube_gv_tcp=$(ensure_circular_payload_empty "$youtube_gv_tcp")
rkn_tcp=$(ensure_circular_payload_empty "$rkn_tcp")
@ -212,6 +214,7 @@ AUSTERUS_OPT
nfqws2_opt_lines="$nfqws2_opt_lines$*\\n"
else
echo "WARN: hostlist file missing or empty: $list_path (skip profile)" 1>&2
print_warning "Hostlist missing or empty: $list_path — profile skipped"
fi
}
@ -221,7 +224,7 @@ AUSTERUS_OPT
add_hostlist_line "${extra_strats_dir}/TCP/RKN/List.txt" "--hostlist-exclude=${lists_dir}/whitelist.txt $rkn_hostlists $rkn_tcp --new"
# YouTube TCP
add_hostlist_line "${extra_strats_dir}/TCP/YT/List.txt" "--hostlist-exclude=${lists_dir}/whitelist.txt --hostlist=${extra_strats_dir}/TCP/YT/List.txt $youtube_tcp_tcp --new"
add_hostlist_line "${extra_strats_dir}/TCP/YT/List.txt" "--hostlist-exclude=${lists_dir}/whitelist.txt --hostlist=${extra_strats_dir}/TCP/YT/List.txt $youtube_tcp --new"
# YouTube GV (список доменов статичен)
nfqws2_opt_lines="$nfqws2_opt_lines--hostlist-exclude=${lists_dir}/whitelist.txt --hostlist-domains=googlevideo.com $youtube_gv_tcp --new\\n"
@ -248,7 +251,7 @@ AUSTERUS_OPT
# Strategy 5: fakedsplit at method+2 with badsum
# Strategy 6: z4r original (fake 0x0E + tcp_md5 + multisplit host+1)
# Strategy 7: fake badsum + multisplit method+2
add_hostlist_line "${extra_strats_dir}/TCP/RKN/List.txt" "--filter-tcp=80 --hostlist-exclude=${lists_dir}/whitelist.txt --hostlist=${extra_strats_dir}/TCP/RKN/List.txt --in-range=-s5556 --payload=http_req,empty --lua-desync=circular:fails=2:time=60:reset:key=http_rkn:nld=2 --lua-desync=http_methodeol:payload=http_req:dir=out:strategy=1 --lua-desync=syndata:payload=http_req:dir=out:strategy=2 --lua-desync=multisplit:payload=http_req:dir=out:strategy=2 --lua-desync=hostfakesplit:payload=http_req:dir=out:ip_ttl=2:repeats=1:strategy=3 --lua-desync=fake:payload=http_req:dir=out:blob=fake_default_http:badsum:repeats=1:strategy=4 --lua-desync=fakedsplit:payload=http_req:dir=out:pos=method+2:badsum:strategy=5 --lua-desync=fake:payload=http_req:dir=out:blob=0x0E0E0F0E:tcp_md5:strategy=6 --lua-desync=multisplit:payload=http_req:dir=out:pos=host+1:seqovl=2:strategy=6 --lua-desync=fake:payload=http_req:dir=out:blob=fake_default_http:badsum:repeats=1:strategy=7 --lua-desync=multisplit:payload=http_req:dir=out:pos=method+2:strategy=7 --in-range=x --new"
add_hostlist_line "${extra_strats_dir}/TCP/RKN/List.txt" "--filter-tcp=80 --hostlist-exclude=${lists_dir}/whitelist.txt --hostlist=${extra_strats_dir}/TCP/RKN/List.txt --in-range=-s5556 --payload=http_req,empty --lua-desync=circular:fails=2:time=60:reset:key=http_rkn:nld=2:failure_detector=z2k_tls_alert_fatal --lua-desync=http_methodeol:payload=http_req:dir=out:strategy=1 --lua-desync=syndata:payload=http_req:dir=out:strategy=2 --lua-desync=multisplit:payload=http_req:dir=out:strategy=2 --lua-desync=hostfakesplit:payload=http_req:dir=out:ip_ttl=2:repeats=1:strategy=3 --lua-desync=fake:payload=http_req:dir=out:blob=fake_default_http:badsum:repeats=1:strategy=4 --lua-desync=fakedsplit:payload=http_req:dir=out:pos=method+2:badsum:strategy=5 --lua-desync=fake:payload=http_req:dir=out:blob=0x0E0E0F0E:tcp_md5:strategy=6 --lua-desync=multisplit:payload=http_req:dir=out:pos=host+1:seqovl=2:strategy=6 --lua-desync=fake:payload=http_req:dir=out:blob=fake_default_http:badsum:repeats=1:strategy=7 --lua-desync=multisplit:payload=http_req:dir=out:pos=method+2:strategy=7 --in-range=x --new"
# Catch-all TCP profile for autohostlist failure tracking
# Upstream zapret appends --hostlist-auto to the very end of NFQWS2_OPT,
@ -412,7 +415,7 @@ NFQWS2_UDP_PKT_IN="3"
# ==============================================================================
# This section is auto-generated from z2k strategy database
# Each --new separator creates independent profile with own filters and strategy
# Order: CF TCP → RKN TCP → YouTube TCP → YouTube GV → QUIC YT → QUIC Cloudflare → QUIC Custom → Discord TCP → Discord UDP → Custom
# Order: RKN TCP → YouTube TCP → YouTube GV → QUIC YT → Discord UDP → HTTP RKN → Catch-all TCP
# Profiles use explicit hostlists from z2k list files without placeholder expansion.
# This avoids mixing with global hostlists from MODE_FILTER.
CONFIG

View file

@ -113,15 +113,18 @@ step_update_packages() {
printf "\n"
# 1. Проверка архитектуры системы
local sys_arch=$(uname -m)
local sys_arch
sys_arch=$(uname -m)
print_info "Архитектура системы: $sys_arch"
# 2. Проверка архитектуры Entware
if [ -f "/opt/etc/opkg.conf" ]; then
local entware_arch=$(grep -m1 "^arch" /opt/etc/opkg.conf | awk '{print $2}')
local entware_arch
entware_arch=$(grep -m1 "^arch" /opt/etc/opkg.conf | awk '{print $2}')
print_info "Архитектура Entware: ${entware_arch:-не определена}"
local repo_url=$(grep -m1 "^src" /opt/etc/opkg.conf | awk '{print $3}')
local repo_url
repo_url=$(grep -m1 "^src" /opt/etc/opkg.conf | awk '{print $3}')
print_info "Репозиторий: $repo_url"
# 3. Проверка доступности репозитория
@ -141,7 +144,8 @@ step_update_packages() {
print_error "[FAIL] opkg --version падает (Illegal instruction)"
print_warning "ПРИЧИНА: opkg установлен для неправильной архитектуры CPU!"
elif opkg --version >/dev/null 2>&1; then
local opkg_version=$(opkg --version 2>&1 | head -1)
local opkg_version
opkg_version=$(opkg --version 2>&1 | head -1)
print_success "[OK] opkg бинарник запускается: $opkg_version"
print_warning "Но 'opkg update' падает - возможно проблема в зависимости или скрипте"
else
@ -151,7 +155,8 @@ step_update_packages() {
# 5. Проверка файла opkg
if command -v file >/dev/null 2>&1; then
if [ -f "/opt/bin/opkg" ]; then
local opkg_file_info=$(file /opt/bin/opkg 2>&1 | head -1)
local opkg_file_info
opkg_file_info=$(file /opt/bin/opkg 2>&1 | head -1)
print_info "Бинарник opkg: $opkg_file_info"
fi
fi
@ -445,7 +450,7 @@ unzip
# Проверить busybox gzip
if command -v gzip >/dev/null 2>&1; then
if readlink "$(which gzip)" 2>/dev/null | grep -q busybox; then
if readlink "$(command -v gzip)" 2>/dev/null | grep -q busybox; then
print_info "Обнаружен busybox gzip (медленный, ~3x медленнее GNU)"
printf "Установить GNU gzip для ускорения обработки списков? [y/N]: "
read -r answer </dev/tty
@ -466,7 +471,7 @@ unzip
# Проверить busybox sort
if command -v sort >/dev/null 2>&1; then
if readlink "$(which sort)" 2>/dev/null | grep -q busybox; then
if readlink "$(command -v sort)" 2>/dev/null | grep -q busybox; then
print_info "Обнаружен busybox sort (медленный, использует много RAM)"
printf "Установить GNU sort для ускорения? [y/N]: "
read -r answer </dev/tty
@ -490,7 +495,7 @@ unzip
}
# ==============================================================================
# ШАГ 3: ЗАГРУЗКА МОДУЛЕЙ ЯДРА
# ШАГ 4: ЗАГРУЗКА МОДУЛЕЙ ЯДРА
# ==============================================================================
step_load_kernel_modules() {
@ -507,7 +512,7 @@ step_load_kernel_modules() {
}
# ==============================================================================
# ШАГ 4: УСТАНОВКА ZAPRET2 (ИСПОЛЬЗУЯ ОФИЦИАЛЬНЫЙ install_bin.sh)
# ШАГ 5: УСТАНОВКА ZAPRET2 (ИСПОЛЬЗУЯ ОФИЦИАЛЬНЫЙ install_bin.sh)
# ==============================================================================
step_build_zapret2() {
@ -678,7 +683,8 @@ step_build_zapret2() {
print_info "Вывод --version:"
./nfq2/nfqws2 --version 2>&1 | head -5 || true
else
local version=$(./nfq2/nfqws2 --version 2>&1 | head -1)
local version
version=$(./nfq2/nfqws2 --version 2>&1 | head -1)
print_success "nfqws2 работает: $version"
fi
@ -689,7 +695,7 @@ step_build_zapret2() {
print_info "Установка в $ZAPRET2_DIR..."
cd "$build_dir" || return 1
mv "$release_dir" "$ZAPRET2_DIR" || return 1
cp -a "$release_dir" "$ZAPRET2_DIR" && rm -rf "$release_dir" || return 1
# ВАЖНО: Обновить ZAPRET_BASE на финальный путь (был /tmp/zapret2_build/...)
export ZAPRET_BASE="$ZAPRET2_DIR"
@ -735,6 +741,8 @@ step_build_zapret2() {
print_info "Copying snapshot domain lists..."
mkdir -p "${ZAPRET2_DIR}/files/lists"
cp -Rf "${WORK_DIR}/files/lists/"* "${ZAPRET2_DIR}/files/lists/" 2>/dev/null || true
# Strip CRLF from list files
find "${ZAPRET2_DIR}" -name "*.txt" -path "*/extra_strats/*" -exec sed -i 's/\r$//' {} + 2>/dev/null || true
fi
# Decompress lua.gz files (if any are shipped by embedded builds)
if [ -d "${ZAPRET2_DIR}/lua" ]; then
@ -764,13 +772,16 @@ step_build_zapret2() {
mkdir -p "${ZAPRET2_DIR}/lua"
mkdir -p "${ZAPRET2_DIR}/extra_strats/cache/orchestra"
mkdir -p "${ZAPRET2_DIR}/extra_strats/cache/autocircular"
chmod 777 "${ZAPRET2_DIR}/extra_strats/cache/autocircular" 2>/dev/null || true
chmod 755 "${ZAPRET2_DIR}/extra_strats/cache/autocircular" 2>/dev/null || true
chown nobody "${ZAPRET2_DIR}/extra_strats/cache/autocircular" 2>/dev/null || true
: > "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv" 2>/dev/null || true
chmod 666 "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv" 2>/dev/null || true
chmod 644 "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv" 2>/dev/null || true
chown nobody "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv" 2>/dev/null || true
# Debug is opt-in. Keep log file prepared, but do not enable verbose logging by default.
rm -f "${ZAPRET2_DIR}/extra_strats/cache/autocircular/debug.flag" 2>/dev/null || true
: > "${ZAPRET2_DIR}/extra_strats/cache/autocircular/debug.log" 2>/dev/null || true
chmod 666 "${ZAPRET2_DIR}/extra_strats/cache/autocircular/debug.log" 2>/dev/null || true
chmod 644 "${ZAPRET2_DIR}/extra_strats/cache/autocircular/debug.log" 2>/dev/null || true
chown nobody "${ZAPRET2_DIR}/extra_strats/cache/autocircular/debug.log" 2>/dev/null || true
if curl -fsSL "https://raw.githubusercontent.com/AloofLibra/zapret4rocket/z2r/orchestra/locked.lua" \
-o "${ZAPRET2_DIR}/lua/locked.lua"; then
@ -834,7 +845,7 @@ step_build_zapret2() {
}
# ==============================================================================
# ШАГ 5: ПРОВЕРКА УСТАНОВКИ
# ШАГ 6: ПРОВЕРКА УСТАНОВКИ
# ==============================================================================
step_verify_installation() {
@ -1044,7 +1055,7 @@ step_download_domain_lists() {
}
# ==============================================================================
# ШАГ 7: ОТКЛЮЧЕНИЕ HARDWARE NAT
# ШАГ 9: ОТКЛЮЧЕНИЕ HARDWARE NAT
# ==============================================================================
step_disable_hwnat_and_offload() {
@ -1133,7 +1144,13 @@ step_configure_tmpdir() {
# Получить объём RAM
local ram_mb
if [ -f "${ZAPRET2_DIR}/common/base.sh" ]; then
# Save overrides before re-sourcing
_saved_linux_ipt_avail="$(type linux_ipt_avail 2>/dev/null)"
. "${ZAPRET2_DIR}/common/base.sh"
# Restore override if it was set
if [ -n "$_saved_linux_ipt_avail" ]; then
linux_ipt_avail() { true; }
fi
ram_mb=$(get_ram_mb)
else
# Fallback: определить RAM вручную
@ -1265,7 +1282,7 @@ step_create_config_and_init() {
}
# ==============================================================================
# ШАГ 9: УСТАНОВКА NETFILTER ХУКА
# ШАГ 11: УСТАНОВКА NETFILTER ХУКА
# ==============================================================================
step_install_netfilter_hook() {
@ -1357,7 +1374,7 @@ HOOK
}
# ==============================================================================
# ШАГ 10: ФИНАЛИЗАЦИЯ
# ШАГ 12: ФИНАЛИЗАЦИЯ
# ==============================================================================
step_finalize() {
@ -1496,7 +1513,7 @@ step_finalize() {
printf " %-25s: %s\n" "Конфигурация" "$CONFIG_DIR"
printf " %-25s: %s\n" "Списки доменов" "$LISTS_DIR"
printf " %-25s: %s\n" "Стратегии" "$STRATEGIES_CONF"
printf " %-25s: %s\n" "Tools" "$tools_dir"
printf " %-25s: %s\n" "Tools" "${ZAPRET2_DIR}/ip2net, ${ZAPRET2_DIR}/mdig"
# Save local z2k entrypoint for future runs without curl.
local local_z2k_script="${ZAPRET2_DIR}/z2k.sh"

View file

@ -428,74 +428,6 @@ SUBMENU
pause
}
# ==============================================================================
# ПОДМЕНЮ: ПРОСМОТР СТРАТЕГИИ
# ==============================================================================
menu_view_strategy() {
clear_screen
print_header "[5] Текущие стратегии"
if ! is_zapret2_installed; then
print_error "zapret2 не установлен"
pause
return
fi
# Проверить наличие файла с категориями
if [ -f "$CATEGORY_STRATEGIES_CONF" ]; then
print_info "Стратегии по категориям:"
print_separator
# Прочитать и показать стратегии для каждой категории
while IFS=':' read -r category strategy score; do
[ -z "$category" ] && continue
local params
local type
params=$(get_strategy "$strategy" 2>/dev/null)
type=$(get_strategy_type "$strategy" 2>/dev/null)
printf "\n[%s]\n" "$(echo "$category" | tr '[:lower:]' '[:upper:]')"
printf " Стратегия: #%s (оценка: %s/5)\n" "$strategy" "$score"
printf " Тип: %s\n" "$type"
done < "$CATEGORY_STRATEGIES_CONF"
print_separator
else
# Старый режим - одна стратегия
local current
current=$(get_current_strategy)
if [ "$current" = "не задана" ] || [ -z "$current" ]; then
print_warning "Стратегия не выбрана"
print_info "Используется стратегия по умолчанию из init скрипта"
else
print_info "Текущая стратегия: #$current"
print_separator
local params
params=$(get_strategy "$current")
local type
type=$(get_strategy_type "$current")
printf "Тип: %s\n\n" "$type"
printf "Параметры:\n%s\n" "$params"
print_separator
fi
fi
# Показать статус сервиса
printf "\nСтатус сервиса: %s\n" "$(get_service_status)"
if is_zapret2_running; then
printf "\nПроцессы nfqws2:\n"
pgrep -af "nfqws2" 2>/dev/null || print_info "Процессы не найдены"
fi
pause
}
# ==============================================================================
# ПОДМЕНЮ: ОБНОВЛЕНИЕ СПИСКОВ
# ==============================================================================
@ -561,6 +493,8 @@ SUBMENU
restore_config
;;
3)
print_warning "Это сбросит всю конфигурацию к значениям по умолчанию!"
confirm "Вы уверены?" "N" || { pause; return; }
reset_config
;;
[Bb])
@ -1065,6 +999,7 @@ EOF
print_success "Файл whitelist создан: $whitelist_file"
fi
while true; do
print_separator
cat <<'INFO'
@ -1125,14 +1060,14 @@ INFO
if ! echo "$new_domain" | grep -qE '^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'; then
print_error "Неверный формат домена: $new_domain"
pause
return 1
continue
fi
# Проверить дубликаты
if grep -qx "$new_domain" "$whitelist_file"; then
if grep -qxF "$new_domain" "$whitelist_file"; then
print_warning "Домен $new_domain уже в whitelist"
pause
return 0
continue
fi
# Добавить домен
@ -1155,14 +1090,14 @@ INFO
read_input del_domain
# Проверить наличие
if ! grep -qx "$del_domain" "$whitelist_file"; then
if ! grep -qxF "$del_domain" "$whitelist_file"; then
print_error "Домен $del_domain не найден в whitelist"
pause
return 1
continue
fi
# Удалить домен
sed -i "/^${del_domain}$/d" "$whitelist_file"
grep -vxF "$del_domain" "$whitelist_file" > "${whitelist_file}.tmp" && mv "${whitelist_file}.tmp" "$whitelist_file"
print_success "Домен $del_domain удален из whitelist"
print_separator
@ -1184,6 +1119,7 @@ INFO
pause
;;
esac
done
}
# ==============================================================================

View file

@ -3,19 +3,6 @@
# Парсинг, тестирование, применение стратегий из strats_new2.txt
# QUIC/UDP стратегии берутся из quic_strats.ini
# ==============================================================================
# КОНСТАНТЫ ДЛЯ СТРАТЕГИЙ
# ==============================================================================
# Домены для тестирования стратегий
TEST_DOMAINS="
http://rutracker.org
https://rutracker.org
https://www.youtube.com
https://discord.com
https://googlevideo.com
"
# ==============================================================================
# РАБОТА С ФАЙЛАМИ СТРАТЕГИЙ ПО КАТЕГОРИЯМ (CONFIG-DRIVEN АРХИТЕКТУРА)
# ==============================================================================
@ -446,11 +433,7 @@ apply_strategy() {
# Построить полные TCP параметры
local tcp_params
if [ "$type" = "http" ]; then
tcp_params=$(build_http_profile_params "$params")
else
tcp_params=$(build_tls_profile_params "$params")
fi
tcp_params=$(build_tls_profile_params "$params")
# Получить текущие QUIC параметры
local udp_params
@ -723,6 +706,7 @@ EOF
local args=""
while IFS= read -r line; do
line=$(printf '%s' "$line" | sed 's/\r$//')
line=$(echo "$line" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
[ -z "$line" ] && continue
case "$line" in
@ -1232,148 +1216,6 @@ test_strategy_range() {
fi
}
# ==============================================================================
# ПРИМЕНЕНИЕ СТРАТЕГИЙ ПО КАТЕГОРИЯМ
# ==============================================================================
# Применить разные стратегии для разных категорий
# Параметр: строка вида "youtube:4:5 discord:7:4 custom:11:3"
apply_category_strategies() {
local category_strategies=$1
local init_script="${INIT_SCRIPT:-/opt/etc/init.d/S99zapret2}"
if [ -z "$category_strategies" ]; then
print_error "Не указаны стратегии для категорий"
return 1
fi
if [ ! -f "$init_script" ]; then
print_error "Init скрипт не найден: $init_script"
return 1
fi
print_info "Применение стратегий по категориям..."
# Обработать каждую категорию
for entry in $category_strategies; do
local category=$(echo "$entry" | cut -d: -f1)
local strategy_num=$(echo "$entry" | cut -d: -f2)
local score=$(echo "$entry" | cut -d: -f3)
print_info " $category -> стратегия #$strategy_num (оценка: $score/5)"
# Получить параметры стратегии
local params
params=$(get_strategy "$strategy_num")
if [ -z "$params" ]; then
print_warning "Стратегия #$strategy_num не найдена, пропускаем $category"
continue
fi
# Конвертировать в TCP/UDP профили
local tcp_params
local udp_params
# Определить тип стратегии
local type
type=$(get_strategy_type "$strategy_num")
if [ "$type" = "https" ]; then
tcp_params="--filter-tcp=443 --filter-l7=tls --payload=tls_client_hello ${params}"
udp_params=""
else
tcp_params="--filter-tcp=80,443 --filter-l7=http ${params}"
udp_params=""
fi
# Обновить маркеры в init скрипте
case "$category" in
youtube)
update_init_section "YOUTUBE" "$tcp_params" "$udp_params" "$init_script"
;;
discord)
update_init_section "DISCORD" "$tcp_params" "$udp_params" "$init_script"
;;
custom)
update_init_section "CUSTOM" "$tcp_params" "$udp_params" "$init_script"
;;
esac
done
print_success "Стратегии применены к init скрипту"
# Перезапустить сервис
print_info "Перезапуск сервиса..."
"$init_script" restart >/dev/null 2>&1
sleep 2
if is_zapret2_running; then
print_success "Сервис перезапущен с новыми стратегиями"
return 0
else
print_warning "Сервис не запустился, проверьте логи"
return 1
fi
}
# Обновить секцию в init скрипте для конкретной категории
update_init_section() {
local marker=$1
local tcp_params=$2
local udp_params=$3
local init_script=$4
local start_marker="${marker}_MARKER_START"
local end_marker="${marker}_MARKER_END"
# Создать временный файл
local temp_file="${init_script}.tmp"
# Флаг - внутри ли мы секции для замены
local inside_section=0
local found_section=0
while IFS= read -r line; do
if echo "$line" | grep -q "# ${start_marker}"; then
# Начало секции - записать маркер и новые параметры
echo "$line"
echo "${marker}_TCP=\"${tcp_params}\""
echo "${marker}_UDP=\"${udp_params}\""
inside_section=1
found_section=1
elif echo "$line" | grep -q "# ${end_marker}"; then
# Конец секции - записать маркер и выйти из режима
echo "$line"
inside_section=0
elif [ "$inside_section" -eq 0 ]; then
# Вне секции - просто копировать
echo "$line"
fi
# Внутри секции - пропускать старые строки (кроме маркеров)
done < "$init_script" > "$temp_file"
# Если секции не было в файле - добавить в конец
if [ "$found_section" -eq 0 ]; then
{
echo ""
echo "# ${start_marker}"
echo "${marker}_TCP=\"${tcp_params}\""
echo "${marker}_UDP=\"${udp_params}\""
echo "# ${end_marker}"
} >> "$temp_file"
fi
# Заменить init скрипт
mv "$temp_file" "$init_script" || {
print_error "Не удалось обновить init скрипт"
return 1
}
chmod +x "$init_script"
}
# ==============================================================================
# АВТОТЕСТ QUIC СТРАТЕГИЙ
# ==============================================================================
@ -2188,33 +2030,6 @@ run_blockcheck_http() {
return 0
}
get_init_tcp_params() {
local marker=$1
local init_script=$2
if [ ! -f "$init_script" ]; then
return 1
fi
local line
line=$(grep "^${marker}_TCP=" "$init_script" 2>/dev/null | head -n 1)
echo "$line" | sed "s/^${marker}_TCP=\"//" | sed 's/\"$//'
}
# Получить текущие UDP параметры из init скрипта для секции
get_init_udp_params() {
local marker=$1
local init_script=$2
if [ ! -f "$init_script" ]; then
return 1
fi
local line
line=$(grep "^${marker}_UDP=" "$init_script" 2>/dev/null | head -n 1)
echo "$line" | sed "s/^${marker}_UDP=\"//" | sed 's/\"$//'
}
# Применить разные стратегии для YouTube TCP, YouTube GV, RKN (Z4R метод)
# Параметры: номера стратегий для каждой категории
apply_category_strategies_v2() {

View file

@ -70,59 +70,3 @@ init_system_vars() {
return 0
}
# ==============================================================================
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
# ==============================================================================
# Определить является ли система Keenetic
is_keenetic() {
# Проверка наличия Keenetic-специфичных файлов
[ -f /opt/etc/init.d/rc.func ] && return 0
# Проверка на NDM (Keenetic firmware)
[ -d /opt/etc/ndm ] && return 0
return 1
}
# Получить версию Keenetic firmware (если доступно)
get_keenetic_version() {
if [ -f /etc/os-release ]; then
grep VERSION_ID /etc/os-release | cut -d'=' -f2 | tr -d '"'
elif command -v ndmc >/dev/null 2>&1; then
ndmc -c "show version" 2>/dev/null | grep "release:" | awk '{print $2}'
else
echo "unknown"
fi
}
# ==============================================================================
# KEENETIC СПЕЦИФИЧНЫЕ ПРОВЕРКИ
# ==============================================================================
check_keenetic_specifics() {
if is_keenetic; then
print_info "Обнаружен роутер Keenetic"
local fw_version
fw_version=$(get_keenetic_version)
[ "$fw_version" != "unknown" ] && print_info "Firmware версия: $fw_version"
# Проверить наличие NDM hooks
if [ -d /opt/etc/ndm ]; then
print_info "NDM hooks доступны"
fi
# Проверить fastnat
if [ -f /sys/kernel/fastnat/mode ]; then
local fastnat_mode
fastnat_mode=$(cat /sys/kernel/fastnat/mode 2>/dev/null || echo "unknown")
print_info "Fastnat mode: $fastnat_mode"
fi
return 0
fi
return 1
}

View file

@ -40,7 +40,6 @@ Z2R_BASE_URL="https://raw.githubusercontent.com/AloofLibra/zapret4rocket/z2r"
# Файлы конфигурации
STRATEGIES_CONF="${CONFIG_DIR}/strategies.conf"
HTTP_STRATEGIES_CONF="${CONFIG_DIR}/http_strategies.conf"
CURRENT_STRATEGY_FILE="${CONFIG_DIR}/current_strategy"
QUIC_STRATEGIES_CONF="${CONFIG_DIR}/quic_strategies.conf"
QUIC_STRATEGY_FILE="${CONFIG_DIR}/quic_strategy.conf"
@ -387,7 +386,7 @@ verify_binary() {
check_kernel_module() {
local module=$1
if lsmod | grep -q "^${module}"; then
if lsmod | grep -q "^${module} "; then
return 0
else
return 1

49
z2k.sh
View file

@ -243,7 +243,7 @@ quic_5.bin
quic_test_00.bin
"
echo "$files" | while read -r file; do
while read -r file; do
[ -z "$file" ] && continue
local url="${GITHUB_RAW}/files/fake/${file}"
local output="${fake_dir}/${file}"
@ -252,7 +252,9 @@ quic_test_00.bin
else
die "Ошибка загрузки files/fake/${file}"
fi
done
done <<EOF
$files
EOF
}
download_init_script() {
@ -320,7 +322,7 @@ extra_strats/TCP/RKN/Discord.txt
extra_strats/UDP/YT/List.txt
"
echo "$list_files" | while read -r list_file; do
while read -r list_file; do
[ -z "$list_file" ] && continue
local list_url="${GITHUB_RAW}/files/lists/${list_file}"
local list_out="${lists_dir}/${list_file}"
@ -331,7 +333,9 @@ extra_strats/UDP/YT/List.txt
else
die "Ошибка загрузки files/lists/${list_file}"
fi
done
done <<EOF
$list_files
EOF
}
generate_strategies_database() {
@ -365,10 +369,10 @@ generate_strategies_database() {
show_welcome() {
clear_screen
cat <<'EOF'
cat <<EOF
+===================================================+
| z2k - Zapret2 для Keenetic (BETA) |
| Версия 2.0.0 |
| Версия $Z2K_VERSION |
+===================================================+
[INFO] Проект в активной разработке. Статус: beta.
@ -380,18 +384,6 @@ EOF
print_info "Инициализация..."
}
check_installation_status() {
if is_zapret2_installed; then
print_info "zapret2 уже установлен"
print_info "Статус сервиса: $(get_service_status)"
print_info "Текущая стратегия: #$(get_current_strategy)"
return 0
else
print_info "zapret2 не установлен"
return 1
fi
}
prompt_install_or_menu() {
printf "\n"
@ -401,6 +393,7 @@ prompt_install_or_menu() {
show_main_menu
else
print_info "zapret2 не установлен - запускаю установку..."
check_root || die "Требуются права root для установки"
run_full_install
fi
}
@ -503,6 +496,13 @@ update_z2k() {
local current_script
current_script=$(readlink -f "$0")
case "$current_script" in
*/sh|*/bash|*/ash|*/dash)
print_error "Cannot self-update: script was run via pipe. Please download and run directly."
return 1
;;
esac
print_info "Текущая версия: $Z2K_VERSION"
print_info "Загрузка последней версии..."
@ -552,6 +552,18 @@ update_z2k() {
# ==============================================================================
main() {
# Early-exit for help/version — no downloads needed
case "$1" in
help|h|-h|--help)
show_help
exit 0
;;
version|v|--version)
echo "z2k v${Z2K_VERSION}"
exit 0
;;
esac
# Показать приветствие
show_welcome
@ -559,6 +571,7 @@ main() {
check_environment
# Инициализировать рабочую директорию
rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR" "$LIB_DIR"
# Установить обработчики сигналов (будет переопределено после загрузки utils.sh)

View file

@ -93,7 +93,7 @@ log_info "Очистка iptables правил zapret/zapret2..."
# Список известных цепочек zapret/zapret2
chains_mangle="ZAPRET ZAPRET2 z2k_connmark"
chains_nat="z2k_masq_fix"
chains_nat="z2k_masq_fix zapret2_nat"
chains_raw="z2k_dpi_rst"
# Удаление из mangle