tools/net80211: add mlme_assoc

mlme_assoc is a tool to trigger net80211::ieee80211_sta_join1() calls
which in certain conditions cause problems to the LinuxKPI 802.11 compat
code (but also believed to possibly cause problems in case of race to
other firmware based drivers).  This has proven to be a good reproducer
for the problem even on setups which otherwise could run for days without
hitting it.

Sponsored by:	The FreeBSD Foundation
PR:		271979
This commit is contained in:
Bjoern A. Zeeb 2023-12-01 01:37:25 +00:00
parent 682b069c5c
commit 643d6dce6c
3 changed files with 258 additions and 0 deletions

View file

@ -0,0 +1,7 @@
PROG= mlme_assoc
BINDIR= /usr/bin
MAN=
SRCS= mlme_assoc.c
.include <bsd.prog.mk>

View file

@ -0,0 +1,51 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2023 The FreeBSD Foundation
#
# This documentation was written by Björn Zeeb under sponsorship from
# the FreeBSD Foundation.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
This is a simple program to drive net80211::ieee80211_sta_join1() calls from
user space.
The program optionally accepts an interface name (e.g., wlan42), or an
interface name, an SSID and a BSSID.
In the former case of no SSID/BSSID passed it will query the scan results and
then try to join each entry from the scan with a short delay.
In the lastter case giving the SSID/BSSID one can trigger the "canreassoc" case
in ieee80211_sta_join1() or not depending on whether one passes the currently
associated SSID/BSSID or not.
The tool is useful to trigger net80211::newstate() changes while other
newstate() changes are pending or being executed.
I was specifically developed to show a problem with the LinuxKPI 802.11 compat
code. The reason is that ieee80211_sta_join1() also calls in (*iv_update_bss)()
swapping nodes before initiating the state changes and in LinuxKPI state is on
the sta and not the vif causing all kinds of troubles, especially if we lose
a state transition before the taskq is run or if the iv_bss node gets swapped
before a task is executed.

View file

@ -0,0 +1,200 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2023 The FreeBSD Foundation
*
* This software was developed by Björn Zeeb under sponsorship from
* the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* First get scan results in a hurry.
* Pick a random BSSID and try to assoc.
* Hopefully this is enough to trigger the newstate race along with the
* (*iv_update_bss)() logic.
*
* Alternatively pass IF SSID BSSID in and just try that.
*/
#include <err.h>
#include <stdio.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <net80211/ieee80211.h>
#include <net80211/ieee80211_ioctl.h>
static int
if_up(int sd, const char *ifnam)
{
struct ifreq ifr;
int error;
memset(&ifr, 0, sizeof(ifr));
strlcpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name));
error = ioctl(sd, SIOCGIFFLAGS, &ifr);
if (error == -1) {
warn("SIOCGIFFLAGS");
return (error);
}
if (ifr.ifr_flags & IFF_UP)
return (0);
ifr.ifr_flags |= IFF_UP;
error = ioctl(sd, SIOCSIFFLAGS, &ifr);
if (error == -1) {
warn("SIOCSIFFLAGS");
return (error);
}
return (0);
}
static int
try_mlme_assoc(int sd, const char *ifnam, uint8_t *ssid, uint8_t ssid_len, uint8_t *bssid)
{
struct ieee80211req ireq;
struct ieee80211req_mlme mlme;
int error;
memset(&mlme, 0, sizeof(mlme));
mlme.im_op = IEEE80211_MLME_ASSOC;
if (ssid != NULL)
memcpy(mlme.im_ssid, ssid, ssid_len);
mlme.im_ssid_len = ssid_len;
if (bssid != NULL)
memcpy(mlme.im_macaddr, bssid, IEEE80211_ADDR_LEN);
memset(&ireq, 0, sizeof(ireq));
strlcpy(ireq.i_name, ifnam, sizeof(ireq.i_name));
ireq.i_type = IEEE80211_IOC_MLME;
ireq.i_val = 0;
ireq.i_data = (void *)&mlme;
ireq.i_len = sizeof(mlme);
error = ioctl(sd, SIOCS80211, &ireq);
if (error == -1) {
warn("SIOCS80211, %#x", ireq.i_type);
return (error);
}
return (0);
}
static int
mlme_assoc_scan_results(int sd, const char *ifnam)
{
struct ieee80211req ireq;
struct ieee80211req_scan_result *sr;
uint8_t buf[32 * 1024], *p;
ssize_t len;
int error;
memset(&ireq, 0, sizeof(ireq));
strlcpy(ireq.i_name, ifnam, sizeof(ireq.i_name));
ireq.i_type = IEEE80211_IOC_SCAN_RESULTS;
ireq.i_data = (void *)buf;
ireq.i_len = sizeof(buf);
error = ioctl(sd, SIOCG80211, &ireq);
if (error == -1 || ireq.i_len < 0) {
warn("SIOCG80211, %#x", ireq.i_type);
return (error);
}
p = buf;
len = ireq.i_len;
while (len > (ssize_t)sizeof(*sr)) {
sr = (struct ieee80211req_scan_result *)(void *)p;
p += sr->isr_len;
len -= sr->isr_len;
error = try_mlme_assoc(sd, ifnam, (void *)(sr + 1), sr->isr_ssid_len,
sr->isr_bssid);
if (error != 0) {
warnx("try_mlme_assoc");
return (error);
}
usleep(100000);
}
return (0);
}
int
main(int argc, char *argv[])
{
const char *ifnam;
uint8_t *ssid, *bssid;
struct ether_addr ea;
int error, sd;
ifnam = "wlan0";
ssid = NULL;
bssid = NULL;
if (argc == 4) {
ifnam = argv[1];
ssid = (uint8_t *)argv[2];
bssid = (uint8_t *)ether_aton_r(argv[3], &ea);
if (bssid == NULL)
warnx("ether_aton_r, ignoring BSSID");
} else if (argc == 2) {
ifnam = argv[1];
}
sd = socket(AF_LOCAL, SOCK_DGRAM, 0);
if (sd == -1)
errx(EX_UNAVAILABLE, "socket");
error = if_up(sd, ifnam);
if (error != 0)
errx(EX_UNAVAILABLE, "if_up");
if (argc == 4) {
error = try_mlme_assoc(sd, ifnam, ssid, strlen((const char *)ssid), bssid);
if (error != 0)
errx(EX_UNAVAILABLE, "try_mlme_assoc");
} else {
error = mlme_assoc_scan_results(sd, ifnam);
if (error != 0)
errx(EX_UNAVAILABLE, "mlme_assoc_scan_results");
}
close(sd);
return (0);
}