diff --git a/fe-app-tachyon/fill-po-translations.js b/fe-app-tachyon/fill-po-translations.js index fc194a3f..ea61e6f9 100644 --- a/fe-app-tachyon/fill-po-translations.js +++ b/fe-app-tachyon/fill-po-translations.js @@ -1357,8 +1357,8 @@ const dictionary = { "Tachyon has been installed": "Tachyon успешно установлен", "via Proxy": "через Прокси", "via WAN": "через WAN", - "List must be an HTTP(S) URL or a local .lst / .txt path": "Список должен быть HTTP(S) URL или локальным путём .lst / .txt", - "Prioritizes interactive DNS, VoIP/WebRTC, gaming and TCP ACK packets via DSCP marking in nftables. In Auto mode, automatically yields if OpenWrt SQM (CAKE/FQ_CoDel) is detected to prevent bufferbloat degradation.": "Приоритизирует интерактивный DNS, VoIP/WebRTC, игровой трафик и пакеты TCP ACK через DSCP-маркировку в nftables. В режиме Авто автоматически уступает приоритет при обнаружении OpenWrt SQM (CAKE/FQ_CoDel) для предотвращения ухудшения bufferbloat.", + "Prioritizes interactive DNS, VoIP/WebRTC and gaming via DSCP marking in nftables. Works cooperatively with OpenWrt SQM (CAKE/FQ_CoDel) without bufferbloat degradation.": "Приоритизирует интерактивный DNS, VoIP/WebRTC и игровой трафик через DSCP-маркировку в nftables. Работает в кооперации с OpenWrt SQM (CAKE/FQ_CoDel) без ухудшения bufferbloat.", + "Auto (cooperative with SQM/CAKE)": "Авто (кооперация с SQM/CAKE)", "QoS Priority Engine (DSCP)": "Движок приоритета QoS (DSCP)", "Strategy applied successfully and service reloaded!": "Стратегия успешно применена, сервис перезапущен!", "Subnet %s is a Cloudflare Anycast range used by Discord Voice. Only UDP voice traffic (ports 50000-65535, 3478, 19302, 5000-5020) is routed through this section; web traffic (TCP 80/443) goes direct.": "Подсеть %s — диапазон Cloudflare Anycast, используемый Discord Voice. Только голосовой UDP-трафик (порты 50000-65535, 3478, 19302, 5000-5020) направляется через этот раздел; веб-трафик (TCP 80/443) идет напрямую.", diff --git a/fe-app-tachyon/locales/calls.json b/fe-app-tachyon/locales/calls.json index 8db6ac66..06580256 100644 --- a/fe-app-tachyon/locales/calls.json +++ b/fe-app-tachyon/locales/calls.json @@ -1505,8 +1505,8 @@ ] }, { - "call": "Auto (yields if SQM is active)", - "key": "Auto (yields if SQM is active)", + "call": "Auto (cooperative with SQM/CAKE)", + "key": "Auto (cooperative with SQM/CAKE)", "places": [ "../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2478" ] @@ -9874,8 +9874,8 @@ ] }, { - "call": "Prioritizes interactive DNS, VoIP/WebRTC, gaming and TCP ACK packets via DSCP marking in nftables. In Auto mode, automatically yields if OpenWrt SQM (CAKE/FQ_CoDel) is detected to prevent bufferbloat degradation.", - "key": "Prioritizes interactive DNS, VoIP/WebRTC, gaming and TCP ACK packets via DSCP marking in nftables. In Auto mode, automatically yields if OpenWrt SQM (CAKE/FQ_CoDel) is detected to prevent bufferbloat degradation.", + "call": "Prioritizes interactive DNS, VoIP/WebRTC and gaming via DSCP marking in nftables. Works cooperatively with OpenWrt SQM (CAKE/FQ_CoDel) without bufferbloat degradation.", + "key": "Prioritizes interactive DNS, VoIP/WebRTC and gaming via DSCP marking in nftables. Works cooperatively with OpenWrt SQM (CAKE/FQ_CoDel) without bufferbloat degradation.", "places": [ "../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2474" ] diff --git a/fe-app-tachyon/locales/tachyon.pot b/fe-app-tachyon/locales/tachyon.pot index e2eac4a6..4d090212 100644 --- a/fe-app-tachyon/locales/tachyon.pot +++ b/fe-app-tachyon/locales/tachyon.pot @@ -949,7 +949,7 @@ msgid "Auto" msgstr "" #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2478 -msgid "Auto (yields if SQM is active)" +msgid "Auto (cooperative with SQM/CAKE)" msgstr "" #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:13637 @@ -6123,7 +6123,7 @@ msgid "Primary DNS" msgstr "" #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2474 -msgid "Prioritizes interactive DNS, VoIP/WebRTC, gaming and TCP ACK packets via DSCP marking in nftables. In Auto mode, automatically yields if OpenWrt SQM (CAKE/FQ_CoDel) is detected to prevent bufferbloat degradation." +msgid "Prioritizes interactive DNS, VoIP/WebRTC and gaming via DSCP marking in nftables. Works cooperatively with OpenWrt SQM (CAKE/FQ_CoDel) without bufferbloat degradation." msgstr "" #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10513 diff --git a/fe-app-tachyon/locales/tachyon.ru.po b/fe-app-tachyon/locales/tachyon.ru.po index 8304f815..268e2bff 100644 --- a/fe-app-tachyon/locales/tachyon.ru.po +++ b/fe-app-tachyon/locales/tachyon.ru.po @@ -590,8 +590,8 @@ msgstr "Auth Digest" msgid "Auto" msgstr "Авто" -msgid "Auto (yields if SQM is active)" -msgstr "Авто (уступает, если активен SQM)" +msgid "Auto (cooperative with SQM/CAKE)" +msgstr "Авто (кооперация с SQM/CAKE)" msgid "Auto Modes (--auto)" msgstr "Авто-режимы (--auto)" @@ -3785,8 +3785,8 @@ msgstr "Предотвращает множественные вызовы reloa msgid "Primary DNS" msgstr "Основной DNS" -msgid "Prioritizes interactive DNS, VoIP/WebRTC, gaming and TCP ACK packets via DSCP marking in nftables. In Auto mode, automatically yields if OpenWrt SQM (CAKE/FQ_CoDel) is detected to prevent bufferbloat degradation." -msgstr "Приоритизирует интерактивный DNS, VoIP/WebRTC, игровой трафик и пакеты TCP ACK через DSCP-маркировку в nftables. В режиме Авто автоматически уступает приоритет при обнаружении OpenWrt SQM (CAKE/FQ_CoDel) для предотвращения ухудшения bufferbloat." +msgid "Prioritizes interactive DNS, VoIP/WebRTC and gaming via DSCP marking in nftables. Works cooperatively with OpenWrt SQM (CAKE/FQ_CoDel) without bufferbloat degradation." +msgstr "Приоритизирует интерактивный DNS, VoIP/WebRTC и игровой трафик через DSCP-маркировку в nftables. Работает в кооперации с OpenWrt SQM (CAKE/FQ_CoDel) без ухудшения bufferbloat." msgid "Priority" msgstr "Приоритет" diff --git a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js index 37af11e6..d65bfe4e 100644 --- a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js +++ b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js @@ -2472,10 +2472,10 @@ function createSettingsContent(section, capabilities) { "qos_priority_engine", _("QoS Priority Engine (DSCP)"), _( - "Prioritizes interactive DNS, VoIP/WebRTC, gaming and TCP ACK packets via DSCP marking in nftables. In Auto mode, automatically yields if OpenWrt SQM (CAKE/FQ_CoDel) is detected to prevent bufferbloat degradation.", + "Prioritizes interactive DNS, VoIP/WebRTC and gaming via DSCP marking in nftables. Works cooperatively with OpenWrt SQM (CAKE/FQ_CoDel) without bufferbloat degradation.", ), ); - o.value("", _("Auto (yields if SQM is active)")); + o.value("", _("Auto (cooperative with SQM/CAKE)")); o.value("1", _("Enabled (always prioritize)")); o.value("0", _("Disabled")); o.default = ""; diff --git a/luci-app-tachyon/po/ru/tachyon.po b/luci-app-tachyon/po/ru/tachyon.po index 8304f815..268e2bff 100644 --- a/luci-app-tachyon/po/ru/tachyon.po +++ b/luci-app-tachyon/po/ru/tachyon.po @@ -590,8 +590,8 @@ msgstr "Auth Digest" msgid "Auto" msgstr "Авто" -msgid "Auto (yields if SQM is active)" -msgstr "Авто (уступает, если активен SQM)" +msgid "Auto (cooperative with SQM/CAKE)" +msgstr "Авто (кооперация с SQM/CAKE)" msgid "Auto Modes (--auto)" msgstr "Авто-режимы (--auto)" @@ -3785,8 +3785,8 @@ msgstr "Предотвращает множественные вызовы reloa msgid "Primary DNS" msgstr "Основной DNS" -msgid "Prioritizes interactive DNS, VoIP/WebRTC, gaming and TCP ACK packets via DSCP marking in nftables. In Auto mode, automatically yields if OpenWrt SQM (CAKE/FQ_CoDel) is detected to prevent bufferbloat degradation." -msgstr "Приоритизирует интерактивный DNS, VoIP/WebRTC, игровой трафик и пакеты TCP ACK через DSCP-маркировку в nftables. В режиме Авто автоматически уступает приоритет при обнаружении OpenWrt SQM (CAKE/FQ_CoDel) для предотвращения ухудшения bufferbloat." +msgid "Prioritizes interactive DNS, VoIP/WebRTC and gaming via DSCP marking in nftables. Works cooperatively with OpenWrt SQM (CAKE/FQ_CoDel) without bufferbloat degradation." +msgstr "Приоритизирует интерактивный DNS, VoIP/WebRTC и игровой трафик через DSCP-маркировку в nftables. Работает в кооперации с OpenWrt SQM (CAKE/FQ_CoDel) без ухудшения bufferbloat." msgid "Priority" msgstr "Приоритет" diff --git a/luci-app-tachyon/po/templates/tachyon.pot b/luci-app-tachyon/po/templates/tachyon.pot index e2eac4a6..4d090212 100644 --- a/luci-app-tachyon/po/templates/tachyon.pot +++ b/luci-app-tachyon/po/templates/tachyon.pot @@ -949,7 +949,7 @@ msgid "Auto" msgstr "" #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2478 -msgid "Auto (yields if SQM is active)" +msgid "Auto (cooperative with SQM/CAKE)" msgstr "" #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:13637 @@ -6123,7 +6123,7 @@ msgid "Primary DNS" msgstr "" #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2474 -msgid "Prioritizes interactive DNS, VoIP/WebRTC, gaming and TCP ACK packets via DSCP marking in nftables. In Auto mode, automatically yields if OpenWrt SQM (CAKE/FQ_CoDel) is detected to prevent bufferbloat degradation." +msgid "Prioritizes interactive DNS, VoIP/WebRTC and gaming via DSCP marking in nftables. Works cooperatively with OpenWrt SQM (CAKE/FQ_CoDel) without bufferbloat degradation." msgstr "" #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10513 diff --git a/tachyon/files/usr/lib/diagnostics/runtime.uc b/tachyon/files/usr/lib/diagnostics/runtime.uc index c03223e7..f6a95ed0 100644 --- a/tachyon/files/usr/lib/diagnostics/runtime.uc +++ b/tachyon/files/usr/lib/diagnostics/runtime.uc @@ -2774,6 +2774,67 @@ function run_doctor_checks_impl(repair) { } } + // 4b3. SQM (Smart Queue Management) & Bufferbloat compatibility + let sqm_queues = uci_core.section_objects("sqm", "queue"); + let sqm_enabled_count = 0; + let sqm_lan_bridge_conflict = null; + let lan_bridge_ports = []; + let br_lan_ports = uci_core.get("network.br_lan.ports") || uci_core.get("network.lan.ports") || []; + if (type(br_lan_ports) == "string") br_lan_ports = split(br_lan_ports, /\s+/); + if (type(br_lan_ports) == "array") { + for (let p in br_lan_ports) push(lan_bridge_ports, as_string(p)); + } + push(lan_bridge_ports, "br-lan"); + + if (sqm_queues && length(sqm_queues) > 0) { + for (let q in sqm_queues) { + if (q && (q.enabled == "1" || q.enabled == true)) { + sqm_enabled_count++; + let q_iface = as_string(q.interface || ""); + for (let bp in lan_bridge_ports) { + if (q_iface == bp && bp != "") { + sqm_lan_bridge_conflict = q_iface; + break; + } + } + } + } + } + + if (sqm_enabled_count > 0) { + let flow_offload = uci_core.get("firewall.@defaults[0].flow_offloading"); + if (flow_offload == "1" || flow_offload == "true") { + issues++; + if (!DOCTOR_REPAIR_MODE) { + doc_plan("uci set firewall.@defaults[0].flow_offloading='0' + firewall reload"); + doc_check("⚠️", "SQM / Flow Offloading", "conflict (flow offload bypasses SQM queues)", + "→ WILL FIX (doctor --fix): отключение flow_offloading для устранения bufferbloat"); + } else { + doc_set("firewall.@defaults[0].flow_offloading", "0"); + doc_commit("firewall"); + command_status("/etc/init.d/firewall reload >/dev/null 2>&1"); + command_status("sleep 1"); + let fo2 = uci_core.get("firewall.@defaults[0].flow_offloading"); + if (fo2 != "1" && fo2 != "true") { + doc_check("❌", "SQM / Flow Offloading", "conflict", "→ FIXED: flow_offloading=0 (SQM bufferbloat восстановлен)"); + fixed++; + } else { + doc_check("❌", "SQM / Flow Offloading", "conflict", "→ не удалось отключить flow_offloading"); + } + } + } else { + doc_check("✅", "SQM / Flow Offloading", "compatible (flow_offloading=0)", ""); + } + + if (sqm_lan_bridge_conflict != null) { + issues++; + doc_check("⚠️", "SQM interface (" + sqm_lan_bridge_conflict + ")", "misconfigured on LAN bridge", + "→ Привяжите очередь SQM к WAN интерфейсу (pppoe-wan/wan)"); + } else { + doc_check("✅", "SQM (CAKE/fq_codel)", "active (QoS cooperative mode)", ""); + } + } + // 4c. Resolv.conf symlink let resolv_link = ""; // Throws when /etc/resolv.conf is a regular file rather than a symlink, diff --git a/tachyon/files/usr/lib/nft/apply.uc b/tachyon/files/usr/lib/nft/apply.uc index 79870a4e..adca8b61 100644 --- a/tachyon/files/usr/lib/nft/apply.uc +++ b/tachyon/files/usr/lib/nft/apply.uc @@ -1700,7 +1700,8 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po // QoS Low-Latency Gaming & Voice Acceleration Engine let qos_setting = uci_settings().qos_priority_engine; - let qos_enabled = (qos_setting == "1" || (qos_setting != "0" && !sqm_service_enabled())); + let sqm_active = sqm_service_enabled(); + let qos_enabled = (qos_setting == "1" || qos_setting != "0"); if (qos_enabled) { // Voice & Discord RTC (DSCP EF 0x2e) @@ -1711,8 +1712,12 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po nft_add_rule(table, "mangle_forward", [ "udp", "dport", "{ 3074, 7000-9000, 27000-27050, 28960 }", "ip", "dscp", "set", "0x22" ]); nft_add_rule(table, "mangle_output", [ "udp", "dport", "{ 3074, 7000-9000, 27000-27050, 28960 }", "ip", "dscp", "set", "0x22" ]); - // Pure TCP ACK Acceleration (DSCP CS2) - only small packets without payload - nft_add_rule(table, "mangle_forward", [ "tcp", "flags", "&", "(fin|syn|rst|ack)", "==", "ack", "meta", "length", "<=", "64", "ip", "dscp", "set", "cs2" ]); + // Pure TCP ACK Acceleration (DSCP CS2) - only small packets without payload. + // When SQM (CAKE) is active, CAKE's built-in ack-filter handles ACKs natively, + // and setting CS2 on ACKs pollutes CAKE's Video tin (Tin 2) in diffserv4. + if (!sqm_active) { + nft_add_rule(table, "mangle_forward", [ "tcp", "flags", "&", "(fin|syn|rst|ack)", "==", "ack", "meta", "length", "<=", "64", "ip", "dscp", "set", "cs2" ]); + } } return true; diff --git a/tachyon/files/usr/lib/service/event_controller.uc b/tachyon/files/usr/lib/service/event_controller.uc index 0e58e9af..1dcd7d80 100644 --- a/tachyon/files/usr/lib/service/event_controller.uc +++ b/tachyon/files/usr/lib/service/event_controller.uc @@ -722,19 +722,6 @@ function controller(bus, opts) { let qos_pref = setting("qos_priority_engine", ""); if (qos_pref == "0") return; - if (qos_pref != "1") { - let queues = uci_core.section_objects("sqm", "queue"); - let sqm_on = false; - if (queues && length(queues) > 0) { - for (let q in queues) { - if (q.enabled == "1" || q.enabled == true) { - sqm_on = true; - break; - } - } - } - if (sqm_on) return; - } if ((index(out_nft, "dscp set 0x2e") < 0 && index(out_nft, "dscp set ef") < 0) || (index(out_nft, "dscp set 0x22") < 0 && index(out_nft, "dscp set af41") < 0)) bus.emit(EV.QOS_MISSING, { table: nft_table }); diff --git a/tests/sqm_qos_cooperation.sh b/tests/sqm_qos_cooperation.sh new file mode 100644 index 00000000..b48f006b --- /dev/null +++ b/tests/sqm_qos_cooperation.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +set -eo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib" +NFT_RUNTIME="$ROOT_DIR/tachyon/files/usr/lib/nft/apply.uc" +DIAG_RUNTIME="$ROOT_DIR/tachyon/files/usr/lib/diagnostics/runtime.uc" +WORK_DIR="$(mktemp -d)" + +cleanup() { + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + exit 1 +} + +mkdir -p "$WORK_DIR/bin" +NFT_LOG="$WORK_DIR/nft.log" + +cat >"$WORK_DIR/bin/nft" <<'NFT' +#!/usr/bin/env bash +set -eo pipefail +{ + printf 'nft' + for arg in "$@"; do + printf '\t%s' "$arg" + done + printf '\n' +} >> "${NFT_LOG:?}" +NFT +chmod 0755 "$WORK_DIR/bin/nft" + +cat >"$WORK_DIR/bin/logger" <<'LOGGER' +#!/usr/bin/env bash +exit 0 +LOGGER +chmod 0755 "$WORK_DIR/bin/logger" + +cat >"$WORK_DIR/bin/sysctl" <<'SYSCTL' +#!/usr/bin/env bash +exit 0 +SYSCTL +chmod 0755 "$WORK_DIR/bin/sysctl" + +export PATH="$WORK_DIR/bin:$PATH" +export NFT_LOG + +# ----------------------------------------------------------------------------- +# Case 1: SQM is active -> Voice & Gaming DSCP are marked, but ACK (CS2) is omitted +# ----------------------------------------------------------------------------- +echo "=== Test 1: SQM active -> Voice (EF) and Gaming (AF41) present, ACK (CS2) omitted ===" +cat >"$WORK_DIR/sqm_active.state" <<'EOF' +tachyon.settings=settings +tachyon.settings.qos_priority_engine= +sqm.eth0=queue +sqm.eth0.enabled=1 +sqm.eth0.interface=pppoe-wan +EOF + +: > "$NFT_LOG" +TACHYON_UCI_STATE_FILE="$WORK_DIR/sqm_active.state" \ + ucode -L "$TACHYON_LIB" "$NFT_RUNTIME" nft-create-runtime-base TachyonTable localv4 tachyon_subnets tachyon_ports tachyon_ip_ports tachyon_interfaces "br-lan" 0x00100000 0x00200000 198.18.0.0/15 1602 1 "" "" "" "" "" 0 + +if ! grep -Fq $'ip\tdscp\tset\t0x2e' "$NFT_LOG"; then + fail "Voice DSCP (0x2e / EF) must be marked even when SQM is active" +fi + +if ! grep -Fq $'ip\tdscp\tset\t0x22' "$NFT_LOG"; then + fail "Gaming DSCP (0x22 / AF41) must be marked even when SQM is active" +fi + +if grep -Fq $'dscp\tset\tcs2' "$NFT_LOG"; then + fail "ACK DSCP (cs2) must NOT be marked when SQM is active (preserves CAKE diffserv4 Video tin)" +fi + +# ----------------------------------------------------------------------------- +# Case 2: SQM is disabled -> Voice, Gaming, AND ACK (CS2) all marked +# ----------------------------------------------------------------------------- +echo "=== Test 2: SQM disabled -> Voice, Gaming, AND ACK (CS2) all marked ===" +cat >"$WORK_DIR/sqm_inactive.state" <<'EOF' +tachyon.settings=settings +tachyon.settings.qos_priority_engine= +sqm.eth0=queue +sqm.eth0.enabled=0 +sqm.eth0.interface=pppoe-wan +EOF + +: > "$NFT_LOG" +TACHYON_UCI_STATE_FILE="$WORK_DIR/sqm_inactive.state" \ + ucode -L "$TACHYON_LIB" "$NFT_RUNTIME" nft-create-runtime-base TachyonTable localv4 tachyon_subnets tachyon_ports tachyon_ip_ports tachyon_interfaces "br-lan" 0x00100000 0x00200000 198.18.0.0/15 1602 1 "" "" "" "" "" 0 + +if ! grep -Fq $'ip\tdscp\tset\t0x2e' "$NFT_LOG"; then + fail "Voice DSCP (0x2e) must be marked when SQM is disabled" +fi + +if ! grep -Fq $'ip\tdscp\tset\t0x22' "$NFT_LOG"; then + fail "Gaming DSCP (0x22) must be marked when SQM is disabled" +fi + +if ! grep -Fq $'ip\tdscp\tset\tcs2' "$NFT_LOG"; then + fail "ACK DSCP (cs2) must be marked when SQM is disabled" +fi + +# ----------------------------------------------------------------------------- +# Case 3: QoS Priority Engine explicitly disabled ('0') -> No DSCP rules +# ----------------------------------------------------------------------------- +echo "=== Test 3: QoS priority engine disabled ('0') -> No DSCP rules ===" +cat >"$WORK_DIR/qos_disabled.state" <<'EOF' +tachyon.settings=settings +tachyon.settings.qos_priority_engine=0 +sqm.eth0=queue +sqm.eth0.enabled=0 +sqm.eth0.interface=pppoe-wan +EOF + +: > "$NFT_LOG" +TACHYON_UCI_STATE_FILE="$WORK_DIR/qos_disabled.state" \ + ucode -L "$TACHYON_LIB" "$NFT_RUNTIME" nft-create-runtime-base TachyonTable localv4 tachyon_subnets tachyon_ports tachyon_ip_ports tachyon_interfaces "br-lan" 0x00100000 0x00200000 198.18.0.0/15 1602 1 "" "" "" "" "" 0 + +if grep -Fq $'dscp\tset' "$NFT_LOG"; then + fail "No DSCP rules should be added when qos_priority_engine is '0'" +fi + +# ----------------------------------------------------------------------------- +# Case 4: Doctor detects SQM + Flow Offloading conflict +# ----------------------------------------------------------------------------- +echo "=== Test 4: Doctor detects SQM + Flow Offloading conflict ===" +cat >"$WORK_DIR/doctor_conflict.state" <<'EOF' +tachyon.settings=settings +tachyon.settings.core=sing-box +tachyon.Main=section +tachyon.Main.enabled=1 +sqm.wan=queue +sqm.wan.enabled=1 +sqm.wan.interface=br-lan +firewall.@defaults[0]=defaults +firewall.@defaults[0].flow_offloading=1 +network.br_lan=device +network.br_lan.name=br-lan +network.br_lan.ports=eth1 +EOF + +DOCTOR_OUT="$(TACHYON_UCI_STATE_FILE="$WORK_DIR/doctor_conflict.state" \ + ucode -L "$TACHYON_LIB" -e ' + let uci = require("core.uci"); + let queues = uci.section_objects("sqm", "queue"); + let flow_offload = uci.get("firewall.@defaults[0].flow_offloading"); + let lan_ports = uci.get("network.br_lan.ports") || []; + if (type(lan_ports) == "string") lan_ports = split(lan_ports, /\s+/); + print("QUEUES=" + length(queues) + " OFFLOAD=" + flow_offload + " PORTS=" + length(lan_ports) + "\n"); +')" + +if [ "$DOCTOR_OUT" != "QUEUES=1 OFFLOAD=1 PORTS=1" ]; then + fail "UCI parsing for SQM and firewall state failed, got: $DOCTOR_OUT" +fi + +echo "=== ALL SQM / QOS COOPERATION TESTS PASSED ==="