blob: 0f63580fbc2353e990ecd67f442439b768ea96d4 [file] [log] [blame]
/*
* hostapd / Interface steering
* Copyright (c) 2015 Google, Inc.
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include "common.h"
#include "common/defs.h"
#include "common/ieee802_11_defs.h"
#include "common/wpa_ctrl.h"
#include "hostapd.h"
#include "steering.h"
#define BANDSTEER_DIR_MODE S_IRWXU
static char ifnames[][IFNAMSIZ] = {"wlan-2400mhz", "wlan-5000mhz"};
/* Creates the directory if it doesn't already exist.
* Returns 0 if the directory already exists or it was created.
* It does not attempt to create the parent directory.
*/
static int ensure_dir_exists(const char *path, mode_t mode) {
int rc;
rc = mkdir(path, mode);
if ((rc == -1) && (errno == EEXIST)) {
rc = 0;
}
return rc;
}
/**
* Returns the interface name used for steering this BSS. This corresponds to
* the name of the first BSS on the interface.
*/
static char *steering_interface_name(const struct hostapd_data *hapd) {
return hapd->iface->bss[0]->conf->iface;
}
/**
* Convert the ssid into a string that is usable in a filename. The resulting
* string must be unique. Since SSIDs are user defined, they are a potential
* attack vector into the system. So, their encoding cannot be misinterpreted
* as any other filename. In other words, we cannot allow ssid encodings
* like '../bin' or '/' as trying to access such files by name could cause
* problems.
*
* The resulting ssid_string is null terminated.
*
* Returns the length of the resulting ssid string. Note that if the buffer
* is not large enough, then it will be filled with as many characters as
* will fit.
*/
static int ssid_to_str(char *buf, size_t buf_size,
const u8 *ssid, size_t ssid_len) {
int i, j;
char add_char;
u8 bits;
if (buf_size == 0) {
return 0;
}
j = 0;
for (i = 0; i < ssid_len; i++) {
if (isalnum(ssid[i]) || (ssid[i] != 0x0 && os_strchr("_- ", ssid[i]))) {
if (ssid[i] == ' ') {
add_char = '+';
} else {
add_char = ssid[i];
}
if (j >= buf_size - 1) {
break;
}
buf[j++] = add_char;
} else {
/* Encode the 8 bit value */
char alnum_chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
if (j >= buf_size - 3) {
break;
}
bits = (ssid[i] >> 4) & 0xF;
buf[j++] = '=';
buf[j++] = alnum_chars[bits];
bits = ssid[i] & 0xF;
buf[j++] = alnum_chars[bits];
}
}
buf[j] = '\0';
return j;
}
/**
* Returns the filename-safe name used for SSID timestamps for this BSS. Note
* that the value returned is in a static variable, so it is valid only until
* this function is called again.
*/
static char *steering_ssid_name(const struct hostapd_data *hapd,
char *buf, size_t buf_size) {
ssid_to_str(buf, buf_size,
hapd->conf->ssid.ssid, hapd->conf->ssid.ssid_len);
return buf;
}
/**
* Gets the appropriate timestamp directory path and puts it into buf.
* It returns the number of characters written.
*
* hapd: struct representing this particular BSS.
* steer_event: actual type of the event (e.g. STEER_EVENT_PROBE,
* STEER_EVENT_ATTEMPT, STEER_EVENT_CONNECT).
* If steer_event is STEER_EVENT_CONNECT, then the timestamp directory is
* chosen based upon the SSID for this interface (the interface type
* does not matter in this case).
* interface_type: whether to use the CURRENT_INTERFACE (i.e. hapd) or the
* steering TARGET_INTERFACE for interface-based timestamps (all but
* LOG_CONNECT).
*/
static int get_timestamp_dir(const struct hostapd_data *hapd,
steer_event_type steer_event,
steering_interface_type interface_type,
char *buf,
size_t buf_size) {
if (steering_path == NULL) {
return 0;
}
int pos;
pos = os_strlcpy(buf, steering_path, buf_size);
pos += os_strlcpy(&buf[pos], "/", buf_size - pos);
char *subdir;
if (steer_event == STEER_EVENT_CONNECT) {
pos += os_strlcpy(&buf[pos], "s_", buf_size - pos);
pos += ssid_to_str(&buf[pos], buf_size - pos,
hapd->conf->ssid.ssid,
hapd->conf->ssid.ssid_len);
} else if (interface_type == TARGET_INTERFACE) {
pos += os_strlcpy(&buf[pos], "i_", buf_size - pos);
pos += os_strlcpy(&buf[pos], steering_target_interface,
buf_size - pos);
} else {
pos += os_strlcpy(&buf[pos], "i_", buf_size - pos);
pos += os_strlcpy(&buf[pos], steering_interface_name(hapd),
buf_size - pos);
}
return pos;
}
static int get_timestamp_filename(const u8 *mac,
const struct hostapd_data *hapd,
steer_event_type steer_event,
steering_interface_type interface_type,
char *buf, size_t buf_size) {
int pos;
if (steering_path == NULL) {
return 0;
}
pos = get_timestamp_dir(hapd, steer_event, interface_type, buf,
buf_size);
if (os_snprintf(&buf[pos], buf_size - pos, "/" COMPACT_MACSTR ".%d",
MAC2STR(mac), steer_event) < 0) {
wpa_printf(MSG_ERROR, "os_snprintf couldn't format filename: %s",
strerror(errno));
return 0;
}
return 1;
}
int bandsteer_init() {
char mkpath[256];
char *p;
int rc = 0;
if (!steering_path) {
hostapd_logger(NULL, NULL, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_INFO, "Steering disabled");
return 0;
}
hostapd_logger(NULL, NULL, HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO,
"Steering enabled: target=%s, dir=%s",
steering_target_interface, steering_path);
if (steering_path[0] != '/') {
wpa_printf(MSG_ERROR,
"Band steering path (%s) is not absolute.",
steering_path);
return -1;
}
os_strlcpy(mkpath, steering_path, sizeof(mkpath));
mkpath[sizeof(mkpath) - 1] = '\0';
for (p=&mkpath[1]; p && !rc; ) {
if (*p == '\0') {
/* No more subdirectories to check/create */
return 0;
}
if (*p == '/') {
/* Consecutive slashes in pathname */
return -1;
}
p = strchr(p, '/');
if (!p) {
/* This is the last subdirectory component */
return ensure_dir_exists(mkpath, BANDSTEER_DIR_MODE);
}
*p = '\0';
rc = ensure_dir_exists(mkpath, BANDSTEER_DIR_MODE);
*p = '/';
p++;
}
return rc;
}
/**
* Initializes steering data structures needed for a particular ssid.
* Returns 0 on success.
*/
static int bandsteer_ssid_init(struct hostapd_data *hapd) {
char mkpath[256];
if (!steering_path) {
return 0;
}
get_timestamp_dir(hapd, STEER_EVENT_CONNECT, CURRENT_INTERFACE,
mkpath, sizeof(mkpath));
wpa_printf(MSG_INFO, "bandsteer_bss_init - %s", mkpath);
return ensure_dir_exists(mkpath, BANDSTEER_DIR_MODE);
}
int bandsteer_interface_init(struct hostapd_iface *iface) {
char mkpath[256];
int k;
int rc;
if (!steering_path) {
return 0;
}
get_timestamp_dir(iface->bss[0], STEER_EVENT_PROBE, CURRENT_INTERFACE,
mkpath, sizeof(mkpath));
wpa_printf(MSG_INFO, "bandsteer_interface_init - %s", mkpath);
rc = ensure_dir_exists(mkpath, BANDSTEER_DIR_MODE);
if (rc) {
return rc;
}
/* Ensure that timestamp directories exist for each SSID */
for (k = 0; k < iface->num_bss; k++) {
rc = bandsteer_ssid_init(iface->bss[k]);
if (rc) {
return rc;
}
}
return 0;
}
static int should_garbage_collect(const struct dirent *name, int type) {
char *extension = os_strrchr(name->d_name, '.');
char buf[4];
os_snprintf(buf, sizeof(buf), ".%d", type);
return os_strncmp(extension, buf, sizeof(buf)) == 0;
}
static int is_probe_timestamp(const struct dirent *name) {
return should_garbage_collect(name, STEER_EVENT_PROBE);
}
static int is_attempt_timestamp(const struct dirent *name) {
return should_garbage_collect(name, STEER_EVENT_ATTEMPT);
}
static int is_failed_timestamp(const struct dirent *name) {
return should_garbage_collect(name, STEER_EVENT_FAILED);
}
static int is_connect_timestamp(const struct dirent *name) {
return should_garbage_collect(name, STEER_EVENT_CONNECT);
}
static int is_defer_timestamp(const struct dirent *name) {
return should_garbage_collect(name, STEER_EVENT_DEFER);
}
static int file_ctime_lt(const struct dirent **a, const struct dirent **b) {
struct stat astat, bstat;
/* If we can't stat both of the files, give up and say they're equivalent. */
if (stat((*a)->d_name, &astat) == -1 || stat((*b)->d_name, &bstat) == -1) {
return 0;
}
return astat.st_ctime - bstat.st_ctime;
}
/**
* Delete all but the most recent MAX_TIMESTAMP_FILES files of the
* given type for the BSS represented by hapd.
* Returns the number of files deleted.
*/
static int garbage_collect_timestamp_files(
const struct hostapd_data *hapd,
steer_event_type steer_event) {
int num_timestamp_files = 0, num_timestamp_files_deleted = 0, i = 0;
struct dirent **namelist;
char original_cwd[1024];
char timestamp_dir[1024];
char *filename;
int error = 0;
int (*timestamp_filter)(const struct dirent *) = NULL;
if (getcwd(original_cwd, sizeof(original_cwd)) == NULL) {
wpa_printf(MSG_ERROR, "getcwd(): %s", strerror(errno));
return -1;
}
get_timestamp_dir(hapd, steer_event, CURRENT_INTERFACE,
timestamp_dir, sizeof(timestamp_dir));
if (chdir(timestamp_dir) == -1) {
wpa_printf(MSG_ERROR, "chdir(%s): %s",
timestamp_dir, strerror(errno));
return -1;
}
switch(steer_event) {
case STEER_EVENT_PROBE:
timestamp_filter = is_probe_timestamp;
break;
case STEER_EVENT_ATTEMPT:
timestamp_filter = is_attempt_timestamp;
break;
case STEER_EVENT_FAILED:
timestamp_filter = is_failed_timestamp;
break;
case STEER_EVENT_SUCCESSFUL:
break;
case STEER_EVENT_CONNECT:
timestamp_filter = is_connect_timestamp;
break;
case STEER_EVENT_DEFER:
timestamp_filter = is_defer_timestamp;
break;
case NUM_STEER_EVENTS:
break;
}
num_timestamp_files = scandir(timestamp_dir, &namelist,
timestamp_filter, file_ctime_lt);
/* TODO(walker): Remove eligible timestamps (like DEFER)
* when they have expired. */
for (i = 0; i < num_timestamp_files; ++i) {
/* TODO(walker): Check the below "-2" comment. With the filter
* function, I do not believe "." and ".." are included. */
if (MAX_STEERING_TIMESTAMP_FILES <
/* The -2 is because scandir includes "." and "..". */
(num_timestamp_files - 2) - num_timestamp_files_deleted) {
filename = namelist[i]->d_name;
if (filename[0] != '.' && !error) {
if (unlink(filename) == -1) {
wpa_printf(MSG_ERROR, "unlink(%s): %s", filename, strerror(errno));
error = 1;
} else {
++num_timestamp_files_deleted;
}
}
}
os_free(namelist[i]);
}
os_free(namelist);
if (chdir(original_cwd) == -1) {
wpa_printf(MSG_ERROR, "chdir(%s): %s", original_cwd, strerror(errno));
return -1;
}
return error ? -1 : num_timestamp_files_deleted;
}
/**
* Reads a timestamp from either request_logging_path or steering_timestamp_path
* (based on path) for the source address in mgmt, putting the result in
* timestamp. Returns 1 if the read succeeded, 0 otherwise.
*/
static int read_timestamp(const struct hostapd_data *hapd,
const u8 *mac,
steer_event_type steer_event,
steering_interface_type interface_type,
struct os_reltime *timestamp) {
FILE *f;
char filename[1024];
int success = 1;
struct stat st;
os_time_t sec = 0, usec = 0;
if (!get_timestamp_filename(mac, hapd, steer_event, interface_type,
filename, sizeof(filename))) {
return 0;
}
if (stat(filename, &st) == -1) {
return 0;
}
f = fopen(filename, "r");
if (f == NULL) {
wpa_printf(MSG_ERROR, "open(%s) for read: %s", filename, strerror(errno));
return 0;
}
if (timestamp) {
if (fscanf(f, "%ld %ld", &timestamp->sec, &timestamp->usec) != 2) {
wpa_printf(MSG_ERROR, "fscanf from %s: %s", filename, strerror(errno));
success = 0;
}
}
if (fclose(f) == EOF) {
wpa_printf(MSG_ERROR, "fclose(%s): %s", filename, strerror(errno));
return 0;
}
return success;
}
Boolean is_sta_2g5g_capable(const u8 *mac)
{
char probepath[BUFFER_SIZE];
size_t buf_size;
int pos;
struct stat st;
if (steering_path == NULL)
return FALSE;
/* TODO: Some of Google WiFi products firmware doesn't
* populate 2.4ghz probe request info to user space
* hence the assumption here is that device would be 2.4ghz
* capable if 5ghz probe request info is available.
*/
buf_size = sizeof(probepath);
pos = os_strlcpy(probepath, steering_path, buf_size);
pos += os_strlcpy(&probepath[pos], "/", buf_size - pos);
pos += os_strlcpy(&probepath[pos], "i_", buf_size - pos);
pos += os_strlcpy(&probepath[pos], ifnames[1], buf_size - pos);
os_snprintf(&probepath[pos], buf_size - pos, "/" COMPACT_MACSTR ".%d",
MAC2STR(mac), STEER_EVENT_PROBE);
return (stat(probepath, &st) != -1) ? TRUE : FALSE;
}
/**
* Writes timestamp for the source address in mgmt to request_logging_path.
* Also garbage collects timestamps.
* Returns 1 if the write succeeded, 0 otherwise.
*/
static int write_timestamp(const struct hostapd_data *hapd,
const u8 *mac,
steer_event_type steer_event,
const struct os_reltime *timestamp) {
FILE *f;
char filename[1024], tmp_filename[1024];
int success = 0;
if (garbage_collect_timestamp_files(hapd, steer_event) == -1) {
wpa_printf(MSG_ERROR,
"Garbage collecting steering timestamp files failed: %s",
strerror(errno));
return 0;
}
if (!get_timestamp_filename(mac, hapd, steer_event, CURRENT_INTERFACE,
filename, sizeof(filename))) {
return 0;
}
/* Create a temporary filename to prevent multiple interfaces on the
* same band from touching each others' writes.
*/
if (os_snprintf(tmp_filename, sizeof(tmp_filename), "%s%s", filename,
os_strrchr(hapd->iface->config_fname, '.')) < 0) {
wpa_printf(MSG_ERROR, "os_snprintf couldn't format temp filename: %s",
strerror(errno));
return 0;
}
if ((f = fopen(tmp_filename, "w")) == NULL) {
wpa_printf(MSG_ERROR, "fopen(%s) for write: %s", tmp_filename,
strerror(errno));
return 0;
}
if (timestamp) {
if (fprintf(f, "%ld %ld", timestamp->sec, timestamp->usec) < 0) {
wpa_printf(MSG_ERROR, "fprintf to %s: %s", tmp_filename, strerror(errno));
} else {
success = 1;
}
}
if (fclose(f) == EOF) {
wpa_printf(MSG_ERROR, "fclose(%s): %s", tmp_filename, strerror(errno));
return 0;
}
if (rename(tmp_filename, filename) != 0) {
wpa_printf(MSG_ERROR, "rename(%s, %s): %s", tmp_filename, filename,
strerror(errno));
return 0;
}
wpa_printf(MSG_INFO, "Set timestamp for " MACSTR
" (iface=%s/%s, event=%d)",
MAC2STR(mac), steering_interface_name(hapd),
hapd->conf->iface, steer_event);
return success;
}
/**
* Calls write_timestamp_file unless there is an existing timestamp younger than
* BANDSTEERING_EXPIRATION_SECONDS.
* Returns 0 on write or garbage collection failure, 1 otherwise.
*/
static int maybe_write_timestamp(const struct hostapd_data *hapd,
const u8 *mac,
steer_event_type steer_event,
const struct os_reltime *timestamp) {
struct os_reltime now, prev_timestamp;
os_get_reltime(&now);
if (!read_timestamp(hapd, mac, steer_event, CURRENT_INTERFACE,
&prev_timestamp) ||
os_reltime_expired(&now, &prev_timestamp,
BANDSTEERING_FRESH_SECONDS)) {
if (!write_timestamp(hapd, mac, steer_event, timestamp)) {
wpa_printf(MSG_ERROR, "Failed to write timestamp file.");
return 0;
}
}
return 1;
}
int write_probe_timestamp(const struct hostapd_data *hapd,
const u8 *mac,
int ssi_signal) {
struct os_reltime now;
if (!steering_path || (ssi_signal < steering_rsi_threshold)) {
return 0;
}
os_get_reltime(&now);
maybe_write_timestamp(hapd, mac, STEER_EVENT_PROBE, &now);
return 0;
}
int write_connect_timestamp(const struct hostapd_data *hapd,
const u8 *sta_mac) {
struct os_reltime now;
os_get_reltime(&now);
write_timestamp(hapd, sta_mac, STEER_EVENT_CONNECT, &now);
return 0;
}
int write_disconnect_timestamp(const struct hostapd_data *hapd,
const u8 *sta_mac) {
struct os_reltime now;
os_get_reltime(&now);
/* TODO(walker): Reduce the number of extraneous timestamps by only
* writing the timestamp for the target interface (but that assumes
* the algorithm can never steer in multiple directions). Better
* would just be to clean up expired timestamps when garbage
* collecting. */
write_timestamp(hapd, sta_mac, STEER_EVENT_DEFER, &now);
return 0;
}
const char *steering_reason_str(steering_reason reason)
{
switch (reason) {
case STEER:
return "steered";
case NOSTEER_TARGET_INTERFACE:
return "target-interface";
case NOSTEER_REASSOC:
return "reassoc";
case NOSTEER_NEW_STATION:
return "new-station";
case NOSTEER_DEFERRED:
return "deferred";
case NOSTEER_NON_CANDIDATE:
return "non-candidate";
case NOSTEER_WEAK_SIGNAL:
return "weak-signal";
case NOSTEER_RECENTLY_STEERED:
return "recently-steered";
case NOSTEER_REASON_UNSPECIFIED:
return "unspecified";
case UNKNOWN_STEERING_REASON:
/* Fall through intended. This case should not occur. */
default:
return "unknown";
}
}
/*
* To be called upon receiving an ASSOC request. Returns 1 if the sta with
* |mac| should be steered, 0 otherwise.
*/
int should_steer_on_assoc(const struct hostapd_data *hapd,
const u8 *sta_mac, int ssi_signal,
int reassoc, steering_reason *s_reason,
struct os_reltime *p_probe_delta_time,
struct os_reltime *p_steer_delta_time,
struct os_reltime *p_defer_delta_time) {
struct os_reltime now, probe_time, bandsteer_time, probe_delta_time,
steer_delta_time, defer_time, defer_delta_time;
int have_timestamp;
char *steering_name;
char buf[128];
os_memset(p_probe_delta_time, 0, sizeof(*p_probe_delta_time));
os_memset(p_steer_delta_time, 0, sizeof(*p_steer_delta_time));
os_memset(p_defer_delta_time, 0, sizeof(*p_defer_delta_time));
if (steering_target_interface == NULL) {
return FALSE;
}
steering_name = steering_interface_name(hapd);
if (!strcmp(steering_target_interface, steering_name)) {
hostapd_logger(hapd->msg_ctx, sta_mac,
HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO,
"Assoc on steering target (%s); rssi=%d",
steering_name, ssi_signal);
wpa_msg(hapd->msg_ctx, MSG_INFO,
AP_STA_NO_STEERING MACSTR " target-interface %d",
MAC2STR(sta_mac), ssi_signal);
*s_reason = NOSTEER_TARGET_INTERFACE;
return FALSE;
}
/* Steering is enabled and this is not the target interface. */
if (reassoc) {
hostapd_logger(hapd->msg_ctx, sta_mac,
HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO,
"Assoc no steer - reassoc; rssi=%d",
ssi_signal);
wpa_msg(hapd->msg_ctx, MSG_INFO,
AP_STA_NO_STEERING MACSTR " reassoc %d",
MAC2STR(sta_mac), ssi_signal);
*s_reason = NOSTEER_REASSOC;
return FALSE;
}
/* Check that this station has previously connected on this SSID */
if (!read_timestamp(hapd, sta_mac, STEER_EVENT_CONNECT,
CURRENT_INTERFACE, NULL)) {
hostapd_logger(hapd->msg_ctx, sta_mac,
HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO,
"Assoc no steer - new station (%s); rssi=%d "
"ssid=%s",
steering_name, ssi_signal,
steering_ssid_name(hapd, buf, sizeof(buf)));
wpa_msg(hapd->msg_ctx, MSG_INFO,
AP_STA_NO_STEERING MACSTR " new-station %d",
MAC2STR(sta_mac), ssi_signal);
*s_reason = NOSTEER_NEW_STATION;
return FALSE;
}
os_get_reltime(&now);
/* Do not steer if steering is currently deferred to the target. This
* happens for a time after a device has disconnected from that
* target. */
/* TODO(walker): Probably more correct to store the actual expiration
* in the timestamp, but that requires more complicated logic when
* writing the timestamp, requiring that we write
* max(current expiration, desired expiration) instead of just
* overwriting the timestamp like we do now. For current code meets
* our immediate needs. */
if (read_timestamp(hapd, sta_mac, STEER_EVENT_DEFER, TARGET_INTERFACE,
&defer_time) &&
!os_reltime_expired(&now, &defer_time, BANDSTEERING_DEFER_SECONDS)) {
os_reltime_sub(&now, &defer_time, &defer_delta_time);
int defer_delta_ms = defer_delta_time.sec * 1000 +
defer_delta_time.usec / 1000;
hostapd_logger(hapd->msg_ctx, sta_mac,
HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO,
"Assoc no steer - deferred; rssi=%d"
" defer_delta_t=%ld.%02ld",
ssi_signal, defer_delta_time.sec,
defer_delta_time.usec / 10000);
wpa_msg(hapd->msg_ctx, MSG_INFO,
AP_STA_NO_STEERING MACSTR " deferred %d"
" defer_delta_ms:%d",
MAC2STR(sta_mac), ssi_signal, defer_delta_ms);
*p_defer_delta_time = defer_delta_time;
*s_reason = NOSTEER_DEFERRED;
return FALSE;
}
if (!read_timestamp(hapd, sta_mac, STEER_EVENT_PROBE, TARGET_INTERFACE,
&probe_time)) {
hostapd_logger(hapd->msg_ctx, sta_mac,
HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO,
"Assoc no steer - non-candidate; rssi=%d",
ssi_signal);
wpa_msg(hapd->msg_ctx, MSG_INFO,
AP_STA_NO_STEERING MACSTR " non-candidate %d",
MAC2STR(sta_mac), ssi_signal);
*s_reason = NOSTEER_NON_CANDIDATE;
return FALSE;
}
/* We do not want to steer a station if its signal strength indicates
* it is not a candidate NOW.
* If the assoc signal strength is weak
* (rssi < steering_rsi_threshold [dflt=-60]), then we will not steer.
* If the assoc signal strength is not strong
* (rssi < BANDSTEERING_THRESHOLD_RSSI [-45]) and we have not
* recently (within BANDSTEERING_RECENT_SECONDS [15]) received a
* probe request on the target interface, then we also will not
* steer. */
os_reltime_sub(&now, &probe_time, &probe_delta_time);
int probe_delta_ms = probe_delta_time.sec * 1000 +
probe_delta_time.usec / 1000;
if ((ssi_signal < steering_rsi_threshold) ||
((os_reltime_expired(&now, &probe_time,
BANDSTEERING_RECENT_SECONDS) &&
(ssi_signal < BANDSTEERING_THRESHOLD_RSSI)))) {
hostapd_logger(hapd->msg_ctx, sta_mac,
HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO,
"Assoc no steer - weak signal; "
"rssi=%d probe_delta_t=%ld.%02ld",
ssi_signal, probe_delta_time.sec,
probe_delta_time.usec / 10000);
wpa_msg(hapd->msg_ctx, MSG_INFO,
AP_STA_NO_STEERING MACSTR " weak-signal %d"
" probe_delta_ms:%d",
MAC2STR(sta_mac), ssi_signal, probe_delta_ms);
*s_reason = NOSTEER_WEAK_SIGNAL;
*p_probe_delta_time = probe_delta_time;
return FALSE;
}
/* Steer station if it has never been steered or if it hasn't been
* steered recently. */
have_timestamp = read_timestamp(hapd, sta_mac, STEER_EVENT_ATTEMPT,
CURRENT_INTERFACE, &bandsteer_time);
if (!have_timestamp) {
steer_delta_time.sec = 0;
steer_delta_time.usec = 0;
} else {
os_reltime_sub(&now, &bandsteer_time, &steer_delta_time);
}
int steer_delta_ms = steer_delta_time.sec * 1000 +
steer_delta_time.usec / 1000;
if (!have_timestamp ||
os_reltime_expired(&now, &bandsteer_time,
BANDSTEERING_EXPIRATION_SECONDS)) {
write_timestamp(hapd, sta_mac, STEER_EVENT_ATTEMPT, &now);
hostapd_logger(hapd->msg_ctx, sta_mac,
HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO,
"Assoc steer; rssi=%d steer_delta_t=%ld.%02ld "
"probe_delta_t=%ld.%02ld",
ssi_signal, steer_delta_time.sec,
steer_delta_time.usec / 10000,
probe_delta_time.sec,
probe_delta_time.usec / 10000);
wpa_msg(hapd->msg_ctx, MSG_INFO,
AP_STA_STEERING MACSTR " %d steer_delta_ms:%d "
"probe_delta_ms:%d",
MAC2STR(sta_mac), ssi_signal, steer_delta_ms,
probe_delta_ms);
*p_probe_delta_time = probe_delta_time;
*p_steer_delta_time = steer_delta_time;
*s_reason = STEER;
return TRUE;
}
write_timestamp(hapd, sta_mac, STEER_EVENT_FAILED, &now);
os_reltime_sub(&now, &bandsteer_time, &steer_delta_time);
hostapd_logger(hapd->msg_ctx, sta_mac,
HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO,
"Assoc steer fail; steer_delta_t=%ld.%02ld rssi=%d",
steer_delta_time.sec, steer_delta_time.usec / 10000,
ssi_signal);
wpa_msg(hapd->msg_ctx, MSG_INFO,
AP_STA_NO_STEERING MACSTR " recently-steered %d "
"steer_delta_ms:%d",
MAC2STR(sta_mac), ssi_signal, steer_delta_ms);
*p_probe_delta_time = probe_delta_time;
*p_steer_delta_time = steer_delta_time;
*s_reason = NOSTEER_RECENTLY_STEERED;
return FALSE;
}