Last active
June 1, 2026 06:43
-
-
Save danpawlik/6da726e240b4e15807f91e28b363a02a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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