fix(qos): harmonize SQM and CAKE cooperation, resolve bufferbloat degradation

This commit is contained in:
Dushnilin
2026-09-14 17:03:45 +03:00
parent 24c11bbca8
commit 7375e26b44
11 changed files with 249 additions and 36 deletions
+2 -2
View File
@@ -1357,8 +1357,8 @@ const dictionary = {
"Tachyon has been installed": "Tachyon успешно установлен", "Tachyon has been installed": "Tachyon успешно установлен",
"via Proxy": "через Прокси", "via Proxy": "через Прокси",
"via WAN": "через WAN", "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 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.",
"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.", "Auto (cooperative with SQM/CAKE)": "Авто (кооперация с SQM/CAKE)",
"QoS Priority Engine (DSCP)": "Движок приоритета QoS (DSCP)", "QoS Priority Engine (DSCP)": "Движок приоритета QoS (DSCP)",
"Strategy applied successfully and service reloaded!": "Стратегия успешно применена, сервис перезапущен!", "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) идет напрямую.", "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) идет напрямую.",
+4 -4
View File
@@ -1505,8 +1505,8 @@
] ]
}, },
{ {
"call": "Auto (yields if SQM is active)", "call": "Auto (cooperative with SQM/CAKE)",
"key": "Auto (yields if SQM is active)", "key": "Auto (cooperative with SQM/CAKE)",
"places": [ "places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2478" "../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.", "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, 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 and gaming via DSCP marking in nftables. Works cooperatively with OpenWrt SQM (CAKE/FQ_CoDel) without bufferbloat degradation.",
"places": [ "places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2474" "../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2474"
] ]
+2 -2
View File
@@ -949,7 +949,7 @@ msgid "Auto"
msgstr "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2478 #: ../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 "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:13637 #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:13637
@@ -6123,7 +6123,7 @@ msgid "Primary DNS"
msgstr "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2474 #: ../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 "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10513 #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10513
+4 -4
View File
@@ -590,8 +590,8 @@ msgstr "Auth Digest"
msgid "Auto" msgid "Auto"
msgstr "Авто" msgstr "Авто"
msgid "Auto (yields if SQM is active)" msgid "Auto (cooperative with SQM/CAKE)"
msgstr "Авто (уступает, если активен SQM)" msgstr "Авто (кооперация с SQM/CAKE)"
msgid "Auto Modes (--auto)" msgid "Auto Modes (--auto)"
msgstr "Авто-режимы (--auto)" msgstr "Авто-режимы (--auto)"
@@ -3785,8 +3785,8 @@ msgstr "Предотвращает множественные вызовы reloa
msgid "Primary DNS" msgid "Primary DNS"
msgstr "Основной 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." 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, игровой трафик и пакеты TCP ACK через DSCP-маркировку в nftables. В режиме Авто автоматически уступает приоритет при обнаружении OpenWrt SQM (CAKE/FQ_CoDel) для предотвращения ухудшения bufferbloat." msgstr "Приоритизирует интерактивный DNS, VoIP/WebRTC и игровой трафик через DSCP-маркировку в nftables. Работает в кооперации с OpenWrt SQM (CAKE/FQ_CoDel) без ухудшения bufferbloat."
msgid "Priority" msgid "Priority"
msgstr "Приоритет" msgstr "Приоритет"
@@ -2472,10 +2472,10 @@ function createSettingsContent(section, capabilities) {
"qos_priority_engine", "qos_priority_engine",
_("QoS Priority Engine (DSCP)"), _("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("1", _("Enabled (always prioritize)"));
o.value("0", _("Disabled")); o.value("0", _("Disabled"));
o.default = ""; o.default = "";
+4 -4
View File
@@ -590,8 +590,8 @@ msgstr "Auth Digest"
msgid "Auto" msgid "Auto"
msgstr "Авто" msgstr "Авто"
msgid "Auto (yields if SQM is active)" msgid "Auto (cooperative with SQM/CAKE)"
msgstr "Авто (уступает, если активен SQM)" msgstr "Авто (кооперация с SQM/CAKE)"
msgid "Auto Modes (--auto)" msgid "Auto Modes (--auto)"
msgstr "Авто-режимы (--auto)" msgstr "Авто-режимы (--auto)"
@@ -3785,8 +3785,8 @@ msgstr "Предотвращает множественные вызовы reloa
msgid "Primary DNS" msgid "Primary DNS"
msgstr "Основной 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." 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, игровой трафик и пакеты TCP ACK через DSCP-маркировку в nftables. В режиме Авто автоматически уступает приоритет при обнаружении OpenWrt SQM (CAKE/FQ_CoDel) для предотвращения ухудшения bufferbloat." msgstr "Приоритизирует интерактивный DNS, VoIP/WebRTC и игровой трафик через DSCP-маркировку в nftables. Работает в кооперации с OpenWrt SQM (CAKE/FQ_CoDel) без ухудшения bufferbloat."
msgid "Priority" msgid "Priority"
msgstr "Приоритет" msgstr "Приоритет"
+2 -2
View File
@@ -949,7 +949,7 @@ msgid "Auto"
msgstr "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2478 #: ../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 "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:13637 #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:13637
@@ -6123,7 +6123,7 @@ msgid "Primary DNS"
msgstr "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2474 #: ../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 "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10513 #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10513
@@ -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 // 4c. Resolv.conf symlink
let resolv_link = ""; let resolv_link = "";
// Throws when /etc/resolv.conf is a regular file rather than a symlink, // Throws when /etc/resolv.conf is a regular file rather than a symlink,
+7 -2
View File
@@ -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 // QoS Low-Latency Gaming & Voice Acceleration Engine
let qos_setting = uci_settings().qos_priority_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) { if (qos_enabled) {
// Voice & Discord RTC (DSCP EF 0x2e) // Voice & Discord RTC (DSCP EF 0x2e)
@@ -1711,9 +1712,13 @@ 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_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" ]); 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 // 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" ]); nft_add_rule(table, "mangle_forward", [ "tcp", "flags", "&", "(fin|syn|rst|ack)", "==", "ack", "meta", "length", "<=", "64", "ip", "dscp", "set", "cs2" ]);
} }
}
return true; return true;
} }
@@ -722,19 +722,6 @@ function controller(bus, opts) {
let qos_pref = setting("qos_priority_engine", ""); let qos_pref = setting("qos_priority_engine", "");
if (qos_pref == "0") return; 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) || 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)) (index(out_nft, "dscp set 0x22") < 0 && index(out_nft, "dscp set af41") < 0))
bus.emit(EV.QOS_MISSING, { table: nft_table }); bus.emit(EV.QOS_MISSING, { table: nft_table });
+160
View File
@@ -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 ==="