Skip to content

Instantly share code, notes, and snippets.

@danpawlik
Last active June 1, 2026 06:43
Show Gist options
  • Select an option

  • Save danpawlik/6da726e240b4e15807f91e28b363a02a to your computer and use it in GitHub Desktop.

Select an option

Save danpawlik/6da726e240b4e15807f91e28b363a02a to your computer and use it in GitHub Desktop.
#!/bin/sh
# bridger-roam-cleanup: removes stale bridger TC flower rules when
# a WiFi client roams between bands (e.g. 5GHz <-> 6GHz).
#
# Bridger creates TC flower rules with skip_sw (hardware-only) to
# accelerate bridge forwarding. When a client roams, bridger sometimes
# leaves stale rules pointing to the old WiFi interface, causing the
# PPE to redirect download traffic to the wrong radio (1 Mbit symptom).
### START THAT SCRIPT USING /etc/rc.local
# # Clean up stale bridger TC flower rules on WiFi roaming
# /usr/sbin/bridger-roam-cleanup &
POLL_INTERVAL=1
PREF=47875
get_wifi_ifaces() {
ls /var/run/hostapd/ 2>/dev/null | grep -v global
}
get_bridge_ports() {
ip link show 2>/dev/null | awk -F'[ :@]+' '/master br-lan/{print $2}'
}
# Get all flower rule handles for a MAC on a given device
# Also outputs the redirect target
get_flower_handles() {
local dev="$1" mac="$2"
tc filter show dev "$dev" ingress 2>/dev/null | awk -v mac="$mac" '
/handle 0x/ { handle=$NF; found=0; redir="" }
{ low=tolower($0) }
/dst_mac/ { if (tolower($2) == mac) found=1 }
/src_mac/ { if (tolower($2) == mac) found=1 }
/Redirect to device/ {
split($0, a, "device ")
split(a[2], b, ")")
redir=b[1]
if (found) print handle, redir
}
'
}
cleanup_mac() {
local mac="$1" connected_iface="$2"
# Check uplink ports (lan2 etc) for rules redirecting to wrong wifi iface
for port in $(get_bridge_ports | grep -v '^phy'); do
get_flower_handles "$port" "$mac" | while read handle redir; do
case "$redir" in
phy*)
if [ "$redir" != "$connected_iface" ]; then
tc filter del dev "$port" ingress pref $PREF handle "$handle" flower 2>/dev/null
logger -t bridger-roam "DEL $port $handle: $mac redirect $redir, should be $connected_iface"
fi
;;
esac
done
done
# Delete rules for this MAC on wifi ifaces where client is NOT connected
for iface in $(get_wifi_ifaces); do
[ "$iface" = "$connected_iface" ] && continue
get_flower_handles "$iface" "$mac" | while read handle redir; do
tc filter del dev "$iface" ingress pref $PREF handle "$handle" flower 2>/dev/null
logger -t bridger-roam "DEL $iface $handle: $mac (moved to $connected_iface)"
done
done
}
logger -t bridger-roam "Started (poll every ${POLL_INTERVAL}s)"
while true; do
for iface in $(get_wifi_ifaces); do
iw dev "$iface" station dump 2>/dev/null | awk '/^Station/{print tolower($2)}' | while read mac; do
cleanup_mac "$mac" "$iface"
done
done
sleep "$POLL_INTERVAL"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment