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)
This commit is contained in:
Sukru Senli 2026-03-13 00:54:05 +01:00
parent f2f1fa4ad4
commit 1ed031afdc
3 changed files with 137 additions and 0 deletions

View file

@ -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() {

View file

@ -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

View file

@ -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
}