How does rrm work?

Hi, @muddyfeet

Now I've another question. I made my wireless adapter to monitor mode and used wireshark to capture beacon frames. As this article, I should capture action frames, but I could't capture them. So I can't sure the neighbor report runs correctly.

I assume you're using iwcap to capture the data. The neighbour reports are only requested when the client decides to do that, so will depend on your clients.

I'll have a go here and see if I can capture something. A little busy today so I might take a little while getting back to you.

Beacon request is working. :slight_smile:

ubus call hostapd.wlan1 rrm_beacon_req '{"addr":"xx:xx:xx:xx:xx:xx", "op_class":0, "channel":36,"duration":1,"mode":0,"bssid":"ff:ff:ff:ff:ff:ff", "ssid":"OpenWrt"}'

And look in the

logread -f

There you will find something like

BEACON-RESP-RX ....

But actually, I have no idea, how to convert this values... Someone an idea?

Thanks @PolynomialDivision - that was quite helpful in that it prompted me to come up with a way to easily test the neighbour reports. First set up a wpa_supplicant hanging off the AP with the neighbour reports active. Then use the wpa_cli command "neighbor_rep_request" to trigger a request. The mac addresses have been modified.

It appears to be working.

>neighbor_rep_request
OK
<3>RRM-NEIGHBOR-REP-RECEIVED bssid=14:xx:xx:xx:xx:c9 info=0x9ef op_class=81 chan=11 phy_type=7
<3>RRM-NEIGHBOR-REP-RECEIVED bssid=14:xx:xx:xx:xx:c8 info=0x19ff op_class=122 chan=108 phy_type=9
<3>RRM-NEIGHBOR-REP-RECEIVED bssid=14:xx:xx:xx:xx:e4 info=0x19ff op_class=122 chan=132 phy_type=9
<3>RRM-NEIGHBOR-REP-RECEIVED bssid=14:xx:xx:xx:xx:e5 info=0x9ef op_class=81 chan=6 phy_type=7
1 Like

@sotux Seems to be some sort of issue on restarting the AP. The script doesn't populate the neighbour list properly. I'll have a bit of think as to why - not sure at this stage. It's fine when started manually after everything is up.

Hi, @muddyfeet
Now I use another way to auto update neighbor list.
Below is the service script, put this file into the /etc/inid.d directory and name it as "neighbor_report".

#!/bin/sh /etc/rc.common

START=90

USE_PROCD=1

radios=$(ubus list | grep hostapd.wlan)

start_service() {
	local rrm_own
	
	OIFS=$IFS
	IFS=$'\n'

	for value in ${radios}
	do
		rrm_own=${rrm_own}",$(ubus call ${value} rrm_nr_get_own | jsonfilter -e '$.value')"
	done

	IFS=$OIFS

	procd_open_instance
	procd_set_param command /bin/sh "/usr/bin/rrm"
	procd_add_mdns "rrm" "nr" "" "${rrm_own:1}"
	procd_close_instance
}

service_triggers()
{
	procd_add_reload_trigger wireless
}

And then is rrm file, put it in the /usr/bin directory and name it "rrm". It will be called by the serivce script. This script do a while loop and update neighbor list every 60s.

#!/bin/sh

radios=$(ubus list | grep hostapd.wlan)

_discover_neighbors() {
	local rrm_neighbors

	ubus call umdns update
	sleep 2

	rrm_neighbors=$(ubus call umdns browse | jsonfilter -e '@["_rrm._nr"][*].txt')

	for value in ${rrm_neighbors}
	do
		rrm_nr_lists=${rrm_nr_lists}",${value}"
	done
}

_append_own() {
	for value in ${radios}
	do
		rrm_nr_lists=${rrm_nr_lists}",$(ubus call ${value} rrm_nr_get_own | jsonfilter -e '$.value')"
	done
}

while true
do
	OIFS=$IFS
	IFS=$'\n'

	rrm_nr_lists=""

	_discover_neighbors
	_append_own

	for value in ${radios}
	do
		ubus call ${value} bss_mgmt_enable '{"neighbor_report": true}'
		eval "ubus call ${value} rrm_nr_set '{ \"list\": [ ${rrm_nr_lists:1} ] }'"
	done

	sleep 60

	IFS=$OIFS
done

exit 0
1 Like

Thanks @sotux

Still have the problem on startup. It appears to be that my 5 GHz radio is doing the CAC and is not listed when you enumerate the radios in the init script. The 2.4 GHz radio is fine.

I've made a couple of changes to make it work but you might want to find a more elegant way.

One thing I have noticed (but doesn't interfere with the operation) is when using a couple of iPhone bonjour browsers, they can't cope with the "nr" protocol. One crashes completely and the other just refuses to display the published neighbours.

#!/bin/sh /etc/rc.common

START=90

USE_PROCD=1

start_service() {
	local rrm_own
	
	OIFS=$IFS
	IFS=$'\n'

	sleep 120
	radios=$(ubus list | grep hostapd.wlan)

	for value in ${radios}
	do
		rrm_own=${rrm_own}",$(ubus call ${value} rrm_nr_get_own | jsonfilter -e '$.value')"
	done

	IFS=$OIFS

	procd_open_instance
	procd_set_param command /bin/sh "/etc/scripts/rrm"
	procd_add_mdns "rrm" "nr" "" "${rrm_own:1}"
	procd_close_instance
}

service_triggers()
{
	procd_add_reload_trigger wireless
}

and

#!/bin/sh

_discover_neighbors() {
	local rrm_neighbors

	ubus call umdns update
	sleep 2

	rrm_neighbors=$(ubus call umdns browse | jsonfilter -e '@["_rrm._nr"][*].txt')

	for value in ${rrm_neighbors}
	do
		rrm_nr_lists=${rrm_nr_lists}",${value}"
	done
}

_append_own() {
	radios=$(ubus list | grep hostapd.wlan)
	for value in ${radios}
	do
		rrm_nr_lists=${rrm_nr_lists}",$(ubus call ${value} rrm_nr_get_own | jsonfilter -e '$.value')"
	done
}

while true
do
	OIFS=$IFS
	IFS=$'\n'

	rrm_nr_lists=""

	_discover_neighbors
	_append_own

	for value in ${radios}
	do
		ubus call ${value} bss_mgmt_enable '{"neighbor_report": true}'
		eval "ubus call ${value} rrm_nr_set '{ \"list\": [ ${rrm_nr_lists:1} ] }'"
	done

	sleep 60

	IFS=$OIFS
done

exit 0

_rrm_nr is a temporary text, I'm not sure it's correct or not. Is there any reference?

Probably should change _nr to be _udp as https://tools.ietf.org/html/rfc6763 suggests (section 7).

Also, just in case you don't notice it, I put the rrm script in /etc/scripts on my APs as I back that up on system upgrades (see the procd_set_param_command line in my version of the first script above). You may want to change it back in your version.

I patched the hostapd, to handle beacon responses. I will do a PR if everything is finished.


Further Mediatek seems not to work:

For hostapd, you'd (probably) have to upstream it first before OpenWRT will accept it i.e. contact and attach the patch to https://lists.infradead.org/mailman/listinfo/hostap. @hauke will probably be the reviewer once it's upstreamed and you open up a PR backporting the patch to OpenWrt.

Mediatek – https://github.com/openwrt/mt76/issues – headed in part @nbd.

Thanks a lot. I'm only exposing the values through ubus allowing me to collect them by another daemon to do fancy hearing map stuff. Sadly, I have to use the Nexus 5X of a friend to develop and test. Is there maybe some cheap device I could use for this?

Hi @PolynomialDivision
Yes, I saw what you did with the beacon responses - that was good stuff.

I think there is some improvement yet to make on this. The sleep 120 in my version blocks the restart on the init script when done from Luci which in turn causes a webpage timeout. It would be better to find some more elegant way to refresh things when radios come and go - maybe something using "procd_set_param watch hostapd". I'm not across how this works so will need to leave it to better programmers than me. Also, the sleep 120 deals with the normal CAC of 60 seconds for DFS channels but there are some channels (5600-5650MHz sub-band) where the CAC is 10 minutes.

Also, the change from_nr to _udp did fix the problem on the mdns browsers.

Finished.

Now, we only have to implement some process that listens to those reports and creates them. And then we can steer clients. :smiley:

1 Like

Can somebody try to steer a client?
First enable

ubus call hostapd.wlan0 bss_mgmt_enable '{"neighbor_report":True, "beacon_report":True, "bss_transition": True}'
ubus call hostapd.wlan1 bss_mgmt_enable '{"neighbor_report":True, "beacon_report":True, "bss_transition": True}'

Get Neighbor Reports

ubus call hostapd.wlan0 rrm_nr_get_own
ubus call hostapd.wlan1 rrm_nr_get_own

Now steer a client from wlan0 to wlan1 or or the other way around

ubus call hostapd.wlan0 wnm_disassoc_imminent '{"addr":"00:xx:xx:xx:xx:xx", "duration": 120, "neighbors":["[THE STRING OF THE NEIGHBOR REPORT FROM WLAN0 OR WLAN1]"]}'

I'm not sure if the client gets deauthed, or it switches the interface on it's own. I have to increase the debug level.

You can set the debug level of hostapd with

option log_level '0'

and check with

grep _level /var/run/hostapd-phy0.conf

Okay, I tried with a Xiaomi Mi 8 and it works

daemon.notice hostapd: wlan1: BSS-TM-RESP xx:xx:xx:xx:xx:xx status_code=0 bss_termination_delay=0 target_bssid=xx:xx:xx:xx:xx:xx

Can someone confirm this bug I reported?
https://bugzilla.kernel.org/show_bug.cgi?id=205813
I can crash my system with a TL-WN722N (ath9k) by sending several beacon requests...

Thanks, this script is working (modded it to include udp instead of nr). I can see a successful Neighbor Request and Neighbor Response on ath10k-ct-htt, hostapd-2.9 with openssl. The setup includes 2x Archer A7s Dumb APs with a WRT1900ACS router (disabled wireless functionality) – all three have OpenWrt built from the master branch.

Additional roaming settings were: 802.11r with FT-over-the-Air, 802.11v with BSS transition, 802.11k with list populated by your script.

This is my config in /etc/config/wireless:

config wifi-iface 'default_radio0'
	option device 'radio0'
	option network 'lan'
	option mode 'ap'
	option ssid 'OpenWrt'
	option encryption 'psk2+ccmp'
	option key 'XXXXXX'
	option dtim_period '3'
	option ieee80211r '1'
	option ft_psk_generate_local '1'
	option ft_over_ds '0'
	option ieee80211v '1'
	option bss_transition '1'
	option ieee80211k '1'

The following capture shows an exchange between an Archer A7v5 and an iPhone 11 Pro on iOS 13.3.

Screen Shot 2019-12-26 at 08.36.16

In the neighbor report I see four APs, (corresponding to 2x N SSIDs and 2x AC SSIDs).

Screen Shot 2019-12-26 at 08.40.15

However, I have an issue with your script whenever I reboot the APs. I have to keep restarting them (3x on each AP) to populate:

  1. ubus call hostapd.wlan0 rrm_nr_list
  2. ubus call umdns browse

Because when the script staring, the radio may not started yet. So the umdns can't broadcast the local AP. I always restart the script after all radios have started.
If you add a sleep in the script to wait radios, LUCI may report timeout in the startup page as @muddyfeet said.

1 Like

Alright – worked around this by putting:

sleep 120
/etc/init.d/neighbor_report restart

in /etc/rc.local

Thus, preventing LUCI from timing out in the startup page.

EDIT:
I've attempted to do a proper fix by adding this:

procd_add_interface_trigger "interface.*" "wlan0" /etc/init.d/neighbor_report restart
procd_add_interface_trigger "interface.*" "wlan1" /etc/init.d/neighbor_report restart

But it doesn't work. Maybe moving the script to /etc/hotplug.d/iface and making it listen to ifup events work as per https://openwrt.org/docs/guide-user/base-system/hotplug#the_iface_folder?

@PolynomialDivision

I just tested, after quite a bit of trying:

A) Windows 7 Laptop - Lenovo T470 with a fairly recent Intel adapter - DOES NOT WORK - Reauths to the same adapter

B) Samsung S7 Edge running - DOES NOT WORK - Reauths to the same adapter

C) IPad Mini - OLD OLD model... Works, switches from wlan1 (5ghz) to wlan0 (2ghz) at will

Looks like its going to be time to upgrade my phone :wink:

1 Like

I discovered that devices sometimes roam, and somtimes not...
I upgraded my controller DAWN, to use disassoc_imm... function, instead of deauthing clients.
But I think, something is still broken. I have to look into this again, when I have time. And I call ubus methods on each interface. That's not good... :confused:

1 Like