fix(memory): ограничить чтения неограниченного вывода в память

На роутере со 128 МБ несколько мест читали в память вывод, размер которого
ничем не ограничен. Распаковка gzip подписки шла через пайп с чтением целиком:
скачивание curl ограничено 15 МБ, но коэффициент сжатия base64-текста высокий,
так что пик доходил до десятков мегабайт. Оба вызывающих места передают файл и
ждут файл, поэтому распакованный поток теперь уходит редиректом сразу в
выходной файл, а успех определяется по коду возврата и ненулевому размеру.

Таблица nftables содержит сеты с десятками тысяч элементов из списков, и
`nft list table` печатает их все. Проба контроллера делала это каждый тик,
хотя ищет только имена цепочек и правил; четыре проверки доктора и вывод
чужих меток из полного рулесета — то же самое. Везде добавлен grep на
источнике, отбирающий только те строки, которые код потом и разбирает.

Два просмотра логов читали кольцевой буфер syslog целиком, чтобы показать
несколько сотен строк; теперь количество строк ограничено на стороне logread.
This commit is contained in:
Dushnilin
2026-08-08 23:02:18 +03:00
parent e6e3853f05
commit 106ce9352a
3 changed files with 16 additions and 19 deletions
+9 -7
View File
@@ -14,6 +14,7 @@ const TACHYON_SERVICE_NAME = getenv("TACHYON_SERVICE_NAME") || constants.TACHYON
const TACHYON_RELEASE_REPO = getenv("TACHYON_RELEASE_REPO") || constants.TACHYON_RELEASE_REPO || "Dushnilin/tachyon";
const TACHYON_LUCI_VIEW_DIR = getenv("TACHYON_LUCI_VIEW_DIR") || constants.TACHYON_LUCI_VIEW_DIR || "/www/luci-static/resources/view/tachyon";
const RUNTIME_STATE_DIR = getenv("TACHYON_RUNTIME_STATE_DIR") || "/var/run/tachyon";
const LOGREAD_LINE_LIMIT = "500";
const SYSTEM_INFO_CACHE_FILE = getenv("TACHYON_SYSTEM_INFO_CACHE_FILE") || RUNTIME_STATE_DIR + "/system-info.json";
const SYSTEM_INFO_CACHE_TTL = int(getenv("TACHYON_SYSTEM_INFO_CACHE_TTL") || "3600");
const TMP_SING_BOX_FOLDER = getenv("TMP_SING_BOX_FOLDER") || constants.TMP_SING_BOX_FOLDER || "/tmp/sing-box";
@@ -955,7 +956,7 @@ function check_logs() {
nolog("Error: logread command not found");
return 1;
}
let rendered = status_capture([ "tachyon-logs" ], command_output_from_args([ "logread" ]));
let rendered = status_capture([ "tachyon-logs" ], command_output_from_args([ "logread", "-l", LOGREAD_LINE_LIMIT ]));
if (rendered.output != "")
print(rendered.output);
if (rendered.status != 0) {
@@ -970,7 +971,7 @@ function check_sing_box_logs() {
nolog("Error: logread command not found");
return 1;
}
let rendered = status_capture([ "matching-log-tail", "sing-box", "100" ], command_output_from_args([ "logread" ]));
let rendered = status_capture([ "matching-log-tail", "sing-box", "100" ], command_output_from_args([ "logread", "-l", LOGREAD_LINE_LIMIT ]));
if (rendered.output != "")
print(rendered.output);
if (rendered.status != 0) {
@@ -1947,7 +1948,8 @@ function global_check(arg1, arg2) {
if (nft_check_json != "") {
let nft_render = render_or_fail([ "global-nft-check" ], nft_check_json, "тЭМ Failed to parse NFT rules info", [ 0 ]);
if (nft_render == 0 && status_success([ "global-nft-other-mark-exists" ], nft_check_json))
print(status_output([ "nft-ruleset-other-mark-lines", NFT_TABLE_NAME ], command_output_from_args([ "nft", "list", "ruleset" ])));
print(status_output([ "nft-ruleset-other-mark-lines", NFT_TABLE_NAME ],
command_output_from_args([ "sh", "-c", "nft list ruleset | grep -E '^table|mark set|meta mark'; exit 0" ])));
}
else
print_global("тЭМ Failed to get NFT rules info");
@@ -2192,7 +2194,7 @@ function run_doctor_checks() {
// 3. Nftables Table Check
let routing_mode = cfg.routing_mode || "nftables";
if (routing_mode == "nftables") {
let out_nft = command_capture("nft list table inet " + NFT_TABLE_NAME).output;
let out_nft = command_capture("nft list table inet " + NFT_TABLE_NAME + " | grep tproxy").output;
if (index(out_nft, "tproxy") >= 0) {
doc_check("✅", "nftables table", "present", "");
} else {
@@ -2202,7 +2204,7 @@ function run_doctor_checks() {
issues++;
command_status("nft delete table inet " + NFT_TABLE_NAME + " >/dev/null 2>&1");
let rebuild_status = command_status("/usr/bin/tachyon restart >/dev/null 2>&1");
let out_nft_check = command_capture("nft list table inet " + NFT_TABLE_NAME).output;
let out_nft_check = command_capture("nft list table inet " + NFT_TABLE_NAME + " | grep tproxy").output;
if (index(out_nft_check, "tproxy") >= 0) {
doc_check("❌", "nftables table", "missing or incomplete", "→ FIXED: правила пересозданы");
fixed++;
@@ -2532,7 +2534,7 @@ function run_doctor_checks() {
// 11. MSS Clamping Check
if (routing_mode == "nftables") {
let out_clamping = command_capture("nft list table inet " + NFT_TABLE_NAME).output;
let out_clamping = command_capture("nft list table inet " + NFT_TABLE_NAME + " | grep maxseg").output;
if (index(out_clamping, "tcp flags syn tcp option maxseg size set rt mtu") >= 0 || index(out_clamping, "tcp flags syn tcp option maxseg size set 1400") >= 0) {
doc_check("✅", "MSS Clamping rule", "active", "");
} else {
@@ -2549,7 +2551,7 @@ function run_doctor_checks() {
command_status("nft add rule inet " + NFT_TABLE_NAME + " mangle_output tcp flags syn tcp option maxseg size set 1400 >/dev/null 2>&1");
}
let out_clamping_check = command_capture("nft list table inet " + NFT_TABLE_NAME).output;
let out_clamping_check = command_capture("nft list table inet " + NFT_TABLE_NAME + " | grep maxseg").output;
if (index(out_clamping_check, "tcp flags syn tcp option maxseg size set rt mtu") >= 0 || index(out_clamping_check, "tcp flags syn tcp option maxseg size set 1400") >= 0) {
doc_check("❌", "MSS Clamping rule", "missing", "→ FIXED: MSS Clamping rules applied");
fixed++;
@@ -630,8 +630,8 @@ function controller(bus, opts) {
let nft_table = getenv("NFT_TABLE_NAME") || "TachyonTable";
let routing_mode = setting("routing_mode", "nftables");
// One `nft list` serves both the table check and the QoS check.
let out_nft = command_output_from_args(["nft", "list", "table", "inet", nft_table]);
let out_nft = command_output_from_args(["sh", "-c",
"nft list table inet " + nft_table + " | grep -E 'chain|tproxy|priority_rules|dscp'; exit 0"]);
if (routing_mode == "nftables") {
if (index(out_nft, "tproxy") < 0 || index(out_nft, "priority_rules") < 0) {
+5 -10
View File
@@ -2790,17 +2790,12 @@ function gzip_decode_file(input_file, output_file) {
return false;
for (let command in [ "gzip -dc", "gunzip -c", "zcat" ]) {
let pipe = fs.popen(command + " " + shell_quote(input_file) + " 2>/dev/null", "r");
if (!pipe)
continue;
let data = pipe.read("all");
let status = pipe.close();
if (status == 0 && data != null && data != "") {
if (fs.writefile(output_file, data))
let status = system(command + " " + shell_quote(input_file) +
" > " + shell_quote(output_file) + " 2>/dev/null");
if (status == 0) {
let stat = fs.stat(output_file);
if (stat && int(stat.size) > 0)
return true;
fs.unlink(output_file);
return false;
}
fs.unlink(output_file);
}