So before I begin, I am doing this on my mini PC running Debian up stairs... Not my OpenWRT boxes (living room floor x 1, downstairs entrance x 1). Those will be next.
But it all applies, since I have checked and assuming you are running hostapd full on OpenWRT, it will all work. Im running hostapd-ct upstairs (google search hostapd-ct, reason for this below). Note, I am no wifi expert... Although I have close to two decades of linux experience, so it all went quite quickly...
It's going to be a bit scattered, but it works... I am able to jump from 2.4ghz to 5ghz, and from 5ghz to 2.4ghz at will.
A) Hostapd-ct... The only reason for this is because hostapd-ct's version of hostapd_cli has a command to pull the neighbour report (long string) for your current adapter(s).
show_neighbor = show neighbor database entries
Super helpful, but openwrt's ubus has rrm_nr_get_own which will give you the string so this is not needed on an openwrt + openwrt setup.
B) Populate the neighbor report list fully. Both adapters (5 and 2.4) should contain ALL of the neighbors you wish to jump between. This was the first issue I ran into... Like I said, I don't know much about 802.11 internals... For whatever reason, initially I just populated my 5ghz on my 5ghz adapters, and my 2.4ghz on all my 2.4ghz adapters... Nope, fill them all fully. Essentially on all your adapters, the neighbour list should be the same (full). UNLESS you do not want (like me) the downstairs access point to roam to the upstairs router since there is a router in between. That should make sense.
C) If you are using 802.11r, which you probably are since thats usually everyones first step into 'better roaming', you need to make sure to use ft_over_ds and have rsn_preauth=1 rsn_preauth_interfaces set correctly. Just a pointer for those who dont know, hostapd will use rsn_preauth_interfaces as a 'back channel' to authenticate. So yes, not only does 802.11v work, I am about to FT between 2.4 and 5ghz. This was another issue... Since my mobile devices sit on 5ghz 99% of the time, I didn't bother setting up 802.11r on my 2.4ghz adapters... I could see the BSS working when sending the 802.11v requests, but it would NOT authenticate to the opposite frequency (2.4 to 5, 5 to 2.4)... I couldnt figure out why... Some debugging logging showed that hostapd was failing to authenticate because it was trying to FT and unable to do so... After I enabled 802.11r on my 2.4ghz band, presto.. Works!
D) The commands I am using to force a jump from one freq to the other are:
hostapd_cli -i XXXX bss_tm_req AA:AA:AA:AA:AA:AA neighbor=-BB:BB:BB:BB:BB:BB abridged=1
XXXX = The adapter you wish to switch FROM.
AA:AA... = The MAC of the client you want to transfer over.
BB:BB... = The MAC of the access point you want to EXCLUDE from possible access points where it will roam to.
NOTE: For samsung mobile devices (possibly all of android?) abridged=1 is a STRONG hint that the roam should happen. It states that the access points are part of the same network and gives it 'higher weight'. Note to developers, maybe it would be helpful to expose this via ubus?
NOTE : There is a way to pass a target AP via the neighbour= parameter... But I am thus far unable to figure out how to do so for the 2.4 to 5ghz band... The syntax is (as best I can tell, from the hostapd source):
[TARGET AP MAC],[OP CLASS, as far as I can tell, 2.4ghz=81, 5ghz=122],[CHANNEL],[PHY TYPE, as far as I can tell 7=2.4ghz,9=5ghz],[NEIGHBOUR REPORT LONG STRING]
But like I said, this works for my 5 to 2.4, but not the other way around... Still unsure why...
That's about it, I have this running on my dated Samsung S7 Edge BTW... Switching bands is nearly instant and I don't even notice it on the mobile... Really fast band steering pretty much like one would expect it to be.
Feel free to ask questions sorry for the scattered post, but I'm quite excited about this... Going to bang out a little bash script now to check rssi's and do this on the fly for me... We have a wifi baby monitor and I find it annoying that when I watch it as I move floors it pauses for about 5-10 seconds sometimes requiring an app restart... First world problems