From 1ed031afdca689aef4ddb4329ceb699709849ff5 Mon Sep 17 00:00:00 2001 From: Sukru Senli Date: Fri, 13 Mar 2026 00:54:05 +0100 Subject: [PATCH] qosmngr: integrate device priority QoS with WiFi/EasyMesh Device priority classification rules (src_mac + dscp_mark) only affected wired traffic via ebtables traffic_class marks and VLAN pbit translation. WiFi clients were unaffected because: 1. DSCP in the IP header was never modified for L2-only (src_mac) rules 2. No integration existed between qosmngr and the EasyMesh map-controller Fix both gaps: - Add ebt_ftos DSCP remarking in the ebtables broute chain so bridged WiFi traffic has the correct DSCP before the WiFi driver selects the WMM access category (ebtables.sh) - Auto-generate a mapcontroller qos_rule (type dscp_pcp) from active classify rules so the EasyMesh QoS Map is pushed to all mesh agents, ensuring consistent DSCP-to-WMM AC mapping across the network (mapcontroller_sync.sh, classify.sh) --- qosmngr/files/common/lib/qos/classify.sh | 6 + qosmngr/files/common/lib/qos/ebtables.sh | 24 ++++ .../common/lib/qos/mapcontroller_sync.sh | 107 ++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 qosmngr/files/common/lib/qos/mapcontroller_sync.sh diff --git a/qosmngr/files/common/lib/qos/classify.sh b/qosmngr/files/common/lib/qos/classify.sh index b20b72028..862628291 100755 --- a/qosmngr/files/common/lib/qos/classify.sh +++ b/qosmngr/files/common/lib/qos/classify.sh @@ -111,6 +111,12 @@ configure_classify() { sh /tmp/qos/classify.iptables sh /tmp/qos/classify.ip6tables sh /tmp/qos/classify.iprule + + # Sync DSCP/PCP mappings to mapcontroller for WiFi QoS + if [ -f /lib/qos/mapcontroller_sync.sh ]; then + . /lib/qos/mapcontroller_sync.sh + sync_mapcontroller_qos + fi } setup_qos() { diff --git a/qosmngr/files/common/lib/qos/ebtables.sh b/qosmngr/files/common/lib/qos/ebtables.sh index d07b35673..c1887f20a 100755 --- a/qosmngr/files/common/lib/qos/ebtables.sh +++ b/qosmngr/files/common/lib/qos/ebtables.sh @@ -450,6 +450,30 @@ handle_ebtables_rules() { return fi + # DSCP remarking via ftos target (airoha) - must be a separate rule + # before the traffic_class mark rule, as each rule can have only one target. + # This modifies the DSCP in the IP header so WiFi drivers select the + # correct WMM access category for bridged traffic. + config_get dscp_mark "$sid" "dscp_mark" + if [ -n "$dscp_mark" ] && [ "$dscp_mark" -ge 0 ] 2>/dev/null; then + local tos_val ftos_chain + tos_val=$((dscp_mark << 2)) + + if [ -n "$vid_mark" ] || [ -n "$pcp_mark" ]; then + ftos_chain="prevlanxlate" + else + ftos_chain="qos" + fi + + # Emit ftos rule with same match criteria, CONTINUE target + if [ -n "$BR_RULE" ]; then + echo "ebtables --concurrent -t broute -A $ftos_chain $BR_RULE -j ftos --set-ftos $tos_val" >> /tmp/qos/classify.ebtables + fi + if [ -n "$BR6_RULE" ]; then + echo "ebtables --concurrent -t broute -A $ftos_chain $BR6_RULE -j ftos --set-ftos $tos_val" >> /tmp/qos/classify.ebtables + fi + fi + [ -n "$traffic_class" ] && broute_rule_set_traffic_class "$traffic_class" if [ -n "$vid_mark" ] || [ -n "$pcp_mark" ]; then diff --git a/qosmngr/files/common/lib/qos/mapcontroller_sync.sh b/qosmngr/files/common/lib/qos/mapcontroller_sync.sh new file mode 100644 index 000000000..f881bb767 --- /dev/null +++ b/qosmngr/files/common/lib/qos/mapcontroller_sync.sh @@ -0,0 +1,107 @@ +#!/bin/sh +# Sync DSCP/PCP mappings from qosmngr classify rules to mapcontroller +# for WiFi QoS (EasyMesh QoS Map) integration. +# +# When device priority classify rules set dscp_mark/pcp_mark, this script +# generates a mapcontroller qos_rule (type dscp_pcp) so all EasyMesh agents +# apply the correct DSCP-to-WMM AC mapping on WiFi interfaces. + +MAPCNTLR_CFG="mapcontroller" +AUTO_RULE_ID="1000" +AUTO_RULE_NAME="qos_auto_dscp_pcp" + +# Collect dscp_mark/pcp_mark pairs from enabled classify sections. +# Stores results in /tmp/qos/dscp_pcp_map (sorted by order, first wins). +_collect_dscp_pcp_mapping() { + local cid="$1" + local is_enable dscp_mark pcp_mark c_order + + config_get is_enable "$cid" "enable" "1" + [ "$is_enable" = "0" ] && return + + config_get dscp_mark "$cid" "dscp_mark" + config_get pcp_mark "$cid" "pcp_mark" + + # Skip rules without both dscp_mark and pcp_mark + [ -z "$dscp_mark" ] && return + [ -z "$pcp_mark" ] && return + + # Skip negative values (means "no change" in TR-181) + [ "$dscp_mark" -lt 0 ] 2>/dev/null && return + [ "$pcp_mark" -lt 0 ] 2>/dev/null && return + + # Validate ranges + [ "$dscp_mark" -gt 63 ] 2>/dev/null && return + [ "$pcp_mark" -gt 7 ] 2>/dev/null && return + + config_get c_order "$cid" "order" "999" + + # Format: order dscp pcp (sorted later, first occurrence per dscp wins) + echo "$c_order $dscp_mark $pcp_mark" >> /tmp/qos/dscp_pcp_map +} + +# Remove previously auto-generated qos_rule sections from mapcontroller config +_cleanup_auto_rules() { + local section auto_gen + + # Find all qos_rule sections with auto_generated='1' + for section in $(uci -q show "$MAPCNTLR_CFG" | grep '\.auto_generated=.1.' | sed "s/\.auto_generated=.*//;s/${MAPCNTLR_CFG}\.//"); do + uci -q delete "${MAPCNTLR_CFG}.${section}" + done +} + +# Main sync function - called after configure_classify() +sync_mapcontroller_qos() { + # Guard: skip if mapcontroller/EasyMesh is not installed + [ -f "/etc/config/$MAPCNTLR_CFG" ] || return 0 + + rm -f /tmp/qos/dscp_pcp_map + + # Collect mappings from all classify rules + config_load qos + config_foreach _collect_dscp_pcp_mapping classify + + # Remove old auto-generated rules + _cleanup_auto_rules + + # If no mappings found, just commit cleanup and signal + if [ ! -s /tmp/qos/dscp_pcp_map ]; then + uci -q commit "$MAPCNTLR_CFG" + killall -HUP mapcontroller 2>/dev/null + return 0 + fi + + # Deduplicate: sort by order (ascending), keep first occurrence per DSCP + sort -n -k1 /tmp/qos/dscp_pcp_map | awk '!seen[$2]++ {print $2 "," $3}' > /tmp/qos/dscp_pcp_list + + if [ ! -s /tmp/qos/dscp_pcp_list ]; then + uci -q commit "$MAPCNTLR_CFG" + killall -HUP mapcontroller 2>/dev/null + return 0 + fi + + # Ensure QoS is enabled in mapcontroller + uci -q set "${MAPCNTLR_CFG}.qos=qos" + uci -q set "${MAPCNTLR_CFG}.qos.enabled=1" + + # Create auto-generated qos_rule + uci -q set "${MAPCNTLR_CFG}.${AUTO_RULE_NAME}=qos_rule" + uci -q set "${MAPCNTLR_CFG}.${AUTO_RULE_NAME}.id=${AUTO_RULE_ID}" + uci -q set "${MAPCNTLR_CFG}.${AUTO_RULE_NAME}.type=dscp_pcp" + uci -q set "${MAPCNTLR_CFG}.${AUTO_RULE_NAME}.precedence=200" + uci -q set "${MAPCNTLR_CFG}.${AUTO_RULE_NAME}.output=0" + uci -q set "${MAPCNTLR_CFG}.${AUTO_RULE_NAME}.always_match=1" + uci -q set "${MAPCNTLR_CFG}.${AUTO_RULE_NAME}.auto_generated=1" + + # Clear any existing dscp_pcp list entries and add new ones + uci -q delete "${MAPCNTLR_CFG}.${AUTO_RULE_NAME}.dscp_pcp" + while read -r mapping; do + uci -q add_list "${MAPCNTLR_CFG}.${AUTO_RULE_NAME}.dscp_pcp=${mapping}" + done < /tmp/qos/dscp_pcp_list + + # Commit and signal mapcontroller to reload + uci -q commit "$MAPCNTLR_CFG" + killall -HUP mapcontroller 2>/dev/null + + rm -f /tmp/qos/dscp_pcp_map /tmp/qos/dscp_pcp_list +}