@ParanoidZoid, @kaputorwon: Please indicate on Github, that you tested the PR.
I have yet to test your patch since my build is not ready yet. It's very interesting. I appreciate your great work!
I would like to know how AP collects neighboring AP prior to sending neighboring report down to client. Where does AP keep the AP lists?
My intention is to exercise 11k protocol by itself. We have two RM capabilities in the beacon to enable through ubus call hostapd.wlan0 bss_mgmt_enable '{"neighbor_report":True, "beacon_report":True}'. I just wanted to find out how an AP collects neighbor AP list. My query is if AP collects neighbor ap list by scanning or by sending action frame to client and client responds with it's scan results. I am not attempting to do client guided roaming. Thanks.
I have no plan to do any coding at the moment as regards to rrm feature. I'm just curious to see whether there is any of 802.11k feature/protocol in place in the OpenWrt. Anyway I appreciate your contribution!
#!/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" "udp" "" "${rrm_own:1}"
procd_close_instance
}
service_triggers()
{
procd_add_reload_trigger wireless
}
In /usr/bin/rrm
#!/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._udp"][*].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
These scripts would probably fare better if they were (1) integrated as easily as 802.11r is now enabled via mac80211.sh and (2) lives as a hotplud.d script watching the wlan0/wlan1 interface(s). Right now, I don't really have the time to figure this out myself and this script from @sotux and @muddyfeet does what it needs to do albeit a few caveats if something is shut down/rebooted.
I'm playing with the ubus commands to see how my devices respond to manual instructions. I'm finding that one of the APs is missing the wnm_disassoc_imminent method from ubus -v list hostapd.wlan0-1.
/tmp/run/hostapd-phy0.conf look very similar on each device, and both have disassoc_low_ack=1
Can anyone give me a clue if that is a hardware, driver, configuration or perhaps package snag?
In case it helps, the one without the method is running ath79/tiny DTS style, the one with it is ar71xx/nand.
EDIT: Looks like this may depend on hostapd and / or other things being compiled with CONFIG_WNM_AP defined...
Well it's appeared now after some random phaffing that included a reboot. My best guess is that swapping wpad-mini to wpad is what was needed, but I missed the correct init.d restart until the reboot sorted it.
I can now effortlessly push at least one of my devices between the two APs at will with shell commands. Now need a script to push the right ones at the right time, so will reread above...
For those that want to wait for 20.x to be released, and not fiddle with dawn (I don't want to risk messing up my main gateway yet), I have updated the scripts above to address some of the shortcomings.
Fixed some race conditions and minor bugs
More robust parsing of inputs
Init script neighbor_reporting waits for interfaces (keep START=99, otherwise with DFS you may get a super-long boot)
Information is only published for proper SSIDs - if you have >1 SSID, now information is only published for the same SSID (e.g: you do 802.11r only on one of them, or 2.4 and 5 bands have different SSIDs)
Checks whether an action needs applying - if nothing changes, no action is performed: this helps with low-mem devices that cannot reload the configuration every 60s
Reports basic errors to logread when conditions are not met
Periodic script rrm also checks whether own_rrm is ok, and if not applies it
Info is published delimited by pipe "|", for easier parsing at the receivers
Limitations:
Assumes wireless lan interfaces are named wlanX
Assumes each station has exactly 1 IP address
/etc/init.d/neighbor_reporting
#!/bin/sh /etc/rc.common
START=99
NAME=neighbor_reporting
USE_PROCD=1
start_service() {
[ ! -f /etc/config/wireless ] && logger -t "${NAME}" -pdaemon.error "/etc/config/wireless does not exist" && exit 1
[ $(grep -q 'wifi-iface' /etc/config/wireless) ] && logger -t "${NAME}" -pdaemon.error "/etc/config/wireless does not have any wifi-iface stanzas" && exit 1
local rrm_own
while [ "$(ubus list hostapd.wlan* | wc -l)" != "$(grep 'wifi-iface' /etc/config/wireless | wc -l)" ]; do
logger -t "${NAME}" -pdaemon.info "Waiting for all interfaces to initialize"
sleep 30
done
OIFS=$IFS
IFS=$'\x0a'
for value in $(ubus list hostapd.wlan*); do
rrm_own="${rrm_own}|$(/bin/ubus call ${value} rrm_nr_get_own | /usr/bin/jsonfilter -e '$.value')"
done
IFS=$OIFS
procd_open_instance
procd_set_param command /bin/sh "/usr/bin/rrm"
procd_add_mdns "rrm" "udp" "0" "${rrm_own:1}"
procd_close_instance
}
service_triggers()
{
procd_add_reload_trigger wireless
}
/usr/bin/rrm
#!/bin/sh /etc/rc.common
NAME=rrm
USE_PROCD=1
# Check if our own rrm is set properly
function _check_own_rrm() {
HOST_IP=$(ubus -v call network.interface.lan status | jsonfilter -e '@["ipv4-address"][0].address')
CUR_UMDNS=$(ubus call umdns browse | jsonfilter -e "@[\"_rrm._udp\"][@.ipv4=\"${HOST_IP}\"].ipv4")
[ -z "${CUR_UMDNS}" ] && return 1
return 0
}
# Sets own rrm
function _setup_own_rrm() {
[ ! -f /etc/config/wireless ] && logger -t "${NAME}" -pdaemon.error "/etc/config/wireless does not exist" && exit 1
[ $(grep -q 'wifi-iface' /etc/config/wireless) ] && logger -t "${NAME}" -pdaemon.error "/etc/config/wireless does not have any wifi-iface stanzas" && exit 1
local rrm_own
while [ "$(ubus list hostapd.wlan* | wc -l)" != "$(grep 'wifi-iface' /etc/config/wireless | wc -l)" ]; do
logger -t "${NAME}" -pdaemon.info "Waiting for all interfaces to initialize"
sleep 30
done
OIFS=$IFS
IFS=$'\x0a'
for value in $(ubus list hostapd.wlan*); do
rrm_own="${rrm_own}|$(/bin/ubus call ${value} rrm_nr_get_own | /usr/bin/jsonfilter -e '$.value')"
done
IFS=$OIFS
procd_open_instance
procd_set_param command /bin/sh "/usr/bin/rrm"
procd_add_mdns "rrm" "udp" "0" "${rrm_own:1}"
procd_close_instance
}
# We wrap everything into this function to allow GC by ash, esp. for low-mem devices
function _do_updates() {
local rrm_nr_lists
OIFS=$IFS
IFS=$'\n'
# Discover neighbors and self
ubus call umdns update
sleep 2
for wifi_iface in $(ubus list hostapd.wlan* | awk -F. '{ print $2; }'); do
net_ssid=$(uci get wireless.${wifi_iface}.ssid)
[ -z "${net_ssid}" ] && logger -t "rrm" -p daemon.error "${wifi_iface}: does not have ssid according to uci" && continue
# Discover other nodes
rrm_nr_lists=""
# Sort at the end stabilizes the result, so we can compare it across runs
for discovered_node in $(ubus call umdns browse | jsonfilter -e '@["_rrm._udp"][*].txt' | tr '|' '\n' | grep "\"${net_ssid}\"" | sort -u); do
rrm_nr_lists="${rrm_nr_lists},${discovered_node}"
done
# Add own rrm (worst case this is the only node)
# Own's rrm is actually ignored when setting the list, no need to add it
# rrm_nr_lists="${rrm_nr_lists},$(/bin/ubus call hostapd.${wifi_iface} rrm_nr_get_own | /usr/bin/jsonfilter -e '$.value')"
[[ "${rrm_nr_lists}" == "," ]] && logger -t "rrm" -p daemon.error "${wifi_iface}: no neighbors detected, nothing to do" && continue
ubus call hostapd.${wifi_iface} bss_mgmt_enable '{"neighbor_report": true}'
prev_rrm_list=$(ubus call hostapd.${wifi_iface} rrm_nr_list | jsonfilter -e '@.list')
if [[ "$(echo -n ${prev_rrm_list} | sed 's: ::g')" == "$(echo -n [${rrm_nr_lists:1}] | sed 's: ::g')" ]]; then
# Setting a new list will cause the wifi to quickly cycle, which we do not want every 60s
continue
fi
ubus call hostapd.${wifi_iface} rrm_nr_set "{ \"list\": [ ${rrm_nr_lists:1} ] }"
done
IFS=$OIFS
}
while true; do
_check_own_rrm || _setup_own_rrm
_do_updates
sleep 60
done
exit 0
Interesting, I commented that line exactly because when setting the list, the host's own AP gets ignored:
host A, address X
host B, address Y
Setting [X,Y] on host A, ubus call hostapd.<iface> rrm_nr_list yields only [Y].
I am not sure whether this behavior depends on drivers/hostapd etc. If that's the case also on your router, keep it commented, otherwise the comparison at the end of the loop will always be false, and configuration will be reapplied every 60s even though nothing changed.
Another update to /usr/bin/rrm: I couldn't test _setup_own_rrm it properly before, procd support wasn't really working, so it's just easier to restart the service when own rrm is not setup in umdns.
#!/bin/sh
NAME=rrm
# Check if our own rrm is set, otherwise force restart of neighbor_reporting service
function _check_own_rrm() {
HOST_IP=$(ubus -v call network.interface.lan status | jsonfilter -e '@["ipv4-address"][0].address')
CUR_UMDNS=$(ubus call umdns browse | jsonfilter -e "@[\"_rrm._udp\"][@.ipv4=\"${HOST_IP}\"].ipv4")
[ -z "${CUR_UMDNS}" ] && return 1
return 0
}
# Sets own rrm
function _setup_own_rrm() {
nohup /etc/init.d/neighbor_reporting restart
}
# We wrap everything into this function to allow GC by ash, esp. for low-mem devices
function _do_updates() {
local rrm_nr_lists
OIFS=$IFS
IFS=$'\n'
# Discover neighbors and self
ubus call umdns update
sleep 2
for wifi_iface in $(ubus list hostapd.wlan* | awk -F. '{ print $2; }'); do
net_ssid=$(uci get wireless.${wifi_iface}.ssid)
[ -z "${net_ssid}" ] && logger -t "rrm" -p daemon.error "${wifi_iface}: does not have ssid according to uci" && continue
# Discover other nodes
rrm_nr_lists=""
# Sort at the end stabilizes the result, so we can compare it across runs
for discovered_node in $(ubus call umdns browse | jsonfilter -e '@["_rrm._udp"][*].txt' | tr '|' '\n' | grep "\"${net_ssid}\"" | sort -u); do
rrm_nr_lists="${rrm_nr_lists},${discovered_node}"
done
# Add own rrm (worst case this is the only node)
# Own's rrm is actually ignored when setting the list, no need to add it
# rrm_nr_lists="${rrm_nr_lists},$(/bin/ubus call hostapd.${wifi_iface} rrm_nr_get_own | /usr/bin/jsonfilter -e '$.value')"
[[ "${rrm_nr_lists}" == "," ]] && logger -t "rrm" -p daemon.error "${wifi_iface}: no neighbors detected, nothing to do" && continue
ubus call hostapd.${wifi_iface} bss_mgmt_enable '{"neighbor_report": true}'
prev_rrm_list=$(ubus call hostapd.${wifi_iface} rrm_nr_list | jsonfilter -e '@.list')
if [[ "$(echo -n ${prev_rrm_list} | sed 's: ::g')" == "$(echo -n [${rrm_nr_lists:1}] | sed 's: ::g')" ]]; then
# Setting a new list will cause the wifi to quickly cycle, which we do not want every 60s
continue
fi
ubus call hostapd.${wifi_iface} rrm_nr_set "{ \"list\": [ ${rrm_nr_lists:1} ] }"
done
IFS=$OIFS
}
while true; do
_check_own_rrm || _setup_own_rrm
_do_updates
sleep 60
done
exit 0
Just to add a neighbor, but always the list from ubus call hostapd.wlan0 "rrm_nr_list" is empty:
{
"list": [
]
}
I understand that I can not send a BTM request to a client indicating another BSSID if I don't have it registered as a neighbor, right? So I need to find the way to add the desired BSSID (30:23:03:dc:fc:8a) to the neighbor list... but this seems not to be happening.